Skip to main content
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.
See the Rust Fuzz Book for a general tutorial on using both cargo-fuzz and cargo-afl.

How to Write Fuzz Tests with cargo-fuzz

  1. Install the nightly Rust toolchain. Nightly Rust is required to run cargo-fuzz.
  2. Install cargo-fuzz.
  3. Initialize a fuzz project by running the following command inside your contract directory.
  4. Open the contract’s Cargo.toml file. Add lib as a crate-type.
  5. Open the generated fuzz/Cargo.toml file. Add the soroban-sdk dependency.
  6. Open the generated fuzz/src/fuzz_target_1.rs file. It will look like the below.
  7. Fill out the fuzz_target! call with test setup and assertions. For example, for the increment example:
  8. Execute the fuzz target.
    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.
For a full detailed example, see the fuzzing example.

How to Write Fuzz Tests with cargo-afl

cargo-afl drives AFL++ and, unlike cargo-fuzz, runs on stable Rust.
  1. Install cargo-afl. Installing it builds AFL++ from source, so a C compiler needs to be available.
  2. Configure the machine for fuzzing.
  3. Open the contract’s Cargo.toml file. Add lib as a crate-type. The fuzz target imports the contract as a Rust library.
  4. Create a fuzz target crate, for example with cargo new --bin fuzz inside your contract’s directory. Unlike a cargo-fuzz target, an AFL++ target depends on the arbitrary crate directly, because the fuzz! macro expands to code that refers to it by an absolute path, which only resolves if arbitrary is a direct dependency. Put the following in the new crate’s Cargo.toml. It replaces the empty [dependencies] table that cargo new generated.
  5. Write the fuzz target at src/fuzz_target_1.rs, and delete the default src/main.rs cargo new created. An AFL++ target is a regular binary crate with a main function that calls the afl::fuzz! macro. For example, for the increment example:
  6. Build the target. The remaining steps also run from inside the fuzz crate.
  7. Fuzz the target, pointing at an input directory containing at least one seed input and an output directory to write results to.
Crashing inputs are written to out/default/crashes/. The target reads an input on stdin when it isn’t being driven by AFL++, so a crash can be replayed by feeding the file back in:

How to Get Code Coverage of cargo-fuzz Tests

Getting code coverage data for fuzz tests requires some different tooling than when doing the same for regular Rust tests.
  1. Run the cargo-fuzz target until it has produced a corpus.
  2. Install the llvm-tools for the nightly compiler.
  3. Run the fuzz coverage command that’ll execute the corpus and write coverage data to the coverage directory in the profdata format.
  4. Run the llvm-cov command to convert the profdata file to an lcov file.
    Load the lcov.info file into your IDE using its coverage feature. In VSCode this can be done by installing the Coverage Gutters extension and executing the Coverage Gutters: Watch command.
To measure code coverage of regular Rust tests, see Code Coverage.