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 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(&fuzz_state, &fuzz_fixtures, target_address, func)
46 })
47 })
48}
49
50pub fn invariant_strat(
61 fuzz_state: EvmFuzzState,
62 senders: SenderFilters,
63 contracts: FuzzRunIdentifiedContracts,
64 config: InvariantConfig,
65 fuzz_fixtures: FuzzFixtures,
66) -> impl Strategy<Value = BasicTxDetails> {
67 let senders = Rc::new(senders);
68 let dictionary_weight = config.dictionary.dictionary_weight;
69
70 let warp_roll_strat = |cond: bool| {
72 if cond { any::<U256>().prop_map(Some).boxed() } else { Just(None).boxed() }
73 };
74
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
81 let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight);
82
83 let call_details = fuzz_contract_with_calldata(
84 &fuzz_state,
85 &fuzz_fixtures,
86 *target_address,
87 target_function.clone(),
88 );
89
90 let warp = warp_roll_strat(config.max_time_delay.is_some());
91 let roll = warp_roll_strat(config.max_block_delay.is_some());
92
93 (warp, roll, sender, call_details)
94 })
95 .prop_map(move |(warp, roll, sender, call_details)| {
96 let warp =
97 warp.map(|time| time % U256::from(config.max_time_delay.unwrap_or_default()));
98 let roll =
99 roll.map(|block| block % U256::from(config.max_block_delay.unwrap_or_default()));
100 BasicTxDetails { warp, roll, sender, call_details }
101 })
102}
103
104fn select_random_sender(
108 fuzz_state: &EvmFuzzState,
109 senders: Rc<SenderFilters>,
110 dictionary_weight: u32,
111) -> impl Strategy<Value = Address> + use<> {
112 if !senders.targeted.is_empty() {
113 any::<prop::sample::Index>().prop_map(move |index| *index.get(&senders.targeted)).boxed()
114 } else {
115 assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
116 proptest::prop_oneof![
117 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address),
118 dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state),
119 ]
120 .prop_map(move |addr| {
121 let mut addr = addr.as_address().unwrap();
122 loop {
127 if !senders.excluded.contains(&addr) {
128 break;
129 }
130 addr = Address::random();
131 }
132 addr
133 })
134 .boxed()
135 }
136}
137
138pub fn fuzz_contract_with_calldata(
141 fuzz_state: &EvmFuzzState,
142 fuzz_fixtures: &FuzzFixtures,
143 target: Address,
144 func: Function,
145) -> impl Strategy<Value = CallDetails> + use<> {
146 prop_oneof![
150 60 => fuzz_calldata(func.clone(), fuzz_fixtures),
151 40 => fuzz_calldata_from_state(func, fuzz_state),
152 ]
153 .prop_map(move |calldata| {
154 trace!(input=?calldata);
155 CallDetails { target, calldata }
156 })
157}