REF is a minimalistic property-based fuzzer for EVM programs (ie. smart contracts).
It is written in Rust and uses the alloy
crate to interact with a local node (anvil
), which is used to deploy and call the target contracts. Additionally, forge
is used to compile the contracts before deploying them.
Run the fuzzer with the following command:
cargo run -- <target_contract_path> <target_contract_name> [max_steps]
For example:
cargo run -- examples/SimpleInvariantCheck.sol SimpleInvariantCheck 50
You can find an example contract to fuzz in the
examples
directory.
Example output:
Fuzzing contract SimpleInvariantCheck (max steps = 50)
Call: solveWrapper([Uint(43970993566387452639593763277240575612, 256)])
Call: setFlagWrapper([Bool(true)])
Call: setFlagWrapper([Bool(false)])
Call: setFlagWrapper([Bool(true)])
Call: solveWrapper([Uint(22171698411337210811977953420121770230, 256)])
invariant_unsolvable broken 💥
-
Take a target contract as input.
-
Deploy the contract on a local network.
- The contract could have a constructor or set up function (without parameters) to initialize its state.
-
Inspect the contract ABI to look for:
- Functions beginning with
invariant_
to check invariants. These should return a boolean to indicate if the invariant holds (true) or not (false). - Non-view functions to fuzz.
- Functions beginning with
-
Randomly call the functions to fuzz with random inputs, and check the invariants after each call.
-
If an invariant fails, the fuzzer should log the failing invariant and the sequence of function calls, along with the inputs that caused the invariant to fail.
-
Add a
max_steps
parameter to limit the number of function calls before stopping the fuzzer if no invariant fails.