foundry_evm_fuzz/strategies/
invariants.rsuse super::{fuzz_calldata, fuzz_param_from_state};
use crate::{
invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters},
strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState},
FuzzFixtures,
};
use alloy_json_abi::Function;
use alloy_primitives::Address;
use parking_lot::RwLock;
use proptest::prelude::*;
use rand::seq::IteratorRandom;
use std::{rc::Rc, sync::Arc};
pub fn override_call_strat(
fuzz_state: EvmFuzzState,
contracts: FuzzRunIdentifiedContracts,
target: Arc<RwLock<Address>>,
fuzz_fixtures: FuzzFixtures,
) -> impl Strategy<Value = CallDetails> + Send + Sync + 'static {
let contracts_ref = contracts.targets.clone();
proptest::prop_oneof![
80 => proptest::strategy::LazyJust::new(move || *target.read()),
20 => any::<prop::sample::Selector>()
.prop_map(move |selector| *selector.select(contracts_ref.lock().keys())),
]
.prop_flat_map(move |target_address| {
let fuzz_state = fuzz_state.clone();
let fuzz_fixtures = fuzz_fixtures.clone();
let func = {
let contracts = contracts.targets.lock();
let contract = contracts.get(&target_address).unwrap_or_else(|| {
contracts.values().choose(&mut rand::thread_rng()).unwrap()
});
let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect();
any::<prop::sample::Index>().prop_map(move |index| index.get(&fuzzed_functions).clone())
};
func.prop_flat_map(move |func| {
fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func)
})
})
}
pub fn invariant_strat(
fuzz_state: EvmFuzzState,
senders: SenderFilters,
contracts: FuzzRunIdentifiedContracts,
dictionary_weight: u32,
fuzz_fixtures: FuzzFixtures,
) -> impl Strategy<Value = BasicTxDetails> {
let senders = Rc::new(senders);
any::<prop::sample::Selector>()
.prop_flat_map(move |selector| {
let contracts = contracts.targets.lock();
let functions = contracts.fuzzed_functions();
let (target_address, target_function) = selector.select(functions);
let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight);
let call_details = fuzz_contract_with_calldata(
&fuzz_state,
&fuzz_fixtures,
*target_address,
target_function.clone(),
);
(sender, call_details)
})
.prop_map(|(sender, call_details)| BasicTxDetails { sender, call_details })
}
fn select_random_sender(
fuzz_state: &EvmFuzzState,
senders: Rc<SenderFilters>,
dictionary_weight: u32,
) -> impl Strategy<Value = Address> {
if !senders.targeted.is_empty() {
any::<prop::sample::Index>().prop_map(move |index| *index.get(&senders.targeted)).boxed()
} else {
assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
proptest::prop_oneof![
100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address),
dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state),
]
.prop_map(move |addr| addr.as_address().unwrap())
.prop_filter("excluded sender", move |addr| !senders.excluded.contains(addr))
.boxed()
}
}
pub fn fuzz_contract_with_calldata(
fuzz_state: &EvmFuzzState,
fuzz_fixtures: &FuzzFixtures,
target: Address,
func: Function,
) -> impl Strategy<Value = CallDetails> {
#[allow(clippy::arc_with_non_send_sync)]
prop_oneof![
60 => fuzz_calldata(func.clone(), fuzz_fixtures),
40 => fuzz_calldata_from_state(func, fuzz_state),
]
.prop_map(move |calldata| {
trace!(input=?calldata);
CallDetails { target, calldata }
})
}