Fuzzing
Fuzzing is the process of providing random data to programs to identify unexpected behavior, such as crashes and panics.
Fuzz tests can also be written as property tests that instead of seeking to identify panics and crashes, assert on some property remaining true. Fuzzing as demonstrated here and elsewhere in these docs will use principles from both property testing and fuzzing, but will only use the term fuzzing to refer to both.
The following steps can be used in any Stellar contract workspace. If experimenting, try them in the increment example. The contract has an increment
function that increases a counter value by one on every invocation.
How to Write Fuzz Testsβ
-
Install
cargo-fuzz
.cargo install --locked cargo-fuzz
-
Initialize a fuzz project by running the following command inside your contract directory.
cargo fuzz init
-
Open the contract's
Cargo.toml
file. Addlib
as acrate-type
.[lib]
-crate-type = ["cdylib"]
+crate-type = ["lib", "cdylib"] -
Open the generated
fuzz/Cargo.toml
file. Add thesoroban-sdk
dependency.[dependencies]
libfuzzer-sys = "0.4"
+soroban-sdk = { version = "*", features = ["testutils"] } -
Open the generated
fuzz/src/fuzz_target_1.rs
file. It will look like the below.#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
// fuzzed code goes here
}); -
Fill out the
fuzz_target!
call with test setup and assertions. For example, for the increment example:#![no_main]
use libfuzzer_sys::fuzz_target;
use soroban_increment_with_fuzz_contract::{IncrementContract, IncrementContractClient};
use soroban_sdk::{
testutils::arbitrary::{self, Arbitrary},
Env,
};
#[derive(Debug, Arbitrary)]
pub struct Input {
pub by: u64,
}
fuzz_target!(|input: Input| {
let env = Env::default();
let id = env.register(IncrementContract, ());
let client = IncrementContractClient::new(&env, &id);
let mut last: Option<u32> = None;
for _ in input.by.. {
match client.try_increment() {
Ok(Ok(current)) => assert!(Some(current) > last),
Err(Ok(_)) => {} // Expected error
Ok(Err(_)) => panic!("success with wrong type returned"),
Err(Err(_)) => panic!("unrecognised error"),
}
}
}); -
Execute the fuzz target.
cargo +nightly fuzz run --sanitizer=thread fuzz_target_1
infoIf you're developing on MacOS you need to add the
--sanitizer=thread
flag in order to work around a known issue.
This test uses the same patterns used in unit tests and integration tests:
- Create an environment, the
Env
. - Register the contract to be tested.
- Invoke functions using a client.
- Assert expectations.
For a full detailed example, see the fuzzing example.
There is another tool for fuzzing Rust code, cargo-afl
. See the Rust Fuzz book for a tutorial for how to use it.
Guides in this category:
ποΈ Unit Tests
Unit tests are small tests that test smart contracts.
ποΈ Mocking
Mocking dependency contracts in tests.
ποΈ Integration Tests
Integration testing uses dependency contracts instead of mocks.
ποΈ Fuzzing
Fuzzing and property testing to find unexpected behavior.
ποΈ Differential Tests
Differential testing detects unintended changes.
ποΈ Differential Tests with Test Snapshots
Differential testing using automatic test snapshots.
ποΈ Mutation Testing
Mutation testing finds code not tested.
ποΈ Test Authorization
Write tests that test contract authorization.
ποΈ Test Events
Write tests that test contract events.