foundry_evm_fuzz/strategies/
param.rs
1use super::state::EvmFuzzState;
2use alloy_dyn_abi::{DynSolType, DynSolValue};
3use alloy_primitives::{Address, B256, I256, U256};
4use proptest::prelude::*;
5use rand::{rngs::StdRng, SeedableRng};
6
7const MAX_ARRAY_LEN: usize = 256;
9
10pub fn fuzz_param(param: &DynSolType) -> BoxedStrategy<DynSolValue> {
14 fuzz_param_inner(param, None)
15}
16
17pub fn fuzz_param_with_fixtures(
33 param: &DynSolType,
34 fixtures: Option<&[DynSolValue]>,
35 name: &str,
36) -> BoxedStrategy<DynSolValue> {
37 fuzz_param_inner(param, fixtures.map(|f| (f, name)))
38}
39
40fn fuzz_param_inner(
41 param: &DynSolType,
42 mut fuzz_fixtures: Option<(&[DynSolValue], &str)>,
43) -> BoxedStrategy<DynSolValue> {
44 if let Some((fixtures, name)) = fuzz_fixtures {
45 if !fixtures.iter().all(|f| f.matches(param)) {
46 error!("fixtures for {name:?} do not match type {param}");
47 fuzz_fixtures = None;
48 }
49 }
50 let fuzz_fixtures = fuzz_fixtures.map(|(f, _)| f);
51
52 let value = || {
53 let default_strategy = DynSolValue::type_strategy(param);
54 if let Some(fixtures) = fuzz_fixtures {
55 proptest::prop_oneof![
56 50 => {
57 let fixtures = fixtures.to_vec();
58 any::<prop::sample::Index>()
59 .prop_map(move |index| index.get(&fixtures).clone())
60 },
61 50 => default_strategy,
62 ]
63 .boxed()
64 } else {
65 default_strategy.boxed()
66 }
67 };
68
69 match *param {
70 DynSolType::Address => value(),
71 DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures)
72 .prop_map(move |x| DynSolValue::Int(x, n))
73 .boxed(),
74 DynSolType::Uint(n @ 8..=256) => super::UintStrategy::new(n, fuzz_fixtures)
75 .prop_map(move |x| DynSolValue::Uint(x, n))
76 .boxed(),
77 DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(),
78 DynSolType::Bytes => value(),
79 DynSolType::FixedBytes(_size @ 1..=32) => value(),
80 DynSolType::String => value()
81 .prop_map(move |value| {
82 DynSolValue::String(
83 value.as_str().unwrap().trim().trim_end_matches('\0').to_string(),
84 )
85 })
86 .boxed(),
87 DynSolType::Tuple(ref params) => params
88 .iter()
89 .map(|param| fuzz_param_inner(param, None))
90 .collect::<Vec<_>>()
91 .prop_map(DynSolValue::Tuple)
92 .boxed(),
93 DynSolType::FixedArray(ref param, size) => {
94 proptest::collection::vec(fuzz_param_inner(param, None), size)
95 .prop_map(DynSolValue::FixedArray)
96 .boxed()
97 }
98 DynSolType::Array(ref param) => {
99 proptest::collection::vec(fuzz_param_inner(param, None), 0..MAX_ARRAY_LEN)
100 .prop_map(DynSolValue::Array)
101 .boxed()
102 }
103 _ => panic!("unsupported fuzz param type: {param}"),
104 }
105}
106
107pub fn fuzz_param_from_state(
112 param: &DynSolType,
113 state: &EvmFuzzState,
114) -> BoxedStrategy<DynSolValue> {
115 let value = || {
117 let state = state.clone();
118 let param = param.clone();
119 any::<(bool, prop::sample::Index)>().prop_map(move |(bias, index)| {
123 let state = state.dictionary_read();
124 let values = if bias { state.samples(¶m) } else { None }
125 .unwrap_or_else(|| state.values())
126 .as_slice();
127 values[index.index(values.len())]
128 })
129 };
130
131 match *param {
133 DynSolType::Address => {
134 let deployed_libs = state.deployed_libs.clone();
135 value()
136 .prop_map(move |value| {
137 let mut fuzzed_addr = Address::from_word(value);
138 if deployed_libs.contains(&fuzzed_addr) {
139 let mut rng = StdRng::seed_from_u64(0x1337); loop {
147 fuzzed_addr.randomize_with(&mut rng);
148 if !deployed_libs.contains(&fuzzed_addr) {
149 break;
150 }
151 }
152 }
153 DynSolValue::Address(fuzzed_addr)
154 })
155 .boxed()
156 }
157 DynSolType::Function => value()
158 .prop_map(move |value| {
159 DynSolValue::Function(alloy_primitives::Function::from_word(value))
160 })
161 .boxed(),
162 DynSolType::FixedBytes(size @ 1..=32) => value()
163 .prop_map(move |mut v| {
164 v[size..].fill(0);
165 DynSolValue::FixedBytes(B256::from(v), size)
166 })
167 .boxed(),
168 DynSolType::Bool => DynSolValue::type_strategy(param).boxed(),
169 DynSolType::String => DynSolValue::type_strategy(param)
170 .prop_map(move |value| {
171 DynSolValue::String(
172 value.as_str().unwrap().trim().trim_end_matches('\0').to_string(),
173 )
174 })
175 .boxed(),
176 DynSolType::Bytes => {
177 value().prop_map(move |value| DynSolValue::Bytes(value.0.into())).boxed()
178 }
179 DynSolType::Int(n @ 8..=256) => match n / 8 {
180 32 => value()
181 .prop_map(move |value| DynSolValue::Int(I256::from_raw(value.into()), 256))
182 .boxed(),
183 1..=31 => value()
184 .prop_map(move |value| {
185 let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
188 let max_int_plus1 = U256::from(1).wrapping_shl(n - 1);
189 let num = I256::from_raw(uint.wrapping_sub(max_int_plus1));
190 DynSolValue::Int(num, n)
191 })
192 .boxed(),
193 _ => unreachable!(),
194 },
195 DynSolType::Uint(n @ 8..=256) => match n / 8 {
196 32 => value()
197 .prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value.0), 256))
198 .boxed(),
199 1..=31 => value()
200 .prop_map(move |value| {
201 let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
202 DynSolValue::Uint(uint, n)
203 })
204 .boxed(),
205 _ => unreachable!(),
206 },
207 DynSolType::Tuple(ref params) => params
208 .iter()
209 .map(|p| fuzz_param_from_state(p, state))
210 .collect::<Vec<_>>()
211 .prop_map(DynSolValue::Tuple)
212 .boxed(),
213 DynSolType::FixedArray(ref param, size) => {
214 proptest::collection::vec(fuzz_param_from_state(param, state), size)
215 .prop_map(DynSolValue::FixedArray)
216 .boxed()
217 }
218 DynSolType::Array(ref param) => {
219 proptest::collection::vec(fuzz_param_from_state(param, state), 0..MAX_ARRAY_LEN)
220 .prop_map(DynSolValue::Array)
221 .boxed()
222 }
223 _ => panic!("unsupported fuzz param type: {param}"),
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use crate::{
230 strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
231 FuzzFixtures,
232 };
233 use foundry_common::abi::get_func;
234 use foundry_config::FuzzDictionaryConfig;
235 use revm::database::{CacheDB, EmptyDB};
236
237 #[test]
238 fn can_fuzz_array() {
239 let f = "testArray(uint64[2] calldata values)";
240 let func = get_func(f).unwrap();
241 let db = CacheDB::new(EmptyDB::default());
242 let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), &[]);
243 let strategy = proptest::prop_oneof![
244 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()),
245 40 => fuzz_calldata_from_state(func, &state),
246 ];
247 let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() };
248 let mut runner = proptest::test_runner::TestRunner::new(cfg);
249 let _ = runner.run(&strategy, |_| Ok(()));
250 }
251}