Skip to main content

foundry-testing

Use when writing Solidity tests with Foundry/Forge. Covers cheatcodes, assertions, fork testing, gas reporting, event testing, and test organization patterns.

Jump to install

Source facts

Repository
ccashwell/evm-cortex
Last source activity
April 10, 2026 at 16:31
Detected SKILL.md language
English
Stars
131
Forks
18

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
foundry-testing
description
Use when writing Solidity tests with Foundry/Forge. Covers cheatcodes, assertions, fork testing, gas reporting, event testing, and test organization patterns.
# Foundry Testing Patterns ## Test File Template ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test, console2} from "forge-std/Test.sol"; import {MyContract} from "../src/MyContract.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract MyContractTest is Test { MyContract public target; address public alice = makeAddr("alice"); address public bob = makeAddr("bob"); address public deployer = makeAddr("deployer"); function setUp() public { vm.startPrank(deployer); target = new MyContract(); vm.stopPrank(); vm.deal(alice, 100 ether); vm.deal(bob, 100 ether); } function test_basicOperation() public { vm.prank(alice); target.doSomething(42); assertEq(target.value(), 42); } function test_revertOnUnauthorized() public { vm.prank(bob); vm.expectRevert("unauthorized"); target.adminFunction(); } } ``` ## Essential Cheatcodes ### Identity & Balance ```solidity // Set msg.sender for next call vm.prank(alice); // Set msg.sender for all subsequent calls until stopPrank() vm.startPrank(alice); // ... multiple calls as alice vm.stopPrank(); // Set ETH balance vm.deal(alice, 100 ether); // Set ERC20 balance (writes to storage slot) deal(address(usdc), alice, 1_000_000e6); // Create labeled address address alice = makeAddr("alice"); // Create address with private key (address signer, uint256 pk) = makeAddrAndKey("signer"); ``` ### Time & Block Manipulation ```solidity // Set block.timestamp vm.warp(1700000000); // Set block.number vm.roll(18_000_000); // Skip forward in time skip(1 days); // Rewind time rewind(1 hours); ``` ### Revert Expectations ```solidity // Expect next call to revert with message vm.expectRevert("insufficient balance"); target.withdraw(1000); // Expect revert with custom error vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector, 1000, 500)); target.withdraw(1000); // Expect revert with no message check vm.expectRevert(); target.failingFunction(); ``` ### Event Expectations ```solidity // expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData) vm.expectEmit(true, true, false, true); emit Transfer(alice, bob, 100); token.transfer(bob, 100); // Alternative: expectEmit on specific emitter vm.expectEmit(address(token)); emit Transfer(alice, bob, 100); token.transfer(bob, 100); ``` ### Storage Manipulation ```solidity // Read storage slot bytes32 value = vm.load(address(target), bytes32(uint256(0))); // Write storage slot vm.store(address(target), bytes32(uint256(0)), bytes32(uint256(42))); // Record storage accesses vm.record(); target.doSomething(); (bytes32[] memory reads, bytes32[] memory writes) = vm.accesses(address(target)); ``` ### Snapshots ```solidity // Save state uint256 snapshot = vm.snapshotState(); // Do some operations target.modifyState(); // Revert to snapshot vm.revertToState(snapshot); ``` ## Fork Testing ```solidity function setUp() public { // Fork mainnet at latest block uint256 forkId = vm.createFork("mainnet"); vm.selectFork(forkId); // Or fork at specific block uint256 pinnedFork = vm.createFork("mainnet", 19_000_000); } function test_withFork() public { // Interact with real deployed contracts IERC20 usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); uint256 balance = usdc.balanceOf(0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503); // Binance assertGt(balance, 0); } ``` ## Gas Reporting ```solidity // Label addresses for gas reports vm.label(address(target), "MyContract"); vm.label(address(token), "USDC"); // Measure gas manually uint256 gasBefore = gasleft(); target.expensiveOperation(); uint256 gasUsed = gasBefore - gasleft(); console2.log("Gas used:", gasUsed); ``` Run with: `forge test --gas-report` ## Test Organization ``` test/ ├── unit/ │ ├── MyContract.t.sol # Unit tests │ └── MyToken.t.sol ├── integration/ │ ├── AaveIntegration.t.sol # Protocol integration │ └── UniswapSwap.t.sol ├── invariant/ │ ├── handlers/Handler.sol # Invariant handlers │ └── MyContract.invariant.t.sol ├── fork/ │ └── MainnetFork.t.sol # Fork tests └── helpers/ ├── BaseTest.sol # Shared setUp └── Tokens.sol # Token deployment helpers ``` ## Common Patterns ```solidity // Bound fuzz inputs to valid range function testFuzz_deposit(uint256 amount) public { amount = bound(amount, 1e18, 1_000_000e18); // ... } // Test with multiple actors function test_multipleUsers() public { for (uint256 i = 0; i < 10; i++) { address user = makeAddr(string.concat("user", vm.toString(i))); vm.deal(user, 1 ether); vm.prank(user); target.deposit{value: 1 ether}(); } } // Expect multiple events in sequence vm.expectEmit(true, true, false, true); emit Deposit(alice, 100); vm.expectEmit(true, true, false, true); emit BalanceUpdated(alice, 100); target.deposit(100); ``` ## Checklist - [ ] `setUp()` creates clean state for each test - [ ] Use `makeAddr()` for labeled test addresses - [ ] Use `vm.expectRevert()` to test failure paths - [ ] Use `vm.expectEmit()` to verify events - [ ] Fork tests pinned to specific block numbers for reproducibility - [ ] Gas report generated for critical functions - [ ] Fuzz tests use `bound()` to constrain inputs - [ ] Labels applied to addresses for readable traces
View on GitHub