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 DynSolValue::Address(fuzzed_addr)
140 } else {
141 let mut rng = StdRng::seed_from_u64(0x1337); loop {
149 fuzzed_addr.randomize_with(&mut rng);
150 if !deployed_libs.contains(&fuzzed_addr) {
151 break;
152 }
153 }
154
155 DynSolValue::Address(fuzzed_addr)
156 }
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| DynSolValue::Int(I256::from_raw(value.into()), 256))
185 .boxed(),
186 1..=31 => value()
187 .prop_map(move |value| {
188 let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
191 let max_int_plus1 = U256::from(1).wrapping_shl(n - 1);
192 let num = I256::from_raw(uint.wrapping_sub(max_int_plus1));
193 DynSolValue::Int(num, n)
194 })
195 .boxed(),
196 _ => unreachable!(),
197 },
198 DynSolType::Uint(n @ 8..=256) => match n / 8 {
199 32 => value()
200 .prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value.0), 256))
201 .boxed(),
202 1..=31 => value()
203 .prop_map(move |value| {
204 let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
205 DynSolValue::Uint(uint, n)
206 })
207 .boxed(),
208 _ => unreachable!(),
209 },
210 DynSolType::Tuple(ref params) => params
211 .iter()
212 .map(|p| fuzz_param_from_state(p, state))
213 .collect::<Vec<_>>()
214 .prop_map(DynSolValue::Tuple)
215 .boxed(),
216 DynSolType::FixedArray(ref param, size) => {
217 proptest::collection::vec(fuzz_param_from_state(param, state), size)
218 .prop_map(DynSolValue::FixedArray)
219 .boxed()
220 }
221 DynSolType::Array(ref param) => {
222 proptest::collection::vec(fuzz_param_from_state(param, state), 0..MAX_ARRAY_LEN)
223 .prop_map(DynSolValue::Array)
224 .boxed()
225 }
226 _ => panic!("unsupported fuzz param type: {param}"),
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use crate::{
233 strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
234 FuzzFixtures,
235 };
236 use foundry_common::abi::get_func;
237 use foundry_config::FuzzDictionaryConfig;
238 use revm::db::{CacheDB, EmptyDB};
239
240 #[test]
241 fn can_fuzz_array() {
242 let f = "testArray(uint64[2] calldata values)";
243 let func = get_func(f).unwrap();
244 let db = CacheDB::new(EmptyDB::default());
245 let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), &[]);
246 let strategy = proptest::prop_oneof![
247 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()),
248 40 => fuzz_calldata_from_state(func, &state),
249 ];
250 let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() };
251 let mut runner = proptest::test_runner::TestRunner::new(cfg);
252 let _ = runner.run(&strategy, |_| Ok(()));
253 }
254}