foundry_config/invariant.rs
1//! Configuration for invariant testing
2
3use crate::fuzz::FuzzDictionaryConfig;
4use alloy_primitives::U256;
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8/// Contains for invariant testing
9#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub struct InvariantConfig {
11 /// The number of runs that must execute for each invariant test group.
12 pub runs: u32,
13 /// The number of calls executed to attempt to break invariants in one run.
14 pub depth: u32,
15 /// Fails the invariant fuzzing if a revert occurs
16 pub fail_on_revert: bool,
17 /// Allows overriding an unsafe external call when running invariant tests. eg. reentrancy
18 /// checks
19 pub call_override: bool,
20 /// The fuzz dictionary configuration
21 #[serde(flatten)]
22 pub dictionary: FuzzDictionaryConfig,
23 /// The maximum number of attempts to shrink the sequence
24 pub shrink_run_limit: u32,
25 /// The maximum number of rejects via `vm.assume` which can be encountered during a single
26 /// invariant run.
27 pub max_assume_rejects: u32,
28 /// Number of runs to execute and include in the gas report.
29 pub gas_report_samples: u32,
30 /// Path where invariant corpus is stored, enables coverage guided fuzzing and edge coverage
31 /// metrics.
32 pub corpus_dir: Option<PathBuf>,
33 /// Whether corpus to use gzip file compression and decompression.
34 pub corpus_gzip: bool,
35 // Number of corpus mutations until marked as eligible to be flushed from memory.
36 pub corpus_min_mutations: usize,
37 // Number of corpus that won't be evicted from memory.
38 pub corpus_min_size: usize,
39 /// Path where invariant failures are recorded and replayed.
40 pub failure_persist_dir: Option<PathBuf>,
41 /// Whether to collect and display fuzzed selectors metrics.
42 pub show_metrics: bool,
43 /// Optional timeout (in seconds) for each invariant test.
44 pub timeout: Option<u32>,
45 /// Display counterexample as solidity calls.
46 pub show_solidity: bool,
47 /// Whether to collect and display edge coverage metrics.
48 pub show_edge_coverage: bool,
49 /// Maximum value for fuzzed integers, used to simulate smaller integer types.
50 /// When set, unsigned integers are clamped to [0, max_fuzz_int] and signed integers
51 /// are clamped to [-(max_fuzz_int+1), max_fuzz_int] to match real signed type ranges.
52 /// Used for Polkadot compatibility where balances are u128.
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub max_fuzz_int: Option<U256>,
55}
56
57impl Default for InvariantConfig {
58 fn default() -> Self {
59 Self {
60 runs: 256,
61 depth: 500,
62 fail_on_revert: false,
63 call_override: false,
64 dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() },
65 shrink_run_limit: 5000,
66 max_assume_rejects: 65536,
67 gas_report_samples: 256,
68 corpus_dir: None,
69 corpus_gzip: true,
70 corpus_min_mutations: 5,
71 corpus_min_size: 0,
72 failure_persist_dir: None,
73 show_metrics: true,
74 timeout: None,
75 show_solidity: false,
76 show_edge_coverage: false,
77 max_fuzz_int: None,
78 }
79 }
80}
81
82impl InvariantConfig {
83 /// Creates invariant configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir.
84 pub fn new(cache_dir: PathBuf) -> Self {
85 Self {
86 runs: 256,
87 depth: 500,
88 fail_on_revert: false,
89 call_override: false,
90 dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() },
91 shrink_run_limit: 5000,
92 max_assume_rejects: 65536,
93 gas_report_samples: 256,
94 corpus_dir: None,
95 corpus_gzip: true,
96 corpus_min_mutations: 5,
97 corpus_min_size: 0,
98 failure_persist_dir: Some(cache_dir),
99 show_metrics: true,
100 timeout: None,
101 show_solidity: false,
102 show_edge_coverage: false,
103 max_fuzz_int: None,
104 }
105 }
106}