Forge Testing

Forge is the testing and deployment subsystem of the Foundry framework — providing a battery-testing environment for Ethereum smart contracts written in Solidity, supporting three distinct testing methodologies (unit testing, fuzz testing, and invariant testing) that together cover different classes of bugs and vulnerabilities. Where traditional smart contract test suites used JavaScript or Python test runners that abstract away from the actual execution environment, Forge tests are written in Solidity and run in an isolated Forge EVM — meaning the same language, the same opcodes, the same gas semantics as the actual deployed contract, eliminating translation errors where JS test behavior diverges from actual EVM behavior. Unit tests validate specific functions with specific inputs; fuzz tests automatically generate thousands of random inputs to find edge cases the developer didn’t manually write; invariant tests (property-based testing) define properties that must always be true and bombard the contract with random sequences of function calls looking for violations. Together these three testing modes are considered professional-grade smart contract validation — most audit firms now require Foundry test coverage as a baseline before auditing.


Test Types

The following sections cover this in detail.

Unit Tests

contract MyTest is Test {

MyContract c;

function setUp() public {

c = new MyContract();

}

function test_BasicTransfer() public {

vm.prank(alice); // next call is from alice

c.transfer(bob, 100);

assertEq(c.balanceOf(bob), 100);

}

function test_RevertOnZeroAmount() public {

vm.expectRevert(“Amount cannot be zero”);

c.transfer(bob, 0);

}

}

“`

Fuzz Tests

// Forge automatically generates random amount inputs

function testFuzz_TransferNeverExceedsBalance(uint256 amount) public {

vm.assume(amount > 0); // discard invalid inputs

vm.assume(amount <= alice.balance);

uint256 before = c.balanceOf(bob);

c.transfer(bob, amount);

assertEq(c.balanceOf(bob), before + amount);

assertEq(c.balanceOf(alice), alice.balance – amount);

}

“`

Invariant Tests

// Forge calls random sequences of functions on c

// Then checks this invariant after EACH call

function invariant_TotalSupplyAlwaysConstant() public {

assertEq(

c.totalSupply(),

INITIAL_SUPPLY,

“Total supply changed unexpectedly”

);

}

“`


Fuzz Testing Deep Dive

How Forge fuzzes:

  1. Generates random bytes → decodes to function parameter types
  2. Runs the test with those inputs
  3. If test fails: counterexample shrinking finds the minimal failing input
  4. Default: 256 runs per fuzz test; configurable via foundry.toml

vm.assume() vs. bounded inputs:

“`solidity

// assume: skip runs where condition fails (can waste budget)

vm.assume(x != 0);

// bound: clamp input to valid range (more efficient)

x = bound(x, 1, type(uint256).max);

“`

Fuzz corpus persistence: Forge saves failing inputs; reruns them first in future runs


Invariant Testing Deep Dive

Components:

  • Target contracts: which contracts Forge calls randomly
  • Selector filtering: which functions to include/exclude
  • Invariant function: the property that must hold

“`solidity

function setUp() public {

c = new MyContract();

targetContract(address(c)); // Forge fuzzes this contract

}

// Forge: 100 campaigns × up to 500 calls per campaign

// Each campaign: random function calls from random senders

// After EACH call: assert this invariant

function invariant_SolvencyAlwaysHolds() public {

assertGe(

address(c).balance,

c.getTotalDebt(),

“Protocol is insolvent”

);

}

“`


Key Cheatcodes in Testing

Cheatcode Purpose
vm.prank(address) Next call impersonates address
vm.startPrank(addr) All following calls from addr
vm.deal(addr, amount) Set ETH balance
vm.warp(timestamp) Set block.timestamp
vm.roll(blockNum) Set block.number
vm.expectRevert(bytes) Assert next call reverts
vm.expectEmit(...) Assert next call emits event
vm.store(addr, slot, val) Write raw storage slot
vm.createFork(url) Create mainnet fork

Related Terms


Sources

  1. “Fuzzing Smart Contracts: Foundations and Forge Implementation” — Trail of Bits / Paradigm (2022). Technical analysis of smart contract fuzzing — covering fuzzer architecture, input generation strategies, coverage-guided vs. black-box fuzzing, and how Forge implements its fuzzer against Solidity contract ABIs.
  1. “Invariant Testing in Foundry: Property-Based Smart Contract Verification” — Paradigm / Foundry Docs (2022-2023). Deep-dive into Foundry’s invariant testing system — covering how Forge generates random function call sequences, the difference between invariant testing and fuzz testing, how to configure target contracts and selectors, and real-world examples of invariant tests that caught production bugs.
  1. “Forge Coverage: Measuring Test Completeness” — Foundry Community / Development Practices (2023). Analysis of Forge’s code coverage capabilities — how –coverage works, interpreting line, statement, branch, and function coverage metrics, why coverage alone is insufficient for security, and how to use coverage reports to identify untested code paths.
  1. “Fork Testing: Testing Against Mainnet State in Forge” — Alchemy / Foundry Documentation (2023). Practical guide to using Forge’s forking capabilities — running tests against forked mainnet state to test contract integrations with DeFi protocols (Uniswap, Aave, Chainlink), using vm.createFork() and vm.selectFork() for multi-fork tests, and performance considerations for fork testing.
  1. “From Zero to 90% Coverage: A Forge Testing Guide for Protocol Security” — Spearbit / Cantina Security (2023). Practical security-focused guide to achieving comprehensive Forge test coverage — covering test setup patterns, how auditors evaluate test suites, the role of fuzzing vs. invariants vs. unit tests in finding different vulnerability classes, and what level of testing is required before professional auditing.