A curated list of Foundry tips and tricks
1. Installing specific branch of a library:
You can install a specific branch of a library by adding @<branch_name>
to the forge install
command.
For ex: If you wanna use the v7 branch of the solmate lib you can do so as forge install transmissions11/solmate@v7
. This avoids us from using the nightly or unstable branches. Further, it helps auditors as well to avoid assumptions.
2. Use hoax()
instead of prank()
+deal()
:
hoax() sets msg.sender and provides the address with some ETH in a single call rather than using prank()
and deal()
Credits: MiloTruck
3. Add labels to your asserts for easy debugging:
Including labels to your assert statements as an additional param helps you quickly identify when a test files. This is very useful especially if you have multiple asserts in the same test.
function testDeposit() public {
// Do something
assertEq(user1Balance, user2Balance, "ub1 == ub2");
assertNotEq(poolBalanceBefore, poolBalanceAfter, "pbBefore != pbAfter")
}
4. Use --diff
flag to compare gas costs:
If you want to check the gas cost difference add --diff
flag to forge snapshot
command.
The command forge snapshot --diff .gas-snapshot
will print the gas snapshot difference. This will be helpful when you're doing gas optimizations.
...
...
...
testCumulativePrices() (gas: 604 (0.181%))
testPairFor() (gas: 82 (0.490%))
testRemoveLiquidity() (gas: 43934 (2.252%))
testAddLiquidityAmountBOptimalIsOk() (gas: 43872 (2.255%))
testAddLiquidityNoPair() (gas: 43916 (2.270%))
testSwapExactTokensForTokens() (gas: 88334 (2.275%))
testSwapTokensForExactTokens() (gas: 88334 (2.275%))
testCreatePairPairExists() (gas: 43682 (2.551%))
testCreatePair() (gas: 43660 (2.555%))
Overall gas change: 660823 (1.887%)
5. Using modifier for consistent and cleaner tests:
Instead of doing this:
function testDeposit() public {
vm.startPrank(admin);
// do something
vm.stopPrank();
}
Do this:
modifier prank(address caller) {
vm.startPrank(caller);
_;
vm.stopPrank();
}
function testDeposit() public prank(user) {}
function testWithdraw() public prank(user){}
6. Pin block number during fork tests for better speed:
Add a block number when running fork tests. Foundry caches data so when you run the tests for the second time it actually runs much faster.
vm.createSelectFork(vm.envString("MAINNET_RPC"), <BLOCK_NUMBER>);
7. Group tests by type:
It's always a good practice to group tests by their type. To do so, you can append your test contracts with the test type: UnitTests, ForkTests, IntegrationTests, etc., It helps us to only run certain type of tests rather than the entire suite.
contract Pool_UnitTests {}
contract Pool_ForkTests{}
contract Factory_UnitTests{}
And you can run specific tests by using the --match-contract
flag:
forge test --mc UnitTests # Run unit tests only
forge test --nmc ForkTests # Run tests other than fork tests
This will be helpful when writing CI test workflows as well.
8. Run tests with --summary
flag:
If your test suite is quite big, and you don't wanna see the verbose test results in your terminal, you can add --summary
flag to get a clean and concise layout of your test results.
9. Use makeAddr()
to create random addresses:
If you want to create random addresses (for simulating users, etc), you can use makeAddr()
cheatcode. It also set labels to those addresses which makes it easy to debug as the stack traces would be more readable with labels.
function setUp() public {
user1 = makeAddr("user1");
user2 = makeAddr("user2");
}
10. Use recordLogs()
cheatcode to test event data:
You can do the lot of tests with your event data by using vm.recordLogs()
to record events and vm.getRecordedLogs()
to retrieve them. Indexed data resides in topics
array, non-indexed data resides in the data
field of Vm.Log
struct.
11. Add alias for forge test command:
Copy & paste this into your .bashrc
or .zshrc
file:
test() {
if [ $# -eq 1 ]; then
forge test -vvv --match-test $1
elif [ $# -eq 0 ]; then
forge test -vvv
fi
}
Now you can type ftest
to run all forge tests, or ftest [string]
for matching.
Credits to @zachobront
12. View code coverage reports in VSCode code editor:
- Install the Coverage Gutters extension.
- Run
forge coverage --report lcov
- Click on
Display Coverage
option to view the coverage report visually in your VSCode editor
13. You can provide inline config for fuzz/invariant tests:
You can set the number of fuzz runs or invariant runs for a specific test using in-line test config statements. This is especially useful if you have a test that takes a lot of time to run with the global config params. Credits: [@beskay][https://twitter.com/beskay0x]
14. Easily generate the list of function and error selectors:
You can find the function selectors for a given function or error using cast sig <FUNC_SIG>
but do you know that forge can list all the selectors from the current project?
Run forge selectors list
to see the entire list of selectors neatly segregated by the contract name.
ZuniswapV2Library
+----------+---------------------------------------+------------+
| Type | Signature | Selector |
+===============================================================+
| Function | getAmountIn(uint256,uint256,uint256) | 0x85f8c259 |
|----------+---------------------------------------+------------|
| Function | getAmountOut(uint256,uint256,uint256) | 0x054d50d4 |
|----------+---------------------------------------+------------|
| Function | quote(uint256,uint256,uint256) | 0xad615dec |
|----------+---------------------------------------+------------|
| Error | InsufficientAmount() | 0x5945ea56 |
|----------+---------------------------------------+------------|
| Error | InsufficientLiquidity() | 0xbb55fd27 |
|----------+---------------------------------------+------------|
| Error | InvalidPath() | 0x20db8267 |
+----------+---------------------------------------+------------+
15. Use appropriate revert statements for different scenarios:
Credits: @Zaevlad
16. Auto-format code on-save in VSCode:
There is no watch mode for forge formatter for now. So you can install the Run on Save extension and add the below config in .vscode/settings.json
to auto-format your contracts as you save.
Credits: MorphoLabs
{
"[solidity]": {
"editor.formatOnSave": false
},
"emeraldwalk.runonsave": {
"commands": [
{
"match": ".sol",
"isAsync": true,
"cmd": "forge fmt ${file}"
},
]
}
}
17. Conditionally skip tests using vm.skip()
:
At times you might wanna conditionally ignore some tests for specific reasons.
For ex, if rpc url is not set, fork tests shall be skipped rather than errored. So in-order to do so, we can use the vm.skip(bool)
cheatcode as the first line of your test function to skip it. If you add it to the setup()
method, the entire test suite will be skipped.