foundry_evm_fuzz/strategies/
calldata.rs

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