foundry_evm_fuzz/strategies/
invariants.rs
1use super::{fuzz_calldata, fuzz_param_from_state};
2use crate::{
3 invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters},
4 strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState},
5 FuzzFixtures,
6};
7use alloy_json_abi::Function;
8use alloy_primitives::Address;
9use parking_lot::RwLock;
10use proptest::prelude::*;
11use rand::seq::IteratorRandom;
12use std::{rc::Rc, sync::Arc};
13
14pub fn override_call_strat(
16 fuzz_state: EvmFuzzState,
17 contracts: FuzzRunIdentifiedContracts,
18 target: Arc<RwLock<Address>>,
19 fuzz_fixtures: FuzzFixtures,
20) -> impl Strategy<Value = CallDetails> + Send + Sync + 'static {
21 let contracts_ref = contracts.targets.clone();
22 proptest::prop_oneof![
23 80 => proptest::strategy::LazyJust::new(move || *target.read()),
24 20 => any::<prop::sample::Selector>()
25 .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())),
26 ]
27 .prop_flat_map(move |target_address| {
28 let fuzz_state = fuzz_state.clone();
29 let fuzz_fixtures = fuzz_fixtures.clone();
30
31 let func = {
32 let contracts = contracts.targets.lock();
33 let contract = contracts.get(&target_address).unwrap_or_else(|| {
34 contracts.values().choose(&mut rand::thread_rng()).unwrap()
38 });
39 let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect();
40 any::<prop::sample::Index>().prop_map(move |index| index.get(&fuzzed_functions).clone())
41 };
42
43 func.prop_flat_map(move |func| {
44 fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func)
45 })
46 })
47}
48
49pub fn invariant_strat(
60 fuzz_state: EvmFuzzState,
61 senders: SenderFilters,
62 contracts: FuzzRunIdentifiedContracts,
63 dictionary_weight: u32,
64 fuzz_fixtures: FuzzFixtures,
65) -> impl Strategy<Value = BasicTxDetails> {
66 let senders = Rc::new(senders);
67 any::<prop::sample::Selector>()
68 .prop_flat_map(move |selector| {
69 let contracts = contracts.targets.lock();
70 let functions = contracts.fuzzed_functions();
71 let (target_address, target_function) = selector.select(functions);
72 let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight);
73 let call_details = fuzz_contract_with_calldata(
74 &fuzz_state,
75 &fuzz_fixtures,
76 *target_address,
77 target_function.clone(),
78 );
79 (sender, call_details)
80 })
81 .prop_map(|(sender, call_details)| BasicTxDetails { sender, call_details })
82}
83
84fn select_random_sender(
88 fuzz_state: &EvmFuzzState,
89 senders: Rc<SenderFilters>,
90 dictionary_weight: u32,
91) -> impl Strategy<Value = Address> {
92 if !senders.targeted.is_empty() {
93 any::<prop::sample::Index>().prop_map(move |index| *index.get(&senders.targeted)).boxed()
94 } else {
95 assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
96 proptest::prop_oneof![
97 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address),
98 dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state),
99 ]
100 .prop_map(move |addr| addr.as_address().unwrap())
101 .prop_filter("excluded sender", move |addr| !senders.excluded.contains(addr))
103 .boxed()
104 }
105}
106
107pub fn fuzz_contract_with_calldata(
110 fuzz_state: &EvmFuzzState,
111 fuzz_fixtures: &FuzzFixtures,
112 target: Address,
113 func: Function,
114) -> impl Strategy<Value = CallDetails> {
115 prop_oneof![
119 60 => fuzz_calldata(func.clone(), fuzz_fixtures),
120 40 => fuzz_calldata_from_state(func, fuzz_state),
121 ]
122 .prop_map(move |calldata| {
123 trace!(input=?calldata);
124 CallDetails { target, calldata }
125 })
126}