foundry-testing
ALWAYS load before writing or modifying Foundry test files (.t.sol). Covers fuzz testing, gas benchmarks, naming conventions, and test patterns.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Foundry Testing Best Practices
Fuzz Testing Strategy
- Fuzz function inputs by default - When a test function has parameters, Foundry automatically fuzzes them
-
Use
bound()overvm.assume- Preferbound(value, min, max)to constrain inputs;vm.assumediscards runs and wastes iterations - Apply Equivalence Class Partitioning - Only fuzz parameters that affect the code path under test. If a parameter doesn't change behavior, use a fixed representative value
- Maximize bounds - Use the widest reasonable bounds to maximize coverage
// Good: bound with max range
amount = bound(amount, 1, type(uint96).max);
// Avoid: vm.assume wastes fuzz runs
vm.assume(amount > 0 && amount < type(uint96).max);
Test Organization
-
Gas benchmarks use
_gassuffix - Enables filtering withforge test --mt gas -
Pattern:
test_foo_gas()callstest_foo(fixed, values)for deterministic gas measurement - Tests can return values for composition and reuse
- Group tests by functionality with section headers
function test_stake_gas() public returns (uint256 depositId) {
depositId = test_stake(address(this), 1 ether, OPERATOR, 0, address(this));
}
function test_stake(
address depositor,
uint96 amount,
address operator,
uint256 commissionRate,
address beneficiary
) public givenOperator(operator, commissionRate) returns (uint256 depositId) {
// Implementation...
}
Modularity
-
Extract setup as modifiers - e.g.,
givenOperator(operator, rate),givenSpaceHasPointedToOperator(space, operator) - Decouple unit tests - Avoid complex dependencies; simpler tests are easier to fuzz
- Use harness contracts for testing internal functions
contract MyContractHarness is MyContractBase {
function internalFunction(uint256 x) external pure returns (uint256) {
return _internalFunction(x);
}
}
Coverage Requirements
-
Verify events with
vm.expectEmit -
Verify reverts with
vm.expectRevert - Test storage slots - Verify EIP-7201 namespaced storage constants
- Cover critical paths - All state transitions, edge cases, and error conditions
// Event verification
vm.expectEmit(address(contract));
emit SomeEvent(expectedArg1, expectedArg2);
contract.someFunction();
// Revert verification
vm.expectRevert(SomeError.selector);
contract.shouldRevert();
// Storage slot verification (EIP-7201)
function test_storageSlot() public pure {
bytes32 slot = keccak256(
abi.encode(uint256(keccak256("namespace.storage")) - 1)
) & ~bytes32(uint256(0xff));
assertEq(slot, MyStorage.STORAGE_SLOT);
}
Naming Convention
| Pattern | Usage |
|---|---|
test_functionName(params) |
Fuzz test (default when has parameters) |
test_functionName_gas() |
Gas benchmark with fixed inputs |
test_functionName_revertIf_Condition |
Revert condition tests |
test_functionName_EdgeCase |
Specific edge case tests |
Gas Benchmarking Workflow
To compare gas usage before/after code changes:
# 1. Stash source changes (keep test changes)
git stash push -m "optimization" -- src/path/to/Changed.sol
# 2. Run baseline snapshot (--mt gas filters to _gas suffix tests)
forge snapshot --mc TestContract --mt gas
# 3. Pop stash to restore changes
git stash pop
# 4. Run diff against baseline
forge snapshot --mc TestContract --mt gas --diff
This workflow:
- Keeps new
_gastest functions while measuring the old implementation - Shows gas delta for each test with percentage change
- Reports overall gas change summary
Reference Examples
- RewardsDistributionV2.t.sol - Comprehensive test patterns
- SubscriptionModuleUnit.t.sol - Harness pattern for internal functions
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!