Skip to content

Latest commit

 

History

History
 
 

forge

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

forge

Forge is a fast and flexible Ethereum testing framework, inspired by Dapp

If you are looking into how to consume the software as an end user, check the CLI README.

For more context on how the package works under the hood, look in the code docs.

Why?

Write your tests in Solidity to minimize context switching

Writing tests in Javascript/Typescript while writing your smart contracts in Solidity can be confusing. Forge lets you write your tests in Solidity, so you can focus on what matters.

contract Foo {
    uint256 public x = 1;
    function set(uint256 _x) external {
        x = _x;
    }

    function double() external {
        x = 2 * x;
    }
}

contract FooTest {
    Foo foo;

    // The state of the contract gets reset before each
    // test is run, with the `setUp()` function being called
    // each time after deployment.
    function setUp() public {
        foo = new Foo();
    }

    // A simple unit test
    function testDouble() public {
        require(foo.x() == 1);
        foo.double();
        require(foo.x() == 2);
    }
}

Fuzzing: Go beyond unit testing

When testing smart contracts, fuzzing can uncover edge cases which would be hard to manually detect with manual unit testing. We support fuzzing natively, where any test function that takes >0 arguments will be fuzzed, using the proptest crate.

An example of how a fuzzed test would look like can be seen below:

function testDoubleWithFuzzing(uint256 x) public {
    foo.set(x);
    require(foo.x() == x);
    foo.double();
    require(foo.x() == 2 * x);
}

Features

  • test
    • Simple unit tests
      • Gas costs
      • DappTools style test output
      • JSON test output
      • Matching on regex
      • DSTest-style assertions support
    • Fuzzing
    • Symbolic execution
    • Coverage
    • HEVM-style Solidity cheatcodes
    • Structured tracing with abi decoding
    • Per-line gas profiling
    • Forking mode
    • Automatic solc selection
  • build
    • Can read DappTools-style .sol.json artifacts
    • Manual remappings
    • Automatic remappings
    • Multiple compiler versions
    • Incremental compilation
    • Can read Hardhat-style artifacts
    • Can read Truffle-style artifacts
  • install
  • update
  • debug
  • CLI Tracing with RUST_LOG=forge=trace

Cheat codes

The below is modified from Dapp's README

We allow modifying blockchain state with "cheat codes". These can be accessed by calling into a contract at address 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, which implements the following methods:

  • function warp(uint x) public Sets the block timestamp to x.

  • function roll(uint x) public Sets the block number to x.

  • function store(address c, bytes32 loc, bytes32 val) public Sets the slot loc of contract c to val.

  • function load(address c, bytes32 loc) public returns (bytes32 val) Reads the slot loc of contract c.

  • function sign(uint sk, bytes32 digest) public returns (uint8 v, bytes32 r, bytes32 s) Signs the digest using the private key sk. Note that signatures produced via hevm.sign will leak the private key.

  • function addr(uint sk) public returns (address addr) Derives an ethereum address from the private key sk. Note that hevm.addr(0) will fail with BadCheatCode as 0 is an invalid ECDSA private key.

  • function ffi(string[] calldata) external returns (bytes memory) Executes the arguments as a command in the system shell and returns stdout. Note that this cheatcode means test authors can execute arbitrary code on user machines as part of a call to dapp test, for this reason all calls to ffi will fail unless the --ffi flag is passed.

  • function deal(address who, uint256 amount): Sets an account's balance

  • function etch(address where, bytes memory what):` Sets the contract code at some address contract code

  • function prank(address from, address to, bytes calldata) (bool success,bytes retdata): Performs a smart contract call as another address

The below example uses the warp cheatcode to override the timestamp:

interface Vm {
    function warp(uint256 x) external;
}

contract MyTest {
    Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function testWarp() public {
        vm.warp(100);
        require(block.timestamp == 100);
    }

Future Features

Dapptools feature parity

Over the next months, we intend to add the following features which are available in upstream dapptools:

  1. Stack Traces: Currently we do not provide any debug information when a call fails. We intend to add a structured printer (something like this which will show all the calls, logs and arguments passed across intermediate smart contract calls, which should help with debugging.
  2. Invariant Tests
  3. Interactive Debugger
  4. Code coverage
  5. Gas snapshots
  6. Symbolic EVM

Unique features?

We also intend to add features which are not available in dapptools:

  1. Even faster tests with parallel EVM execution that produces state diffs instead of modifying the state
  2. Improved UX for assertions:
    1. Check revert error or reason on a Solidity call
    2. Check that an event was emitted with expected arguments
  3. Support more EVM backends (revm, geth's evm, hevm etc.) & benchmark performance across them
  4. Declarative deployment system based on a config file
  5. Formatting & Linting (maybe powered by Solang)
    1. dapp fmt, an automatic code formatter according to standard rules (like prettier-plugin-solidity)
    2. dapp lint, a linter + static analyzer, like a combination of solhint and slither
  6. Flamegraphs for gas profiling