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