🌱
Dev Compendium
  • Ethereum
    • Solidity
      • EVM
      • Architecture
      • Execution Context
      • Transactions
      • Gas
      • Calldata, Memory & Storage
      • Gas Optimisation
      • Function Declarations
      • receive() & fallback()
      • CALL vs. DELEGATE CALL
    • Yul
      • Introduction
      • Types
      • Basic Operations
      • Storage
      • Memory
        • Arrays
        • Structs
        • Tuples, revert, keccak256
        • Logs and Events
        • Gotchas
        • abi.encode
      • Calldata
        • External Calls
        • Dynamic Length Inputs
        • Transferring Value
        • Receiving Contract Calls
      • Contracts in Yul
      • Other Yul Functions
    • Foundry
    • Security
      • Common Vulnerabilities
      • Best Practices
      • Development Workflow
      • Contract Migration
    • Auditing Tools
      • Slither
      • Mythril
      • Fuzzing
    • Upgradable Contracts
      • Upgrade Patterns
      • ERC-1967 Implementation
      • Deployment
    • MEV
    • Tooling
      • Chainlink
      • IPFS
      • Radicle
    • Frontend
      • Contract Hooks
      • Wallet Connection
        • wagmi.sh
        • Rainbow Kit
      • thirdweb
    • Protocol Research
      • Uniswap v2
      • Uniswap v3
      • Curve
      • GMX
  • Starkware
    • Fundamentals
    • Account Abstraction
    • Universal Deployer
    • Cairo 1.0
    • starknet.js
    • Security Model
  • Zero Knowledge
    • Group Theory
    • ECDSA
  • Rust
    • Basic Operations
    • Set up
    • Primitives
    • Control Flow
    • Mutability & Shadowing
    • Adding Behavior
    • Lifetimes
    • Std Library
  • SUI
    • Architecture
    • Consensus Mechanism
    • Local Node Setup
    • Sui Client CLI
    • Move Contracts
      • Move
      • Move.toml
      • Move.lock
      • Accessing Time in Sui Move
      • Set up Development Framework
      • Debug & Publish
      • Package Upgrades
      • Sui Move Library
      • Difference from Core Move
    • Object Programming
      • Object Basics
      • Using Objects
      • Immutable Objects
      • Object Wrapping
      • Dynamic Fields
      • Collections
      • Unit Testing
      • Deployment with CLI
  • NEAR
    • Architecture
    • Contract Standards
      • Fungible Token (NEP-141)
      • Non-Fungible Token (NEP-171)
      • Storage Management (NEP-145)
      • Events (NEP-297)
      • Meta-Transactions
    • Rust Contracts
      • Development Workflow
      • Smart Contract Layout
      • Storage Management
      • Events & Meta-transactions
      • Method Types
      • Upgrading Contracts
      • Unit Testing
    • NEAR Libraries
    • Environment Variables
    • Serialisation
    • Security Concepts
    • Collections
    • JS SDK
Powered by GitBook
On this page
  • Installation
  • Basic Commands
  • Install Dependencies
  • Unit Testing
  • Fuzz Testing
  • Additional Security
  • Checking Test Coverage
  • Static Analysis (Slither)
  • Invariant Testing
  • Deployment
  • Verification
  1. Ethereum

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 {} 
    
} 
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;

import "forge-std/console2.sol";
import "openzeppelin-contracts/contracts/access/Ownable.sol";
import "openzeppelin-contracts/contracts/utils/Counters.sol";

contract SampleContract {
    // initialising new counter variable 
    using Counters for Counters.Counter;
    Counters.Counter public someId; 
    
    .... 

} 

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);
    }
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}

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:

PreviousOther Yul FunctionsNextSecurity

Last updated 3 days ago

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