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
- defines the
SorobanArbitrary
trait, - reexports the
arbitrary
crate and theArbitrary
type.
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:
i32
,u32
,i64
,u64
,i128
,u128
,I256
,U256
,()
, andbool
,Error
,Bytes
,BytesN
,Vec
,Map
,Address
,Symbol
,Val
,
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§
pub use arbitrary;
Traits§
- Arbitrary
- Generate arbitrary structured values from raw, unstructured data.
- Soroban
Arbitrary - An
Env
-hosted contract value that can be randomly generated.
Functions§
- fuzz_
catch_ panic Deprecated - Catch panics within a fuzz test.