foundry_evm_fuzz/strategies/
invariants.rs1use super::{fuzz_calldata, fuzz_param_from_state};
2use crate::{
3 BasicTxDetails, CallDetails, FuzzFixtures,
4 invariant::{FuzzRunIdentifiedContracts, SenderFilters},
5 strategies::{EvmFuzzState, fuzz_calldata_from_state, fuzz_param},
6};
7use alloy_json_abi::Function;
8use alloy_primitives::{Address, U256};
9use foundry_config::InvariantConfig;
10use parking_lot::RwLock;
11use proptest::prelude::*;
12use rand::seq::IteratorRandom;
13use std::{rc::Rc, sync::Arc};
14
15pub fn override_call_strat(
17 fuzz_state: EvmFuzzState,
18 contracts: FuzzRunIdentifiedContracts,
19 target: Arc<RwLock<Address>>,
20 fuzz_fixtures: FuzzFixtures,
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 (actual_target, func) = {
33 let contracts = contracts.targets.lock();
34 let (actual_target, contract) =
39 contracts.get(&target_address).map(|c| (target_address, c)).unwrap_or_else(|| {
40 let entry = contracts
41 .iter()
42 .choose(&mut rand::rng())
43 .expect("at least one target contract");
44 (*entry.0, entry.1)
45 });
46 let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect();
47 (
48 actual_target,
49 any::<prop::sample::Index>()
50 .prop_map(move |index| index.get(&fuzzed_functions).clone()),
51 )
52 };
53
54 func.prop_flat_map(move |func| {
55 fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, actual_target, func)
56 })
57 })
58}
59
60pub fn invariant_strat(
71 fuzz_state: EvmFuzzState,
72 senders: SenderFilters,
73 contracts: FuzzRunIdentifiedContracts,
74 config: InvariantConfig,
75 fuzz_fixtures: FuzzFixtures,
76) -> impl Strategy<Value = BasicTxDetails> {
77 let senders = Rc::new(senders);
78 let dictionary_weight = config.dictionary.dictionary_weight;
79
80 let warp_roll_strat = |cond: bool| {
82 if cond { any::<U256>().prop_map(Some).boxed() } else { Just(None).boxed() }
83 };
84
85 any::<prop::sample::Selector>()
86 .prop_flat_map(move |selector| {
87 let contracts = contracts.targets.lock();
88 let functions = contracts.fuzzed_functions();
89 let (target_address, target_function) = selector.select(functions);
90
91 let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight);
92
93 let call_details = fuzz_contract_with_calldata(
94 &fuzz_state,
95 &fuzz_fixtures,
96 *target_address,
97 target_function.clone(),
98 );
99
100 let warp = warp_roll_strat(config.max_time_delay.is_some());
101 let roll = warp_roll_strat(config.max_block_delay.is_some());
102
103 (warp, roll, sender, call_details)
104 })
105 .prop_map(move |(warp, roll, sender, call_details)| {
106 let warp =
107 warp.map(|time| time % U256::from(config.max_time_delay.unwrap_or_default()));
108 let roll =
109 roll.map(|block| block % U256::from(config.max_block_delay.unwrap_or_default()));
110 BasicTxDetails { warp, roll, sender, call_details }
111 })
112}
113
114fn select_random_sender(
118 fuzz_state: &EvmFuzzState,
119 senders: Rc<SenderFilters>,
120 dictionary_weight: u32,
121) -> impl Strategy<Value = Address> + use<> {
122 if !senders.targeted.is_empty() {
123 any::<prop::sample::Index>().prop_map(move |index| *index.get(&senders.targeted)).boxed()
124 } else {
125 assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
126 proptest::prop_oneof![
127 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address),
128 dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state),
129 ]
130 .prop_map(move |addr| {
131 let mut addr = addr.as_address().unwrap();
132 loop {
137 if !senders.excluded.contains(&addr) {
138 break;
139 }
140 addr = Address::random();
141 }
142 addr
143 })
144 .boxed()
145 }
146}
147
148pub fn fuzz_contract_with_calldata(
151 fuzz_state: &EvmFuzzState,
152 fuzz_fixtures: &FuzzFixtures,
153 target: Address,
154 func: Function,
155) -> impl Strategy<Value = CallDetails> + use<> {
156 prop_oneof![
160 60 => fuzz_calldata(func.clone(), fuzz_fixtures),
161 40 => fuzz_calldata_from_state(func, fuzz_state),
162 ]
163 .prop_map(move |calldata| {
164 trace!(input=?calldata);
165 CallDetails { target, calldata }
166 })
167}