foundry_evm_fuzz/strategies/
param.rsuse super::state::EvmFuzzState;
use alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::{Address, B256, I256, U256};
use proptest::prelude::*;
const MAX_ARRAY_LEN: usize = 256;
pub fn fuzz_param(param: &DynSolType) -> BoxedStrategy<DynSolValue> {
fuzz_param_inner(param, None)
}
pub fn fuzz_param_with_fixtures(
param: &DynSolType,
fixtures: Option<&[DynSolValue]>,
name: &str,
) -> BoxedStrategy<DynSolValue> {
fuzz_param_inner(param, fixtures.map(|f| (f, name)))
}
fn fuzz_param_inner(
param: &DynSolType,
mut fuzz_fixtures: Option<(&[DynSolValue], &str)>,
) -> BoxedStrategy<DynSolValue> {
if let Some((fixtures, name)) = fuzz_fixtures {
if !fixtures.iter().all(|f| f.matches(param)) {
error!("fixtures for {name:?} do not match type {param}");
fuzz_fixtures = None;
}
}
let fuzz_fixtures = fuzz_fixtures.map(|(f, _)| f);
let value = || {
let default_strategy = DynSolValue::type_strategy(param);
if let Some(fixtures) = fuzz_fixtures {
proptest::prop_oneof![
50 => {
let fixtures = fixtures.to_vec();
any::<prop::sample::Index>()
.prop_map(move |index| index.get(&fixtures).clone())
},
50 => default_strategy,
]
.boxed()
} else {
default_strategy.boxed()
}
};
match *param {
DynSolType::Address => value(),
DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures)
.prop_map(move |x| DynSolValue::Int(x, n))
.boxed(),
DynSolType::Uint(n @ 8..=256) => super::UintStrategy::new(n, fuzz_fixtures)
.prop_map(move |x| DynSolValue::Uint(x, n))
.boxed(),
DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(),
DynSolType::Bytes => value(),
DynSolType::FixedBytes(_size @ 1..=32) => value(),
DynSolType::String => value()
.prop_map(move |value| {
DynSolValue::String(
value.as_str().unwrap().trim().trim_end_matches('\0').to_string(),
)
})
.boxed(),
DynSolType::Tuple(ref params) => params
.iter()
.map(|param| fuzz_param_inner(param, None))
.collect::<Vec<_>>()
.prop_map(DynSolValue::Tuple)
.boxed(),
DynSolType::FixedArray(ref param, size) => {
proptest::collection::vec(fuzz_param_inner(param, None), size)
.prop_map(DynSolValue::FixedArray)
.boxed()
}
DynSolType::Array(ref param) => {
proptest::collection::vec(fuzz_param_inner(param, None), 0..MAX_ARRAY_LEN)
.prop_map(DynSolValue::Array)
.boxed()
}
_ => panic!("unsupported fuzz param type: {param}"),
}
}
pub fn fuzz_param_from_state(
param: &DynSolType,
state: &EvmFuzzState,
) -> BoxedStrategy<DynSolValue> {
let value = || {
let state = state.clone();
let param = param.clone();
any::<(bool, prop::sample::Index)>().prop_map(move |(bias, index)| {
let state = state.dictionary_read();
let values = if bias { state.samples(¶m) } else { None }
.unwrap_or_else(|| state.values())
.as_slice();
values[index.index(values.len())]
})
};
match *param {
DynSolType::Address => {
let deployed_libs = state.deployed_libs.clone();
value()
.prop_filter_map("filter address fuzzed from state", move |value| {
let fuzzed_addr = Address::from_word(value);
if !deployed_libs.contains(&fuzzed_addr) {
Some(DynSolValue::Address(fuzzed_addr))
} else {
None
}
})
.boxed()
}
DynSolType::Function => value()
.prop_map(move |value| {
DynSolValue::Function(alloy_primitives::Function::from_word(value))
})
.boxed(),
DynSolType::FixedBytes(size @ 1..=32) => value()
.prop_map(move |mut v| {
v[size..].fill(0);
DynSolValue::FixedBytes(B256::from(v), size)
})
.boxed(),
DynSolType::Bool => DynSolValue::type_strategy(param).boxed(),
DynSolType::String => DynSolValue::type_strategy(param)
.prop_map(move |value| {
DynSolValue::String(
value.as_str().unwrap().trim().trim_end_matches('\0').to_string(),
)
})
.boxed(),
DynSolType::Bytes => {
value().prop_map(move |value| DynSolValue::Bytes(value.0.into())).boxed()
}
DynSolType::Int(n @ 8..=256) => match n / 8 {
32 => value()
.prop_map(move |value| DynSolValue::Int(I256::from_raw(value.into()), 256))
.boxed(),
1..=31 => value()
.prop_map(move |value| {
let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
let max_int_plus1 = U256::from(1).wrapping_shl(n - 1);
let num = I256::from_raw(uint.wrapping_sub(max_int_plus1));
DynSolValue::Int(num, n)
})
.boxed(),
_ => unreachable!(),
},
DynSolType::Uint(n @ 8..=256) => match n / 8 {
32 => value()
.prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value.0), 256))
.boxed(),
1..=31 => value()
.prop_map(move |value| {
let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
DynSolValue::Uint(uint, n)
})
.boxed(),
_ => unreachable!(),
},
DynSolType::Tuple(ref params) => params
.iter()
.map(|p| fuzz_param_from_state(p, state))
.collect::<Vec<_>>()
.prop_map(DynSolValue::Tuple)
.boxed(),
DynSolType::FixedArray(ref param, size) => {
proptest::collection::vec(fuzz_param_from_state(param, state), size)
.prop_map(DynSolValue::FixedArray)
.boxed()
}
DynSolType::Array(ref param) => {
proptest::collection::vec(fuzz_param_from_state(param, state), 0..MAX_ARRAY_LEN)
.prop_map(DynSolValue::Array)
.boxed()
}
_ => panic!("unsupported fuzz param type: {param}"),
}
}
#[cfg(test)]
mod tests {
use crate::{
strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
FuzzFixtures,
};
use foundry_common::abi::get_func;
use foundry_config::FuzzDictionaryConfig;
use revm::db::{CacheDB, EmptyDB};
#[test]
fn can_fuzz_array() {
let f = "testArray(uint64[2] calldata values)";
let func = get_func(f).unwrap();
let db = CacheDB::new(EmptyDB::default());
let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), &[]);
let strategy = proptest::prop_oneof![
60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()),
40 => fuzz_calldata_from_state(func, &state),
];
let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() };
let mut runner = proptest::test_runner::TestRunner::new(cfg);
let _ = runner.run(&strategy, |_| Ok(()));
}
}