/solidity-fuzzing-boilerplate

Template repository intended to ease fuzzing components of Solidity projects, especially libraries.

Primary LanguageSolidityMIT LicenseMIT

Fuzzing of PROJECT_NAME_HERE

DESCRIPTION_HERE

src
│
├── echidna.yaml       # Configuration file for Echidna.
├── foundry.toml       # Configuration file for Foundry.
├── build.sh           # Buildscript for downloading, compiling, initializing, ...
├── implementation     # Implementations to fuzz (downloaded by buildscript).
│   └── ...
├── expose             # Expose functions of libraries for tests.
│   └── ...
├── interface          # Interfaces for accessing exposers with incompatible Solidity versions.
│   └── ...
└── test               # Actual fuzzing testcases.
    ├── ...
    ├── addresses.sol  # Addresses of incompatible libs, generated by buildscript.
    └── helpers.sol    # Reusable helper functions for tests.

Setup

Before any fuzzing can be run, build.sh needs to be executed, which has the following dependencies:

After the buildscript was successfully executed, the implementation directory should be populated, there'll be a echidna-init.json file and a ganache instance will still be running in the background.

Running Echidna Fuzzing

# Simple fuzzing with Echidna:
echidna-test --contract Test --config echidna.yaml src/test/example/BytesLib.sol

# Differential fuzzing against another implementation with incompatible Solidity version via initialization file:
echidna-test --contract Test --config echidna.yaml src/test/example/BytesLib-BytesUtil-diff.sol

# Differential fuzzing against an executable via FFI shell command execution:
echidna-test --contract Test --config echidna.yaml src/test/example/BytesLib-FFI-diff.sol

*The FFI cheatcode is experimental in Echidna and only available when compiling with PR#750

Running Foundry Fuzzing

# Simple fuzzing with Foundry:
forge test --match test_BytesLib_slice

# Differential fuzzing against another implementation with incompatible Solidity version via ganache fork:
forge test --fork-url http://127.0.0.1:8545/ --match-path src/test/example/BytesLib-BytesUtil-diff.sol

# Differential fuzzing against an executable via FFI shell command execution:
forge test --match-path src/test/example/BytesLib-FFI-diff.sol

Note that forge will appear to be stuck, but it's actually running 999999999 tests as configured in foundry.toml. This is intended to be kept running on servers for hours. If you instead want to run quick tests, eg. for CI, adjust the configuration according to your needs.

Reproducing a finding / Manual testing

# Call function of exposed library and show execution trace:
forge run --sig "slice(bytes,uint256,uint256)" --target-contract ExposedBytesLib -vvvv src/expose/example/BytesLib.sol 0x010203 1 1

# Manually execute a testcase to reproduce an issue:
forge run --fork-url http://127.0.0.1:8545/ --sig "test_BytesLib_BytesUtil_diff_slice(bytes,uint256,uint256)" --target-contract Test -vvvv src/test/example/BytesLib-BytesUtil-diff.sol 0x010203 1 1
✂ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SNIP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Solidity Fuzzing Boilerplate

This is a template repository intended to ease fuzzing components of Solidity projects, especially libraries.

  • Write tests once and run them with both Echidna and Foundry's fuzzing.
  • Fuzz components that use incompatible Solidity versions by deploying those into a Ganache instance via Etheno.
  • Use HEVM's FFI cheatcode to generate complex fuzzing inputs or to compare outputs with non-EVM executables while doing differential fuzzing.
  • Publish your fuzzing experiments without worrying about licensing by extending the shellscript to download specific files.

How to use the Template

1. Check & adjust configs

Check the echidna.yaml and foundry.toml configuration files.

  • Turn off FFI if you don't intend to make use of shell commands from your Solidity contracts. Note that FFI is slow and should only be used as a workaround. It can be useful for testing against things that are difficult to implement within Solidity and already exist in other languages. But it can also be dangerous: Before executing tests of a project that has FFI enabled, be sure to check what commands are actually being executed. There's nothing stopping someone to write a malicious testcase and execute malware on your computer.
  • Adjust the compiler optimization options to match those of the project you're fuzzing.
  • The default number of test runs configured, assumes that you intend to leave these tests running for a while to find edge cases, eg. on servers. Reduce the numbers accordingly if you only want to run quick tests.
  • Adjust things like sequence lengths when fuzzing contracts that have a state (where a previous transaction can impact the next one).

2. Adjust Buildscript

Edit the build.sh file and adjust it for your usecase

  • Fetch the implementations that you want to apply fuzzing on.
  • During RECORDing, deploy contracts of incompatible Solidity versions in order to access them during tests.
  • If you're not dealing with any contracts of incompatible versions, you can simply omit the RECORD and DEPLOY calls.

3. Create testcases and exposers/interfaces as needed

Take a look at the example testcases and write your own.

4. Adjust the README.md

Don't forget to document the intention, setup and commands for your fuzzing campaign.