Skip to main content

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​

  1. Install cargo-fuzz.

    cargo install --locked cargo-fuzz
  2. Initialize a fuzz project by running the following command inside your contract directory.

    cargo fuzz init
  3. Open the contract's Cargo.toml file. Add lib as a crate-type.

     [lib]
    -crate-type = ["cdylib"]
    +crate-type = ["lib", "cdylib"]
  4. Open the generated fuzz/Cargo.toml file. Add the soroban-sdk dependency.

     [dependencies]
    libfuzzer-sys = "0.4"
    +soroban-sdk = { version = "*", features = ["testutils"] }
  5. 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
    });
  6. 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"),
    }
    }
    });
  7. Execute the fuzz target.

    cargo +nightly fuzz run --sanitizer=thread fuzz_target_1
    info

    If 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:

  1. Create an environment, the Env.
  2. Register the contract to be tested.
  3. Invoke functions using a client.
  4. Assert expectations.
tip

For a full detailed example, see the fuzzing example.

info

There is another tool for fuzzing Rust code, cargo-afl. See the Rust Fuzz book for a tutorial for how to use it.