From ed476510127ca47bd9a75dd664c333e5192daab4 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 7 Oct 2024 14:40:30 +0200 Subject: [PATCH 1/9] add gas report generation in JSON --- crates/forge/bin/cmd/test/mod.rs | 90 +++++++++++++++++++------------- crates/forge/src/gas_report.rs | 53 ++++++++++++++++++- 2 files changed, 107 insertions(+), 36 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6d53a4756bd7..69437518a312 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -5,7 +5,7 @@ use clap::{Parser, ValueHint}; use eyre::{Context, OptionExt, Result}; use forge::{ decode::decode_console_logs, - gas_report::GasReport, + gas_report::{GasReport, ReportKind}, multi_runner::matches_contract, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ @@ -474,6 +474,8 @@ impl TestArgs { trace!(target: "forge::test", "running all tests"); + let has_serialized_output = self.gas_report && (self.json || self.junit); + let num_filtered = runner.matching_test_functions(filter).count(); if num_filtered != 1 && (self.debug.is_some() || self.flamegraph || self.flamechart) { let action = if self.flamegraph { @@ -499,16 +501,18 @@ impl TestArgs { runner.decode_internal = InternalTraceMode::Full; } - if self.json { - let results = runner.test_collect(filter); - println!("{}", serde_json::to_string(&results)?); - return Ok(TestOutcome::new(results, self.allow_failure)); - } + if !self.gas_report { + if self.json { + let results = runner.test_collect(filter); + println!("{}", serde_json::to_string(&results)?); + return Ok(TestOutcome::new(results, self.allow_failure)); + } - if self.junit { - let results = runner.test_collect(filter); - println!("{}", junit_xml_report(&results, verbosity).to_string()?); - return Ok(TestOutcome::new(results, self.allow_failure)); + if self.junit { + let results = runner.test_collect(filter); + println!("{}", junit_xml_report(&results, verbosity).to_string()?); + return Ok(TestOutcome::new(results, self.allow_failure)); + } } let remote_chain_id = runner.evm_opts.get_remote_chain_id().await; @@ -553,9 +557,19 @@ impl TestArgs { } let mut decoder = builder.build(); - let mut gas_report = self - .gas_report - .then(|| GasReport::new(config.gas_reports.clone(), config.gas_reports_ignore.clone())); + let mut gas_report = self.gas_report.then(|| { + GasReport::new( + config.gas_reports.clone(), + config.gas_reports_ignore.clone(), + if self.json { + ReportKind::JSON + } else if self.junit { + ReportKind::JUnit + } else { + ReportKind::Markdown + }, + ) + }); let mut gas_snapshots = BTreeMap::>::new(); @@ -576,30 +590,34 @@ impl TestArgs { self.flamechart; // Print suite header. - println!(); - for warning in suite_result.warnings.iter() { - eprintln!("{} {warning}", "Warning:".yellow().bold()); - } - if !tests.is_empty() { - let len = tests.len(); - let tests = if len > 1 { "tests" } else { "test" }; - println!("Ran {len} {tests} for {contract_name}"); + if !has_serialized_output { + println!(); + for warning in suite_result.warnings.iter() { + eprintln!("{} {warning}", "Warning:".yellow().bold()); + } + if !tests.is_empty() { + let len = tests.len(); + let tests = if len > 1 { "tests" } else { "test" }; + println!("Ran {len} {tests} for {contract_name}"); + } } // Process individual test results, printing logs and traces when necessary. for (name, result) in tests { - shell::println(result.short_result(name))?; - - // We only display logs at level 2 and above - if verbosity >= 2 { - // We only decode logs from Hardhat and DS-style console events - let console_logs = decode_console_logs(&result.logs); - if !console_logs.is_empty() { - println!("Logs:"); - for log in console_logs { - println!(" {log}"); + if !has_serialized_output { + shell::println(result.short_result(name))?; + + // We only display logs at level 2 and above + if verbosity >= 2 { + // We only decode logs from Hardhat and DS-style console events + let console_logs = decode_console_logs(&result.logs); + if !console_logs.is_empty() { + println!("Logs:"); + for log in console_logs { + println!(" {log}"); + } + println!(); } - println!(); } } @@ -641,7 +659,7 @@ impl TestArgs { } } - if !decoded_traces.is_empty() { + if !has_serialized_output && !decoded_traces.is_empty() { shell::println("Traces:")?; for trace in &decoded_traces { shell::println(trace)?; @@ -748,7 +766,9 @@ impl TestArgs { } // Print suite summary. - shell::println(suite_result.summary())?; + if !has_serialized_output { + shell::println(suite_result.summary())?; + } // Add the suite result to the outcome. outcome.results.insert(contract_name, suite_result); @@ -769,7 +789,7 @@ impl TestArgs { outcome.gas_report = Some(finalized); } - if !outcome.results.is_empty() { + if !has_serialized_output && !outcome.results.is_empty() { shell::println(outcome.summary(duration))?; if self.summary { diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 85f53b5f517b..039d80b2e81e 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -14,11 +14,26 @@ use std::{ }; use yansi::Paint; +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum ReportKind { + Markdown, + JSON, + JUnit, +} + +impl Default for ReportKind { + fn default() -> Self { + Self::Markdown + } +} + /// Represents the gas report for a set of contracts. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasReport { /// Whether to report any contracts. report_any: bool, + /// Whether to format the report in markdown. + report_type: ReportKind, /// Contracts to generate the report for. report_for: HashSet, /// Contracts to ignore when generating the report. @@ -32,11 +47,13 @@ impl GasReport { pub fn new( report_for: impl IntoIterator, ignore: impl IntoIterator, + report_kind: ReportKind, ) -> Self { let report_for = report_for.into_iter().collect::>(); let ignore = ignore.into_iter().collect::>(); let report_any = report_for.is_empty() || report_for.contains("*"); - Self { report_any, report_for, ignore, ..Default::default() } + let report_type = report_kind; + Self { report_any, report_type, report_for, ignore, ..Default::default() } } /// Whether the given contract should be reported. @@ -147,6 +164,40 @@ impl Display for GasReport { continue; } + if self.report_type == ReportKind::JSON { + let mut contract_json = serde_json::to_value(contract).unwrap(); + if let Some(functions) = + contract_json.get_mut("functions").and_then(|f| f.as_object_mut()) + { + for sigs in functions.values_mut() { + if let Some(sigs) = sigs.as_object_mut() { + for gas_info in sigs.values_mut() { + if let Some(gas_info) = gas_info.as_object_mut() { + if let Some(calls) = + gas_info.get("calls").and_then(|v| v.as_array()) + { + gas_info.insert( + "calls".to_string(), + serde_json::Value::Number(serde_json::Number::from( + calls.len(), + )), + ); + } + } + } + } + } + } + writeln!(f, "{}", serde_json::to_string_pretty(&contract_json).unwrap())?; + + continue; + } + + if self.report_type == ReportKind::JUnit { + writeln!(f, "Not implemented")?; + continue; + } + let mut table = Table::new(); table.load_preset(ASCII_MARKDOWN); table.set_header([Cell::new(format!("{name} contract")) From 3d05f2209b13738cf569a240037f785f9eeaddda Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 7 Oct 2024 14:43:52 +0200 Subject: [PATCH 2/9] skip junit for now --- crates/forge/src/gas_report.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 039d80b2e81e..ab6d95fb8726 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -15,13 +15,12 @@ use std::{ use yansi::Paint; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum ReportKind { +pub enum GasReportKind { Markdown, JSON, - JUnit, } -impl Default for ReportKind { +impl Default for GasReportKind { fn default() -> Self { Self::Markdown } @@ -32,8 +31,8 @@ impl Default for ReportKind { pub struct GasReport { /// Whether to report any contracts. report_any: bool, - /// Whether to format the report in markdown. - report_type: ReportKind, + /// What kind of report to generate. + report_type: GasReportKind, /// Contracts to generate the report for. report_for: HashSet, /// Contracts to ignore when generating the report. @@ -47,7 +46,7 @@ impl GasReport { pub fn new( report_for: impl IntoIterator, ignore: impl IntoIterator, - report_kind: ReportKind, + report_kind: GasReportKind, ) -> Self { let report_for = report_for.into_iter().collect::>(); let ignore = ignore.into_iter().collect::>(); @@ -164,7 +163,7 @@ impl Display for GasReport { continue; } - if self.report_type == ReportKind::JSON { + if self.report_type == GasReportKind::JSON { let mut contract_json = serde_json::to_value(contract).unwrap(); if let Some(functions) = contract_json.get_mut("functions").and_then(|f| f.as_object_mut()) @@ -189,12 +188,6 @@ impl Display for GasReport { } } writeln!(f, "{}", serde_json::to_string_pretty(&contract_json).unwrap())?; - - continue; - } - - if self.report_type == ReportKind::JUnit { - writeln!(f, "Not implemented")?; continue; } From c63ca63648c9ec452ad4f17e83f5bd0d5310e4b0 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 7 Oct 2024 15:32:02 +0200 Subject: [PATCH 3/9] add json formatted tests, trailing space and invalid formatting --- crates/forge/bin/cmd/test/mod.rs | 45 ++--- crates/forge/src/gas_report.rs | 2 +- crates/forge/tests/cli/cmd.rs | 301 ++++++++++++------------------- 3 files changed, 137 insertions(+), 211 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 69437518a312..70fbb5fe4102 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -5,7 +5,7 @@ use clap::{Parser, ValueHint}; use eyre::{Context, OptionExt, Result}; use forge::{ decode::decode_console_logs, - gas_report::{GasReport, ReportKind}, + gas_report::{GasReport, GasReportKind}, multi_runner::matches_contract, result::{SuiteResult, TestOutcome, TestStatus}, traces::{ @@ -112,7 +112,7 @@ pub struct TestArgs { json: bool, /// Output test results as JUnit XML report. - #[arg(long, conflicts_with = "json", help_heading = "Display options")] + #[arg(long, conflicts_with_all(["json", "gas_report"]), help_heading = "Display options")] junit: bool, /// Stop running tests after the first failure. @@ -474,7 +474,8 @@ impl TestArgs { trace!(target: "forge::test", "running all tests"); - let has_serialized_output = self.gas_report && (self.json || self.junit); + // If we need to render to a serialized format, we should not print anything else to stdout. + let silent = self.gas_report && self.json; let num_filtered = runner.matching_test_functions(filter).count(); if num_filtered != 1 && (self.debug.is_some() || self.flamegraph || self.flamechart) { @@ -501,18 +502,16 @@ impl TestArgs { runner.decode_internal = InternalTraceMode::Full; } - if !self.gas_report { - if self.json { - let results = runner.test_collect(filter); - println!("{}", serde_json::to_string(&results)?); - return Ok(TestOutcome::new(results, self.allow_failure)); - } + if !self.gas_report && self.json { + let results = runner.test_collect(filter); + println!("{}", serde_json::to_string(&results)?); + return Ok(TestOutcome::new(results, self.allow_failure)); + } - if self.junit { - let results = runner.test_collect(filter); - println!("{}", junit_xml_report(&results, verbosity).to_string()?); - return Ok(TestOutcome::new(results, self.allow_failure)); - } + if self.junit { + let results = runner.test_collect(filter); + println!("{}", junit_xml_report(&results, verbosity).to_string()?); + return Ok(TestOutcome::new(results, self.allow_failure)); } let remote_chain_id = runner.evm_opts.get_remote_chain_id().await; @@ -561,13 +560,7 @@ impl TestArgs { GasReport::new( config.gas_reports.clone(), config.gas_reports_ignore.clone(), - if self.json { - ReportKind::JSON - } else if self.junit { - ReportKind::JUnit - } else { - ReportKind::Markdown - }, + if self.json { GasReportKind::JSON } else { GasReportKind::Markdown }, ) }); @@ -590,7 +583,7 @@ impl TestArgs { self.flamechart; // Print suite header. - if !has_serialized_output { + if !silent { println!(); for warning in suite_result.warnings.iter() { eprintln!("{} {warning}", "Warning:".yellow().bold()); @@ -604,7 +597,7 @@ impl TestArgs { // Process individual test results, printing logs and traces when necessary. for (name, result) in tests { - if !has_serialized_output { + if !silent { shell::println(result.short_result(name))?; // We only display logs at level 2 and above @@ -659,7 +652,7 @@ impl TestArgs { } } - if !has_serialized_output && !decoded_traces.is_empty() { + if !silent && !decoded_traces.is_empty() { shell::println("Traces:")?; for trace in &decoded_traces { shell::println(trace)?; @@ -766,7 +759,7 @@ impl TestArgs { } // Print suite summary. - if !has_serialized_output { + if !silent { shell::println(suite_result.summary())?; } @@ -789,7 +782,7 @@ impl TestArgs { outcome.gas_report = Some(finalized); } - if !has_serialized_output && !outcome.results.is_empty() { + if !silent && !outcome.results.is_empty() { shell::println(outcome.summary(duration))?; if self.summary { diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index ab6d95fb8726..e78783a78bb8 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -187,7 +187,7 @@ impl Display for GasReport { } } } - writeln!(f, "{}", serde_json::to_string_pretty(&contract_json).unwrap())?; + writeln!(f, "{}", serde_json::to_string(&contract_json).unwrap())?; continue; } diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 6c8bab9289f6..8e915f65db8d 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1434,11 +1434,7 @@ Compiler run successful! } ); -forgetest!(gas_report_all_contracts, |prj, cmd| { - prj.insert_ds_test(); - prj.add_source( - "Contracts.sol", - r#" +const GAS_REPORT_CONTRACTS: &str = r#" //SPDX-license-identifier: MIT import "./test.sol"; @@ -1521,9 +1517,11 @@ contract ContractThreeTest is DSTest { c3.baz(); } } - "#, - ) - .unwrap(); +"#; + +forgetest!(gas_report_all_contracts, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); // report for all prj.write_config(Config { @@ -1540,6 +1538,18 @@ contract ContractThreeTest is DSTest { .get_output() .stdout_lossy(); assert!(first_out.contains("foo") && first_out.contains("bar") && first_out.contains("baz")); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]]); prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); let second_out = cmd @@ -1550,6 +1560,18 @@ contract ContractThreeTest is DSTest { .get_output() .stdout_lossy(); assert!(second_out.contains("foo") && second_out.contains("bar") && second_out.contains("baz")); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]]); prj.write_config(Config { gas_reports: (vec!["*".to_string()]), ..Default::default() }); let third_out = cmd @@ -1560,6 +1582,18 @@ contract ContractThreeTest is DSTest { .get_output() .stdout_lossy(); assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]]); prj.write_config(Config { gas_reports: (vec![ @@ -1577,98 +1611,23 @@ contract ContractThreeTest is DSTest { .get_output() .stdout_lossy(); assert!(fourth_out.contains("foo") && fourth_out.contains("bar") && fourth_out.contains("baz")); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]]); }); forgetest!(gas_report_some_contracts, |prj, cmd| { prj.insert_ds_test(); - prj.add_source( - "Contracts.sol", - r#" -//SPDX-license-identifier: MIT - -import "./test.sol"; - -contract ContractOne { - int public i; - - constructor() { - i = 0; - } - - function foo() public{ - while(i<5){ - i++; - } - } -} - -contract ContractOneTest is DSTest { - ContractOne c1; - - function setUp() public { - c1 = new ContractOne(); - } - - function testFoo() public { - c1.foo(); - } -} - - -contract ContractTwo { - int public i; - - constructor() { - i = 0; - } - - function bar() public{ - while(i<50){ - i++; - } - } -} - -contract ContractTwoTest is DSTest { - ContractTwo c2; - - function setUp() public { - c2 = new ContractTwo(); - } - - function testBar() public { - c2.bar(); - } -} - -contract ContractThree { - int public i; - - constructor() { - i = 0; - } - - function baz() public{ - while(i<500){ - i++; - } - } -} - -contract ContractThreeTest is DSTest { - ContractThree c3; - - function setUp() public { - c3 = new ContractThree(); - } - - function testBaz() public { - c3.baz(); - } -} - "#, - ) - .unwrap(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); // report for One prj.write_config(Config { gas_reports: vec!["ContractOne".to_string()], ..Default::default() }); @@ -1679,6 +1638,16 @@ contract ContractThreeTest is DSTest { first_out.contains("foo") && !first_out.contains("bar") && !first_out.contains("baz"), "foo:\n{first_out}" ); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} + + +"#]]); // report for Two prj.write_config(Config { gas_reports: vec!["ContractTwo".to_string()], ..Default::default() }); @@ -1689,6 +1658,13 @@ contract ContractThreeTest is DSTest { !second_out.contains("foo") && second_out.contains("bar") && !second_out.contains("baz"), "bar:\n{second_out}" ); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]], + ); // report for Three prj.write_config(Config { @@ -1702,98 +1678,21 @@ contract ContractThreeTest is DSTest { !third_out.contains("foo") && !third_out.contains("bar") && third_out.contains("baz"), "baz:\n{third_out}" ); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} + + +"#]]); }); forgetest!(gas_ignore_some_contracts, |prj, cmd| { prj.insert_ds_test(); - prj.add_source( - "Contracts.sol", - r#" -//SPDX-license-identifier: MIT - -import "./test.sol"; - -contract ContractOne { - int public i; - - constructor() { - i = 0; - } - - function foo() public{ - while(i<5){ - i++; - } - } -} - -contract ContractOneTest is DSTest { - ContractOne c1; - - function setUp() public { - c1 = new ContractOne(); - } - - function testFoo() public { - c1.foo(); - } -} - - -contract ContractTwo { - int public i; - - constructor() { - i = 0; - } - - function bar() public{ - while(i<50){ - i++; - } - } -} - -contract ContractTwoTest is DSTest { - ContractTwo c2; - - function setUp() public { - c2 = new ContractTwo(); - } - - function testBar() public { - c2.bar(); - } -} - -contract ContractThree { - int public i; - - constructor() { - i = 0; - } - - function baz() public{ - while(i<500){ - i++; - } - } -} - -contract ContractThreeTest is DSTest { - ContractThree c3; - - function setUp() public { - c3 = new ContractThree(); - } - - function testBaz() public { - c3.baz(); - } -} - "#, - ) - .unwrap(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); // ignore ContractOne prj.write_config(Config { @@ -1805,6 +1704,17 @@ contract ContractThreeTest is DSTest { let first_out = cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); assert!(!first_out.contains("foo") && first_out.contains("bar") && first_out.contains("baz")); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]]); // ignore ContractTwo cmd.forge_fuse(); @@ -1819,6 +1729,17 @@ contract ContractThreeTest is DSTest { assert!( second_out.contains("foo") && !second_out.contains("bar") && second_out.contains("baz") ); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} + + +"#]]); // ignore ContractThree cmd.forge_fuse(); @@ -1835,6 +1756,18 @@ contract ContractThreeTest is DSTest { let third_out = cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq(str![[r#" +{"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} +{"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} +{"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} + + +"#]]); }); forgetest_init!(can_use_absolute_imports, |prj, cmd| { From a842ad595065117d0dc2eb4f6fe3329830e3431e Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 8 Oct 2024 11:26:26 +0200 Subject: [PATCH 4/9] avoid redundant modifications for calls count --- crates/forge/src/gas_report.rs | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index e78783a78bb8..bfc31cd52976 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -148,6 +148,7 @@ impl GasReport { func.max = func.calls.last().copied().unwrap_or_default(); func.mean = calc::mean(&func.calls); func.median = calc::median_sorted(&func.calls); + func.count = func.calls.len() as u64; } } } @@ -164,30 +165,7 @@ impl Display for GasReport { } if self.report_type == GasReportKind::JSON { - let mut contract_json = serde_json::to_value(contract).unwrap(); - if let Some(functions) = - contract_json.get_mut("functions").and_then(|f| f.as_object_mut()) - { - for sigs in functions.values_mut() { - if let Some(sigs) = sigs.as_object_mut() { - for gas_info in sigs.values_mut() { - if let Some(gas_info) = gas_info.as_object_mut() { - if let Some(calls) = - gas_info.get("calls").and_then(|v| v.as_array()) - { - gas_info.insert( - "calls".to_string(), - serde_json::Value::Number(serde_json::Number::from( - calls.len(), - )), - ); - } - } - } - } - } - } - writeln!(f, "{}", serde_json::to_string(&contract_json).unwrap())?; + writeln!(f, "{}", serde_json::to_string(&contract).unwrap())?; continue; } @@ -222,7 +200,7 @@ impl Display for GasReport { Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), Cell::new(gas_info.median.to_string()).fg(Color::Yellow), Cell::new(gas_info.max.to_string()).fg(Color::Red), - Cell::new(gas_info.calls.len().to_string()), + Cell::new(gas_info.count.to_string()), ]); }) }); @@ -243,7 +221,9 @@ pub struct ContractInfo { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasInfo { + #[serde(skip)] pub calls: Vec, + pub count: u64, pub min: u64, pub mean: u64, pub median: u64, From d1ec6345df9be375ca3a942ebd7720bbaa800075 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 8 Oct 2024 11:44:00 +0200 Subject: [PATCH 5/9] replace existing tests with snapbox --- crates/forge/src/gas_report.rs | 23 +- crates/forge/tests/cli/cmd.rs | 433 ++++++++++++++++++++++++++++----- 2 files changed, 384 insertions(+), 72 deletions(-) diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index bfc31cd52976..1c11abaf6976 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -131,7 +131,7 @@ impl GasReport { .or_default() .entry(signature.clone()) .or_default(); - gas_info.calls.push(trace.gas_used); + gas_info.frames.push(trace.gas_used); } } } @@ -143,12 +143,12 @@ impl GasReport { for contract in self.contracts.values_mut() { for sigs in contract.functions.values_mut() { for func in sigs.values_mut() { - func.calls.sort_unstable(); - func.min = func.calls.first().copied().unwrap_or_default(); - func.max = func.calls.last().copied().unwrap_or_default(); - func.mean = calc::mean(&func.calls); - func.median = calc::median_sorted(&func.calls); - func.count = func.calls.len() as u64; + func.frames.sort_unstable(); + func.min = func.frames.first().copied().unwrap_or_default(); + func.max = func.frames.last().copied().unwrap_or_default(); + func.mean = calc::mean(&func.frames); + func.median = calc::median_sorted(&func.frames); + func.calls = func.frames.len() as u64; } } } @@ -200,7 +200,7 @@ impl Display for GasReport { Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), Cell::new(gas_info.median.to_string()).fg(Color::Yellow), Cell::new(gas_info.max.to_string()).fg(Color::Red), - Cell::new(gas_info.count.to_string()), + Cell::new(gas_info.calls.to_string()), ]); }) }); @@ -221,11 +221,12 @@ pub struct ContractInfo { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasInfo { - #[serde(skip)] - pub calls: Vec, - pub count: u64, + pub calls: u64, pub min: u64, pub mean: u64, pub median: u64, pub max: u64, + + #[serde(skip)] + pub frames: Vec, } diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 8e915f65db8d..f9c2488f6622 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1530,14 +1530,51 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { ..Default::default() }); - let first_out = cmd - .forge_fuse() - .arg("test") - .arg("--gas-report") - .assert_success() - .get_output() - .stdout_lossy(); - assert!(first_out.contains("foo") && first_out.contains("bar") && first_out.contains("baz")); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1552,14 +1589,49 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { "#]]); prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); - let second_out = cmd - .forge_fuse() - .arg("test") - .arg("--gas-report") - .assert_success() - .get_output() - .stdout_lossy(); - assert!(second_out.contains("foo") && second_out.contains("bar") && second_out.contains("baz")); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1574,14 +1646,49 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { "#]]); prj.write_config(Config { gas_reports: (vec!["*".to_string()]), ..Default::default() }); - let third_out = cmd - .forge_fuse() - .arg("test") - .arg("--gas-report") - .assert_success() - .get_output() - .stdout_lossy(); - assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1603,14 +1710,49 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { ]), ..Default::default() }); - let fourth_out = cmd - .forge_fuse() - .arg("test") - .arg("--gas-report") - .assert_success() - .get_output() - .stdout_lossy(); - assert!(fourth_out.contains("foo") && fourth_out.contains("bar") && fourth_out.contains("baz")); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1632,12 +1774,35 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { // report for One prj.write_config(Config { gas_reports: vec!["ContractOne".to_string()], ..Default::default() }); cmd.forge_fuse(); - let first_out = - cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); - assert!( - first_out.contains("foo") && !first_out.contains("bar") && !first_out.contains("baz"), - "foo:\n{first_out}" - ); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1652,12 +1817,33 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { // report for Two prj.write_config(Config { gas_reports: vec!["ContractTwo".to_string()], ..Default::default() }); cmd.forge_fuse(); - let second_out = - cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); - assert!( - !second_out.contains("foo") && second_out.contains("bar") && !second_out.contains("baz"), - "bar:\n{second_out}" - ); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( str![[r#" {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} @@ -1672,12 +1858,33 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { ..Default::default() }); cmd.forge_fuse(); - let third_out = - cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); - assert!( - !third_out.contains("foo") && !third_out.contains("bar") && third_out.contains("baz"), - "baz:\n{third_out}" - ); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1701,9 +1908,43 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { ..Default::default() }); cmd.forge_fuse(); - let first_out = - cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); - assert!(!first_out.contains("foo") && first_out.contains("bar") && first_out.contains("baz")); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1724,11 +1965,41 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { ..Default::default() }); cmd.forge_fuse(); - let second_out = - cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); - assert!( - second_out.contains("foo") && !second_out.contains("bar") && second_out.contains("baz") - ); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") @@ -1753,9 +2024,49 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { ..Default::default() }); cmd.forge_fuse(); - let third_out = - cmd.arg("test").arg("--gas-report").assert_success().get_output().stdout_lossy(); - assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/Contracts.sol:ContractOneTest +[PASS] testFoo() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractTwoTest +[PASS] testBar() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test for src/Contracts.sol:ContractThreeTest +[PASS] testBaz() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +| src/Contracts.sol:ContractOne contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| foo | 45387 | 45387 | 45387 | 45387 | 1 | + + +| src/Contracts.sol:ContractThree contract | | | | | | +|------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103591 | 256 | | | | | +| Function Name | min | avg | median | max | # calls | +| baz | 260712 | 260712 | 260712 | 260712 | 1 | + + +| src/Contracts.sol:ContractTwo contract | | | | | | +|----------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 103375 | 255 | | | | | +| Function Name | min | avg | median | max | # calls | +| bar | 64984 | 64984 | 64984 | 64984 | 1 | + + + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); cmd.forge_fuse() .arg("test") .arg("--gas-report") From e9d484966f91a5653d28be605aba3231cebdd241 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 8 Oct 2024 11:46:36 +0200 Subject: [PATCH 6/9] clean up snapbox tests --- crates/forge/tests/cli/cmd.rs | 206 ++++------------------------------ 1 file changed, 20 insertions(+), 186 deletions(-) diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index f9c2488f6622..ce8d5f87eea0 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1531,21 +1531,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -1568,11 +1554,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1590,19 +1572,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -1625,11 +1595,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1647,19 +1613,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) prj.write_config(Config { gas_reports: (vec!["*".to_string()]), ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -1682,11 +1636,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1711,19 +1661,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -1746,11 +1684,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1775,32 +1709,14 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { prj.write_config(Config { gas_reports: vec!["ContractOne".to_string()], ..Default::default() }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | foo | 45387 | 45387 | 45387 | 45387 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1818,30 +1734,14 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) prj.write_config(Config { gas_reports: vec!["ContractTwo".to_string()], ..Default::default() }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractTwo contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( @@ -1859,30 +1759,14 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractThree contract | | | | | | |------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | | 103591 | 256 | | | | | | Function Name | min | avg | median | max | # calls | | baz | 260712 | 260712 | 260712 | 260712 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1909,21 +1793,7 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractThree contract | | | | | | |------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -1938,11 +1808,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -1966,19 +1832,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -1993,11 +1847,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103591 | 256 | | | | | | Function Name | min | avg | median | max | # calls | | baz | 260712 | 260712 | 260712 | 260712 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() @@ -2025,19 +1875,7 @@ Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) }); cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped - -Ran 1 test for src/Contracts.sol:ContractOneTest -[PASS] testFoo() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractTwoTest -[PASS] testBar() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for src/Contracts.sol:ContractThreeTest -[PASS] testBaz() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... | src/Contracts.sol:ContractOne contract | | | | | | |----------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -2060,11 +1898,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] | 103375 | 255 | | | | | | Function Name | min | avg | median | max | # calls | | bar | 64984 | 64984 | 64984 | 64984 | 1 | - - - - -Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) +... "#]]); cmd.forge_fuse() From e8c0480f07a841cc51d1128e61d68decba5d7bad Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 8 Oct 2024 11:48:04 +0200 Subject: [PATCH 7/9] merge in master --- Cargo.lock | 60 +++--- Cargo.toml | 3 +- crates/anvil/src/cmd.rs | 5 + crates/anvil/src/config.rs | 11 ++ crates/anvil/src/eth/api.rs | 6 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 7 +- crates/anvil/src/eth/backend/mem/mod.rs | 5 + crates/anvil/src/eth/backend/mem/storage.rs | 3 +- crates/anvil/src/eth/fees.rs | 8 + crates/anvil/src/eth/pool/transactions.rs | 14 +- crates/cheatcodes/Cargo.toml | 1 - crates/cheatcodes/assets/cheatcodes.json | 62 ++++++- crates/cheatcodes/spec/src/vm.rs | 14 +- crates/cheatcodes/src/evm.rs | 58 +++--- crates/cheatcodes/src/evm/mock.rs | 38 +++- crates/cheatcodes/src/fs.rs | 3 +- crates/cheatcodes/src/inspector.rs | 43 +++-- crates/cheatcodes/src/test/expect.rs | 7 +- crates/common/Cargo.toml | 1 - crates/common/src/evm.rs | 8 +- crates/common/src/selectors.rs | 3 +- crates/evm/abi/Cargo.toml | 1 - crates/evm/abi/src/console/hardhat.rs | 7 +- crates/evm/core/Cargo.toml | 1 - crates/evm/core/src/backend/cow.rs | 9 + crates/evm/core/src/backend/mod.rs | 99 ++++++---- crates/evm/core/src/decode.rs | 5 +- crates/evm/core/src/fork/database.rs | 3 +- crates/evm/core/src/ic.rs | 10 +- crates/evm/coverage/Cargo.toml | 1 - crates/evm/coverage/src/analysis.rs | 4 +- crates/evm/coverage/src/anchors.rs | 6 +- crates/evm/coverage/src/lib.rs | 4 +- crates/evm/evm/src/lib.rs | 6 - crates/evm/fuzz/src/strategies/state.rs | 8 +- crates/evm/traces/Cargo.toml | 1 - crates/evm/traces/src/debug/sources.rs | 3 +- crates/evm/traces/src/decoder/mod.rs | 18 +- .../evm/traces/src/identifier/signatures.rs | 11 +- crates/forge/Cargo.toml | 1 - crates/forge/bin/cmd/clone.rs | 3 +- crates/forge/bin/cmd/coverage.rs | 10 +- crates/forge/bin/cmd/test/mod.rs | 2 +- crates/forge/src/coverage.rs | 6 +- crates/forge/src/gas_report.rs | 6 +- crates/forge/src/result.rs | 1 - crates/forge/src/runner.rs | 9 +- crates/forge/tests/cli/test_cmd.rs | 41 +++++ .../SimpleContractTestNonVerbose.json | 27 +++ .../fixtures/SimpleContractTestVerbose.json | 172 ++++++++++++++++++ crates/script/src/providers.rs | 7 +- crates/script/src/simulate.rs | 4 +- crates/test-utils/src/rpc.rs | 3 +- crates/verify/src/etherscan/mod.rs | 3 +- crates/verify/src/sourcify.rs | 4 +- crates/wallets/src/wallet.rs | 3 +- deny.toml | 1 + testdata/cheats/Vm.sol | 3 + testdata/default/cheats/CloneAccount.t.sol | 45 +++++ testdata/default/cheats/MockCalls.t.sol | 59 ++++++ 60 files changed, 730 insertions(+), 237 deletions(-) create mode 100644 crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json create mode 100644 crates/forge/tests/fixtures/SimpleContractTestVerbose.json create mode 100644 testdata/default/cheats/CloneAccount.t.sol create mode 100644 testdata/default/cheats/MockCalls.t.sol diff --git a/Cargo.lock b/Cargo.lock index 80fbb6212154..d94ee063df48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b499852e1d0e9b8c6db0f24c48998e647c0d5762a01090f955106a7700e4611" +checksum = "1109c57718022ac84c194f775977a534e1b3969b405e55693a61c42187cc0612" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -231,9 +231,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a438d4486b5d525df3b3004188f9d5cd1d65cd30ecc41e5a3ccef6f6342e8af9" +checksum = "c4cc0e59c803dd44d14fc0cfa9fea1f74cfa8fd9fb60ca303ced390c58c28d4e" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260d3ff3bff0bb84599f032a2f2c6828180b0ea0cd41fdaf44f39cef3ba41861" +checksum = "a289ffd7448036f2f436b377f981c79ce0b2090877bad938d43387dc09931877" dependencies = [ "alloy-rlp", "arbitrary", @@ -314,8 +314,9 @@ dependencies = [ "const-hex", "derive_arbitrary", "derive_more 1.0.0", + "foldhash", "getrandom", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "hex-literal", "indexmap 2.6.0", "itoa", @@ -680,9 +681,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e7f6e8fe5b443f82b3f1e15abfa191128f71569148428e49449d01f6f49e8b" +checksum = "0409e3ba5d1de409997a7db8b8e9d679d52088c1dee042a85033affd3cadeab4" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -694,9 +695,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b96ce28d2fde09abb6135f410c41fad670a3a770b6776869bd852f1df102e6f" +checksum = "a18372ef450d59f74c7a64a738f546ba82c92f816597fed1802ef559304c81f1" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -713,9 +714,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906746396a8296537745711630d9185746c0b50c033d5e9d18b0a6eba3d53f90" +checksum = "f7bad89dd0d5f109e8feeaf787a9ed7a05a91a9a0efc6687d147a70ebca8eff7" dependencies = [ "alloy-json-abi", "const-hex", @@ -730,9 +731,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc85178909a49c8827ffccfc9103a7ce1767ae66a801b69bdc326913870bf8e6" +checksum = "dbd3548d5262867c2c4be6223fe4f2583e21ade0ca1c307fd23bc7f28fca479e" dependencies = [ "serde", "winnow", @@ -740,9 +741,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86a533ce22525969661b25dfe296c112d35eb6861f188fd284f8bd4bb3842ae" +checksum = "4aa666f1036341b46625e72bd36878bf45ad0185f1b88601223e1ec6ed4b72b1" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -1994,9 +1995,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d9e0b4957f635b8d3da819d0db5603620467ecf1f692d22a8c2717ce27e6d8" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "shlex", ] @@ -3305,6 +3306,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -3386,7 +3393,6 @@ dependencies = [ "regex", "reqwest", "revm-inspectors", - "rustc-hash", "semver 1.0.23", "serde", "serde_json", @@ -3666,7 +3672,6 @@ dependencies = [ "proptest", "rand", "revm", - "rustc-hash", "semver 1.0.23", "serde_json", "thiserror", @@ -3754,7 +3759,6 @@ dependencies = [ "foundry-macros", "num-format", "reqwest", - "rustc-hash", "semver 1.0.23", "serde", "serde_json", @@ -3987,7 +3991,6 @@ dependencies = [ "foundry-macros", "foundry-test-utils", "itertools 0.13.0", - "rustc-hash", ] [[package]] @@ -4016,7 +4019,6 @@ dependencies = [ "parking_lot", "revm", "revm-inspectors", - "rustc-hash", "serde", "serde_json", "thiserror", @@ -4035,7 +4037,6 @@ dependencies = [ "foundry-evm-core", "rayon", "revm", - "rustc-hash", "semver 1.0.23", "tracing", ] @@ -4086,7 +4087,6 @@ dependencies = [ "rayon", "revm", "revm-inspectors", - "rustc-hash", "serde", "solang-parser", "tempfile", @@ -4740,6 +4740,10 @@ name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "foldhash", + "serde", +] [[package]] name = "heck" @@ -8411,9 +8415,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab661c8148c2261222a4d641ad5477fd4bea79406a99056096a0b41b35617a5" +checksum = "f3a850d65181df41b83c6be01a7d91f5e9377c43d48faa5af7d95816f437f5a3" dependencies = [ "paste", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index e6ccd75bd3ae..bbefa8c4bf52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -208,7 +208,7 @@ alloy-json-abi = "0.8.5" alloy-primitives = { version = "0.8.5", features = [ "getrandom", "rand", - "map-fxhash", + "map-foldhash", ] } alloy-sol-macro-expander = "0.8.5" alloy-sol-macro-input = "0.8.5" @@ -249,7 +249,6 @@ k256 = "0.13" parking_lot = "0.12" mesc = "0.3" rand = "0.8" -rustc-hash = "2.0" semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 149b9c863065..76eb0510a591 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -254,6 +254,7 @@ impl NodeArgs { .fork_compute_units_per_second(compute_units_per_second) .with_eth_rpc_url(self.evm_opts.fork_url.map(|fork| fork.url)) .with_base_fee(self.evm_opts.block_base_fee_per_gas) + .disable_min_priority_fee(self.evm_opts.disable_min_priority_fee) .with_storage_caching(self.evm_opts.no_storage_caching) .with_server_config(self.server_config) .with_host(self.host) @@ -547,6 +548,10 @@ pub struct AnvilEvmArgs { )] pub block_base_fee_per_gas: Option, + /// Disable the enforcement of a minimum suggested priority fee. + #[arg(long, visible_alias = "no-priority-fee", help_heading = "Environment config")] + pub disable_min_priority_fee: bool, + /// The chain ID. #[arg(long, alias = "chain", help_heading = "Environment config")] pub chain_id: Option, diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 189cf51752d1..273dbad89ffd 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -99,6 +99,8 @@ pub struct NodeConfig { pub gas_price: Option, /// Default base fee pub base_fee: Option, + /// If set to `true`, disables the enforcement of a minimum suggested priority fee + pub disable_min_priority_fee: bool, /// Default blob excess gas and price pub blob_excess_gas_and_price: Option, /// The hardfork to use @@ -432,6 +434,7 @@ impl Default for NodeConfig { fork_choice: None, account_generator: None, base_fee: None, + disable_min_priority_fee: false, blob_excess_gas_and_price: None, enable_tracing: true, enable_steps_tracing: false, @@ -623,6 +626,13 @@ impl NodeConfig { self } + /// Disable the enforcement of a minimum suggested priority fee + #[must_use] + pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { + self.disable_min_priority_fee = disable_min_priority_fee; + self + } + /// Sets the init genesis (genesis.json) #[must_use] pub fn with_genesis(mut self, genesis: Option) -> Self { @@ -994,6 +1004,7 @@ impl NodeConfig { let fees = FeeManager::new( cfg.handler_cfg.spec_id, self.get_base_fee(), + !self.disable_min_priority_fee, self.get_gas_price(), self.get_blob_excess_gas_and_price(), ); diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index c8fd497a3e81..78b52d49496a 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -592,7 +592,11 @@ impl EthApi { /// Returns the current gas price pub fn gas_price(&self) -> u128 { if self.backend.is_eip1559() { - (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) + if self.backend.is_min_priority_fee_enforced() { + (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) + } else { + self.backend.base_fee() as u128 + } } else { self.backend.fees().raw_gas_price() } diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index 01243c90f47b..9e34448ad311 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -8,12 +8,9 @@ use crate::{ mem::state::state_root, revm::{db::DbAccount, primitives::AccountInfo}, }; -use alloy_primitives::{Address, B256, U256, U64}; +use alloy_primitives::{map::HashMap, Address, B256, U256, U64}; use alloy_rpc_types::BlockId; -use foundry_evm::{ - backend::{BlockchainDb, DatabaseResult, StateSnapshot}, - hashbrown::HashMap, -}; +use foundry_evm::backend::{BlockchainDb, DatabaseResult, StateSnapshot}; // reexport for convenience pub use foundry_evm::{backend::MemDb, revm::db::DatabaseRef}; diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 05a7a3ff7a7b..0b7777f2db20 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -668,6 +668,11 @@ impl Backend { self.fees.base_fee() } + /// Returns whether the minimum suggested priority fee is enforced + pub fn is_min_priority_fee_enforced(&self) -> bool { + self.fees.is_min_priority_fee_enforced() + } + pub fn excess_blob_gas_and_price(&self) -> Option { self.fees.excess_blob_gas_and_price() } diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 29ae472071e7..942ce5a9d23c 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -581,9 +581,8 @@ pub struct MinedTransactionReceipt { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; use crate::eth::backend::db::Db; use alloy_primitives::{hex, Address}; diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index 72d66b113055..f41c51505ff6 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -48,6 +48,8 @@ pub struct FeeManager { /// /// This value will be updated after a new block was mined base_fee: Arc>, + /// Whether the minimum suggested priority fee is enforced + is_min_priority_fee_enforced: bool, /// Tracks the excess blob gas, and the base fee, for the next block post Cancun /// /// This value will be updated after a new block was mined @@ -63,12 +65,14 @@ impl FeeManager { pub fn new( spec_id: SpecId, base_fee: u64, + is_min_priority_fee_enforced: bool, gas_price: u128, blob_excess_gas_and_price: BlobExcessGasAndPrice, ) -> Self { Self { spec_id, base_fee: Arc::new(RwLock::new(base_fee)), + is_min_priority_fee_enforced, gas_price: Arc::new(RwLock::new(gas_price)), blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)), elasticity: Arc::new(RwLock::new(default_elasticity())), @@ -105,6 +109,10 @@ impl FeeManager { } } + pub fn is_min_priority_fee_enforced(&self) -> bool { + self.is_min_priority_fee_enforced + } + /// Raw base gas price pub fn raw_gas_price(&self) -> u128 { *self.gas_price.read() diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index a1b7beb68062..631064549ce7 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,17 +1,13 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; -use alloy_primitives::{Address, TxHash}; +use alloy_primitives::{ + map::{HashMap, HashSet}, + Address, TxHash, +}; use alloy_rpc_types::Transaction as RpcTransaction; use alloy_serde::WithOtherFields; use anvil_core::eth::transaction::{PendingTransaction, TypedTransaction}; use parking_lot::RwLock; -use std::{ - cmp::Ordering, - collections::{BTreeSet, HashMap, HashSet}, - fmt, - str::FromStr, - sync::Arc, - time::Instant, -}; +use std::{cmp::Ordering, collections::BTreeSet, fmt, str::FromStr, sync::Arc, time::Instant}; /// A unique identifying marker for a transaction pub type TxMarker = Vec; diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index cbf66d684ee1..7f990a8e56be 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -58,7 +58,6 @@ p256 = "0.13.2" ecdsa = "0.16" rand = "0.8" revm.workspace = true -rustc-hash.workspace = true semver.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index ac42e63c1d96..e6fef48b0891 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3191,6 +3191,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "cloneAccount", + "description": "Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state.", + "declaration": "function cloneAccount(address source, address target) external;", + "visibility": "external", + "mutability": "", + "signature": "cloneAccount(address,address)", + "selector": "0x533d61c9", + "selectorBytes": [ + 83, + 61, + 97, + 201 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "closeFile", @@ -5600,7 +5620,7 @@ { "func": { "id": "loadAllocs", - "description": "Load a genesis JSON file's `allocs` into the in-memory revm state.", + "description": "Load a genesis JSON file's `allocs` into the in-memory EVM state.", "declaration": "function loadAllocs(string calldata pathToAllocsJson) external;", "visibility": "external", "mutability": "", @@ -5777,6 +5797,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "mockCalls_0", + "description": "Mocks multiple calls to an address, returning specified data for each call.", + "declaration": "function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCalls(address,bytes,bytes[])", + "selector": "0x5c5c3de9", + "selectorBytes": [ + 92, + 92, + 61, + 233 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCalls_1", + "description": "Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call.", + "declaration": "function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCalls(address,uint256,bytes,bytes[])", + "selector": "0x08bcbae1", + "selectorBytes": [ + 8, + 188, + 186, + 225 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "mockFunction", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 129c553a96fc..0ee95e43fd02 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -283,10 +283,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function load(address target, bytes32 slot) external view returns (bytes32 data); - /// Load a genesis JSON file's `allocs` into the in-memory revm state. + /// Load a genesis JSON file's `allocs` into the in-memory EVM state. #[cheatcode(group = Evm, safety = Unsafe)] function loadAllocs(string calldata pathToAllocsJson) external; + /// Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state. + #[cheatcode(group = Evm, safety = Unsafe)] + function cloneAccount(address source, address target) external; + // -------- Record Storage -------- /// Records all storage reads and writes. @@ -465,6 +469,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; + /// Mocks multiple calls to an address, returning specified data for each call. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external; + + /// Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external; + /// Reverts a call to an address with specified revert data. #[cheatcode(group = Evm, safety = Unsafe)] function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external; diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index af2026a9e1a2..b9a3d7047492 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloy_consensus::TxEnvelope; use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; use foundry_common::fs::{read_json_file, write_json_file}; @@ -16,10 +16,7 @@ use foundry_evm_core::{ }; use rand::Rng; use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; -use std::{ - collections::{BTreeMap, HashMap}, - path::Path, -}; +use std::{collections::BTreeMap, path::Path}; mod fork; pub(crate) mod mapping; @@ -159,6 +156,22 @@ impl Cheatcode for loadAllocsCall { } } +impl Cheatcode for cloneAccountCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { source, target } = self; + + let account = ccx.ecx.journaled_state.load_account(*source, &mut ccx.ecx.db)?; + ccx.ecx.db.clone_account( + &genesis_account(account.data), + target, + &mut ccx.ecx.journaled_state, + )?; + // Cloned account should persist in forked envs. + ccx.ecx.db.add_persistent_account(*target); + Ok(Default::default()) + } +} + impl Cheatcode for dumpStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToStateJson } = self; @@ -181,23 +194,7 @@ impl Cheatcode for dumpStateCall { .state() .iter_mut() .filter(|(key, val)| !skip(key, val)) - .map(|(key, val)| { - ( - key, - GenesisAccount { - nonce: Some(val.info.nonce), - balance: val.info.balance, - code: val.info.code.as_ref().map(|o| o.original_bytes()), - storage: Some( - val.storage - .iter() - .map(|(k, v)| (B256::from(*k), B256::from(v.present_value()))) - .collect(), - ), - private_key: None, - }, - ) - }) + .map(|(key, val)| (key, genesis_account(val))) .collect::>(); write_json_file(path, &alloc)?; @@ -960,3 +957,20 @@ fn get_state_diff(state: &mut Cheatcodes) -> Result { .collect::>(); Ok(res.abi_encode()) } + +/// Helper function that creates a `GenesisAccount` from a regular `Account`. +fn genesis_account(account: &Account) -> GenesisAccount { + GenesisAccount { + nonce: Some(account.info.nonce), + balance: account.info.balance, + code: account.info.code.as_ref().map(|o| o.original_bytes()), + storage: Some( + account + .storage + .iter() + .map(|(k, v)| (B256::from(*k), B256::from(v.present_value()))) + .collect(), + ), + private_key: None, + } +} diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index ab858c612da6..50551d179c84 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -1,7 +1,7 @@ use crate::{inspector::InnerEcx, Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, Bytes, U256}; use revm::{interpreter::InstructionResult, primitives::Bytecode}; -use std::cmp::Ordering; +use std::{cmp::Ordering, collections::VecDeque}; /// Mocked call data. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] @@ -65,6 +65,25 @@ impl Cheatcode for mockCall_1Call { } } +impl Cheatcode for mockCalls_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, data, returnData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_calls(ccx.state, callee, data, None, returnData, InstructionResult::Return); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCalls_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, msgValue, data, returnData } = self; + ccx.ecx.load_account(*callee)?; + mock_calls(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); + Ok(Default::default()) + } +} + impl Cheatcode for mockCallRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; @@ -94,7 +113,6 @@ impl Cheatcode for mockFunctionCall { } } -#[allow(clippy::ptr_arg)] // Not public API, doesn't matter fn mock_call( state: &mut Cheatcodes, callee: &Address, @@ -102,10 +120,24 @@ fn mock_call( value: Option<&U256>, rdata: &Bytes, ret_type: InstructionResult, +) { + mock_calls(state, callee, cdata, value, std::slice::from_ref(rdata), ret_type) +} + +fn mock_calls( + state: &mut Cheatcodes, + callee: &Address, + cdata: &Bytes, + value: Option<&U256>, + rdata_vec: &[Bytes], + ret_type: InstructionResult, ) { state.mocked_calls.entry(*callee).or_default().insert( MockCallDataContext { calldata: Bytes::copy_from_slice(cdata), value: value.copied() }, - MockCallReturnData { ret_type, data: Bytes::copy_from_slice(rdata) }, + rdata_vec + .iter() + .map(|rdata| MockCallReturnData { ret_type, data: rdata.clone() }) + .collect::>(), ); } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index f36c8d6fe8f8..c8c512b6f15e 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -4,7 +4,7 @@ use super::string::parse; use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_primitives::{hex, Bytes, U256}; +use alloy_primitives::{hex, map::Entry, Bytes, U256}; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use foundry_common::fs; @@ -12,7 +12,6 @@ use foundry_config::fs_permissions::FsAccessKind; use revm::interpreter::CreateInputs; use semver::Version; use std::{ - collections::hash_map::Entry, io::{BufRead, BufReader, Write}, path::{Path, PathBuf}, process::Command, diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index fe8834d5f5a8..9a40c3cb1b31 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -49,7 +49,6 @@ use revm::{ primitives::{BlockEnv, CreateScheme, EVMError, EvmStorageSlot, SpecId, EOF_MAGIC_BYTES}, EvmContext, InnerEvmContext, Inspector, }; -use rustc_hash::FxHashMap; use serde_json::Value; use std::{ collections::{BTreeMap, VecDeque}, @@ -402,7 +401,7 @@ pub struct Cheatcodes { /// Mocked calls // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext` - pub mocked_calls: HashMap>, + pub mocked_calls: HashMap>>, /// Mocked functions. Maps target address to be mocked to pair of (calldata, mock address). pub mocked_functions: HashMap>, @@ -413,7 +412,7 @@ pub struct Cheatcodes { pub expected_emits: VecDeque, /// Map of context depths to memory offset ranges that may be written to within the call depth. - pub allowed_mem_writes: FxHashMap>>, + pub allowed_mem_writes: HashMap>>, /// Current broadcasting information pub broadcast: Option, @@ -889,26 +888,36 @@ where { } // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get(&call.bytecode_address) { + if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { let ctx = MockCallDataContext { calldata: call.input.clone(), value: call.transfer_value() }; - if let Some(return_data) = mocks.get(&ctx).or_else(|| { - mocks - .iter() + + if let Some(return_data_queue) = match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() .find(|(mock, _)| { call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) && mock.value.map_or(true, |value| Some(value) == call.transfer_value()) }) - .map(|(_, v)| v) - }) { - return Some(CallOutcome { - result: InterpreterResult { - result: return_data.ret_type, - output: return_data.data.clone(), - gas, - }, - memory_offset: call.return_memory_offset.clone(), - }); + .map(|(_, v)| v), + } { + if let Some(return_data) = if return_data_queue.len() == 1 { + // If the mocked calls stack has a single element in it, don't empty it + return_data_queue.front().map(|x| x.to_owned()) + } else { + // Else, we pop the front element + return_data_queue.pop_front() + } { + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }); + } } } diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 7a58c7ab8aa8..602ac512514e 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -1,5 +1,9 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*}; -use alloy_primitives::{address, hex, Address, Bytes, LogData as RawLog, U256}; +use alloy_primitives::{ + address, hex, + map::{hash_map::Entry, HashMap}, + Address, Bytes, LogData as RawLog, U256, +}; use alloy_sol_types::{SolError, SolValue}; use foundry_common::ContractsByArtifact; use foundry_evm_core::decode::RevertDecoder; @@ -7,7 +11,6 @@ use revm::interpreter::{ return_ok, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }; use spec::Vm; -use std::collections::{hash_map::Entry, HashMap}; /// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. /// Solidity will see a successful call and attempt to decode the return data. Therefore, we need diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 73bd90cc7126..fbc5c82ca771 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -52,7 +52,6 @@ dunce.workspace = true eyre.workspace = true num-format.workspace = true reqwest.workspace = true -rustc-hash.workspace = true semver.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index d281d56529b9..30bcd4d0918a 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -1,5 +1,6 @@ -//! cli arguments for configuring the evm settings -use alloy_primitives::{Address, B256, U256}; +//! CLI arguments for configuring the EVM settings. + +use alloy_primitives::{map::HashMap, Address, B256, U256}; use clap::{ArgAction, Parser}; use eyre::ContextCompat; use foundry_config::{ @@ -11,11 +12,10 @@ use foundry_config::{ }, Chain, Config, }; -use rustc_hash::FxHashMap; use serde::Serialize; /// Map keyed by breakpoints char to their location (contract address, pc) -pub type Breakpoints = FxHashMap; +pub type Breakpoints = HashMap; /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. /// diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index 3b10739162cb..23a272a2a8e3 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -579,9 +579,8 @@ pub fn parse_signatures(tokens: Vec) -> ParsedSignatures { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/evm/abi/Cargo.toml b/crates/evm/abi/Cargo.toml index 330ea6626d6b..b3202c96799f 100644 --- a/crates/evm/abi/Cargo.toml +++ b/crates/evm/abi/Cargo.toml @@ -22,7 +22,6 @@ alloy-sol-types = { workspace = true, features = ["json"] } derive_more.workspace = true itertools.workspace = true -rustc-hash.workspace = true [dev-dependencies] foundry-test-utils.workspace = true diff --git a/crates/evm/abi/src/console/hardhat.rs b/crates/evm/abi/src/console/hardhat.rs index a99ad8fc6451..8154c9ff7926 100644 --- a/crates/evm/abi/src/console/hardhat.rs +++ b/crates/evm/abi/src/console/hardhat.rs @@ -1,8 +1,7 @@ -use alloy_primitives::Selector; +use alloy_primitives::{map::HashMap, Selector}; use alloy_sol_types::sol; use foundry_common_fmt::*; use foundry_macros::ConsoleFmt; -use rustc_hash::FxHashMap; use std::sync::LazyLock; sol!( @@ -39,8 +38,8 @@ pub fn hh_console_selector(input: &[u8]) -> Option<&'static Selector> { /// `hardhat/console.log` logs its events manually, and in functions that accept integers they're /// encoded as `abi.encodeWithSignature("log(int)", p0)`, which is not the canonical ABI encoding /// for `int` that Solidity and [`sol!`] use. -pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: LazyLock> = - LazyLock::new(|| FxHashMap::from_iter(include!("./patches.rs"))); +pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: LazyLock> = + LazyLock::new(|| HashMap::from_iter(include!("./patches.rs"))); #[cfg(test)] mod tests { diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index ce06dc9d0ea8..46441201664e 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -54,7 +54,6 @@ eyre.workspace = true futures.workspace = true itertools.workspace = true parking_lot.workspace = true -rustc-hash.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index fcc2e1596c9f..8623ca2f98ea 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -233,6 +233,15 @@ impl DatabaseExt for CowBackend<'_> { self.backend_mut(&Env::default()).load_allocs(allocs, journaled_state) } + fn clone_account( + &mut self, + source: &GenesisAccount, + target: &Address, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError> { + self.backend_mut(&Env::default()).clone_account(source, target, journaled_state) + } + fn is_persistent(&self, acc: &Address) -> bool { self.backend.is_persistent(acc) } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 8cf59d96e1b2..d6774b89a105 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -279,6 +279,17 @@ pub trait DatabaseExt: Database + DatabaseCommit { journaled_state: &mut JournaledState, ) -> Result<(), BackendError>; + /// Copies bytecode, storage, nonce and balance from the given genesis account to the target + /// address. + /// + /// Returns [Ok] if data was successfully inserted into the journal, [Err] otherwise. + fn clone_account( + &mut self, + source: &GenesisAccount, + target: &Address, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError>; + /// Returns true if the given account is currently marked as persistent. fn is_persistent(&self, acc: &Address) -> bool; @@ -1367,44 +1378,59 @@ impl DatabaseExt for Backend { ) -> Result<(), BackendError> { // Loop through all of the allocs defined in the map and commit them to the journal. for (addr, acc) in allocs.iter() { - // Fetch the account from the journaled state. Will create a new account if it does - // not already exist. - let mut state_acc = journaled_state.load_account(*addr, self)?; - - // Set the account's bytecode and code hash, if the `bytecode` field is present. - if let Some(bytecode) = acc.code.as_ref() { - state_acc.info.code_hash = keccak256(bytecode); - let bytecode = Bytecode::new_raw(bytecode.0.clone().into()); - state_acc.info.code = Some(bytecode); - } + self.clone_account(acc, addr, journaled_state)?; + } - // Set the account's storage, if the `storage` field is present. - if let Some(storage) = acc.storage.as_ref() { - state_acc.storage = storage - .iter() - .map(|(slot, value)| { - let slot = U256::from_be_bytes(slot.0); - ( - slot, - EvmStorageSlot::new_changed( - state_acc - .storage - .get(&slot) - .map(|s| s.present_value) - .unwrap_or_default(), - U256::from_be_bytes(value.0), - ), - ) - }) - .collect(); - } - // Set the account's nonce and balance. - state_acc.info.nonce = acc.nonce.unwrap_or_default(); - state_acc.info.balance = acc.balance; + Ok(()) + } - // Touch the account to ensure the loaded information persists if called in `setUp`. - journaled_state.touch(addr); + /// Copies bytecode, storage, nonce and balance from the given genesis account to the target + /// address. + /// + /// Returns [Ok] if data was successfully inserted into the journal, [Err] otherwise. + fn clone_account( + &mut self, + source: &GenesisAccount, + target: &Address, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError> { + // Fetch the account from the journaled state. Will create a new account if it does + // not already exist. + let mut state_acc = journaled_state.load_account(*target, self)?; + + // Set the account's bytecode and code hash, if the `bytecode` field is present. + if let Some(bytecode) = source.code.as_ref() { + state_acc.info.code_hash = keccak256(bytecode); + let bytecode = Bytecode::new_raw(bytecode.0.clone().into()); + state_acc.info.code = Some(bytecode); + } + + // Set the account's storage, if the `storage` field is present. + if let Some(storage) = source.storage.as_ref() { + state_acc.storage = storage + .iter() + .map(|(slot, value)| { + let slot = U256::from_be_bytes(slot.0); + ( + slot, + EvmStorageSlot::new_changed( + state_acc + .storage + .get(&slot) + .map(|s| s.present_value) + .unwrap_or_default(), + U256::from_be_bytes(value.0), + ), + ) + }) + .collect(); } + // Set the account's nonce and balance. + state_acc.info.nonce = source.nonce.unwrap_or_default(); + state_acc.info.balance = source.balance; + + // Touch the account to ensure the loaded information persists if called in `setUp`. + journaled_state.touch(target); Ok(()) } @@ -1960,9 +1986,8 @@ fn apply_state_changeset( } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use crate::{backend::Backend, fork::CreateFork, opts::EvmOpts}; use alloy_primitives::{Address, U256}; use alloy_provider::Provider; diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 95fcaf02a38b..d6e8b3dec88f 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -3,12 +3,11 @@ use crate::abi::{Console, Vm}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::{Error, JsonAbi}; -use alloy_primitives::{hex, Log, Selector}; +use alloy_primitives::{hex, map::HashMap, Log, Selector}; use alloy_sol_types::{SolEventInterface, SolInterface, SolValue}; use foundry_common::SELECTOR_LEN; use itertools::Itertools; use revm::interpreter::InstructionResult; -use rustc_hash::FxHashMap; use std::{fmt, sync::OnceLock}; /// A skip reason. @@ -60,7 +59,7 @@ pub fn decode_console_log(log: &Log) -> Option { #[derive(Clone, Debug, Default)] pub struct RevertDecoder { /// The custom errors to use for decoding. - pub errors: FxHashMap>, + pub errors: HashMap>, } impl Default for &RevertDecoder { diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 1bab76515000..29dccb9c7dec 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -262,9 +262,8 @@ impl DatabaseRef for ForkDbStateSnapshot { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; use crate::backend::BlockchainDbMeta; use foundry_common::provider::get_http_provider; diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index acb9cc50e6b3..f7ab3093c520 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,12 +1,12 @@ +use alloy_primitives::map::HashMap; use revm::interpreter::opcode::{PUSH0, PUSH1, PUSH32}; -use rustc_hash::FxHashMap; /// Maps from program counter to instruction counter. /// /// Inverse of [`IcPcMap`]. #[derive(Debug, Clone)] pub struct PcIcMap { - pub inner: FxHashMap, + pub inner: HashMap, } impl PcIcMap { @@ -35,7 +35,7 @@ impl PcIcMap { /// /// Inverse of [`PcIcMap`]. pub struct IcPcMap { - pub inner: FxHashMap, + pub inner: HashMap, } impl IcPcMap { @@ -60,8 +60,8 @@ impl IcPcMap { } } -fn make_map(code: &[u8]) -> FxHashMap { - let mut map = FxHashMap::default(); +fn make_map(code: &[u8]) -> HashMap { + let mut map = HashMap::default(); let mut pc = 0; let mut cumulative_push_size = 0; diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index 777c37d99429..e38d33e2a590 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -23,5 +23,4 @@ eyre.workspace = true revm.workspace = true semver.workspace = true tracing.workspace = true -rustc-hash.workspace = true rayon.workspace = true diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 1b68c89d3666..e5eda5262d34 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -1,8 +1,8 @@ use super::{CoverageItem, CoverageItemKind, SourceLocation}; +use alloy_primitives::map::HashMap; use foundry_common::TestFunctionExt; use foundry_compilers::artifacts::ast::{self, Ast, Node, NodeType}; use rayon::prelude::*; -use rustc_hash::FxHashMap; use std::sync::Arc; /// A visitor that walks the AST of a single contract and finds coverage items. @@ -583,7 +583,7 @@ impl<'a> SourceAnalyzer<'a> { #[derive(Debug, Default)] pub struct SourceFiles<'a> { /// The versioned sources. - pub sources: FxHashMap>, + pub sources: HashMap>, } /// The source code and AST of a file. diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index c5bb8196a9c5..f42fd1a62726 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -1,9 +1,9 @@ use super::{CoverageItem, CoverageItemKind, ItemAnchor, SourceLocation}; +use alloy_primitives::map::{DefaultHashBuilder, HashMap, HashSet}; use eyre::ensure; use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap}; use foundry_evm_core::utils::IcPcMap; use revm::interpreter::opcode; -use rustc_hash::{FxHashMap, FxHashSet}; /// Attempts to find anchors for the given items using the given source map and bytecode. pub fn find_anchors( @@ -11,9 +11,9 @@ pub fn find_anchors( source_map: &SourceMap, ic_pc_map: &IcPcMap, items: &[CoverageItem], - items_by_source_id: &FxHashMap>, + items_by_source_id: &HashMap>, ) -> Vec { - let mut seen = FxHashSet::default(); + let mut seen = HashSet::with_hasher(DefaultHashBuilder::default()); source_map .iter() .filter_map(|element| items_by_source_id.get(&(element.index()? as usize))) diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 4481813aca8a..220ab6b41407 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -8,12 +8,12 @@ #[macro_use] extern crate tracing; -use alloy_primitives::{Bytes, B256}; +use alloy_primitives::{map::HashMap, Bytes, B256}; use eyre::{Context, Result}; use foundry_compilers::artifacts::sourcemap::SourceMap; use semver::Version; use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fmt::Display, ops::{AddAssign, Deref, DerefMut}, path::{Path, PathBuf}, diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 8bbd7f1414d8..15858c0f393a 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -19,9 +19,3 @@ pub use foundry_evm_traces as traces; // TODO: We should probably remove these, but it's a pretty big breaking change. #[doc(hidden)] pub use revm; - -#[doc(hidden)] -#[deprecated = "use `{hash_map, hash_set, HashMap, HashSet}` in `std::collections` or `revm::primitives` instead"] -pub mod hashbrown { - pub use revm::primitives::{hash_map, hash_set, HashMap, HashSet}; -} diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index f4f1dde92482..4df55772c436 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -1,7 +1,7 @@ use crate::invariant::{BasicTxDetails, FuzzRunIdentifiedContracts}; use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt}; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Bytes, Log, B256, U256}; +use alloy_primitives::{map::HashMap, Address, Bytes, Log, B256, U256}; use foundry_config::FuzzDictionaryConfig; use foundry_evm_core::utils::StateChangeset; use indexmap::IndexSet; @@ -11,11 +11,7 @@ use revm::{ interpreter::opcode, primitives::AccountInfo, }; -use std::{ - collections::{BTreeMap, HashMap}, - fmt, - sync::Arc, -}; +use std::{collections::BTreeMap, fmt, sync::Arc}; type AIndexSet = IndexSet>; diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 96fb8d1c6dc0..3423213fbb9e 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -38,7 +38,6 @@ itertools.workspace = true serde.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true -rustc-hash.workspace = true tempfile.workspace = true rayon.workspace = true solang-parser.workspace = true diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index 6ad335302fb2..40e540a972d1 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -11,7 +11,6 @@ use foundry_compilers::{ use foundry_evm_core::utils::PcIcMap; use foundry_linking::Linker; use rayon::prelude::*; -use rustc_hash::FxHashMap; use solang_parser::pt::SourceUnitPart; use std::{ collections::{BTreeMap, HashMap}, @@ -117,7 +116,7 @@ impl ArtifactData { #[derive(Clone, Debug, Default)] pub struct ContractSources { /// Map over build_id -> file_id -> (source code, language) - pub sources_by_id: HashMap>>, + pub sources_by_id: HashMap>>, /// Map over contract name -> Vec<(bytecode, build_id, file_id)> pub artifacts_by_name: HashMap>, } diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 1bb20381b9ec..25d9f4f2b894 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -7,7 +7,10 @@ use crate::{ }; use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt}; use alloy_json_abi::{Error, Event, Function, JsonAbi}; -use alloy_primitives::{Address, LogData, Selector, B256}; +use alloy_primitives::{ + map::{hash_map::Entry, HashMap}, + Address, LogData, Selector, B256, +}; use foundry_common::{ abi::get_indexed_event, fmt::format_token, get_contract_name, ContractsByArtifact, SELECTOR_LEN, }; @@ -25,11 +28,7 @@ use foundry_evm_core::{ }; use itertools::Itertools; use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; -use rustc_hash::FxHashMap; -use std::{ - collections::{hash_map::Entry, BTreeMap, HashMap}, - sync::OnceLock, -}; +use std::{collections::BTreeMap, sync::OnceLock}; mod precompiles; @@ -124,7 +123,7 @@ pub struct CallTraceDecoder { pub receive_contracts: Vec
, /// All known functions. - pub functions: FxHashMap>, + pub functions: HashMap>, /// All known events. pub events: BTreeMap<(B256, usize), Vec>, /// Revert decoder. Contains all known custom errors. @@ -171,7 +170,7 @@ impl CallTraceDecoder { Self { contracts: Default::default(), - labels: [ + labels: HashMap::from_iter([ (CHEATCODE_ADDRESS, "VM".to_string()), (HARDHAT_CONSOLE_ADDRESS, "console".to_string()), (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()), @@ -187,8 +186,7 @@ impl CallTraceDecoder { (EC_PAIRING, "ECPairing".to_string()), (BLAKE_2F, "Blake2F".to_string()), (POINT_EVALUATION, "PointEvaluation".to_string()), - ] - .into(), + ]), receive_contracts: Default::default(), functions: hh_funcs() diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index 2d7cadad1b82..1e3924aa3f49 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -1,16 +1,12 @@ use alloy_json_abi::{Event, Function}; -use alloy_primitives::hex; +use alloy_primitives::{hex, map::HashSet}; use foundry_common::{ abi::{get_event, get_func}, fs, selectors::{OpenChainClient, SelectorType}, }; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashSet}, - path::PathBuf, - sync::Arc, -}; +use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; use tokio::sync::RwLock; pub type SingleSignaturesIdentifier = Arc>; @@ -161,9 +157,8 @@ impl Drop for SignaturesIdentifier { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index f636e4c6279f..2cd6607859a1 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -98,7 +98,6 @@ watchexec-events = "3.0" watchexec-signals = "3.0" clearscreen = "3.0" evm-disassembler.workspace = true -rustc-hash.workspace = true # doc server axum = { workspace = true, features = ["ws"] } diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index f3879b931b77..1c9ee47ec9fe 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -608,9 +608,8 @@ impl EtherscanClient for Client { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; use alloy_primitives::hex; use foundry_compilers::Artifact; diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 487c0a7f1580..e21153d09c19 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -1,5 +1,5 @@ use super::{install, test::TestArgs}; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{map::HashMap, Address, Bytes, U256}; use clap::{Parser, ValueEnum, ValueHint}; use eyre::{Context, Result}; use forge::{ @@ -24,10 +24,8 @@ use foundry_compilers::{ }; use foundry_config::{Config, SolcReq}; use rayon::prelude::*; -use rustc_hash::FxHashMap; use semver::Version; use std::{ - collections::HashMap, path::{Path, PathBuf}, sync::Arc, }; @@ -150,7 +148,7 @@ impl CoverageArgs { // Collect source files. let project_paths = &project.paths; - let mut versioned_sources = HashMap::>::new(); + let mut versioned_sources = HashMap::>::default(); for (path, source_file, version) in output.output().sources.sources_with_version() { report.add_source(version.clone(), source_file.id as usize, path.clone()); @@ -191,7 +189,7 @@ impl CoverageArgs { let source_analysis = SourceAnalyzer::new(sources).analyze()?; // Build helper mapping used by `find_anchors` - let mut items_by_source_id = FxHashMap::<_, Vec<_>>::with_capacity_and_hasher( + let mut items_by_source_id = HashMap::<_, Vec<_>>::with_capacity_and_hasher( source_analysis.items.len(), Default::default(), ); @@ -410,7 +408,7 @@ impl BytecodeData { pub fn find_anchors( &self, source_analysis: &SourceAnalysis, - items_by_source_id: &FxHashMap>, + items_by_source_id: &HashMap>, ) -> Vec { find_anchors( &self.bytecode, diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 70fbb5fe4102..6e2654eb23b2 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -519,7 +519,7 @@ impl TestArgs { let libraries = runner.libraries.clone(); - // Run tests. + // Run tests in a streaming fashion. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); let show_progress = config.show_progress; diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index ef9c7b0c6f0f..4a00675dd754 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -1,15 +1,17 @@ //! Coverage reports. +use alloy_primitives::map::HashMap; use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Row, Table}; use evm_disassembler::disassemble_bytes; use foundry_common::fs; -pub use foundry_evm::coverage::*; use std::{ - collections::{hash_map, HashMap}, + collections::hash_map, io::Write, path::{Path, PathBuf}, }; +pub use foundry_evm::coverage::*; + /// A coverage reporter. pub trait CoverageReporter { fn report(self, report: &CoverageReport) -> eyre::Result<()>; diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 1c11abaf6976..59c417b97097 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -4,14 +4,12 @@ use crate::{ constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; +use alloy_primitives::map::HashSet; use comfy_table::{presets::ASCII_MARKDOWN, *}; use foundry_common::{calc, TestFunctionExt}; use foundry_evm::traces::CallKind; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashSet}, - fmt::Display, -}; +use std::{collections::BTreeMap, fmt::Display}; use yansi::Paint; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 0e00bb5e2773..a6d7fded9c48 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -393,7 +393,6 @@ pub struct TestResult { pub kind: TestKind, /// Traces - #[serde(skip)] pub traces: Traces, /// Additional traces to use for gas report. diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 2a964789b5b3..e580fb8b2fba 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -9,7 +9,7 @@ use crate::{ }; use alloy_dyn_abi::DynSolValue; use alloy_json_abi::Function; -use alloy_primitives::{address, Address, Bytes, U256}; +use alloy_primitives::{address, map::HashMap, Address, Bytes, U256}; use eyre::Result; use foundry_common::{ contracts::{ContractsByAddress, ContractsByArtifact}, @@ -35,12 +35,7 @@ use foundry_evm::{ }; use proptest::test_runner::TestRunner; use rayon::prelude::*; -use std::{ - borrow::Cow, - cmp::min, - collections::{BTreeMap, HashMap}, - time::Instant, -}; +use std::{borrow::Cow, cmp::min, collections::BTreeMap, time::Instant}; /// When running tests, we deploy all external libraries present in the project. To avoid additional /// libraries affecting nonces of senders used in tests, we are using separate address to diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 28362671b756..f8b54cc4126e 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -274,6 +274,47 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); +const SIMPLE_CONTRACT: &str = r#" +import "./test.sol"; + +contract SimpleContract { + uint256 public num; + + function setValues(uint256 _num) public { + num = _num; + } +} + +contract SimpleContractTest is DSTest { + function test() public { + SimpleContract c = new SimpleContract(); + c.setValues(100); + } +} + "#; + +forgetest!(can_run_test_with_json_output_verbose, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source("Simple.t.sol", SIMPLE_CONTRACT).unwrap(); + + // Assert that with verbose output the json output includes the traces + cmd.args(["test", "-vvv", "--json"]) + .assert_success() + .stdout_eq(file!["../fixtures/SimpleContractTestVerbose.json": Json]); +}); + +forgetest!(can_run_test_with_json_output_non_verbose, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source("Simple.t.sol", SIMPLE_CONTRACT).unwrap(); + + // Assert that without verbose output the json output does not include the traces + cmd.args(["test", "--json"]) + .assert_success() + .stdout_eq(file!["../fixtures/SimpleContractTestNonVerbose.json": Json]); +}); + // tests that `forge test` will pick up tests that are stored in the `test = ` config value forgetest!(can_run_test_in_custom_test_folder, |prj, cmd| { prj.insert_ds_test(); diff --git a/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json new file mode 100644 index 000000000000..8fd8a0faef53 --- /dev/null +++ b/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json @@ -0,0 +1,27 @@ +{ + "src/Simple.t.sol:SimpleContractTest": { + "duration": "{...}", + "test_results": { + "test()": { + "status": "Success", + "reason": null, + "counterexample": null, + "logs": [], + "kind": { + "Unit": { + "gas": "{...}" + } + }, + "traces": [], + "labeled_addresses": {}, + "duration": { + "secs": "{...}", + "nanos": "{...}" + }, + "breakpoints": {}, + "gas_snapshots": {} + } + }, + "warnings": [] + } +} \ No newline at end of file diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json new file mode 100644 index 000000000000..c7f47cf53477 --- /dev/null +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -0,0 +1,172 @@ +{ + "src/Simple.t.sol:SimpleContractTest": { + "duration": "{...}", + "test_results": { + "test()": { + "status": "Success", + "reason": null, + "counterexample": null, + "logs": [], + "kind": { + "Unit": { + "gas": "{...}" + } + }, + "traces": [ + [ + "Deployment", + { + "arena": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38", + "address": "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496", + "maybe_precompile": false, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CREATE", + "value": "0x0", + "data": "{...}", + "output": "{...}", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Return", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + } + ] + } + ], + [ + "Execution", + { + "arena": [ + { + "parent": null, + "children": [ + 1, + 2 + ], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38", + "address": "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0xf8a8fd6d", + "output": "0x", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [ + { + "Call": 0 + }, + { + "Call": 1 + } + ] + }, + { + "parent": 0, + "children": [], + "idx": 1, + "trace": { + "depth": 1, + "success": true, + "caller": "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496", + "address": "0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f", + "maybe_precompile": false, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CREATE", + "value": "0x0", + "data": "{...}", + "output": "{...}", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Return", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + }, + { + "parent": 0, + "children": [], + "idx": 2, + "trace": { + "depth": 1, + "success": true, + "caller": "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496", + "address": "0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0xe26d14740000000000000000000000000000000000000000000000000000000000000064", + "output": "0x", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + } + ] + } + ] + ], + "labeled_addresses": {}, + "duration": { + "secs": "{...}", + "nanos": "{...}" + }, + "breakpoints": {}, + "gas_snapshots": {} + } + }, + "warnings": [] + } +} \ No newline at end of file diff --git a/crates/script/src/providers.rs b/crates/script/src/providers.rs index 7f1aa0eb38c9..eb6ea9319a77 100644 --- a/crates/script/src/providers.rs +++ b/crates/script/src/providers.rs @@ -1,12 +1,9 @@ +use alloy_primitives::map::{hash_map::Entry, HashMap}; use alloy_provider::{utils::Eip1559Estimation, Provider}; use eyre::{Result, WrapErr}; use foundry_common::provider::{get_http_provider, RetryProvider}; use foundry_config::Chain; -use std::{ - collections::{hash_map::Entry, HashMap}, - ops::Deref, - sync::Arc, -}; +use std::{ops::Deref, sync::Arc}; /// Contains a map of RPC urls to single instances of [`ProviderInfo`]. #[derive(Default)] diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 7c692ea232ef..432a8ccd544e 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -13,7 +13,7 @@ use crate::{ ScriptArgs, ScriptConfig, ScriptResult, }; use alloy_network::TransactionBuilder; -use alloy_primitives::{utils::format_units, Address, Bytes, TxKind, U256}; +use alloy_primitives::{map::HashMap, utils::format_units, Address, Bytes, TxKind, U256}; use dialoguer::Confirm; use eyre::{Context, Result}; use foundry_cheatcodes::ScriptWallets; @@ -23,7 +23,7 @@ use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; use std::{ - collections::{BTreeMap, HashMap, VecDeque}, + collections::{BTreeMap, VecDeque}, sync::Arc, }; use yansi::Paint; diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 51ec2c8e0ee1..a9ff6a7e62a2 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -177,9 +177,8 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; use alloy_primitives::address; use foundry_config::Chain; diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 3839b845b39e..8b8c2bc3549f 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -451,9 +451,8 @@ async fn ensure_solc_build_metadata(version: Version) -> Result { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use super::*; use clap::Parser; use foundry_common::fs; diff --git a/crates/verify/src/sourcify.rs b/crates/verify/src/sourcify.rs index bbb5e9f9eddc..9dbe027efe1a 100644 --- a/crates/verify/src/sourcify.rs +++ b/crates/verify/src/sourcify.rs @@ -8,7 +8,6 @@ use eyre::Result; use foundry_common::{fs, retry::Retry}; use futures::FutureExt; use reqwest::Url; -use revm_primitives::map::FxBuildHasher; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -114,8 +113,7 @@ impl SourcifyVerificationProvider { let metadata = context.get_target_metadata()?; let imports = context.get_target_imports()?; - let mut files = - HashMap::with_capacity_and_hasher(2 + imports.len(), FxBuildHasher::default()); + let mut files = HashMap::with_capacity_and_hasher(2 + imports.len(), Default::default()); let metadata = serde_json::to_string_pretty(&metadata)?; files.insert("metadata.json".to_string(), metadata); diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index 37b731ddb142..01a232895112 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -148,9 +148,8 @@ impl From for WalletOpts { } #[cfg(test)] +#[allow(clippy::needless_return)] mod tests { - #![allow(clippy::needless_return)] - use alloy_signer::Signer; use std::{path::Path, str::FromStr}; diff --git a/deny.toml b/deny.toml index e908828cc2a0..b932b03885a2 100644 --- a/deny.toml +++ b/deny.toml @@ -53,6 +53,7 @@ allow = [ "0BSD", "MPL-2.0", "CDDL-1.0", + "Zlib", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 64c599a9e856..be6522ac44ed 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -155,6 +155,7 @@ interface Vm { function broadcast(uint256 privateKey) external; function chainId(uint256 newChainId) external; function clearMockedCalls() external; + function cloneAccount(address source, address target) external; function closeFile(string calldata path) external; function coinbase(address newCoinbase) external; function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); @@ -284,6 +285,8 @@ interface Vm { function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external; function mockCall(address callee, bytes calldata data, bytes calldata returnData) external; function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; + function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external; + function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external; function mockFunction(address callee, address target, bytes calldata data) external; function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); diff --git a/testdata/default/cheats/CloneAccount.t.sol b/testdata/default/cheats/CloneAccount.t.sol new file mode 100644 index 000000000000..5342a92d39f2 --- /dev/null +++ b/testdata/default/cheats/CloneAccount.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Source { + uint256 public a; + address public b; + uint256[3] public c; + bool public d; + + constructor() { + a = 100; + b = address(111); + c[0] = 222; + c[1] = 333; + c[2] = 444; + d = true; + } +} + +contract CloneAccountTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + address clone = address(777); + + function setUp() public { + Source src = new Source(); + vm.deal(address(src), 0.123 ether); + vm.cloneAccount(address(src), clone); + } + + function test_clone_account() public { + // Check clone balance. + assertEq(clone.balance, 0.123 ether); + // Check clone storage. + assertEq(Source(clone).a(), 100); + assertEq(Source(clone).b(), address(111)); + assertEq(Source(clone).c(0), 222); + assertEq(Source(clone).c(1), 333); + assertEq(Source(clone).c(2), 444); + assertEq(Source(clone).d(), true); + } +} diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol new file mode 100644 index 000000000000..13e3cb78c992 --- /dev/null +++ b/testdata/default/cheats/MockCalls.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract MockCallsTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testMockCallsLastShouldPersist() public { + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](2); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(7.219 ether); + vm.mockCalls(mockErc20, data, mocks); + (, bytes memory ret1) = mockErc20.call(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call(data); + assertEq(abi.decode(ret2, (uint256)), 7.219 ether); + (, bytes memory ret3) = mockErc20.call(data); + assertEq(abi.decode(ret3, (uint256)), 7.219 ether); + } + + function testMockCallsWithValue() public { + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](3); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(1 ether); + mocks[2] = abi.encode(6.423 ether); + vm.mockCalls(mockErc20, 1 ether, data, mocks); + (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret2, (uint256)), 1 ether); + (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + } + + function testMockCalls() public { + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](3); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(1 ether); + mocks[2] = abi.encode(6.423 ether); + vm.mockCalls(mockErc20, data, mocks); + (, bytes memory ret1) = mockErc20.call(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call(data); + assertEq(abi.decode(ret2, (uint256)), 1 ether); + (, bytes memory ret3) = mockErc20.call(data); + assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + } +} From 69455e6b203425ef0ea24a716269ac7f342cd949 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 8 Oct 2024 12:01:14 +0200 Subject: [PATCH 8/9] calls -> frames --- crates/forge/bin/cmd/test/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6e2654eb23b2..ad7096c27330 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -1064,7 +1064,7 @@ contract FooBarTest is DSTest { let call_cnts = gas_report .contracts .values() - .flat_map(|c| c.functions.values().flat_map(|f| f.values().map(|v| v.calls.len()))) + .flat_map(|c| c.functions.values().flat_map(|f| f.values().map(|v| v.frames.len()))) .collect::>(); // assert that all functions were called at least 100 times assert!(call_cnts.iter().all(|c| *c > 100)); From 8dbb4dd0bc3c46512e3f2f7ca60910e5cbdc5e7d Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 9 Oct 2024 14:10:37 +0200 Subject: [PATCH 9/9] use .is_jsonlines() --- crates/forge/tests/cli/cmd.rs | 41 ++++++++++------------------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index ce8d5f87eea0..61a5aeab8942 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -8,6 +8,7 @@ use foundry_config::{ use foundry_test_utils::{ foundry_compilers::PathStyle, rpc::next_mainnet_etherscan_api_key, + snapbox::IntoData, util::{pretty_err, read_string, OutputExt, TestCommand}, }; use semver::Version; @@ -1566,9 +1567,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]]); +"#]].is_jsonlines()); prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" @@ -1607,9 +1606,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]]); +"#]].is_jsonlines()); prj.write_config(Config { gas_reports: (vec!["*".to_string()]), ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" @@ -1648,9 +1645,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]]); +"#]].is_jsonlines()); prj.write_config(Config { gas_reports: (vec![ @@ -1696,9 +1691,7 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]]); +"#]].is_jsonlines()); }); forgetest!(gas_report_some_contracts, |prj, cmd| { @@ -1726,9 +1719,7 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} - - -"#]]); +"#]].is_jsonlines()); // report for Two prj.write_config(Config { gas_reports: vec!["ContractTwo".to_string()], ..Default::default() }); @@ -1747,9 +1738,7 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( str![[r#" {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]], +"#]].is_jsonlines(), ); // report for Three @@ -1776,9 +1765,7 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} - - -"#]]); +"#]].is_jsonlines()); }); forgetest!(gas_ignore_some_contracts, |prj, cmd| { @@ -1819,9 +1806,7 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { .stdout_eq(str![[r#" {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]]); +"#]].is_jsonlines()); // ignore ContractTwo cmd.forge_fuse(); @@ -1858,9 +1843,7 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { .stdout_eq(str![[r#" {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} - - -"#]]); +"#]].is_jsonlines()); // ignore ContractThree cmd.forge_fuse(); @@ -1910,9 +1893,7 @@ forgetest!(gas_ignore_some_contracts, |prj, cmd| { {"gas":103375,"size":255,"functions":{"foo":{"foo()":{"calls":1,"min":45387,"mean":45387,"median":45387,"max":45387}}}} {"gas":103591,"size":256,"functions":{"baz":{"baz()":{"calls":1,"min":260712,"mean":260712,"median":260712,"max":260712}}}} {"gas":103375,"size":255,"functions":{"bar":{"bar()":{"calls":1,"min":64984,"mean":64984,"median":64984,"max":64984}}}} - - -"#]]); +"#]].is_jsonlines()); }); forgetest_init!(can_use_absolute_imports, |prj, cmd| {