Introduction

Setup

Install foundry toolchain:

curl -L https://foundry.paradigm.xyz | bash
foundryup

If everything goes well, you will now have four binaries at your disposal: forge, cast, anvil, and chisel.

To update foundryup after installation, simply run foundryup again, and it will update to the latest Foundry release. You can also revert to a specific version of Foundry with foundryup -v $VERSION.

First Steps with Foundry

Start a new project:

forge init <project_name>

Suppose we named the project "hello_foundry", then the project layout is:

$ cd hello_foundry
$ tree . -d -L 1
.
├── lib
├── script
├── src
└── test

4 directories

Build the prject:

forge build

Test the project:

forge test

Forge Cheatsheet

Dependency

Install dependency, such as solmate:

forge install transmissions11/solmate

The dependency is going to be installed in the /lib directory.

Forge can remap dependencies to make them easier to import. Forge will automatically try to deduce some remappings for you:

$ forge remappings
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
solmate/=lib/solmate/src/
weird-erc20/=lib/weird-erc20/src/

These remappings mean:

  • To import from forge-std we would write: import "forge-std/Contract.sol";

  • To import from ds-test we would write: import "ds-test/Contract.sol";

  • To import from solmate we would write: import "solmate/Contract.sol";

  • To import from weird-erc20 we would write: import "weird-erc20/Contract.sol";

You can customize these remappings by creating a remappings.txt file in the root of your project.

Update dependency:

forge update lib/solmate

Remove dependency:

forge remove solmate

Tests

Forge will look for the tests anywhere in your source directory. Any contract with a function that starts with test is considered to be a test. Usually, tests will be placed in test/ by convention and end with .t.sol.

Run all tests:

forge test

Run a specific test:

forge test --match-contract ComplicatedContractTest --match-test testDeposit

Inverse versions of these flags also exist (--no-match-contract and --no-match-test).

Match a glob pattern:

forge test --match-path test/ContractB.t.sol

The inverse of the --match-path flag is --no-match-path.

Verbosity:

  • Level 2 (-vv): Logs emitted during tests are also displayed. That includes assertion errors from tests, showing information such as expected vs actual.

  • Level 3 (-vvv): Stack traces for failing tests are also displayed.

  • Level 4 (-vvvv): Stack traces for all tests are displayed, and setup traces for failing tests are displayed.

  • Level 5 (-vvvvv): Stack traces and setup traces are always displayed.

Hello World

Copy the contract from solidity-by-example.org:

// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.17 and less than 0.9.0
pragma solidity ^0.8.17;

contract HelloWorld {
    string public greet = "Hello World!";
}

Compile it:

forge build

Create a test file HelloWorld.t.sol. Copy and paste the content of Counter.t.sol into this new test file and write our own test cases:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/HelloWorld.sol";

contract HelloWorldTest is Test {
    HelloWorld public helloWorld;

    function setUp() public {
        helloWolrd = new HelloWorld();
    }

    function testGreet() public {
        assertEq(helloWorld.greet(), "Hello World!");
    }

}

Note that setup() will be executed before executing each test case, and test case function name must start with the test prefix.

Run test:

forge test

Note that forge test would run all test files in the test/ directory. If you only want to run a single test file:

forge test --match-path test/HelloWorld.t.sol

Let's make the test fail:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/HelloWorld.sol";

contract HelloWorldTest is Test {
    HelloWorld public helloWorld;

    function setUp() public {
        helloWorld = new HelloWorld();
    }

    function testGreet() public {
        assertEq(helloWorld.greet(), "Hello World?");
    }

}

Run test with verbosity marks:

forge test --match-path test/HelloWorld.t.sol -vvv

You can see why the test case failed in the Traces output.

Last updated