Foundry

Installation

(For Linux and MacOS Users)

Install foundryup:

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

This will download foundryup. Then install Foundry by running:

foundryup

Basic Commands

To start a new project with Foundry:

forge init hello_foundry

Compile Solidity code:

forge build

Run Solidity tests:

forge test -vvvv 

Install Dependencies

forge install transmissions11/solmate
forge install openzeppelin/openzeppelin-contracts
forge install smartcontractkit/chainlink-brownie-contracts

Generate remappings for installed libraries

forge remappings > remappings.txt

Update Dependencies

forge update lib/<dependency-name>

Unit Testing

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;

import "forge-std/Test.sol";
import "../src/SampleContract.sol";
import "forge-std/console.sol";

// vm deployer address: 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84

abstract contract StateZero is Test {
    SampleContract internal sampleContract; 
    address alice;
    address bob;
    
    function setUp() public virtual {
        sampleContract = new SampleContract();
        alice = address(0x1);
        bob = address(0x2);

        vm.label(alice, "alice");
        vm.label(bob, "bob");
    }
} 

contract StateZeroTest is StateZero {
    function testChangeStateOne() public {} 
    
    function testChangStateOneReverts() public {
         vm.expectRevert(bytes("revert message"));
         // call function that is intended to revert here 
    }
    
    function testChangStateOneEmitsEvent() public {
         vm.expectEmit(true, true, true, true);
         // 1. emit the event with expected values 
         // 2. call function that is intended to emit the event  
    }
} 

abstract contract StateOne is StateZero {
    function setUp() public virtual override {
        // run initial set-up function from StateZero 
        super.setUp();
        
        // function that changes state from zero to one
        sampleContract.changeStateOne(); 
    }
}

contract StateOneTest is StateOne {
    function testChangeStateTwo() public {} 
    
} 

Fuzz Testing

Fuzz testing is done by using a range of randomized possible inputs to test for edge cases, instead of just testing it with select inputs as in unit testing.

Foundry lets us do this natively.

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

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

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
    }

    // Fuzz test for setNumber function
    function testFuzz_SetNumber(uint256 x) public {
        counter.setNumber(x);
        assertEq(counter.number(), x);
    }

    // Fuzz test for multiple increment operations
    function testFuzz_Increment(uint8 numberOfIncrements) public {
        uint256 startingValue = counter.number();
        
        for (uint i = 0; i < numberOfIncrements; i++) {
            counter.increment();
        }
        
        assertEq(counter.number(), startingValue + numberOfIncrements);
    }

    // Fuzz test for combining setNumber and increment
    function testFuzz_SetAndIncrement(uint256 x, uint8 incrementTimes) public {
        counter.setNumber(x);
        
        uint256 expectedValue = x;
        for (uint i = 0; i < incrementTimes; i++) {
            // Check for potential overflow
            if (expectedValue < type(uint256).max) {
                counter.increment();
                expectedValue++;
            } else {
                // If we would overflow, don't increment anymore
                break;
            }
        }
        
        assertEq(counter.number(), expectedValue);
    }
}

To run these fuzz tests, use:

forge test --match-test testFuzz

Additional Security

Checking Test Coverage

forge coverage --report summary

Static Analysis (Slither)

Invariant Testing

Deployment

Create .env file in root folder:

PRIVATE_KEY=
ETHERSCAN_KEY=
GOERLI_RPC_URL=

Update foundry.toml:

[rpc_endpoints]
goerli = "${GOERLI_RPC_URL}"

[etherscan]
goerli = { key = "${ETHERSCAN_KEY}" }

SampleContract.s.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;

import "../src/SampleContract.sol";
import "forge-std/Script.sol";

contract SampleContractScript is Script {
    function setUp() public {}

    function run() public {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);
        
        SampleContract contract = new SampleContract();        
        vm.stopBroadcast();
    }
}

To run the above script:

forge script script/SampleContract.s.sol

To deploy SampleContract to a live testnet, run the following in your terminal:

forge script script/SampleContract.s.sol:SampleContractScript --rpc-url $GOERLI_RPC_URL --broadcast \

Verification

To verify an existing contract:

forge verify-contract --chain-id 5 --num-of-optimizations 1000000 --watch \
--compiler-version v0.8.16+commit.fc410830 <the_contract_address> \
src/SampleContract.sol:SampleContract <your_etherscan_api_key>

Check verification status:

forge verify-check --chain-id 5 <GUID> <your_etherscan_api_key>

References:

https://book.getfoundry.sh/tutorials/solidity-scripting#deploying-our-contract

Last updated