Skip to main content

foundry_evm_fuzz/strategies/
calldata.rs

1use crate::{
2    FuzzFixtures,
3    strategies::{EvmFuzzState, fuzz_param_from_state, fuzz_param_with_fixtures},
4};
5use alloy_dyn_abi::JsonAbiExt;
6use alloy_json_abi::Function;
7use alloy_primitives::{Bytes, U256};
8use proptest::prelude::Strategy;
9
10/// Given a function, it returns a strategy which generates valid calldata
11/// for that function's input types, following declared test fixtures.
12pub fn fuzz_calldata(
13    func: Function,
14    fuzz_fixtures: &FuzzFixtures,
15    max_fuzz_int: Option<U256>,
16) -> impl Strategy<Value = Bytes> + use<> {
17    // We need to compose all the strategies generated for each parameter in all
18    // possible combinations, accounting any parameter declared fixture
19    let strats = func
20        .inputs
21        .iter()
22        .map(|input| {
23            fuzz_param_with_fixtures(
24                &input.selector_type().parse().unwrap(),
25                fuzz_fixtures.param_fixtures(&input.name),
26                &input.name,
27                max_fuzz_int,
28            )
29        })
30        .collect::<Vec<_>>();
31    strats.prop_map(move |values| {
32        func.abi_encode_input(&values)
33            .unwrap_or_else(|_| {
34                panic!(
35                    "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}",
36                    func.name, func.inputs, values
37                )
38            })
39            .into()
40    })
41}
42
43/// Given a function and some state, it returns a strategy which generated valid calldata for the
44/// given function's input types, based on state taken from the EVM.
45pub fn fuzz_calldata_from_state(
46    func: Function,
47    state: &EvmFuzzState,
48    max_fuzz_int: Option<U256>,
49) -> impl Strategy<Value = Bytes> + use<> {
50    let strats = func
51        .inputs
52        .iter()
53        .map(|input| {
54            fuzz_param_from_state(&input.selector_type().parse().unwrap(), state, max_fuzz_int)
55        })
56        .collect::<Vec<_>>();
57    strats
58        .prop_map(move |values| {
59            func.abi_encode_input(&values)
60                .unwrap_or_else(|_| {
61                    panic!(
62                        "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}",
63                        func.name, func.inputs, values
64                    )
65                })
66                .into()
67        })
68        .no_shrink()
69}
70
71#[cfg(test)]
72mod tests {
73    use crate::{FuzzFixtures, strategies::fuzz_calldata};
74    use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
75    use alloy_json_abi::Function;
76    use alloy_primitives::{Address, map::HashMap};
77    use proptest::prelude::Strategy;
78
79    #[test]
80    fn can_fuzz_with_fixtures() {
81        let function = Function::parse("test_fuzzed_address(address addressFixture)").unwrap();
82
83        let address_fixture = DynSolValue::Address(Address::random());
84        let mut fixtures = HashMap::default();
85        fixtures.insert(
86            "addressFixture".to_string(),
87            DynSolValue::Array(vec![address_fixture.clone()]),
88        );
89
90        let expected = function.abi_encode_input(&[address_fixture]).unwrap();
91        let strategy = fuzz_calldata(function, &FuzzFixtures::new(fixtures), None);
92        let _ = strategy.prop_map(move |fuzzed| {
93            assert_eq!(expected, fuzzed);
94        });
95    }
96}