foundry_config/fuzz.rs
1//! Configuration for fuzz testing.
2
3use alloy_primitives::U256;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7/// Contains for fuzz testing
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9pub struct FuzzConfig {
10 /// The number of test cases that must execute for each property test
11 pub runs: u32,
12 /// Fails the fuzzed test if a revert occurs.
13 pub fail_on_revert: bool,
14 /// The maximum number of test case rejections allowed by proptest, to be
15 /// encountered during usage of `vm.assume` cheatcode. This will be used
16 /// to set the `max_global_rejects` value in proptest test runner config.
17 /// `max_local_rejects` option isn't exposed here since we're not using
18 /// `prop_filter`.
19 pub max_test_rejects: u32,
20 /// Optional seed for the fuzzing RNG algorithm
21 pub seed: Option<U256>,
22 /// The fuzz dictionary configuration
23 #[serde(flatten)]
24 pub dictionary: FuzzDictionaryConfig,
25 /// Number of runs to execute and include in the gas report.
26 pub gas_report_samples: u32,
27 /// Path where fuzz failures are recorded and replayed.
28 pub failure_persist_dir: Option<PathBuf>,
29 /// Name of the file to record fuzz failures, defaults to `failures`.
30 pub failure_persist_file: Option<String>,
31 /// show `console.log` in fuzz test, defaults to `false`
32 pub show_logs: bool,
33 /// Optional timeout (in seconds) for each property test
34 pub timeout: Option<u32>,
35 /// Maximum value for fuzzed integers, used to simulate smaller integer types.
36 /// When set, unsigned integers are clamped to [0, max_fuzz_int] and signed integers
37 /// are clamped to [-(max_fuzz_int+1), max_fuzz_int] to match real signed type ranges.
38 /// Useful for Polkadot compatibility where balances are u128.
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub max_fuzz_int: Option<U256>,
41}
42
43impl Default for FuzzConfig {
44 fn default() -> Self {
45 Self {
46 runs: 256,
47 fail_on_revert: true,
48 max_test_rejects: 65536,
49 seed: None,
50 dictionary: FuzzDictionaryConfig::default(),
51 gas_report_samples: 256,
52 failure_persist_dir: None,
53 failure_persist_file: None,
54 show_logs: false,
55 timeout: None,
56 max_fuzz_int: None,
57 }
58 }
59}
60
61impl FuzzConfig {
62 /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir.
63 pub fn new(cache_dir: PathBuf) -> Self {
64 Self {
65 failure_persist_dir: Some(cache_dir),
66 failure_persist_file: Some("failures".to_string()),
67 ..Default::default()
68 }
69 }
70}
71
72/// Contains for fuzz testing
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
74pub struct FuzzDictionaryConfig {
75 /// The weight of the dictionary
76 #[serde(deserialize_with = "crate::deserialize_stringified_percent")]
77 pub dictionary_weight: u32,
78 /// The flag indicating whether to include values from storage
79 pub include_storage: bool,
80 /// The flag indicating whether to include push bytes values
81 pub include_push_bytes: bool,
82 /// How many addresses to record at most.
83 /// Once the fuzzer exceeds this limit, it will start evicting random entries
84 ///
85 /// This limit is put in place to prevent memory blowup.
86 #[serde(deserialize_with = "crate::deserialize_usize_or_max")]
87 pub max_fuzz_dictionary_addresses: usize,
88 /// How many values to record at most.
89 /// Once the fuzzer exceeds this limit, it will start evicting random entries
90 #[serde(deserialize_with = "crate::deserialize_usize_or_max")]
91 pub max_fuzz_dictionary_values: usize,
92}
93
94impl Default for FuzzDictionaryConfig {
95 fn default() -> Self {
96 Self {
97 dictionary_weight: 40,
98 include_storage: true,
99 include_push_bytes: true,
100 // limit this to 300MB
101 max_fuzz_dictionary_addresses: (300 * 1024 * 1024) / 20,
102 // limit this to 200MB
103 max_fuzz_dictionary_values: (200 * 1024 * 1024) / 32,
104 }
105 }
106}