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