Getting started with Foundry
Introduction
If you're diving into smart contract development, you'll find Foundry to be an incredibly handy companion. It's more than just a tool; it's a full-fledged framework that makes smart contracts' development, testing, and deployment smoother and more efficient. Whether you're new to blockchain or an experienced developer, Foundry is designed to be a reliable and comprehensive toolkit that fits right into your workflow.
Foundry is comprised of several key components, each serving a distinct purpose in the smart contract development lifecycle:
-
Forge: Forge is the heart of the Foundry framework, acting as a powerful compilation and testing tool. It's instrumental in compiling smart contracts, running a suite of tests (including fuzz and property-based tests), and ensuring the contracts are robust and secure before deployment.
-
Anvil: Anvil is a local node tailored for development. This component is invaluable for developers who need a quick and easy way to test their contracts in a local blockchain environment. Anvil enables rapid iteration and debugging without connecting to the main networks or testnets.
-
Cast: Cast is a versatile tool within Foundry designed to interact with Ethereum. It facilitates a range of actions, from sending transactions and querying blockchain data to manipulating local Ethereum states. This utility makes interacting with deployed contracts easier and performs various blockchain-related tasks.
-
Chisel: Chisel enriches the Foundry suite as a Solidity REPL (Read-Eval-Print Loop), enabling developers to interactively test and experiment with Solidity code snippets. It's ideal for immediate feedback and debugging in real-time, and it's compatible both within and outside Foundry projects.
This guide covers installing Foundry, setting up, compiling, deploying, and interacting with smart contracts.
Initializing a Project
Creating a New Project
- In an empty directory, initialize a new Foundry project:
forge init
- To create a new directory with the project:
forge init PROJECT_NAME
- Note: The
src
directory is where the smart contracts are placed.
Compiling Contracts
- To compile the contracts, run either:
or
forge build
forge compile
- The
out
directory will generate a JSON file containing compilation data, such as ABI.
Setting Up a Local Blockchain
- Use Anvil to start a local blockchain for testing:
anvil
- The local blockchain will run at
127.0.0.1:8545
, and you can add it to MetaMask for ease of testing.
Deploying Contracts
Deploying Locally or to a Custom RPC
- To deploy smart contracts, use
forge create
. Forge defaults to the Anvil local blockchain, but other RPCs can be specified using the--rpc-url
flag.
- Deploying locally with Anvil running:
forge create CONTRACT_NAME
- Deploying to a custom endpoint:
forge create CONTRACT_NAME --rpc-url YOUR_ENDPOINT
- This will likely not work because it needs a private key to deploy.
Error:
Error accessing local wallet. Did you set a private key, mnemonic or keystore?
Run `cast send --help` or `forge create --help` and use the corresponding CLI
flag to set your key via:
--private-key, --mnemonic-path, --aws, --interactive, --trezor or --ledger.
Alternatively, if you're using a local node with unlocked accounts,
use the --unlocked flag and either set the `ETH_FROM` environment variable to the address
of the unlocked account you want to use, or provide the --from flag with the address directly.
Options for Specifying Private Key
- Run the create command with the
--interactive
flag for a prompt to add a private key:forge create CONTRACT_NAME --interactive
- Or, directly include the private key in the command:
forge create CONTRACT_NAME --private-key YOUR_PRIVATE_KEY
Writing Deploy Scripts
- Scripts in Foundry are written in Solidity. We'll use a Solidity script to deploy a contract. By convention, script files end with
.s.sol
. - Example: Deploying
SimpleStorage.sol
.
Creating the Deploy Script
-
Create a file named
deploySimpleStorage.s.sol
with the following content:// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {Script} from "forge-std/Script.sol"; import {SimpleStorage} from "../src/SimpleStorage.sol"; contract DeploySimpleStorage is Script { function run() external returns (SimpleStorage) { vm.startBroadcast(); SimpleStorage simpleStorage = new SimpleStorage(); vm.stopBroadcast(); return simpleStorage; } }
-
Deploy the contract using the script:
forge script script/DeploySimpleStorage.s.sol --rpc-url YOUR_RPC --broadcast --private-key YOUR_PRIVATE_KEY
Let's break down its key components and functionalities:
-
Pragma Directive:
pragma solidity ^0.8.19;
: Specifies that the script is compatible with Solidity version 0.8.19 or any newer version of the 0.8 series but not version 0.9 or above.
-
Imports:
import {Script} from "forge-std/Script.sol";
: Imports theScript
class from theforge-std
library, which is a part of Foundry, a development environment for Ethereum smart contracts.import {SimpleStorage} from "../src/SimpleStorage.sol";
: Imports theSimpleStorage
contract, presumably a custom contract located in thesrc
directory.
-
Contract Declaration:
contract DeploySimpleStorage is Script
: Defines a new contract namedDeploySimpleStorage
that inherits from theScript
class. This setup is typical for deployment scripts in Foundry.
-
Function Definition:
function run() external returns (SimpleStorage)
: Therun
function is the main entry point for the deployment script. It's markedexternal
as it's intended to be called externally, and it returns an instance ofSimpleStorage
.
-
Deployment Process:
vm.startBroadcast();
: Initiates a transaction broadcast. Thevm
object is a special component in Foundry, providing various functionalities related to the Ethereum Virtual Machine (EVM).SimpleStorage simpleStorage = new SimpleStorage();
: Instantiates theSimpleStorage
contract.vm.stopBroadcast();
: Ends the transaction broadcast.
-
Return Statement:
return simpleStorage;
: Returns the deployed instance ofSimpleStorage
.
This script is a typical example of a deployment script used in the Foundry environment for deploying Ethereum smart contracts. It's concise and follows the pattern of starting a broadcast, deploying the contract, and stopping it. The SimpleStorage
contract, which is not detailed here, would contain the actual business logic or data storage mechanisms.
Interacting with Contracts Using Cast
Sending Transactions
- To send transactions, use
cast send
:cast send ADDRESS FUNCTION_SIG PARAMS
- Example:
cast send 0x5FbDB2315678afecb367f032d93F642f64180aa3 "store(uint256)" 3333 --rpc-url $RPC_URL --private-key $PRIVATE_KEY
Reading from Contracts
- Use
cast call
for reading view functions:cast call ADDRESS FUNCTION_SIGNATURE
- Example:
cast call 0x5FbDB2315678afecb367f032d93F642f64180aa3 "retrieve()"
You can use cast for conversions, for example, hex to dec:
cast --to-base 0x0000000000000000000000000000000000000000000000000000000000000d05 dec
Or use the Chainstack EVM Swiss Knife.
Managing Dependencies
Installing Smart Contract Dependencies
- Use the following command to install dependencies from a repository:
forge install smartcontractkit/chainlink-brownie-contracts --no-commit
- Dependencies are added to the
lib
directory.
Remapping Dependencies
- Add remappings in the
foundry.toml
file for syntax convenience:remappings = ['@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/']
chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/']
Writing and Running Tests
Example Test Contract
- Tests in Foundry are also written in Solidity. Here's an example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Test, console} from "forge-std/Test.sol";
contract FundMeTest is Test {
uint256 number = 33;
function setUp() external {
number = 3333;
}
function testDemo() public {
console.log("The saved number is", number);
assertEq(number, 3333);
}
}
- Run tests with:
forge test -vv
- The
-vv
flag outputs detailed logs for better insight.
Let's break down its components:
-
License and Solidity Version Declaration:
// SPDX-License-Identifier: MIT
: This is a comment specifying the license under which this file is released, in this case, the MIT License.pragma solidity ^0.8.18;
: This line specifies the compiler version. The file is compatible with Solidity version 0.8.18 and above within the 0.8.x range.
-
Imports:
import {Test, console} from "forge-std/Test.sol";
: This line imports two elements from the Forge standard library (forge-std
):Test
: A base contract that provides testing functionalities.console
: A utility to log output to the console. This is particularly useful for debugging and tracking variable values during test execution.
-
Test Contract Declaration:
contract FundMeTest is Test {
: This line declares a new contractFundMeTest
which inherits from theTest
contract. In the context of Forge, this meansFundMeTest
is a test suite.
-
State Variable:
uint256 number = 33;
: A state variablenumber
of typeuint256
(unsigned integer of 256 bits) is declared and initialized to 33. This variable is used to demonstrate state manipulation and assertion in the test.
-
Setup Function:
function setUp() external { number = 3333; }
: ThesetUp()
function is a special function in the Forge framework that runs before each test function. It's used for initializing or resetting the state. Here, it sets thenumber
variable to 3333.
-
Test Function:
function testDemo() public { ... }
: This is the actual test function. In Forge, any function with a name starting withtest
is considered a test case.console.log("The saved number is", number);
: This line logs the value ofnumber
to the console, which is useful for debugging or verifying the test state.assertEq(number, 3333);
: This is an assertion statement provided by theTest
contract. It checks whether the value ofnumber
is equal to 3333. If the assertion fails (i.e., ifnumber
is not 3333), the test will fail.
Testing on a Fork
- Run tests on a forked network by adding an RPC URL:
forge test -vvv --fork-url $SEPOLIA_RPC
Coverage Analysis
- Use
forge coverage
to analyze how much of your contracts are tested:It will display a nice table:forge coverage --fork-url $SEPOLIA_RPC
[⠢] Compiling...
[⠢] Compiling 26 files with 0.8.20
[⠆] Solc 0.8.20 finished in 4.07s
Compiler run successful!
Analysing contracts...
Running tests...
| File | % Lines | % Statements | % Branches | % Funcs |
|---------------------------|---------------|---------------|---------------|--------------|
| script/DeployFundme.s.sol | 0.00% (0/3) | 0.00% (0/3) | 100.00% (0/0) | 0.00% (0/1) |
| src/FundMe.sol | 16.67% (2/12) | 23.53% (4/17) | 0.00% (0/4) | 25.00% (1/4) |
| src/PriceConverter.sol | 0.00% (0/6) | 0.00% (0/11) | 100.00% (0/0) | 0.00% (0/2) |
| Total | 9.52% (2/21) | 12.90% (4/31) | 0.00% (0/4) | 14.29% (1/7) |
Updated 6 months ago