1use alloy_primitives::U256;
4use foundry_compilers::utils::canonicalized;
5use serde::{Deserialize, Deserializer, Serialize, de::Error};
6use std::path::PathBuf;
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub struct FuzzConfig {
11 #[serde(deserialize_with = "deserialize_fuzz_runs")]
13 pub runs: u32,
14 pub run: Option<u32>,
16 pub worker: Option<u32>,
18 pub fail_on_revert: bool,
20 pub max_test_rejects: u32,
23 pub seed: Option<U256>,
25 #[serde(flatten)]
27 pub dictionary: FuzzDictionaryConfig,
28 pub gas_report_samples: u32,
30 #[serde(flatten)]
32 pub corpus: FuzzCorpusConfig,
33 pub failure_persist_dir: Option<PathBuf>,
35 pub show_logs: bool,
37 pub timeout: Option<u32>,
39}
40
41impl Default for FuzzConfig {
42 fn default() -> Self {
43 Self {
44 runs: 256,
45 run: None,
46 worker: None,
47 fail_on_revert: true,
48 max_test_rejects: 65536,
49 seed: None,
50 dictionary: FuzzDictionaryConfig::default(),
51 gas_report_samples: 256,
52 corpus: FuzzCorpusConfig { payable_value_weight: 0, ..Default::default() },
53 failure_persist_dir: None,
54 show_logs: false,
55 timeout: None,
56 }
57 }
58}
59
60impl FuzzConfig {
61 pub fn new(cache_dir: PathBuf) -> Self {
63 Self { failure_persist_dir: Some(cache_dir), ..Default::default() }
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
69pub struct FuzzDictionaryConfig {
70 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
72 pub dictionary_weight: u32,
73 pub include_storage: bool,
75 pub include_push_bytes: bool,
77 #[serde(
82 deserialize_with = "crate::deserialize_usize_or_max",
83 serialize_with = "crate::serialize_usize_or_max"
84 )]
85 pub max_fuzz_dictionary_addresses: usize,
86 #[serde(
90 deserialize_with = "crate::deserialize_usize_or_max",
91 serialize_with = "crate::serialize_usize_or_max"
92 )]
93 pub max_fuzz_dictionary_values: usize,
94 #[serde(
98 deserialize_with = "crate::deserialize_usize_or_max",
99 serialize_with = "crate::serialize_usize_or_max"
100 )]
101 pub max_fuzz_dictionary_literals: usize,
102}
103
104impl Default for FuzzDictionaryConfig {
105 fn default() -> Self {
106 const MB: usize = 1024 * 1024;
107
108 Self {
109 dictionary_weight: 40,
110 include_storage: true,
111 include_push_bytes: true,
112 max_fuzz_dictionary_addresses: 300 * MB / 20,
113 max_fuzz_dictionary_values: 300 * MB / 32,
114 max_fuzz_dictionary_literals: 200 * MB / 32,
115 }
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
120pub struct FuzzCorpusConfig {
121 pub corpus_dir: Option<PathBuf>,
124 pub frontier_dir: Option<PathBuf>,
126 pub frontier_limit: usize,
128 pub corpus_gzip: bool,
130 pub corpus_min_mutations: usize,
133 pub corpus_min_size: usize,
135 pub show_edge_coverage: bool,
137 pub evm_edge_coverage_collision_free: bool,
139 pub evm_edge_coverage_include_call_depth: bool,
141 pub sancov_edges: bool,
145 pub sancov_trace_cmp: bool,
148 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
151 pub corpus_random_sequence_weight: u32,
152 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
154 pub payable_value_weight: u32,
155 #[serde(flatten)]
157 pub mutation_weights: FuzzCorpusMutationWeights,
158}
159
160impl FuzzCorpusConfig {
161 pub const DEFAULT_CORPUS_RANDOM_SEQUENCE_WEIGHT: u32 = 10;
162 pub const ENSEMBLE_CORPUS_RANDOM_SEQUENCE_WEIGHT: u32 = 50;
163 pub const DEFAULT_FRONTIER_LIMIT: usize = 256;
164
165 pub fn with_test(&mut self, contract: &str, test: &str) {
166 if let Some(corpus_dir) = &self.corpus_dir {
167 self.corpus_dir = Some(canonicalized(corpus_dir.join(contract).join(test)));
168 }
169 if let Some(frontier_dir) = &self.frontier_dir {
170 self.frontier_dir = Some(canonicalized(frontier_dir.join(contract).join(test)));
171 }
172 }
173
174 pub const fn collect_edge_coverage(&self) -> bool {
176 self.corpus_dir.is_some() || self.show_edge_coverage || self.sancov_edges
177 }
178
179 pub const fn collect_evm_edge_coverage(&self) -> bool {
186 !self.sancov_edges && (self.corpus_dir.is_some() || self.show_edge_coverage)
187 }
188
189 pub fn collect_evm_cmp_log(&self) -> bool {
196 self.capture_branch_frontiers()
197 || (!self.sancov_edges
198 && self.corpus_dir.is_some()
199 && self.mutation_weights.effective().mutation_weight_cmp > 0)
200 }
201
202 pub const fn capture_branch_frontiers(&self) -> bool {
204 self.frontier_dir.is_some() && self.frontier_limit > 0
205 }
206
207 pub const fn evm_edge_coverage_collision_free(&self) -> bool {
209 self.evm_edge_coverage_collision_free
210 }
211
212 pub const fn evm_edge_coverage_include_call_depth(&self) -> bool {
214 self.evm_edge_coverage_include_call_depth
215 }
216
217 pub const fn collect_sancov_edges(&self) -> bool {
219 self.sancov_edges
220 }
221
222 pub const fn collect_sancov_trace_cmp(&self) -> bool {
224 self.sancov_trace_cmp
225 }
226
227 pub const fn sancov_active(&self) -> bool {
229 self.sancov_edges || self.sancov_trace_cmp
230 }
231
232 pub const fn is_coverage_guided(&self) -> bool {
234 self.corpus_dir.is_some()
235 }
236}
237
238impl Default for FuzzCorpusConfig {
239 fn default() -> Self {
240 Self {
241 corpus_dir: None,
242 frontier_dir: None,
243 frontier_limit: Self::DEFAULT_FRONTIER_LIMIT,
244 corpus_gzip: true,
245 corpus_min_mutations: 5,
246 corpus_min_size: 0,
247 show_edge_coverage: false,
248 evm_edge_coverage_collision_free: true,
249 evm_edge_coverage_include_call_depth: false,
250 sancov_edges: false,
251 sancov_trace_cmp: false,
252 corpus_random_sequence_weight: Self::DEFAULT_CORPUS_RANDOM_SEQUENCE_WEIGHT,
253 payable_value_weight: 15,
254 mutation_weights: FuzzCorpusMutationWeights::default(),
255 }
256 }
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
260pub struct FuzzCorpusMutationWeights {
261 pub mutation_weight_splice: u32,
263 pub mutation_weight_repeat: u32,
265 pub mutation_weight_interleave: u32,
267 pub mutation_weight_prefix: u32,
269 pub mutation_weight_suffix: u32,
271 pub mutation_weight_abi: u32,
273 pub mutation_weight_cmp: u32,
275}
276
277impl FuzzCorpusMutationWeights {
278 pub const fn total(&self) -> u64 {
279 self.mutation_weight_splice as u64
280 + self.mutation_weight_repeat as u64
281 + self.mutation_weight_interleave as u64
282 + self.mutation_weight_prefix as u64
283 + self.mutation_weight_suffix as u64
284 + self.mutation_weight_abi as u64
285 + self.mutation_weight_cmp as u64
286 }
287
288 pub fn effective(self) -> Self {
290 if self.total() == 0 { Self::default() } else { self }
291 }
292}
293
294impl Default for FuzzCorpusMutationWeights {
295 fn default() -> Self {
296 Self {
297 mutation_weight_splice: 1,
298 mutation_weight_repeat: 1,
299 mutation_weight_interleave: 1,
300 mutation_weight_prefix: 1,
301 mutation_weight_suffix: 1,
302 mutation_weight_abi: 1,
303 mutation_weight_cmp: 1,
304 }
305 }
306}
307
308fn deserialize_fuzz_runs<'de, D>(deserializer: D) -> Result<u32, D::Error>
309where
310 D: Deserializer<'de>,
311{
312 let runs = u32::deserialize(deserializer)?;
313 if runs == 0 {
314 return Err(D::Error::custom("`fuzz.runs` must be greater than 0"));
315 }
316 Ok(runs)
317}