foundry_evm_fuzz/strategies/
invariants.rs1use super::{fuzz_calldata, fuzz_msg_value, fuzz_param_from_state};
2use crate::{
3 BasicTxDetails, CallDetails, FuzzFixtures,
4 invariant::{FuzzRunIdentifiedContracts, SenderFilters},
5 strategies::{
6 EvmFuzzState, FuzzStateReader, InvariantFuzzState, fuzz_calldata_from_state, fuzz_param,
7 },
8};
9use alloy_json_abi::Function;
10use alloy_primitives::{Address, U256};
11use foundry_config::InvariantConfig;
12use parking_lot::RwLock;
13use proptest::prelude::*;
14use rand::seq::IteratorRandom;
15use std::{rc::Rc, sync::Arc};
16
17pub fn override_call_strat(
19 fuzz_state: EvmFuzzState,
20 contracts: Vec<(Address, Vec<Function>)>,
21 target: Arc<RwLock<Address>>,
22 fuzz_fixtures: FuzzFixtures,
23) -> impl Strategy<Value = CallDetails> + Send + Sync + 'static {
24 let contracts = Arc::new(contracts);
25 let contracts_ref = contracts.clone();
26 proptest::prop_oneof![
27 80 => proptest::strategy::LazyJust::new(move || *target.read()),
28 20 => any::<prop::sample::Selector>()
29 .prop_map(move |selector| {
30 let (target, _) = selector.select(contracts_ref.iter());
31 *target
32 }),
33 ]
34 .prop_flat_map(move |target_address| {
35 let fuzz_state = fuzz_state.clone();
36 let fuzz_fixtures = fuzz_fixtures.clone();
37 let contracts = contracts.clone();
38
39 let (actual_target, func) = {
40 let (actual_target, fuzzed_functions) = contracts
45 .iter()
46 .find(|(address, _)| *address == target_address)
47 .map(|(address, functions)| (*address, functions.clone()))
48 .unwrap_or_else(|| {
49 let (address, functions) = contracts
50 .iter()
51 .choose(&mut rand::rng())
52 .expect("at least one target contract");
53 (*address, functions.clone())
54 });
55 (
56 actual_target,
57 any::<prop::sample::Index>()
58 .prop_map(move |index| index.get(&fuzzed_functions).clone()),
59 )
60 };
61
62 func.prop_flat_map(move |func| {
63 fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, actual_target, func)
64 })
65 })
66}
67
68pub fn invariant_strat(
79 fuzz_state: InvariantFuzzState,
80 senders: SenderFilters,
81 contracts: FuzzRunIdentifiedContracts,
82 config: InvariantConfig,
83 fuzz_fixtures: FuzzFixtures,
84) -> impl Strategy<Value = BasicTxDetails> {
85 let senders = Rc::new(senders);
86 let dictionary_weight = config.dictionary.dictionary_weight;
87
88 let warp_roll_strat = |cond: bool| {
90 if cond { any::<U256>().prop_map(Some).boxed() } else { Just(None).boxed() }
91 };
92
93 any::<prop::sample::Selector>()
94 .prop_flat_map(move |selector| {
95 let contracts = contracts.targets();
96 let functions = contracts.fuzzed_functions();
97 let (target_address, target_function) = selector.select(functions);
98
99 let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight);
100
101 let call_details = fuzz_contract_with_calldata(
102 &fuzz_state,
103 &fuzz_fixtures,
104 *target_address,
105 target_function.clone(),
106 );
107
108 let warp = warp_roll_strat(config.max_time_delay.is_some());
109 let roll = warp_roll_strat(config.max_block_delay.is_some());
110
111 (warp, roll, sender, call_details)
112 })
113 .prop_map(move |(warp, roll, sender, call_details)| {
114 let warp =
115 warp.map(|time| time % U256::from(config.max_time_delay.unwrap_or_default()));
116 let roll =
117 roll.map(|block| block % U256::from(config.max_block_delay.unwrap_or_default()));
118 BasicTxDetails { warp, roll, sender, call_details }
119 })
120}
121
122fn select_random_sender<S: FuzzStateReader>(
126 fuzz_state: &S,
127 senders: Rc<SenderFilters>,
128 dictionary_weight: u32,
129) -> impl Strategy<Value = Address> + use<S> {
130 if senders.targeted.is_empty() {
131 assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
132 proptest::prop_oneof![
133 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address),
134 dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state),
135 ]
136 .prop_map(move |addr| {
137 let mut addr = addr.as_address().unwrap();
138 loop {
143 if !senders.excluded.contains(&addr) {
144 break;
145 }
146 addr = Address::random();
147 }
148 addr
149 })
150 .boxed()
151 } else {
152 any::<prop::sample::Index>().prop_map(move |index| *index.get(&senders.targeted)).boxed()
153 }
154}
155
156pub fn fuzz_contract_with_calldata<S: FuzzStateReader>(
159 fuzz_state: &S,
160 fuzz_fixtures: &FuzzFixtures,
161 target: Address,
162 func: Function,
163) -> impl Strategy<Value = CallDetails> + use<S> {
164 let is_payable = func.state_mutability == alloy_json_abi::StateMutability::Payable;
165
166 let calldata_strategy = prop_oneof![
170 60 => fuzz_calldata(func.clone(), fuzz_fixtures),
171 40 => fuzz_calldata_from_state(func, fuzz_state),
172 ];
173
174 let value_strategy = if is_payable { fuzz_msg_value().boxed() } else { Just(None).boxed() };
176
177 (calldata_strategy, value_strategy).prop_map(move |(calldata, value)| {
178 trace!(input=?calldata, ?value);
179 CallDetails { target, calldata, value }
180 })
181}