Skip to main content

foundry_evm_fuzz/strategies/
invariants.rs

1use super::TxGenerator;
2use crate::{CallDetails, FuzzFixtures, strategies::EvmFuzzState};
3use alloy_json_abi::Function;
4use alloy_primitives::Address;
5use parking_lot::RwLock;
6use proptest::prelude::*;
7use rand::seq::IteratorRandom;
8use std::sync::Arc;
9
10/// Given a target address, we generate random calldata.
11pub fn override_call_strat(
12    fuzz_state: EvmFuzzState,
13    contracts: Vec<(Address, Vec<Function>)>,
14    target: Arc<RwLock<Address>>,
15    fuzz_fixtures: FuzzFixtures,
16    dictionary_weight: u32,
17    payable_value_weight: u32,
18) -> impl Strategy<Value = CallDetails> + Send + Sync + 'static {
19    // Each generated call owns its function-selection strategy. Share the functions so
20    // constructing that strategy does not clone the entire target ABI on every call.
21    let contracts = Arc::new(
22        contracts
23            .into_iter()
24            .map(|(address, functions)| (address, Arc::new(functions)))
25            .collect::<Vec<_>>(),
26    );
27    let contracts_ref = contracts.clone();
28    proptest::prop_oneof![
29        80 => proptest::strategy::LazyJust::new(move || *target.read()),
30        20 => any::<prop::sample::Selector>()
31            .prop_map(move |selector| {
32                let (target, _) = selector.select(contracts_ref.iter());
33                *target
34            }),
35    ]
36    .prop_flat_map(move |target_address| {
37        let fuzz_state = fuzz_state.clone();
38        let fuzz_fixtures = fuzz_fixtures.clone();
39        let contracts = contracts.clone();
40
41        let (actual_target, func) = {
42            // If the target address is in the contracts map, use it directly.
43            // Otherwise, fall back to a random contract from the targeted contracts.
44            // This can happen when call_override sets target_reference to a contract
45            // that is not in targetContracts (e.g., the protocol contract during reentrancy).
46            let (actual_target, fuzzed_functions) = contracts
47                .iter()
48                .find(|(address, _)| *address == target_address)
49                .map(|(address, functions)| (*address, functions.clone()))
50                .unwrap_or_else(|| {
51                    let (address, functions) = contracts
52                        .iter()
53                        .choose(&mut rand::rng())
54                        .expect("at least one target contract");
55                    (*address, functions.clone())
56                });
57            (
58                actual_target,
59                any::<prop::sample::Index>()
60                    .prop_map(move |index| index.get(&fuzzed_functions).clone()),
61            )
62        };
63
64        func.prop_flat_map(move |func| {
65            TxGenerator::call_strategy(
66                &fuzz_state,
67                &fuzz_fixtures,
68                actual_target,
69                func,
70                dictionary_weight,
71                payable_value_weight,
72            )
73        })
74    })
75}