iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Foundry

Foundry is the modern Rust-based Ethereum toolchain: `forge` for build and test, `cast` for chain interaction, `anvil` for a local node. Tests are written in Solidity — same language as your contracts — which removes a layer of impedance vs Hardhats TypeScript tests and makes fuzz/invariant testing first-class.

Forge tests, fuzz tests, and a cast call

EXAMPLE
// src/Token.sol — same contract as a Hardhat example, simpler to test in Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Token {
    string  public name = 'DemoToken';
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(uint256 initialSupply) {
        totalSupply = initialSupply;
        balanceOf[msg.sender] = initialSupply;
        emit Transfer(address(0), msg.sender, initialSupply);
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        require(balanceOf[msg.sender] >= amount, 'insufficient');
        unchecked { balanceOf[msg.sender] -= amount; }
        balanceOf[to] += amount;
        emit Transfer(msg.sender, to, amount);
        return true;
    }
}

// test/Token.t.sol
pragma solidity ^0.8.24;
import 'forge-std/Test.sol';
import { Token } from '../src/Token.sol';

contract TokenTest is Test {
    Token token;
    address alice = makeAddr('alice');
    address bob   = makeAddr('bob');

    function setUp() public {
        token = new Token(1_000 ether);
    }

    function test_initialSupplyOnDeployer() public {
        assertEq(token.balanceOf(address(this)), 1_000 ether);
    }

    function test_transferEmitsAndUpdates() public {
        vm.expectEmit(true, true, false, true);
        emit Token.Transfer(address(this), alice, 10 ether);
        token.transfer(alice, 10 ether);
        assertEq(token.balanceOf(alice), 10 ether);
    }

    // FUZZ TEST: 'amount' is generated for each run; vm.assume filters
    // pathological cases. Forge runs hundreds of cases by default.
    function testFuzz_transferConservesSupply(uint96 amount) public {
        vm.assume(amount <= 1_000 ether);
        token.transfer(alice, amount);
        assertEq(
            token.balanceOf(address(this)) + token.balanceOf(alice),
            1_000 ether,
            'supply must be conserved'
        );
    }

    function test_RevertWhen_InsufficientBalance() public {
        vm.prank(alice);                  // act as alice for this call
        vm.expectRevert('insufficient');
        token.transfer(bob, 1 ether);
    }
}

// Run it
// forge test
// forge test --gas-report
// forge test --match-test testFuzz -vvv

// Local node + interact via cast
// anvil &                                    # starts a local chain on :8545
// forge create src/Token.sol:Token --constructor-args 1000000000000000000000 \\
//   --rpc-url http://localhost:8545 --private-key 0xac0974...
// cast call $ADDR 'balanceOf(address)(uint256)' $DEPLOYER --rpc-url http://localhost:8545
// cast send $ADDR 'transfer(address,uint256)' $ALICE 10000000000000000000 \\
//   --private-key 0xac0974... --rpc-url http://localhost:8545

Why it matters

Foundrys fuzzer is the single biggest reason to learn it. testFuzz_* functions catch invariants you would never test by hand — supply conservation, monotonicity, decimal precision bugs — and they run in seconds. Add them to every contract that handles money.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// foundry.toml
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
# Commands:
#   forge test -vvv
#   forge build
#   forge create --rpc-url $RPC --private-key $PK src/MyToken.sol:MyToken
Try it Yourself »

Test yourself

Q1. Foundry tests are written in…
Q2. Run the test suite with…
Q3. Foundry was built by…

Discussion

Loading…