From 4dc2aeb74c5d09f318e381dfac1b73e5863c15fb Mon Sep 17 00:00:00 2001 From: grandizzy Date: Tue, 22 Oct 2024 11:47:07 +0300 Subject: [PATCH 1/2] feat(invariant): add basic metrics report --- Cargo.lock | 1 + crates/evm/evm/Cargo.toml | 1 + crates/evm/evm/src/executors/invariant/mod.rs | 45 +++++++++++- .../evm/evm/src/executors/invariant/result.rs | 6 +- crates/evm/fuzz/src/invariant/mod.rs | 12 ++++ crates/forge/bin/cmd/snapshot.rs | 15 +++- crates/forge/bin/cmd/test/mod.rs | 8 +++ crates/forge/bin/cmd/test/summary.rs | 40 ++++++++++- crates/forge/src/result.rs | 39 ++++++---- crates/forge/src/runner.rs | 1 + crates/forge/tests/it/invariant.rs | 71 +++++++++++++++++++ 11 files changed, 217 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f88eeb08629c..58e182424882 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3942,6 +3942,7 @@ dependencies = [ "proptest", "revm", "revm-inspectors", + "serde", "thiserror", "tracing", ] diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 5250ea448ac4..214b241e31ab 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -50,3 +50,4 @@ proptest.workspace = true thiserror.workspace = true tracing.workspace = true indicatif = "0.17" +serde.workspace = true diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 58c7efd8fe12..25682461e03c 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -31,7 +31,11 @@ use proptest::{ use result::{assert_after_invariant, assert_invariants, can_continue}; use revm::primitives::HashMap; use shrink::shrink_sequence; -use std::{cell::RefCell, collections::btree_map::Entry, sync::Arc}; +use std::{ + cell::RefCell, + collections::{btree_map::Entry, HashMap as Map}, + sync::Arc, +}; mod error; pub use error::{InvariantFailures, InvariantFuzzError}; @@ -42,6 +46,7 @@ pub use replay::{replay_error, replay_run}; mod result; pub use result::InvariantFuzzTestResult; +use serde::{Deserialize, Serialize}; mod shrink; use crate::executors::EvmError; @@ -101,6 +106,17 @@ sol! { } } +/// Contains invariant metrics for a single fuzzed selector. +#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct InvariantMetrics { + // Count of fuzzed selector calls. + pub calls: usize, + // Count of fuzzed selector reverts. + pub reverts: usize, + // Count of fuzzed selector discards (through assume cheatcodes). + pub discards: usize, +} + /// Contains data collected during invariant test runs. pub struct InvariantTestData { // Consumed gas and calldata of every successful fuzz call. @@ -115,6 +131,8 @@ pub struct InvariantTestData { pub last_call_results: Option, // Coverage information collected from all fuzzed calls. pub coverage: Option, + // Metrics for each fuzzed selector. + pub metrics: Map, // Proptest runner to query for random values. // The strategy only comes with the first `input`. We fill the rest of the `inputs` @@ -153,6 +171,7 @@ impl InvariantTest { gas_report_traces: vec![], last_call_results, coverage: None, + metrics: Map::default(), branch_runner, }); Self { fuzz_state, targeted_contracts, execution_data } @@ -191,6 +210,24 @@ impl InvariantTest { } } + /// Update metrics for a fuzzed selector, extracted from tx details. + /// Always increments number of calls; discarded runs (through assume cheatcodes) are tracked + /// separated from reverts. + pub fn record_metrics(&self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) { + if let Some(metric_key) = + self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details) + { + let test_metrics = &mut self.execution_data.borrow_mut().metrics; + let invariant_metrics = test_metrics.entry(metric_key).or_default(); + invariant_metrics.calls += 1; + if discarded { + invariant_metrics.discards += 1; + } else if reverted { + invariant_metrics.reverts += 1; + } + } + } + /// End invariant test run by collecting results, cleaning collected artifacts and reverting /// created fuzz state. pub fn end_run(&self, run: InvariantTestRun, gas_samples: usize) { @@ -331,10 +368,13 @@ impl<'a> InvariantExecutor<'a> { TestCaseError::fail(format!("Could not make raw evm call: {e}")) })?; + let discarded = call_result.result.as_ref() == MAGIC_ASSUME; + invariant_test.record_metrics(tx, call_result.reverted, discarded); + // Collect coverage from last fuzzed call. invariant_test.merge_coverage(call_result.coverage.clone()); - if call_result.result.as_ref() == MAGIC_ASSUME { + if discarded { current_run.inputs.pop(); current_run.assume_rejects_counter += 1; if current_run.assume_rejects_counter > self.config.max_assume_rejects { @@ -443,6 +483,7 @@ impl<'a> InvariantExecutor<'a> { last_run_inputs: result.last_run_inputs, gas_report_traces: result.gas_report_traces, coverage: result.coverage, + metrics: result.metrics, }) } diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs index c6a15930cbae..8920a1209342 100644 --- a/crates/evm/evm/src/executors/invariant/result.rs +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -1,6 +1,6 @@ use super::{ call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, - InvariantFailures, InvariantFuzzError, InvariantTest, InvariantTestRun, + InvariantFailures, InvariantFuzzError, InvariantMetrics, InvariantTest, InvariantTestRun, }; use crate::executors::{Executor, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; @@ -13,7 +13,7 @@ use foundry_evm_fuzz::{ FuzzedCases, }; use revm_inspectors::tracing::CallTraceArena; -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashMap}; /// The outcome of an invariant fuzz test #[derive(Debug)] @@ -30,6 +30,8 @@ pub struct InvariantFuzzTestResult { pub gas_report_traces: Vec>, /// The coverage info collected during the invariant test runs. pub coverage: Option, + /// Fuzzed selectors metrics collected during the invariant test runs. + pub metrics: HashMap, } /// Enriched results of an invariant run check. diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index d6b3e574d8b1..49f27e9f3950 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -125,6 +125,18 @@ impl TargetedContracts { .filter(|(_, c)| !c.abi.functions.is_empty()) .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f))) } + + /// Identifies fuzzed contract and function based on given tx details and returns unique metric + /// key composed from contract identifier and function name. + pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option { + self.inner.get(&tx.call_details.target).and_then(|contract| { + contract + .abi + .functions() + .find(|f| f.selector() == tx.call_details.calldata[..4]) + .map(|function| format!("{}.{}", contract.identifier.clone(), function.name)) + }) + } } impl std::ops::Deref for TargetedContracts { diff --git a/crates/forge/bin/cmd/snapshot.rs b/crates/forge/bin/cmd/snapshot.rs index 0d7c2843a83f..2f964acbde3d 100644 --- a/crates/forge/bin/cmd/snapshot.rs +++ b/crates/forge/bin/cmd/snapshot.rs @@ -242,6 +242,7 @@ impl FromStr for GasSnapshotEntry { runs: runs.as_str().parse().unwrap(), calls: calls.as_str().parse().unwrap(), reverts: reverts.as_str().parse().unwrap(), + metrics: HashMap::default(), }, }) } @@ -486,7 +487,12 @@ mod tests { GasSnapshotEntry { contract_name: "Test".to_string(), signature: "deposit()".to_string(), - gas_used: TestKindReport::Invariant { runs: 256, calls: 100, reverts: 200 } + gas_used: TestKindReport::Invariant { + runs: 256, + calls: 100, + reverts: 200, + metrics: HashMap::default() + } } ); } @@ -500,7 +506,12 @@ mod tests { GasSnapshotEntry { contract_name: "ERC20Invariants".to_string(), signature: "invariantBalanceSum()".to_string(), - gas_used: TestKindReport::Invariant { runs: 256, calls: 3840, reverts: 2388 } + gas_used: TestKindReport::Invariant { + runs: 256, + calls: 3840, + reverts: 2388, + metrics: HashMap::default() + } } ); } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 7038fc0b5a8b..c9d4825c74a2 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -54,7 +54,9 @@ mod summary; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; use summary::TestSummaryReporter; +use crate::cmd::test::summary::print_invariant_metrics; pub use filter::FilterArgs; +use forge::result::TestKind; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); @@ -612,6 +614,12 @@ impl TestArgs { if !silent { shell::println(result.short_result(name))?; + if let TestKind::Invariant { runs: _, calls: _, reverts: _, metrics } = + &result.kind + { + print_invariant_metrics(metrics); + } + // We only display logs at level 2 and above if verbosity >= 2 { // We only decode logs from Hardhat and DS-style console events diff --git a/crates/forge/bin/cmd/test/summary.rs b/crates/forge/bin/cmd/test/summary.rs index 561ea6b6824c..6d20edfd52f5 100644 --- a/crates/forge/bin/cmd/test/summary.rs +++ b/crates/forge/bin/cmd/test/summary.rs @@ -1,7 +1,11 @@ use crate::cmd::test::TestOutcome; use comfy_table::{ - modifiers::UTF8_ROUND_CORNERS, Attribute, Cell, CellAlignment, Color, Row, Table, + modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, + Row, Table, }; +use foundry_evm::executors::invariant::InvariantMetrics; +use itertools::Itertools; +use std::collections::HashMap; /// A simple summary reporter that prints the test results in a table. pub struct TestSummaryReporter { @@ -91,3 +95,37 @@ impl TestSummaryReporter { println!("\n{}", self.table); } } + +/// Helper to create and render invariant metrics summary table: +/// | Contract | Selector | Calls | Reverts | Discards | +/// |-----------------------|----------------|-------|---------|----------| +/// | AnotherCounterHandler | doWork | 7451 | 123 | 4941 | +/// | AnotherCounterHandler | doWorkThing | 7279 | 137 | 4849 | +/// | CounterHandler | doAnotherThing | 7302 | 150 | 4794 | +/// | CounterHandler | doSomething | 7382 | 160 | 4830 | +pub(crate) fn print_invariant_metrics(test_metrics: &HashMap) { + if !test_metrics.is_empty() { + let mut table = Table::new(); + table.load_preset(ASCII_MARKDOWN); + table.set_header(["Contract", "Selector", "Calls", "Reverts", "Discards"]); + + for name in test_metrics.keys().sorted() { + if let Some((contract, selector)) = + name.split_once(':').and_then(|(_, contract)| contract.split_once('.')) + { + let mut row = Row::new(); + row.add_cell(Cell::new(contract).set_alignment(CellAlignment::Left)); + row.add_cell(Cell::new(selector).set_alignment(CellAlignment::Left)); + if let Some(metrics) = test_metrics.get(name) { + row.add_cell(Cell::new(metrics.calls).set_alignment(CellAlignment::Center)); + row.add_cell(Cell::new(metrics.reverts).set_alignment(CellAlignment::Center)); + row.add_cell(Cell::new(metrics.discards).set_alignment(CellAlignment::Center)); + } + + table.add_row(row); + } + } + + println!("{table}\n"); + } +} diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 4fb88dfd0b59..7fc2bf1a97b9 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -13,13 +13,13 @@ use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell}; use foundry_evm::{ coverage::HitMaps, decode::SkipReason, - executors::{EvmError, RawCallResult}, + executors::{invariant::InvariantMetrics, EvmError, RawCallResult}, fuzz::{CounterExample, FuzzCase, FuzzFixtures, FuzzTestResult}, traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, }; use serde::{Deserialize, Serialize}; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap as Map}, fmt::{self, Write}, time::Duration, }; @@ -579,7 +579,8 @@ impl TestResult { /// Returns the skipped result for invariant test. pub fn invariant_skip(mut self, reason: SkipReason) -> Self { - self.kind = TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }; + self.kind = + TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; self.status = TestStatus::Skipped; self.reason = reason.0; self @@ -592,7 +593,8 @@ impl TestResult { invariant_name: &String, call_sequence: Vec, ) -> Self { - self.kind = TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }; + self.kind = + TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; self.status = TestStatus::Failure; self.reason = if replayed_entirely { Some(format!("{invariant_name} replay failure")) @@ -605,13 +607,15 @@ impl TestResult { /// Returns the fail result for invariant test setup. pub fn invariant_setup_fail(mut self, e: Report) -> Self { - self.kind = TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }; + self.kind = + TestKind::Invariant { runs: 0, calls: 0, reverts: 0, metrics: HashMap::default() }; self.status = TestStatus::Failure; self.reason = Some(format!("failed to set up invariant testing environment: {e}")); self } /// Returns the invariant test result. + #[allow(clippy::too_many_arguments)] pub fn invariant_result( mut self, gas_report_traces: Vec>, @@ -620,11 +624,13 @@ impl TestResult { counterexample: Option, cases: Vec, reverts: usize, + metrics: Map, ) -> Self { self.kind = TestKind::Invariant { runs: cases.len(), calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), reverts, + metrics, }; self.status = match success { true => TestStatus::Success, @@ -669,19 +675,19 @@ impl TestResult { pub enum TestKindReport { Unit { gas: u64 }, Fuzz { runs: usize, mean_gas: u64, median_gas: u64 }, - Invariant { runs: usize, calls: usize, reverts: usize }, + Invariant { runs: usize, calls: usize, reverts: usize, metrics: Map }, } impl fmt::Display for TestKindReport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { + match self { Self::Unit { gas } => { write!(f, "(gas: {gas})") } Self::Fuzz { runs, mean_gas, median_gas } => { write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") } - Self::Invariant { runs, calls, reverts } => { + Self::Invariant { runs, calls, reverts, metrics: _ } => { write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})") } } @@ -715,7 +721,7 @@ pub enum TestKind { median_gas: u64, }, /// An invariant test. - Invariant { runs: usize, calls: usize, reverts: usize }, + Invariant { runs: usize, calls: usize, reverts: usize, metrics: Map }, } impl Default for TestKind { @@ -727,14 +733,17 @@ impl Default for TestKind { impl TestKind { /// The gas consumed by this test pub fn report(&self) -> TestKindReport { - match *self { - Self::Unit { gas } => TestKindReport::Unit { gas }, + match self { + Self::Unit { gas } => TestKindReport::Unit { gas: *gas }, Self::Fuzz { first_case: _, runs, mean_gas, median_gas } => { - TestKindReport::Fuzz { runs, mean_gas, median_gas } - } - Self::Invariant { runs, calls, reverts } => { - TestKindReport::Invariant { runs, calls, reverts } + TestKindReport::Fuzz { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas } } + Self::Invariant { runs, calls, reverts, metrics: _ } => TestKindReport::Invariant { + runs: *runs, + calls: *calls, + reverts: *reverts, + metrics: HashMap::default(), + }, } } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index e580fb8b2fba..3c98bb04062e 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -623,6 +623,7 @@ impl ContractRunner<'_> { counterexample, invariant_result.cases, invariant_result.reverts, + invariant_result.metrics, ) } diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 37b3c9b23934..8031e5ec111b 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -855,3 +855,74 @@ contract NoSelectorTest is Test { ... "#]]); }); + +// +forgetest_init!(should_show_invariant_metrics, |prj, cmd| { + prj.add_test( + "SelectorMetricsTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function setUp() public { + CounterHandler handler = new CounterHandler(); + AnotherCounterHandler handler1 = new AnotherCounterHandler(); + // targetContract(address(handler1)); + } + + /// forge-config: default.invariant.runs = 10 + function invariant_counter() public {} + + /// forge-config: default.invariant.runs = 10 + function invariant_counter2() public {} +} + +contract CounterHandler is Test { + function doSomething(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } + + function doAnotherThing(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } +} + +contract AnotherCounterHandler is Test { + function doWork(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } + + function doWorkThing(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_"]).assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/SelectorMetricsTest.t.sol:CounterTest +[PASS] invariant_counter() (runs: 10, calls: 5000, reverts: [..]) +| Contract | Selector | Calls | Reverts | Discards | +|-----------------------|----------------|-------|---------|----------| +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +| CounterHandler | doSomething | [..] | [..] | [..] | + +[PASS] invariant_counter2() (runs: 10, calls: 5000, reverts: [..]) +| Contract | Selector | Calls | Reverts | Discards | +|-----------------------|----------------|-------|---------|----------| +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +| CounterHandler | doSomething | [..] | [..] | [..] | + +... +"#]]); +}); From 3edde163433fcf3d6f210d61fd83d9821b3a5a9b Mon Sep 17 00:00:00 2001 From: grandizzy Date: Tue, 22 Oct 2024 17:02:35 +0300 Subject: [PATCH 2/2] Put metrics behind show-metrics invariant config --- crates/config/src/invariant.rs | 5 +++++ crates/evm/evm/src/executors/invariant/mod.rs | 4 +++- crates/forge/bin/cmd/test/mod.rs | 2 +- crates/forge/tests/it/invariant.rs | 2 ++ crates/forge/tests/it/test_helpers.rs | 1 + 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 698f63c26922..70e9a2b85847 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -34,6 +34,8 @@ pub struct InvariantConfig { pub gas_report_samples: u32, /// Path where invariant failures are recorded and replayed. pub failure_persist_dir: Option, + /// Whether to collect and display fuzzed selectors metrics. + pub show_metrics: bool, } impl Default for InvariantConfig { @@ -48,6 +50,7 @@ impl Default for InvariantConfig { max_assume_rejects: 65536, gas_report_samples: 256, failure_persist_dir: None, + show_metrics: false, } } } @@ -65,6 +68,7 @@ impl InvariantConfig { max_assume_rejects: 65536, gas_report_samples: 256, failure_persist_dir: Some(cache_dir), + show_metrics: false, } } @@ -103,6 +107,7 @@ impl InlineConfigParser for InvariantConfig { conf_clone.failure_persist_dir = Some(PathBuf::from(value)) } "shrink-run-limit" => conf_clone.shrink_run_limit = parse_config_u32(key, value)?, + "show-metrics" => conf_clone.show_metrics = parse_config_bool(key, value)?, _ => Err(InlineConfigParserError::InvalidConfigProperty(key.to_string()))?, } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 25682461e03c..860acdbb26d0 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -369,7 +369,9 @@ impl<'a> InvariantExecutor<'a> { })?; let discarded = call_result.result.as_ref() == MAGIC_ASSUME; - invariant_test.record_metrics(tx, call_result.reverted, discarded); + if self.config.show_metrics { + invariant_test.record_metrics(tx, call_result.reverted, discarded); + } // Collect coverage from last fuzzed call. invariant_test.merge_coverage(call_result.coverage.clone()); diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index c9d4825c74a2..74365b1023fb 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -613,7 +613,7 @@ impl TestArgs { for (name, result) in tests { if !silent { shell::println(result.short_result(name))?; - + // Display invariant metrics if invariant kind. if let TestKind::Invariant { runs: _, calls: _, reverts: _, metrics } = &result.kind { diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 8031e5ec111b..3e09cd465a33 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -871,9 +871,11 @@ contract CounterTest is Test { } /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.show-metrics = true function invariant_counter() public {} /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.show-metrics = true function invariant_counter2() public {} } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index e5aee722ff07..eb5a0bf0a3c0 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -112,6 +112,7 @@ impl ForgeTestProfile { max_assume_rejects: 65536, gas_report_samples: 256, failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), + show_metrics: false, }) .build(output, Path::new(self.project().root())) .expect("Config loaded")