foundry_evm_fuzz/strategies/
invariants.rs1use super::{fuzz_calldata, fuzz_param_from_state};
2use crate::{
3 FuzzFixtures,
4 invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters},
5 strategies::{EvmFuzzState, fuzz_calldata_from_state, fuzz_param},
6};
7use alloy_json_abi::Function;
8use alloy_primitives::{Address, U256};
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 max_fuzz_int: Option<U256>,
21) -> impl Strategy<Value = CallDetails> + Send + Sync + 'static {
22 let contracts_ref = contracts.targets.clone();
23 proptest::prop_oneof![
24 80 => proptest::strategy::LazyJust::new(move || *target.read()),
25 20 => any::<prop::sample::Selector>()
26 .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())),
27 ]
28 .prop_flat_map(move |target_address| {
29 let fuzz_state = fuzz_state.clone();
30 let fuzz_fixtures = fuzz_fixtures.clone();
31
32 let func = {
33 let contracts = contracts.targets.lock();
34 let contract = contracts.get(&target_address).unwrap_or_else(|| {
35 contracts.values().choose(&mut rand::rng()).unwrap()
39 });
40 let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect();
41 any::<prop::sample::Index>().prop_map(move |index| index.get(&fuzzed_functions).clone())
42 };
43
44 func.prop_flat_map(move |func| {
45 fuzz_contract_with_calldata(
46 &fuzz_state,
47 &fuzz_fixtures,
48 target_address,
49 func,
50 max_fuzz_int,
51 )
52 })
53 })
54}
55
56pub fn invariant_strat(
67 fuzz_state: EvmFuzzState,
68 senders: SenderFilters,
69 contracts: FuzzRunIdentifiedContracts,
70 dictionary_weight: u32,
71 fuzz_fixtures: FuzzFixtures,
72 max_fuzz_int: Option<U256>,
73) -> impl Strategy<Value = BasicTxDetails> {
74 let senders = Rc::new(senders);
75 any::<prop::sample::Selector>()
76 .prop_flat_map(move |selector| {
77 let contracts = contracts.targets.lock();
78 let functions = contracts.fuzzed_functions();
79 let (target_address, target_function) = selector.select(functions);
80 let sender =
81 select_random_sender(&fuzz_state, senders.clone(), dictionary_weight, max_fuzz_int);
82 let call_details = fuzz_contract_with_calldata(
83 &fuzz_state,
84 &fuzz_fixtures,
85 *target_address,
86 target_function.clone(),
87 max_fuzz_int,
88 );
89 (sender, call_details)
90 })
91 .prop_map(|(sender, call_details)| BasicTxDetails { sender, call_details })
92}
93
94fn select_random_sender(
98 fuzz_state: &EvmFuzzState,
99 senders: Rc<SenderFilters>,
100 dictionary_weight: u32,
101 max_fuzz_int: Option<U256>,
102) -> impl Strategy<Value = Address> + use<> {
103 if !senders.targeted.is_empty() {
104 any::<prop::sample::Index>().prop_map(move |index| *index.get(&senders.targeted)).boxed()
105 } else {
106 assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
107 proptest::prop_oneof![
108 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address, max_fuzz_int),
109 dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state, max_fuzz_int),
110 ]
111 .prop_map(move |addr| {
112 let mut addr = addr.as_address().unwrap();
113 loop {
118 if !senders.excluded.contains(&addr) {
119 break;
120 }
121 addr = Address::random();
122 }
123 addr
124 })
125 .boxed()
126 }
127}
128
129pub fn fuzz_contract_with_calldata(
132 fuzz_state: &EvmFuzzState,
133 fuzz_fixtures: &FuzzFixtures,
134 target: Address,
135 func: Function,
136 max_fuzz_int: Option<U256>,
137) -> impl Strategy<Value = CallDetails> + use<> {
138 prop_oneof![
142 60 => fuzz_calldata(func.clone(), fuzz_fixtures, max_fuzz_int),
143 40 => fuzz_calldata_from_state(func, fuzz_state, max_fuzz_int),
144 ]
145 .prop_map(move |calldata| {
146 trace!(input=?calldata);
147 CallDetails { target, calldata }
148 })
149}