Skip to content

racytech/evm-diff-fuzz

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

evm-diff-fuzz

Fork-agnostic differential fuzzer for Ethereum EVM clients.

Mutates StateTest fixtures from ethereum/execution-spec-tests and runs them across multiple EVM clients simultaneously via GoEVMLab's runtest.
Any divergence in state root or execution trace is flagged as a consensus fault.

How it works

  StateTest fixtures          evm-diff-fuzz               GoEVMLab runtest
  (one per JSON file)  ──►  mutate pre-state  ──►  execute across N clients
                                    │                         │
                                    │                   compare traces
                                    │                    & state roots
                                    └──────────────── save fault fixtures
  1. Corpus generation — use fill --single-fixture-per-file in execution-spec-tests to produce one fixture per JSON file for any fork.
  2. Mutation — each fuzzing step applies one random bytecode mutation to every fixture's pre section (PUSH immediate replacement, byte flip/delete, no-op insertion, magic-value injection).
  3. Execution — the mutated fixtures are fed to GoEVMLab's runtest, which runs each fixture through all registered EVM clients in parallel and compares execution traces and final state roots.
  4. Fault collection — files that triggered divergence are moved to a per-step results directory for analysis with GoEVMLab's diffview.

Prerequisites

Tool Purpose
Python ≥ 3.11 + uv Python environment
Go ≥ 1.22 Build runtest and Go-based EVM clients
execution-spec-tests StateFixture types + fill corpus generator
GoEVMLabruntest binary Multi-client execution harness
≥ 2 EVM client binaries Clients under test

Setup

The expected workspace layout (all repos side-by-side):

workspace/
├── evm-diff-fuzz/          # this repo
├── execution-specs/        # ethereum/execution-specs
├── goevmlab/               # holiman/goevmlab
├── go-ethereum/            # ethereum/go-ethereum   (client)
└── evmone/                 # ethereum/evmone        (client)

1. Clone repos

git clone https://github.com/racytech/evm-diff-fuzz
git clone https://github.com/ethereum/execution-specs
git clone https://github.com/holiman/goevmlab
# Clone at least two EVM clients:
git clone https://github.com/ethereum/go-ethereum
git clone https://github.com/ethereum/evmone

2. Install Python dependencies

# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc   # or restart your shell

# Install evm-diff-fuzz (resolves ethereum-execution-testing from ../execution-specs)
cd evm-diff-fuzz
uv sync

# Install execution-specs deps (needed for the `fill` command)
cd ../execution-specs
uv sync

The pyproject.toml points ethereum-execution-testing at ../execution-specs/packages/testing via a uv path source. Adjust the path if your checkout is elsewhere.

3. Build the runtest binary

cd goevmlab
go build -o $HOME/go/bin/runtest ./cmd/runtest

4. Build EVM client binaries

go-ethereum (geth):

cd go-ethereum
make evm
# produces: build/bin/evm

evmone:

cd evmone
git submodule update --init --recursive
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DEVMONE_TESTING=ON -DEVMC_TOOLS=ON
cmake --build build --target evmone-statetest --parallel $(nproc)
# produces: build/bin/evmone-statetest

Other supported clients (see runtest --help for the full list):

  • besu--besu /path/to/besu/build/install/besu/bin/evmtool
  • erigon--erigon /path/to/erigon/build/bin/evm
  • nethermind--nethermind /path/to/nethtest
  • nimbus--nimbus /path/to/evmstate
  • revme--revme /path/to/revme

Usage

Step 1 — generate a corpus

Run fill inside the execution-specs repo to produce one StateTest fixture per JSON file. You need at least one EVM client binary (e.g. geth's evm) as the transition tool.

cd /path/to/execution-specs

# Generate fixtures for Prague fork from the frontier test suite
uv run fill \
    tests/frontier/ \
    --fork Prague \
    --output fuzzing \
    --single-fixture-per-file \
    --evm-bin /path/to/go-ethereum/build/bin/evm

You can target different test suites and forks:

# Prague-specific tests
uv run fill tests/prague/ --fork Prague --output fuzzing \
    --single-fixture-per-file --evm-bin /path/to/evm --clean

# All available tests (takes longer)
uv run fill tests/ --fork Prague --output fuzzing \
    --single-fixture-per-file --evm-bin /path/to/evm --clean

The generated fixtures will be at fuzzing/state_tests/. Some tests may fail during filling — this is expected; the passing tests still produce valid fixtures.

Step 2 — run the fuzzer

cd /path/to/evm-diff-fuzz

uv run diff_fuzz \
    --corpus /path/to/execution-specs/fuzzing/state_tests \
    --work   /tmp/diff_fuzz_results \
    --runtest $HOME/go/bin/runtest \
    --client geth   /path/to/go-ethereum/build/bin/evm \
    --client evmone /path/to/evmone/build/bin/evmone-statetest \
    --step-count 1000 \
    --max-gas 100000000

Options

Flag Default Description
-c / --corpus (required) Seed corpus directory
-w / --work /tmp/diff_fuzz Working / results directory
-r / --runtest runtest Path to GoEVMLab runtest binary
--client NAME BINARY (repeat) Register an EVM client (min. 2)
--skip-trace False Compare state roots only (faster)
--max-gas 100000000 Gas cap per transaction
--step-count 10 Number of mutation steps
--step-num 1 Starting step number (for resuming)
--cleanup-tests True Delete passing fixture files after each step

Mutation strategies

Strategy Priority Description
ReplacePushWithRandom 10 Replace a PUSH immediate with random bytes
ReplacePushWithMagic 10 Replace a PUSH immediate with a boundary value (2^255, 2^256-1, …)
ReplacePushWithAddress 5 Replace a PUSH immediate with a pre-state address
FlipRandomByte 2 XOR a random byte with 0xFF
ReplaceRandomByte 2 Replace a random byte with a random value
InsertPushPop 3 Insert a stack-neutral PUSH0/POP pair
DeleteRandomByte 1 Remove one byte (shifts all subsequent offsets)

Custom strategies can be added by subclassing BytecodeMutator and passing them to StateTestMutator.

License

MIT

About

Fork-agnostic differential fuzzer for Ethereum EVM clients

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages