Available on crate feature testutils only.
Expand description

Support for fuzzing Soroban contracts with cargo-fuzz.

This module provides a pattern for generating Soroban contract types for the purpose of fuzzing Soroban contracts. It is focused on implementing the Arbitrary trait that cargo-fuzz relies on to feed fuzzers with generated Rust values.

This module

This module is only available when the “testutils” Cargo feature is defined.

About cargo-fuzz and Arbitrary

In its basic operation cargo-fuzz fuzz generates raw bytes and feeds them to a program-dependent fuzzer designed to exercise a program, in our case a Soroban contract.

cargo-fuzz programs declare their entry points with a macro:

fuzz_target!(|input: &[u8]| {
    // feed bytes to the program
});

More sophisticated fuzzers accept not bytes but Rust types:

#[derive(Arbitrary, Debug)]
struct FuzzDeposit {
    deposit_amount: i128,
}

fuzz_target!(|input: FuzzDeposit| {
    // fuzz the program based on the input
});

Types accepted as input to fuzz_target must implement the Arbitrary trait, which transforms bytes to Rust types.

The SorobanArbitrary trait

Soroban types are managed by the host environment, and so must be created from an Env value; Arbitrary values though must be created from nothing but bytes. The SorobanArbitrary trait, implemented for all Soroban contract types, exists to bridge this gap: it defines a prototype pattern whereby the fuzz_target macro creates prototype values that the fuzz program can convert to contract values with the standard soroban conversion traits, FromVal or IntoVal.

The types of prototypes are identified by the associated type, SorobanArbitrary::Prototype:

pub trait SorobanArbitrary:
    TryFromVal<Env, Self::Prototype> + IntoVal<Env, Val> + TryFromVal<Env, Val>
{
    type Prototype: for <'a> Arbitrary<'a>;
}

Types that implement SorobanArbitrary include:

All user-defined contract types, those with the contracttype attribute, automatically derive SorobanArbitrary. Note that SorobanArbitrary is only derived when the “testutils” Cargo feature is active. This implies that, in general, to make a Soroban contract fuzzable, the contract crate must define a “testutils” Cargo feature, that feature should turn on the “soroban-sdk/testutils” feature, and the fuzz test, which is its own crate, must turn that feature on.

Example: take a Soroban Vec of Address as fuzzer input

use soroban_sdk::{Address, Env, Vec};
use soroban_sdk::testutils::arbitrary::SorobanArbitrary;

fuzz_target!(|input: <Vec<Address> as SorobanArbitrary>::Prototype| {
    let env = Env::default();
    let addresses: Vec<Address> = input.into_val(&env);
    // fuzz the program based on the input
});

Example: take a custom contract type as fuzzer input

use soroban_sdk::{Address, Env, Vec};
use soroban_sdk::contracttype;
use soroban_sdk::testutils::arbitrary::{Arbitrary, SorobanArbitrary};
use std::vec::Vec as RustVec;

#[derive(Arbitrary, Debug)]
struct TestInput {
    deposit_amount: i128,
    claim_address: <Address as SorobanArbitrary>::Prototype,
    time_bound: <TimeBound as SorobanArbitrary>::Prototype,
}

#[contracttype]
pub struct TimeBound {
    pub kind: TimeBoundKind,
    pub timestamp: u64,
}

#[contracttype]
pub enum TimeBoundKind {
    Before,
    After,
}

fuzz_target!(|input: TestInput| {
    let env = Env::default();
    let claim_address: Address = input.claim_address.into_val(&env);
    let time_bound: TimeBound = input.time_bound.into_val(&env);
    // fuzz the program based on the input
});

Re-exports

Traits

  • Generate arbitrary structured values from raw, unstructured data.
  • An Env-hosted contract value that can be randomly generated.

Functions

Derive Macros