Skip to content

fix: enable edge coverage only when needed #11041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/config/src/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ pub struct InvariantConfig {
pub max_assume_rejects: u32,
/// Number of runs to execute and include in the gas report.
pub gas_report_samples: u32,
/// Path where invariant corpus is stored. If not configured then coverage guided fuzzing is
/// disabled.
/// Path where invariant corpus is stored, enables coverage guided fuzzing and edge coverage
/// metrics.
pub corpus_dir: Option<PathBuf>,
/// Whether corpus to use gzip file compression and decompression.
pub corpus_gzip: bool,
Expand All @@ -43,6 +43,8 @@ pub struct InvariantConfig {
pub timeout: Option<u32>,
/// Display counterexample as solidity calls.
pub show_solidity: bool,
/// Whether to collect and display edge coverage metrics.
pub show_edge_coverage: bool,
}

impl Default for InvariantConfig {
Expand All @@ -64,6 +66,7 @@ impl Default for InvariantConfig {
show_metrics: true,
timeout: None,
show_solidity: false,
show_edge_coverage: false,
}
}
}
Expand All @@ -88,6 +91,7 @@ impl InvariantConfig {
show_metrics: true,
timeout: None,
show_solidity: false,
show_edge_coverage: false,
}
}
}
13 changes: 8 additions & 5 deletions crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ impl<'a> InvariantExecutor<'a> {
runs < self.config.runs
};

// Invariant runs with edge coverage if corpus dir is set or showing edge coverage.
let edge_coverage_enabled =
self.config.corpus_dir.is_some() || self.config.show_edge_coverage;

'stop: while continue_campaign(runs) {
let initial_seq = corpus_manager.new_sequence(&invariant_test)?;

Expand Down Expand Up @@ -407,9 +411,9 @@ impl<'a> InvariantExecutor<'a> {

// Collect line coverage from last fuzzed call.
invariant_test.merge_coverage(call_result.line_coverage.clone());
// If coverage guided fuzzing is enabled then merge edge count with current history
// If running with edge coverage then merge edge count with the current history
// map and set new coverage in current run.
if self.config.corpus_dir.is_some() {
if edge_coverage_enabled {
let (new_coverage, is_edge) =
call_result.merge_edge_coverage(&mut self.history_map);
if new_coverage {
Expand Down Expand Up @@ -514,15 +518,14 @@ impl<'a> InvariantExecutor<'a> {

// End current invariant test run.
invariant_test.end_run(current_run, self.config.gas_report_samples as usize);

if let Some(progress) = progress {
// If running with progress then increment completed runs.
progress.inc(1);
// Display metrics in progress bar.
if self.config.corpus_dir.is_some() {
if edge_coverage_enabled {
progress.set_message(format!("{}", &corpus_manager.metrics));
}
} else if self.config.corpus_dir.is_some()
} else if edge_coverage_enabled
&& last_metrics_report.elapsed() > DURATION_BETWEEN_METRICS_REPORT
{
// Display metrics inline if corpus dir set.
Expand Down
1 change: 0 additions & 1 deletion crates/evm/evm/src/inspectors/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ impl InspectorStackBuilder {
stack.set_chisel(chisel_state);
}
stack.collect_line_coverage(line_coverage.unwrap_or(false));
stack.collect_edge_coverage(true);
stack.collect_logs(logs.unwrap_or(true));
stack.print(print.unwrap_or(false));
stack.tracing(trace_mode);
Expand Down
9 changes: 8 additions & 1 deletion crates/forge/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,15 @@ impl<'a> FunctionRunner<'a> {
let runner = self.invariant_runner();
let invariant_config = &self.config.invariant;

let mut executor = self.clone_executor();
// Enable edge coverage if running with coverage guided fuzzing or with edge coverage
// metrics (useful for benchmarking the fuzzer).
executor.inspector_mut().collect_edge_coverage(
invariant_config.corpus_dir.is_some() || invariant_config.show_edge_coverage,
);

let mut evm = InvariantExecutor::new(
self.clone_executor(),
executor,
runner,
invariant_config.clone(),
identified_contracts,
Expand Down
4 changes: 3 additions & 1 deletion crates/forge/tests/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,7 @@ corpus_min_size = 0
failure_persist_dir = "cache/invariant"
show_metrics = true
show_solidity = false
show_edge_coverage = false
[labels]
Expand Down Expand Up @@ -1234,7 +1235,8 @@ exclude = []
"failure_persist_dir": "cache/invariant",
"show_metrics": true,
"timeout": null,
"show_solidity": false
"show_solidity": false,
"show_edge_coverage": false
},
"ffi": false,
"allow_internal_expect_revert": false,
Expand Down
1 change: 1 addition & 0 deletions crates/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ impl ForgeTestProfile {
show_metrics: true,
timeout: None,
show_solidity: false,
show_edge_coverage: false,
};

config.sanitized()
Expand Down