1use alloy_primitives::U256;
4use foundry_compilers::utils::canonicalized;
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub struct FuzzConfig {
11 pub runs: u32,
13 pub run: Option<u32>,
15 pub worker: Option<u32>,
17 pub fail_on_revert: bool,
19 pub max_test_rejects: u32,
22 pub seed: Option<U256>,
24 #[serde(flatten)]
26 pub dictionary: FuzzDictionaryConfig,
27 pub gas_report_samples: u32,
29 #[serde(flatten)]
31 pub corpus: FuzzCorpusConfig,
32 pub failure_persist_dir: Option<PathBuf>,
34 pub show_logs: bool,
36 pub timeout: Option<u32>,
38}
39
40impl Default for FuzzConfig {
41 fn default() -> Self {
42 Self {
43 runs: 256,
44 run: None,
45 worker: None,
46 fail_on_revert: true,
47 max_test_rejects: 65536,
48 seed: None,
49 dictionary: FuzzDictionaryConfig::default(),
50 gas_report_samples: 256,
51 corpus: FuzzCorpusConfig { payable_value_weight: 0, ..Default::default() },
52 failure_persist_dir: None,
53 show_logs: false,
54 timeout: None,
55 }
56 }
57}
58
59impl FuzzConfig {
60 pub fn new(cache_dir: PathBuf) -> Self {
62 Self { failure_persist_dir: Some(cache_dir), ..Default::default() }
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
68pub struct FuzzDictionaryConfig {
69 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
71 pub dictionary_weight: u32,
72 pub include_storage: bool,
74 pub include_push_bytes: bool,
76 #[serde(
81 deserialize_with = "crate::deserialize_usize_or_max",
82 serialize_with = "crate::serialize_usize_or_max"
83 )]
84 pub max_fuzz_dictionary_addresses: usize,
85 #[serde(
89 deserialize_with = "crate::deserialize_usize_or_max",
90 serialize_with = "crate::serialize_usize_or_max"
91 )]
92 pub max_fuzz_dictionary_values: usize,
93 #[serde(
97 deserialize_with = "crate::deserialize_usize_or_max",
98 serialize_with = "crate::serialize_usize_or_max"
99 )]
100 pub max_fuzz_dictionary_literals: usize,
101}
102
103impl Default for FuzzDictionaryConfig {
104 fn default() -> Self {
105 const MB: usize = 1024 * 1024;
106
107 Self {
108 dictionary_weight: 40,
109 include_storage: true,
110 include_push_bytes: true,
111 max_fuzz_dictionary_addresses: 300 * MB / 20,
112 max_fuzz_dictionary_values: 300 * MB / 32,
113 max_fuzz_dictionary_literals: 200 * MB / 32,
114 }
115 }
116}
117
118#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
119pub struct FuzzCorpusConfig {
120 pub corpus_dir: Option<PathBuf>,
123 pub frontier_dir: Option<PathBuf>,
125 pub frontier_limit: usize,
127 pub corpus_gzip: bool,
129 pub corpus_min_mutations: usize,
132 pub corpus_min_size: usize,
134 pub show_edge_coverage: bool,
136 pub evm_edge_coverage_collision_free: bool,
138 pub evm_edge_coverage_include_call_depth: bool,
140 pub sancov_edges: bool,
144 pub sancov_trace_cmp: bool,
147 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
150 pub corpus_random_sequence_weight: u32,
151 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
153 pub payable_value_weight: u32,
154 #[serde(flatten)]
156 pub mutation_weights: FuzzCorpusMutationWeights,
157}
158
159impl FuzzCorpusConfig {
160 pub const DEFAULT_CORPUS_RANDOM_SEQUENCE_WEIGHT: u32 = 10;
161 pub const ENSEMBLE_CORPUS_RANDOM_SEQUENCE_WEIGHT: u32 = 50;
162 pub const DEFAULT_FRONTIER_LIMIT: usize = 256;
163
164 pub fn with_test(&mut self, contract: &str, test: &str) {
165 if let Some(corpus_dir) = &self.corpus_dir {
166 self.corpus_dir = Some(canonicalized(corpus_dir.join(contract).join(test)));
167 }
168 if let Some(frontier_dir) = &self.frontier_dir {
169 self.frontier_dir = Some(canonicalized(frontier_dir.join(contract).join(test)));
170 }
171 }
172
173 pub const fn collect_edge_coverage(&self) -> bool {
175 self.corpus_dir.is_some() || self.show_edge_coverage || self.sancov_edges
176 }
177
178 pub const fn collect_evm_edge_coverage(&self) -> bool {
185 !self.sancov_edges && (self.corpus_dir.is_some() || self.show_edge_coverage)
186 }
187
188 pub fn collect_evm_cmp_log(&self) -> bool {
195 self.capture_branch_frontiers()
196 || (!self.sancov_edges
197 && self.corpus_dir.is_some()
198 && self.mutation_weights.effective().mutation_weight_cmp > 0)
199 }
200
201 pub const fn capture_branch_frontiers(&self) -> bool {
203 self.frontier_dir.is_some() && self.frontier_limit > 0
204 }
205
206 pub const fn evm_edge_coverage_collision_free(&self) -> bool {
208 self.evm_edge_coverage_collision_free
209 }
210
211 pub const fn evm_edge_coverage_include_call_depth(&self) -> bool {
213 self.evm_edge_coverage_include_call_depth
214 }
215
216 pub const fn collect_sancov_edges(&self) -> bool {
218 self.sancov_edges
219 }
220
221 pub const fn collect_sancov_trace_cmp(&self) -> bool {
223 self.sancov_trace_cmp
224 }
225
226 pub const fn sancov_active(&self) -> bool {
228 self.sancov_edges || self.sancov_trace_cmp
229 }
230
231 pub const fn is_coverage_guided(&self) -> bool {
233 self.corpus_dir.is_some()
234 }
235}
236
237impl Default for FuzzCorpusConfig {
238 fn default() -> Self {
239 Self {
240 corpus_dir: None,
241 frontier_dir: None,
242 frontier_limit: Self::DEFAULT_FRONTIER_LIMIT,
243 corpus_gzip: true,
244 corpus_min_mutations: 5,
245 corpus_min_size: 0,
246 show_edge_coverage: false,
247 evm_edge_coverage_collision_free: true,
248 evm_edge_coverage_include_call_depth: false,
249 sancov_edges: false,
250 sancov_trace_cmp: false,
251 corpus_random_sequence_weight: Self::DEFAULT_CORPUS_RANDOM_SEQUENCE_WEIGHT,
252 payable_value_weight: 15,
253 mutation_weights: FuzzCorpusMutationWeights::default(),
254 }
255 }
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
259pub struct FuzzCorpusMutationWeights {
260 pub mutation_weight_splice: u32,
262 pub mutation_weight_repeat: u32,
264 pub mutation_weight_interleave: u32,
266 pub mutation_weight_prefix: u32,
268 pub mutation_weight_suffix: u32,
270 pub mutation_weight_abi: u32,
272 pub mutation_weight_cmp: u32,
274}
275
276impl FuzzCorpusMutationWeights {
277 pub const fn total(&self) -> u64 {
278 self.mutation_weight_splice as u64
279 + self.mutation_weight_repeat as u64
280 + self.mutation_weight_interleave as u64
281 + self.mutation_weight_prefix as u64
282 + self.mutation_weight_suffix as u64
283 + self.mutation_weight_abi as u64
284 + self.mutation_weight_cmp as u64
285 }
286
287 pub fn effective(self) -> Self {
289 if self.total() == 0 { Self::default() } else { self }
290 }
291}
292
293impl Default for FuzzCorpusMutationWeights {
294 fn default() -> Self {
295 Self {
296 mutation_weight_splice: 1,
297 mutation_weight_repeat: 1,
298 mutation_weight_interleave: 1,
299 mutation_weight_prefix: 1,
300 mutation_weight_suffix: 1,
301 mutation_weight_abi: 1,
302 mutation_weight_cmp: 1,
303 }
304 }
305}