Foundry
1. Introduction to Foundry 🏗️
Foundry is a blazing fast, portable, and modular toolkit for Ethereum application development. It's designed to make smart contract development, testing, and deployment more efficient and developer-friendly.
2. Installation and Setup 🔧
To install Foundry, follow these steps:
# Install Foundry
curl -L <https://foundry.paradigm.xyz> | bash
# Update PATH and load env variables
source ~/.bashrc
# Install Foundry components
foundryup
After installation, verify the setup:
forge --version
cast --version
anvil --version
3. Core Concepts 🧠
Foundry consists of three main components:
Forge: A testing framework for Ethereum smart contracts
Cast: A command-line tool for interacting with Ethereum RPC nodes
Anvil: A local Ethereum node for development purposes
Chisel: An interactive Solidity REPL (Read-Eval-Print Loop) for quick experimentation and debugging
4. Key Features 🌟
Fast compilation and testing
Solidity-native testing
Flexible debugging
Fuzz testing
Gas optimization
Forking capabilities
5. Foundry Architecture 🏛️

6. User Flow 🔄

7. Solidity Basics to Advanced Concepts 📚
7.1 Basic Structure of a Solidity Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
// State variables
uint256 public myNumber;
// Constructor
constructor(uint256 _initialNumber) {
myNumber = _initialNumber;
}
// Functions
function setNumber(uint256 _newNumber) public {
myNumber = _newNumber;
}
function getNumber() public view returns (uint256) {
return myNumber;
}
}
7.2 Advanced Solidity Concepts
7.2.1 Inheritance and Interfaces
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ICounter {
function increment() external;
function getCount() external view returns (uint256);
}
contract Counter is ICounter {
uint256 private count;
function increment() external override {
count++;
}
function getCount() external view override returns (uint256) {
return count;
}
}
contract AdvancedCounter is Counter {
function incrementBy(uint256 _value) external {
for (uint256 i = 0; i < _value; i++) {
increment();
}
}
}
7.2.2 Libraries and Using For
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library MathLib {
function square(uint256 _a) internal pure returns (uint256) {
return _a * _a;
}
}
contract MathContract {
using MathLib for uint256;
function calculateSquare(uint256 _number) public pure returns (uint256) {
return _number.square();
}
}
7.2.3 Events and Indexed Parameters
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EventExample {
event Transfer(address indexed from, address indexed to, uint256 amount);
function transfer(address _to, uint256 _amount) public {
// Transfer logic here
emit Transfer(msg.sender, _to, _amount);
}
}
8. Foundry Commands and Usage 🖥️
8.1 Forge Commands
forge build: Compile all contracts in the project
forge test: Run all tests in the project
forge create: Deploy a contract
forge verify-contract: Verify a deployed contract on Etherscan
8.2 Cast Commands
cast call: Perform a call to a contract without publishing a transaction
cast send: Send a transaction to a contract
cast estimate: Estimate the gas cost of a transaction
8.3 Anvil Commands
anvil: Start a local Ethereum node
anvil --fork-url: Start a node that forks from a specified network
8.4 Verbosity Levels in Foundry Commands
Foundry commands support different verbosity levels, which can be very useful for debugging and getting more detailed information about your operations. These levels are:
-v: Displays basic logs and errors
-vv: Shows more detailed logs, including emitted events
-vvv: Provides even more information, including gas usage for each call
-vvvv: Offers the most detailed output, including stack traces for errors
Example usage:
forge test -vv
forge create --rpc-url <your_rpc_url> -vvv
These verbosity levels can be applied to most Foundry commands, allowing you to tailor the output to your specific needs during development, testing, and deployment processes.
9. Real-world Examples 🌍
9.1 DEX (Decentralized Exchange) Smart Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleDEX {
mapping(address => mapping(address => uint256)) public tokenBalances;
event Deposit(address user, address token, uint256 amount);
event Withdraw(address user, address token, uint256 amount);
event Trade(address user, address tokenGive, uint256 amountGive, address tokenGet, uint256 amountGet);
function deposit(address _token, uint256 _amount) external {
require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Transfer failed");
tokenBalances[msg.sender][_token] += _amount;
emit Deposit(msg.sender, _token, _amount);
}
function withdraw(address _token, uint256 _amount) external {
require(tokenBalances[msg.sender][_token] >= _amount, "Insufficient balance");
tokenBalances[msg.sender][_token] -= _amount;
require(IERC20(_token).transfer(msg.sender, _amount), "Transfer failed");
emit Withdraw(msg.sender, _token, _amount);
}
function trade(address _tokenGive, uint256 _amountGive, address _tokenGet, uint256 _amountGet) external {
require(tokenBalances[msg.sender][_tokenGive] >= _amountGive, "Insufficient balance");
require(tokenBalances[address(this)][_tokenGet] >= _amountGet, "Insufficient liquidity");
tokenBalances[msg.sender][_tokenGive] -= _amountGive;
tokenBalances[address(this)][_tokenGive] += _amountGive;
tokenBalances[msg.sender][_tokenGet] += _amountGet;
tokenBalances[address(this)][_tokenGet] -= _amountGet;
emit Trade(msg.sender, _tokenGive, _amountGive, _tokenGet, _amountGet);
}
}
10. Benefits of Using Foundry 🚀
Faster development cycle
Improved testing capabilities
Better debugging tools
Gas optimization features
Seamless integration with existing Solidity projects
11. Core-level Engineering Concepts 🔬
11.1 Gas Optimization
Gas optimization is crucial in Ethereum development. Here's an example of how to optimize gas usage:
// Gas-inefficient
function sumArray(uint256[] memory numbers) public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
// Gas-optimized
function sumArrayOptimized(uint256[] memory numbers) public pure returns (uint256) {
uint256 sum = 0;
uint256 length = numbers.length;
for (uint256 i = 0; i < length; i++) {
sum += numbers[i];
}
return sum;
}
11.2 Memory vs Storage
Understanding the difference between memory and storage is crucial for efficient smart contract development:
contract MemoryVsStorage {
uint256[] public myArray;
// Uses storage - modifies the state
function addToArray(uint256 _number) public {
myArray.push(_number);
}
// Uses memory - doesn't modify the state
function getArraySum() public view returns (uint256) {
uint256[] memory tempArray = myArray;
uint256 sum = 0;
for (uint256 i = 0; i < tempArray.length; i++) {
sum += tempArray[i];
}
return sum;
}
}
Last updated
Was this helpful?