From 41b4359973235c37227a1d485cdb71dc56959b8b Mon Sep 17 00:00:00 2001 From: m4rio <92288535+mario-eth@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:55:37 +0200 Subject: [PATCH 001/107] feat: Update to soldeer 0.5.2 (#9373) --- Cargo.lock | 20 +++++++++++--------- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a7cb9a73..e6f33e224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1779,9 +1779,9 @@ dependencies = [ [[package]] name = "bon" -version = "2.3.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869" +checksum = "a636f83af97c6946f3f5cf5c268ec02375bf5efd371110292dfd57961f57a509" dependencies = [ "bon-macros", "rustversion", @@ -1789,14 +1789,16 @@ dependencies = [ [[package]] name = "bon-macros" -version = "2.3.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663" +checksum = "a7eaf1bfaa5b8d512abfd36d0c432591fef139d3de9ee54f1f839ea109d70d33" dependencies = [ "darling", "ident_case", + "prettyplease", "proc-macro2", "quote", + "rustversion", "syn 2.0.87", ] @@ -8510,9 +8512,9 @@ dependencies = [ [[package]] name = "soldeer-commands" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5969c09f89ae6f0e18d5904e5bdbb8842ba948dad0f8202edb7ea510e35654d" +checksum = "a4bd924da31914871820d1404b63a89b100097957f6dc7f3bbb9c094f16d8f4e" dependencies = [ "bon", "clap", @@ -8525,9 +8527,9 @@ dependencies = [ [[package]] name = "soldeer-core" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63aeee0e78b5fba04f005d23a58d20f897720212bd21ad744201cacb9dd34f8" +checksum = "c7a3129568ab6b38132efa9c956b5ae14c09c0a1a1167353e337081d1d7f0c32" dependencies = [ "bon", "chrono", @@ -8546,7 +8548,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "toml_edit", "uuid 1.11.0", diff --git a/Cargo.toml b/Cargo.toml index f5ec94b90..03ff020fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -278,7 +278,7 @@ semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } similar-asserts = "1.6" -soldeer-commands = "=0.5.1" +soldeer-commands = "=0.5.2" strum = "0.26" tempfile = "3.13" tikv-jemallocator = "0.6" From 2bc7125e913b211b2d6c59ecdc5f1f427440652b Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 22 Nov 2024 05:39:03 +0400 Subject: [PATCH 002/107] fix: `vm.broadcastRawTransaction` (#9378) fix: vm.broadcastRawTransaction --- crates/common/src/transactions.rs | 4 ++++ crates/forge/tests/cli/script.rs | 13 +++++++++---- crates/script/src/broadcast.rs | 7 ++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index b319da0d8..a05a46eae 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -179,6 +179,10 @@ impl TransactionMaybeSigned { Ok(Self::Signed { tx, from }) } + pub fn is_unsigned(&self) -> bool { + matches!(self, Self::Unsigned(_)) + } + pub fn as_unsigned_mut(&mut self) -> Option<&mut WithOtherFields> { match self { Self::Unsigned(tx) => Some(tx), diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 82c61ccbc..38e702a1b 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,7 +1,7 @@ //! Contains various tests related to `forge script`. use crate::constants::TEMPLATE_CONTRACT; -use alloy_primitives::{hex, Address, Bytes}; +use alloy_primitives::{address, hex, Address, Bytes}; use anvil::{spawn, NodeConfig}; use forge_script_sequence::ScriptSequence; use foundry_test_utils::{ @@ -2039,8 +2039,7 @@ forgetest_async!(can_deploy_library_create2_different_sender, |prj, cmd| { // forgetest_async!(test_broadcast_raw_create2_deployer, |prj, cmd| { - let (_api, handle) = - spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + let (api, handle) = spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; foundry_test_utils::util::initialize(prj.root()); prj.add_script( @@ -2051,7 +2050,7 @@ import "forge-std/Script.sol"; contract SimpleScript is Script { function run() external { // send funds to create2 factory deployer - vm.broadcast(); + vm.startBroadcast(); payable(0x3fAB184622Dc19b6109349B94811493BF2a45362).transfer(10000000 gwei); // deploy create2 factory vm.broadcastRawTransaction( @@ -2104,6 +2103,12 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. "#]]); + + assert!(!api + .get_code(address!("4e59b44847b379578588920cA78FbF26c0B4956C"), Default::default()) + .await + .unwrap() + .is_empty()); }); forgetest_init!(can_get_script_wallets, |prj, cmd| { diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 4058aa6c5..51e8baf5b 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -213,7 +213,12 @@ impl BundledState { .sequence .sequences() .iter() - .flat_map(|sequence| sequence.transactions().map(|tx| tx.from().expect("missing from"))) + .flat_map(|sequence| { + sequence + .transactions() + .filter(|tx| tx.is_unsigned()) + .map(|tx| tx.from().expect("missing from")) + }) .collect::(); if required_addresses.contains(&Config::DEFAULT_SENDER) { From 76a2cb0dd6d60684fd64a8180500f9d619ec94d2 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 22 Nov 2024 10:53:21 +0400 Subject: [PATCH 003/107] fix(forge test): install missing dependencies before creating `Project` (#9379) * fix(forge test): install missing dependencies before instantiating the project * optimization --- crates/config/src/lib.rs | 3 +++ crates/forge/bin/cmd/test/mod.rs | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 6444802e3..5a159c392 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -904,6 +904,9 @@ impl Config { ) -> Result>, SolcError> { let mut map = BTreeMap::new(); + if self.compilation_restrictions.is_empty() { + return Ok(BTreeMap::new()); + } let graph = Graph::::resolve(paths)?; let (sources, _) = graph.into_sources(); diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 59434d5eb..1a409b33a 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -280,16 +280,15 @@ impl TestArgs { config.invariant.gas_report_samples = 0; } - // Set up the project. - let mut project = config.project()?; - // Install missing dependencies. if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings config = self.load_config(); - project = config.project()?; } + // Set up the project. + let project = config.project()?; + let mut filter = self.filter(&config); trace!(target: "forge::test", ?filter, "using filter"); From 1332b6d6c09264fe4cc3653f9d117ac9fb6c48c7 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:37:30 +0100 Subject: [PATCH 004/107] chore(chisel): replace solang with solar in SolidityHelper (#9376) --- Cargo.lock | 1 + Cargo.toml | 1 + crates/chisel/Cargo.toml | 1 + crates/chisel/src/dispatcher.rs | 14 +- crates/chisel/src/executor.rs | 2 +- crates/chisel/src/solidity_helper.rs | 252 +++++++++++---------------- 6 files changed, 117 insertions(+), 154 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f33e224..9179edf77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,6 +2067,7 @@ dependencies = [ "serde_json", "serial_test", "solang-parser", + "solar-parse", "strum", "tikv-jemallocator", "time", diff --git a/Cargo.toml b/Cargo.toml index 03ff020fa..8dc0ecd5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ foundry-block-explorers = { version = "0.9.0", default-features = false } foundry-compilers = { version = "0.12.3", default-features = false } foundry-fork-db = "0.7.0" solang-parser = "=0.3.3" +solar-parse = { version = "=0.1.0", default-features = false } ## revm revm = { version = "18.0.0", default-features = false } diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 65b3c9748..5f098817c 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -58,6 +58,7 @@ semver.workspace = true serde_json.workspace = true serde.workspace = true solang-parser.workspace = true +solar-parse.workspace = true strum = { workspace = true, features = ["derive"] } time = { version = "0.3", features = ["formatting"] } tokio = { workspace = true, features = ["full"] } diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index d69de3bf5..2a6a2fc3f 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -38,14 +38,16 @@ use strum::IntoEnumIterator; use tracing::debug; use yansi::Paint; -/// Prompt arrow character -pub static PROMPT_ARROW: char = '➜'; -static DEFAULT_PROMPT: &str = "➜ "; +/// Prompt arrow character. +pub const PROMPT_ARROW: char = '➜'; +/// Prompt arrow string. +pub const PROMPT_ARROW_STR: &str = "➜"; +const DEFAULT_PROMPT: &str = "➜ "; /// Command leader character -pub static COMMAND_LEADER: char = '!'; +pub const COMMAND_LEADER: char = '!'; /// Chisel character -pub static CHISEL_CHAR: &str = "⚒️"; +pub const CHISEL_CHAR: &str = "⚒️"; /// Matches Solidity comments static COMMENT_RE: LazyLock = @@ -320,7 +322,7 @@ impl ChiselDispatcher { }, ChiselCommand::Source => match self.format_source() { Ok(formatted_source) => DispatchResult::CommandSuccess(Some( - SolidityHelper::highlight(&formatted_source).into_owned(), + SolidityHelper::new().highlight(&formatted_source).into_owned(), )), Err(_) => { DispatchResult::CommandFailed(String::from("Failed to format session source")) diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index b7f3973b0..fc24e0b89 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -504,7 +504,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result Self { + Self::new() + } } impl SolidityHelper { /// Create a new SolidityHelper. pub fn new() -> Self { - Self::default() + Self { + errored: false, + do_paint: yansi::is_enabled(), + sess: Session::builder().with_silent_emitter(None).build(), + globals: SessionGlobals::new(), + } + } + + /// Returns whether the helper is in an errored state. + pub fn errored(&self) -> bool { + self.errored } /// Set the errored field. @@ -55,54 +68,9 @@ impl SolidityHelper { self } - /// Get styles for a solidity source string - pub fn get_styles(input: &str) -> Vec { - let mut comments = Vec::with_capacity(DEFAULT_COMMENTS); - let mut errors = Vec::with_capacity(5); - let mut out = Lexer::new(input, 0, &mut comments, &mut errors) - .map(|(start, token, end)| (start, token.style(), end)) - .collect::>(); - - // highlight comments too - let comments_iter = comments.into_iter().map(|comment| { - let loc = match comment { - pt::Comment::Line(loc, _) | - pt::Comment::Block(loc, _) | - pt::Comment::DocLine(loc, _) | - pt::Comment::DocBlock(loc, _) => loc, - }; - (loc.start(), Style::new().dim(), loc.end()) - }); - out.extend(comments_iter); - - out - } - - /// Get contiguous styles for a solidity source string - pub fn get_contiguous_styles(input: &str) -> Vec { - let mut styles = Self::get_styles(input); - styles.sort_unstable_by_key(|(start, _, _)| *start); - - let len = input.len(); - // len / 4 is just a random average of whitespaces in the input - let mut out = Vec::with_capacity(styles.len() + len / 4 + 1); - let mut index = 0; - for (start, style, end) in styles { - if index < start { - out.push((index, Style::default(), start)); - } - out.push((start, style, end)); - index = end; - } - if index < len { - out.push((index, Style::default(), len)); - } - out - } - - /// Highlights a solidity source string - pub fn highlight(input: &str) -> Cow<'_, str> { - if !yansi::is_enabled() { + /// Highlights a Solidity source string. + pub fn highlight<'a>(&self, input: &'a str) -> Cow<'a, str> { + if !self.do_paint() { return Cow::Borrowed(input) } @@ -133,52 +101,53 @@ impl SolidityHelper { Cow::Owned(out) } else { - let styles = Self::get_contiguous_styles(input); - let len = styles.len(); - if len == 0 { - Cow::Borrowed(input) - } else { - let mut out = String::with_capacity(input.len() + MAX_ANSI_LEN * len); - for (start, style, end) in styles { - Self::paint_unchecked(&input[start..end], style, &mut out); + let mut out = String::with_capacity(input.len() * 2); + self.with_contiguous_styles(input, |style, range| { + Self::paint_unchecked(&input[range], style, &mut out); + }); + Cow::Owned(out) + } + } + + /// Returns a list of styles and the ranges they should be applied to. + /// + /// Covers the entire source string, including any whitespace. + fn with_contiguous_styles(&self, input: &str, mut f: impl FnMut(Style, Range)) { + self.enter(|sess| { + let len = input.len(); + let mut index = 0; + for token in Lexer::new(sess, input) { + let range = token.span.lo().to_usize()..token.span.hi().to_usize(); + let style = token_style(&token); + if index < range.start { + f(Style::default(), index..range.start); } - Cow::Owned(out) + index = range.end; + f(style, range); } - } + if index < len { + f(Style::default(), index..len); + } + }); } /// Validate that a source snippet is closed (i.e., all braces and parenthesis are matched). - fn validate_closed(input: &str) -> ValidationResult { - let mut bracket_depth = 0usize; - let mut paren_depth = 0usize; - let mut brace_depth = 0usize; - let mut comments = Vec::with_capacity(DEFAULT_COMMENTS); - // returns on any encountered error, so allocate for just one - let mut errors = Vec::with_capacity(1); - for (_, token, _) in Lexer::new(input, 0, &mut comments, &mut errors) { - match token { - Token::OpenBracket => { - bracket_depth += 1; - } - Token::OpenCurlyBrace => { - brace_depth += 1; + fn validate_closed(&self, input: &str) -> ValidationResult { + let mut depth = [0usize; 3]; + self.enter(|sess| { + for token in Lexer::new(sess, input) { + match token.kind { + TokenKind::OpenDelim(delim) => { + depth[delim as usize] += 1; + } + TokenKind::CloseDelim(delim) => { + depth[delim as usize] = depth[delim as usize].saturating_sub(1); + } + _ => {} } - Token::OpenParenthesis => { - paren_depth += 1; - } - Token::CloseBracket => { - bracket_depth = bracket_depth.saturating_sub(1); - } - Token::CloseCurlyBrace => { - brace_depth = brace_depth.saturating_sub(1); - } - Token::CloseParenthesis => { - paren_depth = paren_depth.saturating_sub(1); - } - _ => {} } - } - if (bracket_depth | brace_depth | paren_depth) == 0 { + }); + if depth == [0; 3] { ValidationResult::Valid(None) } else { ValidationResult::Incomplete @@ -186,8 +155,7 @@ impl SolidityHelper { } /// Formats `input` with `style` into `out`, without checking `style.wrapping` or - /// `yansi::is_enabled` - #[inline] + /// `self.do_paint`. fn paint_unchecked(string: &str, style: Style, out: &mut String) { if style == Style::default() { out.push_str(string); @@ -198,17 +166,26 @@ impl SolidityHelper { } } - #[inline] fn paint_unchecked_owned(string: &str, style: Style) -> String { let mut out = String::with_capacity(MAX_ANSI_LEN + string.len()); Self::paint_unchecked(string, style, &mut out); out } + + /// Returns whether to color the output. + fn do_paint(&self) -> bool { + self.do_paint + } + + /// Enters the session. + fn enter(&self, f: impl FnOnce(&Session)) { + self.globals.set(|| self.sess.enter(|| f(&self.sess))); + } } impl Highlighter for SolidityHelper { fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { - Self::highlight(line) + self.highlight(line) } fn highlight_char(&self, line: &str, pos: usize, _forced: bool) -> bool { @@ -220,7 +197,7 @@ impl Highlighter for SolidityHelper { prompt: &'p str, _default: bool, ) -> Cow<'b, str> { - if !yansi::is_enabled() { + if !self.do_paint() { return Cow::Borrowed(prompt) } @@ -241,14 +218,7 @@ impl Highlighter for SolidityHelper { if let Some(i) = out.find(PROMPT_ARROW) { let style = if self.errored { Color::Red.foreground() } else { Color::Green.foreground() }; - - let mut arrow = String::with_capacity(MAX_ANSI_LEN + 4); - - let _ = style.fmt_prefix(&mut arrow); - arrow.push(PROMPT_ARROW); - let _ = style.fmt_suffix(&mut arrow); - - out.replace_range(i..=i + 2, &arrow); + out.replace_range(i..=i + 2, &Self::paint_unchecked_owned(PROMPT_ARROW_STR, style)); } Cow::Owned(out) @@ -257,7 +227,7 @@ impl Highlighter for SolidityHelper { impl Validator for SolidityHelper { fn validate(&self, ctx: &mut ValidationContext<'_>) -> rustyline::Result { - Ok(Self::validate_closed(ctx.input())) + Ok(self.validate_closed(ctx.input())) } } @@ -271,44 +241,32 @@ impl Hinter for SolidityHelper { impl Helper for SolidityHelper {} -/// Trait that assigns a color to a Token kind -pub trait TokenStyle { - /// Returns the style with which the token should be decorated with. - fn style(&self) -> Style; -} +#[allow(non_upper_case_globals)] +#[deny(unreachable_patterns)] +fn token_style(token: &Token) -> Style { + use solar_parse::{ + interface::kw::*, + token::{TokenKind::*, TokenLitKind::*}, + }; -/// [TokenStyle] implementation for [Token] -impl TokenStyle for Token<'_> { - fn style(&self) -> Style { - use Token::*; - match self { - StringLiteral(_, _) => Color::Green.foreground(), - - AddressLiteral(_) | - HexLiteral(_) | - Number(_, _) | - RationalNumber(_, _, _) | - HexNumber(_) | - True | - False => Color::Yellow.foreground(), + match token.kind { + Literal(Str | HexStr | UnicodeStr, _) => Color::Green.foreground(), + Literal(..) => Color::Yellow.foreground(), + Ident( Memory | Storage | Calldata | Public | Private | Internal | External | Constant | Pure | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | - Modifier | Immutable | Unchecked => Color::Cyan.foreground(), + Modifier | Immutable | Unchecked, + ) => Color::Cyan.foreground(), - Contract | Library | Interface | Function | Pragma | Import | Struct | Event | - Enum | Type | Constructor | As | Is | Using | New | Delete | Do | Continue | - Break | Throw | Emit | Return | Returns | Revert | For | While | If | Else | Try | - Catch | Assembly | Let | Leave | Switch | Case | Default | YulArrow | Arrow => { - Color::Magenta.foreground() - } + Ident(s) if s.is_elementary_type() => Color::Blue.foreground(), + Ident(Mapping) => Color::Blue.foreground(), - Uint(_) | Int(_) | Bytes(_) | Byte | DynamicBytes | Bool | Address | String | - Mapping => Color::Blue.foreground(), + Ident(s) if s.is_used_keyword() || s.is_yul_keyword() => Color::Magenta.foreground(), + Arrow | FatArrow => Color::Magenta.foreground(), - Identifier(_) => Style::default(), + Comment(..) => Color::Primary.dim(), - _ => Style::default(), - } + _ => Color::Primary.foreground(), } } From cf66dea727a6c7f41fa48fbe6dcabe474bfbfd79 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:37:40 +0200 Subject: [PATCH 005/107] fix(chisel): uint/int full word print (#9381) --- crates/chisel/src/executor.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index fc24e0b89..09c00f6ad 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -7,7 +7,7 @@ use crate::prelude::{ }; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_json_abi::EventParam; -use alloy_primitives::{hex, Address, U256}; +use alloy_primitives::{hex, Address, B256, U256}; use core::fmt::Debug; use eyre::{Result, WrapErr}; use foundry_compilers::Artifact; @@ -379,7 +379,7 @@ fn format_token(token: DynSolValue) -> String { .collect::() ) .cyan(), - format!("{i:#x}").cyan(), + hex::encode_prefixed(B256::from(i)).cyan(), i.cyan() ) } @@ -397,7 +397,7 @@ fn format_token(token: DynSolValue) -> String { .collect::() ) .cyan(), - format!("{i:#x}").cyan(), + hex::encode_prefixed(B256::from(i)).cyan(), i.cyan() ) } From 37cc284f939a55bc1886e4bb7ba6ca99930fb4ee Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:14:50 +0530 Subject: [PATCH 006/107] fix: flaky test_broadcast_raw_create2_deployer (#9383) --- crates/forge/tests/cli/script.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 38e702a1b..df2a59bdc 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -2069,6 +2069,7 @@ contract SimpleScript is Script { "--rpc-url", &handle.http_endpoint(), "--broadcast", + "--slow", "SimpleScript", ]); From 8b7d5dfc401aab29a69ff844cfd59c1255d5d106 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:37:31 +0100 Subject: [PATCH 007/107] refactor(forge): rewrite `geiger` with Solar (#9382) --- Cargo.lock | 2 + Cargo.toml | 1 + crates/forge/Cargo.toml | 2 + crates/forge/bin/cmd/geiger.rs | 159 ++++++++++++ crates/forge/bin/cmd/geiger/error.rs | 11 - crates/forge/bin/cmd/geiger/find.rs | 165 ------------ crates/forge/bin/cmd/geiger/mod.rs | 122 --------- crates/forge/bin/cmd/geiger/visitor.rs | 333 ------------------------- crates/forge/bin/main.rs | 3 +- crates/forge/tests/cli/debug.rs | 2 +- crates/forge/tests/cli/geiger.rs | 92 +++++++ crates/forge/tests/cli/main.rs | 1 + crates/test-utils/src/util.rs | 8 +- 13 files changed, 266 insertions(+), 635 deletions(-) create mode 100644 crates/forge/bin/cmd/geiger.rs delete mode 100644 crates/forge/bin/cmd/geiger/error.rs delete mode 100644 crates/forge/bin/cmd/geiger/find.rs delete mode 100644 crates/forge/bin/cmd/geiger/mod.rs delete mode 100644 crates/forge/bin/cmd/geiger/visitor.rs create mode 100644 crates/forge/tests/cli/geiger.rs diff --git a/Cargo.lock b/Cargo.lock index 9179edf77..15a2386be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3408,6 +3408,8 @@ dependencies = [ "similar", "similar-asserts", "solang-parser", + "solar-ast", + "solar-parse", "soldeer-commands", "strum", "svm-rs", diff --git a/Cargo.toml b/Cargo.toml index 8dc0ecd5f..814c43aa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ foundry-block-explorers = { version = "0.9.0", default-features = false } foundry-compilers = { version = "0.12.3", default-features = false } foundry-fork-db = "0.7.0" solang-parser = "=0.3.3" +solar-ast = { version = "=0.1.0", default-features = false } solar-parse = { version = "=0.1.0", default-features = false } ## revm diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index b32a9bd41..208ea8430 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -89,6 +89,8 @@ semver.workspace = true serde_json.workspace = true similar = { version = "2", features = ["inline"] } solang-parser.workspace = true +solar-ast.workspace = true +solar-parse.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = ["time"] } diff --git a/crates/forge/bin/cmd/geiger.rs b/crates/forge/bin/cmd/geiger.rs new file mode 100644 index 000000000..9ffddd3c9 --- /dev/null +++ b/crates/forge/bin/cmd/geiger.rs @@ -0,0 +1,159 @@ +use clap::{Parser, ValueHint}; +use eyre::{Result, WrapErr}; +use foundry_cli::utils::LoadConfig; +use foundry_compilers::{resolver::parse::SolData, Graph}; +use foundry_config::{impl_figment_convert_basic, Config}; +use itertools::Itertools; +use solar_ast::visit::Visit; +use solar_parse::{ast, interface::Session}; +use std::path::{Path, PathBuf}; + +/// CLI arguments for `forge geiger`. +#[derive(Clone, Debug, Parser)] +pub struct GeigerArgs { + /// Paths to files or directories to detect. + #[arg( + conflicts_with = "root", + value_hint = ValueHint::FilePath, + value_name = "PATH", + num_args(1..), + )] + paths: Vec, + + /// The project's root path. + /// + /// By default root of the Git repository, if in one, + /// or the current working directory. + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + root: Option, + + /// Globs to ignore. + #[arg( + long, + value_hint = ValueHint::FilePath, + value_name = "PATH", + num_args(1..), + )] + ignore: Vec, + + #[arg(long, hide = true)] + check: bool, + #[arg(long, hide = true)] + full: bool, +} + +impl_figment_convert_basic!(GeigerArgs); + +impl GeigerArgs { + pub fn sources(&self, config: &Config) -> Result> { + let cwd = std::env::current_dir()?; + + let mut sources: Vec = { + if self.paths.is_empty() { + let paths = config.project_paths(); + Graph::::resolve(&paths)? + .files() + .keys() + .filter(|f| !paths.libraries.iter().any(|lib| f.starts_with(lib))) + .cloned() + .collect() + } else { + self.paths + .iter() + .flat_map(|path| foundry_common::fs::files_with_ext(path, "sol")) + .unique() + .collect() + } + }; + + sources.retain_mut(|path| { + let abs_path = if path.is_absolute() { path.clone() } else { cwd.join(&path) }; + *path = abs_path.strip_prefix(&cwd).unwrap_or(&abs_path).to_path_buf(); + !self.ignore.iter().any(|ignore| { + if ignore.is_absolute() { + abs_path.starts_with(ignore) + } else { + abs_path.starts_with(cwd.join(ignore)) + } + }) + }); + + Ok(sources) + } + + pub fn run(self) -> Result { + if self.check { + sh_warn!("`--check` is deprecated as it's now the default behavior\n")?; + } + if self.full { + sh_warn!("`--full` is deprecated as reports are not generated anymore\n")?; + } + + let config = self.try_load_config_emit_warnings()?; + let sources = self.sources(&config).wrap_err("Failed to resolve files")?; + + if config.ffi { + sh_warn!("FFI enabled\n")?; + } + + let mut sess = Session::builder().with_stderr_emitter().build(); + sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false); + let unsafe_cheatcodes = &[ + "ffi".to_string(), + "readFile".to_string(), + "readLine".to_string(), + "writeFile".to_string(), + "writeLine".to_string(), + "removeFile".to_string(), + "closeFile".to_string(), + "setEnv".to_string(), + "deriveKey".to_string(), + ]; + Ok(sess + .enter(|| sources.iter().map(|file| lint_file(&sess, unsafe_cheatcodes, file)).sum())) + } +} + +fn lint_file(sess: &Session, unsafe_cheatcodes: &[String], path: &Path) -> usize { + try_lint_file(sess, unsafe_cheatcodes, path).unwrap_or(0) +} + +fn try_lint_file( + sess: &Session, + unsafe_cheatcodes: &[String], + path: &Path, +) -> solar_parse::interface::Result { + let arena = solar_parse::ast::Arena::new(); + let mut parser = solar_parse::Parser::from_file(sess, &arena, path)?; + let ast = parser.parse_file().map_err(|e| e.emit())?; + let mut visitor = Visitor::new(sess, unsafe_cheatcodes); + visitor.visit_source_unit(&ast); + Ok(visitor.count) +} + +struct Visitor<'a> { + sess: &'a Session, + count: usize, + unsafe_cheatcodes: &'a [String], +} + +impl<'a> Visitor<'a> { + fn new(sess: &'a Session, unsafe_cheatcodes: &'a [String]) -> Self { + Self { sess, count: 0, unsafe_cheatcodes } + } +} + +impl<'ast> Visit<'ast> for Visitor<'_> { + fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) { + if let ast::ExprKind::Call(lhs, _args) = &expr.kind { + if let ast::ExprKind::Member(_lhs, member) = &lhs.kind { + if self.unsafe_cheatcodes.iter().any(|c| c.as_str() == member.as_str()) { + let msg = format!("usage of unsafe cheatcode `vm.{member}`"); + self.sess.dcx.err(msg).span(member.span).emit(); + self.count += 1; + } + } + } + self.walk_expr(expr); + } +} diff --git a/crates/forge/bin/cmd/geiger/error.rs b/crates/forge/bin/cmd/geiger/error.rs deleted file mode 100644 index 010fb237c..000000000 --- a/crates/forge/bin/cmd/geiger/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -use forge_fmt::FormatterError; -use foundry_common::errors::FsPathError; - -/// Possible errors when scanning a solidity file -#[derive(Debug, thiserror::Error)] -pub enum ScanFileError { - #[error(transparent)] - Io(#[from] FsPathError), - #[error(transparent)] - ParseSol(#[from] FormatterError), -} diff --git a/crates/forge/bin/cmd/geiger/find.rs b/crates/forge/bin/cmd/geiger/find.rs deleted file mode 100644 index e3cd65413..000000000 --- a/crates/forge/bin/cmd/geiger/find.rs +++ /dev/null @@ -1,165 +0,0 @@ -use super::{error::ScanFileError, visitor::CheatcodeVisitor}; -use eyre::Result; -use forge_fmt::{offset_to_line_column, parse2, FormatterError, Visitable}; -use foundry_common::fs; -use solang_parser::pt::Loc; -use std::{ - fmt, - path::{Path, PathBuf}, -}; -use yansi::Paint; - -/// Scan a single file for `unsafe` cheatcode usage. -pub fn find_cheatcodes_in_file(path: &Path) -> Result { - let contents = fs::read_to_string(path)?; - let cheatcodes = find_cheatcodes_in_string(&contents, Some(path))?; - Ok(SolFileMetrics { contents, cheatcodes, file: path.to_path_buf() }) -} - -/// Scan a string for unsafe cheatcodes. -pub fn find_cheatcodes_in_string( - src: &str, - path: Option<&Path>, -) -> Result { - let mut parsed = parse2(src, path)?; - let mut visitor = CheatcodeVisitor::default(); - parsed.pt.visit(&mut visitor).unwrap(); - Ok(visitor.cheatcodes) -} - -/// Scan result for a single Solidity file. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct SolFileMetrics { - /// The Solidity file - pub file: PathBuf, - - /// The file's contents. - pub contents: String, - - /// The unsafe cheatcodes found. - pub cheatcodes: UnsafeCheatcodes, -} - -/// Formats the metrics for a single file using [`fmt::Display`]. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct SolFileMetricsPrinter<'a, 'b> { - pub metrics: &'a SolFileMetrics, - pub root: &'b Path, -} - -impl fmt::Display for SolFileMetricsPrinter<'_, '_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let SolFileMetricsPrinter { metrics, root } = *self; - - let file = metrics.file.strip_prefix(root).unwrap_or(&metrics.file); - - macro_rules! print_unsafe_fn { - ($($name:literal => $field:ident),*) => {$( - let $field = &metrics.cheatcodes.$field[..]; - if !$field.is_empty() { - writeln!(f, " {} {}", metrics.cheatcodes.$field.len().red(), $name.red())?; - - for &loc in $field { - let content = &metrics.contents[loc.range()]; - let (line, col) = offset_to_line_column(&metrics.contents, loc.start()); - let pos = format!(" --> {}:{}:{}", file.display(), line, col); - writeln!(f,"{}", pos.red())?; - for line in content.lines() { - writeln!(f, " {}", line.red())?; - } - } - } - )*}; - } - - if !metrics.cheatcodes.is_empty() { - writeln!(f, "{} {}", metrics.cheatcodes.len().red(), file.display().red())?; - print_unsafe_fn!( - "ffi" => ffi, - "readFile" => read_file, - "readLine" => read_line, - "writeFile" => write_file, - "writeLine" => write_line, - "removeFile" => remove_file, - "closeFile" => close_file, - "setEnv" => set_env, - "deriveKey" => derive_key - ); - } else { - writeln!(f, "0 {}", file.display())? - } - - Ok(()) - } -} - -/// Unsafe usage metrics collection. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct UnsafeCheatcodes { - pub ffi: Vec, - pub read_file: Vec, - pub read_line: Vec, - pub write_file: Vec, - pub write_line: Vec, - pub remove_file: Vec, - pub close_file: Vec, - pub set_env: Vec, - pub derive_key: Vec, -} - -impl UnsafeCheatcodes { - /// Whether there are any unsafe calls. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// The total number of unsafe calls. - pub fn len(&self) -> usize { - self.ffi.len() + - self.read_file.len() + - self.read_line.len() + - self.write_file.len() + - self.write_line.len() + - self.close_file.len() + - self.set_env.len() + - self.derive_key.len() + - self.remove_file.len() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_find_calls() { - let s = r" - contract A is Test { - function do_ffi() public { - string[] memory inputs = new string[](1); - vm.ffi(inputs); - } - } - "; - - let count = find_cheatcodes_in_string(s, None).unwrap(); - assert_eq!(count.ffi.len(), 1); - assert!(!count.is_empty()); - } - - #[test] - fn can_find_call_in_assignment() { - let s = r" - contract A is Test { - function do_ffi() public { - string[] memory inputs = new string[](1); - bytes stuff = vm.ffi(inputs); - } - } - "; - - let count = find_cheatcodes_in_string(s, None).unwrap(); - assert_eq!(count.ffi.len(), 1); - assert!(!count.is_empty()); - } -} diff --git a/crates/forge/bin/cmd/geiger/mod.rs b/crates/forge/bin/cmd/geiger/mod.rs deleted file mode 100644 index 4167b7882..000000000 --- a/crates/forge/bin/cmd/geiger/mod.rs +++ /dev/null @@ -1,122 +0,0 @@ -use clap::{Parser, ValueHint}; -use eyre::{Result, WrapErr}; -use foundry_cli::utils::LoadConfig; -use foundry_compilers::{resolver::parse::SolData, Graph}; -use foundry_config::{impl_figment_convert_basic, Config}; -use itertools::Itertools; -use rayon::prelude::*; -use std::path::PathBuf; - -mod error; - -mod find; -use find::{find_cheatcodes_in_file, SolFileMetricsPrinter}; - -mod visitor; - -/// CLI arguments for `forge geiger`. -#[derive(Clone, Debug, Parser)] -pub struct GeigerArgs { - /// Paths to files or directories to detect. - #[arg( - conflicts_with = "root", - value_hint = ValueHint::FilePath, - value_name = "PATH", - num_args(1..), - )] - paths: Vec, - - /// The project's root path. - /// - /// By default root of the Git repository, if in one, - /// or the current working directory. - #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] - root: Option, - - /// Run in "check" mode. - /// - /// The exit code of the program will be the number of unsafe cheatcodes found. - #[arg(long)] - pub check: bool, - - /// Globs to ignore. - #[arg( - long, - value_hint = ValueHint::FilePath, - value_name = "PATH", - num_args(1..), - )] - ignore: Vec, - - /// Print a report of all files, even if no unsafe functions are found. - #[arg(long)] - full: bool, -} - -impl_figment_convert_basic!(GeigerArgs); - -impl GeigerArgs { - pub fn sources(&self, config: &Config) -> Result> { - let cwd = std::env::current_dir()?; - - let mut sources: Vec = { - if self.paths.is_empty() { - Graph::::resolve(&config.project_paths())? - .files() - .keys() - .cloned() - .collect() - } else { - self.paths - .iter() - .flat_map(|path| foundry_common::fs::files_with_ext(path, "sol")) - .unique() - .collect() - } - }; - - sources.retain(|path| { - let abs_path = if path.is_absolute() { path.clone() } else { cwd.join(path) }; - !self.ignore.iter().any(|ignore| { - if ignore.is_absolute() { - abs_path.starts_with(ignore) - } else { - abs_path.starts_with(cwd.join(ignore)) - } - }) - }); - - Ok(sources) - } - - pub fn run(self) -> Result { - let config = self.try_load_config_emit_warnings()?; - let sources = self.sources(&config).wrap_err("Failed to resolve files")?; - - if config.ffi { - sh_warn!("FFI enabled\n")?; - } - - let root = config.root.0; - - let sum = sources - .par_iter() - .map(|file| match find_cheatcodes_in_file(file) { - Ok(metrics) => { - let len = metrics.cheatcodes.len(); - let printer = SolFileMetricsPrinter { metrics: &metrics, root: &root }; - if self.full || len == 0 { - let _ = sh_eprint!("{printer}"); - } - len - } - Err(err) => { - let _ = sh_err!("{err}"); - 0 - } - }) - .sum(); - - Ok(sum) - } -} diff --git a/crates/forge/bin/cmd/geiger/visitor.rs b/crates/forge/bin/cmd/geiger/visitor.rs deleted file mode 100644 index 703130890..000000000 --- a/crates/forge/bin/cmd/geiger/visitor.rs +++ /dev/null @@ -1,333 +0,0 @@ -use super::find::UnsafeCheatcodes; -use eyre::Result; -use forge_fmt::{Visitable, Visitor}; -use solang_parser::pt::{ - ContractDefinition, Expression, FunctionDefinition, IdentifierPath, Loc, Parameter, SourceUnit, - Statement, TypeDefinition, VariableDeclaration, VariableDefinition, -}; -use std::convert::Infallible; - -/// a [`forge_fmt::Visitor` that scans for invocations of cheatcodes -#[derive(Default)] -pub struct CheatcodeVisitor { - pub cheatcodes: UnsafeCheatcodes, -} - -impl Visitor for CheatcodeVisitor { - type Error = Infallible; - - fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<(), Self::Error> { - source_unit.0.visit(self) - } - - fn visit_contract(&mut self, contract: &mut ContractDefinition) -> Result<(), Self::Error> { - contract.base.visit(self)?; - contract.parts.visit(self) - } - - fn visit_block( - &mut self, - _loc: Loc, - _unchecked: bool, - statements: &mut Vec, - ) -> Result<(), Self::Error> { - statements.visit(self) - } - - fn visit_expr(&mut self, _loc: Loc, expr: &mut Expression) -> Result<(), Self::Error> { - match expr { - Expression::PostIncrement(_, expr) => { - expr.visit(self)?; - } - Expression::PostDecrement(_, expr) => { - expr.visit(self)?; - } - Expression::New(_, expr) => { - expr.visit(self)?; - } - Expression::ArraySubscript(_, expr1, expr2) => { - expr1.visit(self)?; - expr2.visit(self)?; - } - Expression::ArraySlice(_, expr1, expr2, expr3) => { - expr1.visit(self)?; - expr2.visit(self)?; - expr3.visit(self)?; - } - Expression::Parenthesis(_, expr) => { - expr.visit(self)?; - } - Expression::MemberAccess(_, expr, _) => { - expr.visit(self)?; - } - Expression::FunctionCall(loc, lhs, rhs) => { - // all cheatcodes are accessd via .cheatcode - if let Expression::MemberAccess(_, expr, identifier) = &**lhs { - if let Expression::Variable(_) = &**expr { - match identifier.name.as_str() { - "ffi" => self.cheatcodes.ffi.push(*loc), - "readFile" => self.cheatcodes.read_file.push(*loc), - "writeFile" => self.cheatcodes.write_file.push(*loc), - "readLine" => self.cheatcodes.read_line.push(*loc), - "writeLine" => self.cheatcodes.write_line.push(*loc), - "closeFile" => self.cheatcodes.close_file.push(*loc), - "removeFile" => self.cheatcodes.remove_file.push(*loc), - "setEnv" => self.cheatcodes.set_env.push(*loc), - "deriveKey" => self.cheatcodes.derive_key.push(*loc), - _ => {} - } - } - } - rhs.visit(self)?; - } - Expression::FunctionCallBlock(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::NamedFunctionCall(_, lhs, rhs) => { - lhs.visit(self)?; - for arg in rhs.iter_mut() { - arg.expr.visit(self)?; - } - } - Expression::Not(_, expr) => { - expr.visit(self)?; - } - Expression::BitwiseNot(_, expr) => { - expr.visit(self)?; - } - Expression::Delete(_, expr) => { - expr.visit(self)?; - } - Expression::PreIncrement(_, expr) => { - expr.visit(self)?; - } - Expression::PreDecrement(_, expr) => { - expr.visit(self)?; - } - Expression::UnaryPlus(_, expr) => { - expr.visit(self)?; - } - Expression::Negate(_, expr) => { - expr.visit(self)?; - } - Expression::Power(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Multiply(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Divide(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Modulo(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Add(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Subtract(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::ShiftLeft(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::ShiftRight(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::BitwiseAnd(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::BitwiseXor(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::BitwiseOr(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Less(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::More(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::LessEqual(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::MoreEqual(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Equal(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::NotEqual(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::And(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Or(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::ConditionalOperator(_, llhs, lhs, rhs) => { - llhs.visit(self)?; - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Assign(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignOr(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignAnd(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignXor(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignShiftLeft(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignShiftRight(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignAdd(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignSubtract(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignMultiply(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignDivide(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignModulo(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::List(_, param) => { - for (_, param) in param.iter_mut() { - param.visit(self)?; - } - } - _ => {} - } - - Ok(()) - } - - fn visit_emit(&mut self, _: Loc, expr: &mut Expression) -> Result<(), Self::Error> { - expr.visit(self) - } - - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<(), Self::Error> { - var.ty.visit(self)?; - var.initializer.visit(self) - } - - fn visit_var_definition_stmt( - &mut self, - _: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<(), Self::Error> { - declaration.visit(self)?; - expr.visit(self) - } - - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<(), Self::Error> { - var.ty.visit(self) - } - - fn visit_revert( - &mut self, - _: Loc, - _error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - args.visit(self) - } - - fn visit_if( - &mut self, - _loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - _is_frst_stmt: bool, - ) -> Result<(), Self::Error> { - cond.visit(self)?; - if_branch.visit(self)?; - else_branch.visit(self) - } - - fn visit_while( - &mut self, - _loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - cond.visit(self)?; - body.visit(self) - } - - fn visit_for( - &mut self, - _loc: Loc, - init: &mut Option>, - cond: &mut Option>, - update: &mut Option>, - body: &mut Option>, - ) -> Result<(), Self::Error> { - init.visit(self)?; - cond.visit(self)?; - update.visit(self)?; - body.visit(self) - } - - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<(), Self::Error> { - if let Some(ref mut body) = func.body { - body.visit(self)?; - } - Ok(()) - } - - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<(), Self::Error> { - parameter.ty.visit(self) - } - - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<(), Self::Error> { - def.ty.visit(self) - } -} diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index ac0992cf9..d60c1639a 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -105,9 +105,8 @@ fn run() -> Result<()> { ForgeSubcommand::Inspect(cmd) => cmd.run(), ForgeSubcommand::Tree(cmd) => cmd.run(), ForgeSubcommand::Geiger(cmd) => { - let check = cmd.check; let n = cmd.run()?; - if check && n > 0 { + if n > 0 { std::process::exit(n as i32); } Ok(()) diff --git a/crates/forge/tests/cli/debug.rs b/crates/forge/tests/cli/debug.rs index e8cd08418..c217beeb5 100644 --- a/crates/forge/tests/cli/debug.rs +++ b/crates/forge/tests/cli/debug.rs @@ -3,7 +3,7 @@ use std::path::Path; // Sets up a debuggable test case. // Run with `cargo test-debugger`. -forgetest_async!( +forgetest!( #[ignore = "ran manually"] manual_debug_setup, |prj, cmd| { diff --git a/crates/forge/tests/cli/geiger.rs b/crates/forge/tests/cli/geiger.rs new file mode 100644 index 000000000..fd2165628 --- /dev/null +++ b/crates/forge/tests/cli/geiger.rs @@ -0,0 +1,92 @@ +forgetest!(call, |prj, cmd| { + prj.add_source( + "call.sol", + r#" + contract A is Test { + function do_ffi() public { + string[] memory inputs = new string[](1); + vm.ffi(inputs); + } + } + "#, + ) + .unwrap(); + + cmd.arg("geiger").assert_code(1).stderr_eq(str![[r#" +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:7:20 + | +7 | vm.ffi(inputs); + | ^^^ + | + + +"#]]); +}); + +forgetest!(assignment, |prj, cmd| { + prj.add_source( + "assignment.sol", + r#" + contract A is Test { + function do_ffi() public { + string[] memory inputs = new string[](1); + bytes stuff = vm.ffi(inputs); + } + } + "#, + ) + .unwrap(); + + cmd.arg("geiger").assert_code(1).stderr_eq(str![[r#" +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:7:34 + | +7 | bytes stuff = vm.ffi(inputs); + | ^^^ + | + + +"#]]); +}); + +forgetest!(exit_code, |prj, cmd| { + prj.add_source( + "multiple.sol", + r#" + contract A is Test { + function do_ffi() public { + vm.ffi(inputs); + vm.ffi(inputs); + vm.ffi(inputs); + } + } + "#, + ) + .unwrap(); + + cmd.arg("geiger").assert_code(3).stderr_eq(str![[r#" +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:6:20 + | +6 | vm.ffi(inputs); + | ^^^ + | + +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:7:20 + | +7 | vm.ffi(inputs); + | ^^^ + | + +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:8:20 + | +8 | vm.ffi(inputs); + | ^^^ + | + + +"#]]); +}); diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index a53a26d2a..5838fa853 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -17,6 +17,7 @@ mod create; mod debug; mod doc; mod eip712; +mod geiger; mod multi_script; mod script; mod soldeer; diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 8d7f6cbb5..f887a40ce 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -883,7 +883,7 @@ impl TestCommand { let assert = OutputAssert::new(self.execute()); if self.redact_output { return assert.with_assert(test_assert()); - }; + } assert } @@ -914,6 +914,12 @@ impl TestCommand { self.assert().failure() } + /// Runs the command and asserts that the exit code is `expected`. + #[track_caller] + pub fn assert_code(&mut self, expected: i32) -> OutputAssert { + self.assert().code(expected) + } + /// Runs the command and asserts that it **failed** nothing was printed to stderr. #[track_caller] pub fn assert_empty_stderr(&mut self) { From 398ef4a3d55d8dd769ce86cada5ec845e805188b Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 22 Nov 2024 23:58:04 +0800 Subject: [PATCH 008/107] feat(forge, cast): add `cast --with_local_artifacts`/`forge selectors cache` to trace with local artifacts (#7359) * add RunArgs generate_local_signatures to enable trace with local contracts functions and events * make generate_local_signatures as a helper function * rename generate_local_signatures to cache_local_signatures merge project signatures with exists cached local signatures instead of just override them * extract duplicate method for CachedSignatures * fix cache load path * fix for lint * fix fot lint * remove unnecessary `let` binding * fix for format check * fix for clippy check * fix for clippy check * Move cache in forge selectors, use local artifacts for cast run and send traces * Add test * Review changes: - compile without quiet, fix test - merge local sources with etherscan * Update crates/evm/traces/src/debug/sources.rs Co-authored-by: Arsenii Kulikov --------- Co-authored-by: grandizzy Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: Arsenii Kulikov --- crates/cast/bin/cmd/call.rs | 16 ++- crates/cast/bin/cmd/run.rs | 8 +- crates/cast/tests/cli/main.rs | 111 +++++++++++++++++- crates/cli/src/utils/cmd.rs | 102 ++++++++++------ crates/evm/traces/src/debug/sources.rs | 8 ++ crates/evm/traces/src/identifier/mod.rs | 2 +- .../evm/traces/src/identifier/signatures.rs | 35 ++++-- crates/forge/bin/cmd/selectors.rs | 26 +++- 8 files changed, 256 insertions(+), 52 deletions(-) diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index aefc5f1c0..cdc3bd4bc 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -81,6 +81,10 @@ pub struct CallArgs { #[command(flatten)] eth: EthereumOpts, + + /// Use current project artifacts for trace decoding. + #[arg(long, visible_alias = "la")] + pub with_local_artifacts: bool, } #[derive(Debug, Parser)] @@ -127,6 +131,7 @@ impl CallArgs { decode_internal, labels, data, + with_local_artifacts, .. } = self; @@ -195,7 +200,16 @@ impl CallArgs { ), }; - handle_traces(trace, &config, chain, labels, debug, decode_internal, false).await?; + handle_traces( + trace, + &config, + chain, + labels, + with_local_artifacts, + debug, + decode_internal, + ) + .await?; return Ok(()); } diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 79083fa8d..0b85d14fe 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -10,7 +10,7 @@ use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{handle_traces, init_progress, TraceResult}, }; -use foundry_common::{is_known_system_sender, shell, SYSTEM_TRANSACTION_TYPE}; +use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ figment::{ @@ -87,6 +87,10 @@ pub struct RunArgs { /// Enables Alphanet features. #[arg(long, alias = "odyssey")] pub alphanet: bool, + + /// Use current project artifacts for trace decoding. + #[arg(long, visible_alias = "la")] + pub with_local_artifacts: bool, } impl RunArgs { @@ -251,9 +255,9 @@ impl RunArgs { &config, chain, self.label, + self.with_local_artifacts, self.debug, self.decode_internal, - shell::verbosity() > 0, ) .await?; diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index bdc4a6044..332f0f99f 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,10 +1,12 @@ //! Contains various tests for checking cast commands use alloy_chains::NamedChain; +use alloy_network::TransactionResponse; use alloy_primitives::{b256, B256}; +use alloy_rpc_types::{BlockNumberOrTag, Index}; use anvil::{EthereumHardfork, NodeConfig}; use foundry_test_utils::{ - casttest, file, + casttest, file, forgetest_async, rpc::{ next_etherscan_api_key, next_http_rpc_endpoint, next_mainnet_etherscan_api_key, next_rpc_endpoint, next_ws_rpc_endpoint, @@ -1596,3 +1598,110 @@ casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { "#]]); }); + +// tests cast can decode traces when using project artifacts +forgetest_async!(decode_traces_with_project_artifacts, |prj, cmd| { + let (api, handle) = + anvil::spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "LocalProjectContract", + r#" +contract LocalProjectContract { + event LocalProjectContractCreated(address owner); + + constructor() { + emit LocalProjectContractCreated(msg.sender); + } +} + "#, + ) + .unwrap(); + prj.add_script( + "LocalProjectScript", + r#" +import "forge-std/Script.sol"; +import {LocalProjectContract} from "../src/LocalProjectContract.sol"; + +contract LocalProjectScript is Script { + function run() public { + vm.startBroadcast(); + new LocalProjectContract(); + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "LocalProjectScript", + ]); + + cmd.assert_success(); + + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + // Assert cast with local artifacts from outside the project. + cmd.cast_fuse() + .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Compiling project to generate artifacts +Nothing to compile + +"#]]); + + // Run cast from project dir. + cmd.cast_fuse().set_current_dir(prj.root()); + + // Assert cast without local artifacts cannot decode traces. + cmd.cast_fuse() + .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [13520] → new @0x5FbDB2315678afecb367f032d93F642f64180aa3 + ├─ emit topic 0: 0xa7263295d3a687d750d1fd377b5df47de69d7db8decc745aaa4bbee44dc1688d + │ data: 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 + └─ ← [Return] 62 bytes of code + + +Transaction successfully executed. +[GAS] + +"#]]); + + // Assert cast with local artifacts can decode traces. + cmd.cast_fuse() + .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Compiling project to generate artifacts +No files changed, compilation skipped +Traces: + [13520] → new LocalProjectContract@0x5FbDB2315678afecb367f032d93F642f64180aa3 + ├─ emit LocalProjectContractCreated(owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266) + └─ ← [Return] 62 bytes of code + + +Transaction successfully executed. +[GAS] + +"#]]); +}); diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 19e4425b5..523c10478 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -1,7 +1,7 @@ use alloy_json_abi::JsonAbi; use alloy_primitives::Address; use eyre::{Result, WrapErr}; -use foundry_common::{fs, TestFunctionExt}; +use foundry_common::{compile::ProjectCompiler, fs, shell, ContractsByArtifact, TestFunctionExt}; use foundry_compilers::{ artifacts::{CompactBytecode, Settings}, cache::{CacheEntry, CompilerCache}, @@ -14,9 +14,9 @@ use foundry_evm::{ executors::{DeployResult, EvmError, RawCallResult}, opts::EvmOpts, traces::{ - debug::DebugTraceIdentifier, + debug::{ContractSources, DebugTraceIdentifier}, decode_trace_arena, - identifier::{EtherscanIdentifier, SignaturesIdentifier}, + identifier::{CachedSignatures, SignaturesIdentifier, TraceIdentifiers}, render_trace_arena_with_bytecodes, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, @@ -383,10 +383,25 @@ pub async fn handle_traces( config: &Config, chain: Option, labels: Vec, + with_local_artifacts: bool, debug: bool, decode_internal: bool, - verbose: bool, ) -> Result<()> { + let (known_contracts, mut sources) = if with_local_artifacts { + let _ = sh_println!("Compiling project to generate artifacts"); + let project = config.project()?; + let compiler = ProjectCompiler::new(); + let output = compiler.compile(&project)?; + ( + Some(ContractsByArtifact::new( + output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), + )), + ContractSources::from_project_output(&output, project.root(), None)?, + ) + } else { + (None, ContractSources::default()) + }; + let labels = labels.iter().filter_map(|label_str| { let mut iter = label_str.split(':'); @@ -398,45 +413,44 @@ pub async fn handle_traces( None }); let config_labels = config.labels.clone().into_iter(); - let mut decoder = CallTraceDecoderBuilder::new() + + let mut builder = CallTraceDecoderBuilder::new() .with_labels(labels.chain(config_labels)) .with_signature_identifier(SignaturesIdentifier::new( Config::foundry_cache_dir(), config.offline, - )?) - .build(); + )?); + let mut identifier = TraceIdentifiers::new().with_etherscan(config, chain)?; + if let Some(contracts) = &known_contracts { + builder = builder.with_known_contracts(contracts); + identifier = identifier.with_local(contracts); + } - let mut etherscan_identifier = EtherscanIdentifier::new(config, chain)?; - if let Some(etherscan_identifier) = &mut etherscan_identifier { - for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { - decoder.identify(trace, etherscan_identifier); - } + let mut decoder = builder.build(); + + for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { + decoder.identify(trace, &mut identifier); } - if decode_internal { - let sources = if let Some(etherscan_identifier) = ðerscan_identifier { - etherscan_identifier.get_compiled_contracts().await? - } else { - Default::default() - }; + if decode_internal || debug { + if let Some(ref etherscan_identifier) = identifier.etherscan { + sources.merge(etherscan_identifier.get_compiled_contracts().await?); + } + + if debug { + let mut debugger = Debugger::builder() + .traces(result.traces.expect("missing traces")) + .decoder(&decoder) + .sources(sources) + .build(); + debugger.try_run_tui()?; + return Ok(()) + } + decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); } - if debug { - let sources = if let Some(etherscan_identifier) = etherscan_identifier { - etherscan_identifier.get_compiled_contracts().await? - } else { - Default::default() - }; - let mut debugger = Debugger::builder() - .traces(result.traces.expect("missing traces")) - .decoder(&decoder) - .sources(sources) - .build(); - debugger.try_run_tui()?; - } else { - print_traces(&mut result, &decoder, verbose).await?; - } + print_traces(&mut result, &decoder, shell::verbosity() > 0).await?; Ok(()) } @@ -464,3 +478,25 @@ pub async fn print_traces( sh_println!("Gas used: {}", result.gas_used)?; Ok(()) } + +/// Traverse the artifacts in the project to generate local signatures and merge them into the cache +/// file. +pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf) -> Result<()> { + let path = cache_path.join("signatures"); + let mut cached_signatures = CachedSignatures::load(cache_path); + output.artifacts().for_each(|(_, artifact)| { + if let Some(abi) = &artifact.abi { + for func in abi.functions() { + cached_signatures.functions.insert(func.selector().to_string(), func.signature()); + } + for event in abi.events() { + cached_signatures + .events + .insert(event.selector().to_string(), event.full_signature()); + } + } + }); + + fs::write_json_file(&path, &cached_signatures)?; + Ok(()) +} diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index 40e540a97..dfbfe91af 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -212,6 +212,14 @@ impl ContractSources { Ok(()) } + /// Merges given contract sources. + pub fn merge(&mut self, sources: Self) { + self.sources_by_id.extend(sources.sources_by_id); + for (name, artifacts) in sources.artifacts_by_name { + self.artifacts_by_name.entry(name).or_default().extend(artifacts); + } + } + /// Returns all sources for a contract by name. pub fn get_sources( &self, diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 008e5f841..51f949832 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -12,7 +12,7 @@ mod etherscan; pub use etherscan::EtherscanIdentifier; mod signatures; -pub use signatures::{SignaturesIdentifier, SingleSignaturesIdentifier}; +pub use signatures::{CachedSignatures, SignaturesIdentifier, SingleSignaturesIdentifier}; /// An address identity pub struct AddressIdentity<'a> { diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index 1e3924aa3..2a5ef354a 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -12,11 +12,29 @@ use tokio::sync::RwLock; pub type SingleSignaturesIdentifier = Arc>; #[derive(Debug, Default, Serialize, Deserialize)] -struct CachedSignatures { - events: BTreeMap, - functions: BTreeMap, +pub struct CachedSignatures { + pub events: BTreeMap, + pub functions: BTreeMap, } +impl CachedSignatures { + #[instrument(target = "evm::traces")] + pub fn load(cache_path: PathBuf) -> Self { + let path = cache_path.join("signatures"); + if path.is_file() { + fs::read_json_file(&path) + .map_err( + |err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"), + ) + .unwrap_or_default() + } else { + if let Err(err) = std::fs::create_dir_all(cache_path) { + warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); + } + Self::default() + } + } +} /// An identifier that tries to identify functions and events using signatures found at /// `https://openchain.xyz` or a local cache. #[derive(Debug)] @@ -42,16 +60,7 @@ impl SignaturesIdentifier { let identifier = if let Some(cache_path) = cache_path { let path = cache_path.join("signatures"); trace!(target: "evm::traces", ?path, "reading signature cache"); - let cached = if path.is_file() { - fs::read_json_file(&path) - .map_err(|err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file")) - .unwrap_or_default() - } else { - if let Err(err) = std::fs::create_dir_all(cache_path) { - warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); - } - CachedSignatures::default() - }; + let cached = CachedSignatures::load(cache_path); Self { cached, cached_path: Some(path), unavailable: HashSet::default(), client } } else { Self { diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index ddd6a7968..f75dabaff 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -4,13 +4,14 @@ use comfy_table::Table; use eyre::Result; use foundry_cli::{ opts::{CompilerArgs, CoreBuildArgs, ProjectPathsArgs}, - utils::FoundryPathExt, + utils::{cache_local_signatures, FoundryPathExt}, }; use foundry_common::{ compile::{compile_target, ProjectCompiler}, selectors::{import_selectors, SelectorImportData}, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; +use foundry_config::Config; use std::fs::canonicalize; /// CLI arguments for `forge selectors`. @@ -67,11 +68,34 @@ pub enum SelectorsSubcommands { #[command(flatten)] project_paths: ProjectPathsArgs, }, + + /// Cache project selectors (enables trace with local contracts functions and events). + #[command(visible_alias = "c")] + Cache { + #[command(flatten)] + project_paths: ProjectPathsArgs, + }, } impl SelectorsSubcommands { pub async fn run(self) -> Result<()> { match self { + Self::Cache { project_paths } => { + sh_println!("Caching selectors for contracts in the project...")?; + let build_args = CoreBuildArgs { + project_paths, + compiler: CompilerArgs { + extra_output: vec![ContractOutputSelection::Abi], + ..Default::default() + }, + ..Default::default() + }; + + // compile the project to get the artifacts/abis + let project = build_args.project()?; + let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; + cache_local_signatures(&outcome, Config::foundry_cache_dir().unwrap())? + } Self::Upload { contract, all, project_paths } => { let build_args = CoreBuildArgs { project_paths: project_paths.clone(), From e5412ad6dc2d7ecdc7541b6c0c8b41df80b511ee Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:15:27 +0100 Subject: [PATCH 009/107] chore: use has_library_ancestor (#9387) --- crates/forge/bin/cmd/geiger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/geiger.rs b/crates/forge/bin/cmd/geiger.rs index 9ffddd3c9..6d4c735a9 100644 --- a/crates/forge/bin/cmd/geiger.rs +++ b/crates/forge/bin/cmd/geiger.rs @@ -54,7 +54,7 @@ impl GeigerArgs { Graph::::resolve(&paths)? .files() .keys() - .filter(|f| !paths.libraries.iter().any(|lib| f.starts_with(lib))) + .filter(|f| !paths.has_library_ancestor(f)) .cloned() .collect() } else { From d14a7b44fc439407d761fccc4c1637216554bbb6 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:10:44 +0100 Subject: [PATCH 010/107] chore(evm/traces): replace solang with Solar (#9386) --- Cargo.lock | 2 +- crates/evm/traces/Cargo.toml | 2 +- crates/evm/traces/src/debug/sources.rs | 78 +++++++++++++------------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15a2386be..0fb6301f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4076,7 +4076,7 @@ dependencies = [ "revm", "revm-inspectors", "serde", - "solang-parser", + "solar-parse", "tempfile", "tokio", "tracing", diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 3423213fb..53bf8b3bb 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -40,7 +40,7 @@ tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true tempfile.workspace = true rayon.workspace = true -solang-parser.workspace = true +solar-parse.workspace = true revm.workspace = true [dev-dependencies] diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index dfbfe91af..ff1911493 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -11,9 +11,13 @@ use foundry_compilers::{ use foundry_evm_core::utils::PcIcMap; use foundry_linking::Linker; use rayon::prelude::*; -use solang_parser::pt::SourceUnitPart; +use solar_parse::{ + interface::{Pos, Session}, + Parser, +}; use std::{ collections::{BTreeMap, HashMap}, + ops::Range, path::{Path, PathBuf}, sync::Arc, }; @@ -25,7 +29,7 @@ pub struct SourceData { pub path: PathBuf, /// Maps contract name to (start, end) of the contract definition in the source code. /// This is useful for determining which contract contains given function definition. - contract_definitions: Vec<(String, usize, usize)>, + contract_definitions: Vec<(String, Range)>, } impl SourceData { @@ -35,26 +39,26 @@ impl SourceData { match language { MultiCompilerLanguage::Vyper(_) => { // Vyper contracts have the same name as the file name. - if let Some(name) = path.file_name().map(|s| s.to_string_lossy().to_string()) { - contract_definitions.push((name, 0, source.len())); + if let Some(name) = path.file_stem().map(|s| s.to_string_lossy().to_string()) { + contract_definitions.push((name, 0..source.len())); } } MultiCompilerLanguage::Solc(_) => { - if let Ok((parsed, _)) = solang_parser::parse(&source, 0) { - for item in parsed.0 { - let SourceUnitPart::ContractDefinition(contract) = item else { - continue; - }; - let Some(name) = contract.name else { - continue; - }; - contract_definitions.push(( - name.name, - name.loc.start(), - contract.loc.end(), - )); + let sess = Session::builder().with_silent_emitter(None).build(); + let _ = sess.enter(|| -> solar_parse::interface::Result<()> { + let arena = solar_parse::ast::Arena::new(); + let filename = path.clone().into(); + let mut parser = + Parser::from_source_code(&sess, &arena, filename, source.to_string())?; + let ast = parser.parse_file().map_err(|e| e.emit())?; + for item in ast.items { + if let solar_parse::ast::ItemKind::Contract(contract) = &item.kind { + let range = item.span.lo().to_usize()..item.span.hi().to_usize(); + contract_definitions.push((contract.name.to_string(), range)); + } } - } + Ok(()) + }); } } @@ -65,8 +69,8 @@ impl SourceData { pub fn find_contract_name(&self, start: usize, end: usize) -> Option<&str> { self.contract_definitions .iter() - .find(|(_, s, e)| start >= *s && end <= *e) - .map(|(name, _, _)| name.as_str()) + .find(|(_, r)| start >= r.start && end <= r.end) + .map(|(name, _)| name.as_str()) } } @@ -182,26 +186,22 @@ impl ContractSources { let mut files: BTreeMap> = BTreeMap::new(); for (build_id, build) in output.builds() { for (source_id, path) in &build.source_id_to_path { - let source_data = if let Some(source_data) = files.get(path) { - source_data.clone() - } else { - let source = Source::read(path).wrap_err_with(|| { - format!("failed to read artifact source file for `{}`", path.display()) - })?; - - let stripped = path.strip_prefix(root).unwrap_or(path).to_path_buf(); - - let source_data = Arc::new(SourceData::new( - source.content.clone(), - build.language.into(), - stripped, - )); - - files.insert(path.clone(), source_data.clone()); - - source_data + let source_data = match files.entry(path.clone()) { + std::collections::btree_map::Entry::Vacant(entry) => { + let source = Source::read(path).wrap_err_with(|| { + format!("failed to read artifact source file for `{}`", path.display()) + })?; + let stripped = path.strip_prefix(root).unwrap_or(path).to_path_buf(); + let source_data = Arc::new(SourceData::new( + source.content.clone(), + build.language.into(), + stripped, + )); + entry.insert(source_data.clone()); + source_data + } + std::collections::btree_map::Entry::Occupied(entry) => entry.get().clone(), }; - self.sources_by_id .entry(build_id.clone()) .or_default() From 4923529c743f25a0f37503a7bcf7c68caa6901f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:51:26 +0000 Subject: [PATCH 011/107] chore(deps): weekly `cargo update` (#9392) Locking 36 packages to latest compatible versions Updating async-compression v0.4.17 -> v0.4.18 Updating bytemuck v1.19.0 -> v1.20.0 Updating const-hex v1.13.1 -> v1.13.2 Adding core-foundation v0.10.0 Updating cpufeatures v0.2.15 -> v0.2.16 Updating h2 v0.4.6 -> v0.4.7 Updating hyper v1.5.0 -> v1.5.1 Updating impl-trait-for-tuples v0.2.2 -> v0.2.3 Updating interprocess v2.2.1 -> v2.2.2 Updating itoa v1.0.11 -> v1.0.13 Updating litemap v0.7.3 -> v0.7.4 Updating op-alloy-consensus v0.6.5 -> v0.6.8 Updating op-alloy-rpc-types v0.6.5 -> v0.6.8 Updating portable-atomic v1.9.0 -> v1.10.0 Updating proc-macro2 v1.0.89 -> v1.0.92 Updating quick-junit v0.5.0 -> v0.5.1 Updating quick-xml v0.36.2 -> v0.37.1 Updating rustix v0.38.40 -> v0.38.41 Updating rustls v0.23.17 -> v0.23.18 Updating rustls-native-certs v0.8.0 -> v0.8.1 Updating scale-info v2.11.5 -> v2.11.6 Updating scale-info-derive v2.11.5 -> v2.11.6 Updating schannel v0.1.26 -> v0.1.27 Adding security-framework v3.0.1 Updating semver-parser v0.10.2 -> v0.10.3 Updating syn v2.0.87 -> v2.0.89 Updating sync_wrapper v1.0.1 -> v1.0.2 Updating unicode-ident v1.0.13 -> v1.0.14 Updating url v2.5.3 -> v2.5.4 Updating wasmtimer v0.4.0 -> v0.4.1 Updating webpki-roots v0.26.6 -> v0.26.7 Updating yoke v0.7.4 -> v0.7.5 Updating yoke-derive v0.7.4 -> v0.7.5 Updating zerofrom v0.1.4 -> v0.1.5 Updating zerofrom-derive v0.1.4 -> v0.1.5 Updating zip v2.2.0 -> v2.2.1 note: pass `--verbose` to see 18 unchanged dependencies behind latest Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> --- Cargo.lock | 365 ++++++++++++++++++++++++++++------------------------- 1 file changed, 192 insertions(+), 173 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fb6301f5..0e6cdd0f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,7 +374,7 @@ checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -624,7 +624,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -641,7 +641,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "syn-solidity", "tiny-keccak", ] @@ -659,7 +659,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.87", + "syn 2.0.89", "syn-solidity", ] @@ -752,7 +752,7 @@ dependencies = [ "alloy-transport", "futures", "http 1.1.0", - "rustls 0.23.17", + "rustls 0.23.18", "serde_json", "tokio", "tokio-tungstenite", @@ -909,7 +909,7 @@ dependencies = [ "foundry-evm", "foundry-test-utils", "futures", - "hyper 1.5.0", + "hyper 1.5.1", "itertools 0.13.0", "k256", "op-alloy-consensus", @@ -1151,9 +1151,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "flate2", "futures-core", @@ -1179,7 +1179,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1201,7 +1201,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1212,7 +1212,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1265,7 +1265,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1607,7 +1607,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "itoa", "matchit", @@ -1621,7 +1621,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tokio-tungstenite", "tower 0.5.1", @@ -1645,7 +1645,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -1799,7 +1799,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1843,9 +1843,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -2181,7 +2181,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2399,9 +2399,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "487981fa1af147182687064d0a2c336586d337a606595ced9ffb0c685c250c73" dependencies = [ "cfg-if", "cpufeatures", @@ -2435,6 +2435,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2443,9 +2453,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -2586,7 +2596,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2597,7 +2607,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2670,7 +2680,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2691,7 +2701,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2701,7 +2711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2722,7 +2732,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "unicode-xid", ] @@ -2836,7 +2846,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2861,7 +2871,7 @@ checksum = "1b4464d46ce68bfc7cb76389248c7c254def7baca8bece0693b02b83842c4c88" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2988,7 +2998,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -3123,7 +3133,7 @@ dependencies = [ "regex", "serde", "serde_json", - "syn 2.0.87", + "syn 2.0.89", "toml 0.8.19", "walkdir", ] @@ -3151,7 +3161,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.87", + "syn 2.0.89", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -3387,7 +3397,7 @@ dependencies = [ "futures", "globset", "humantime-serde", - "hyper 1.5.0", + "hyper 1.5.1", "indicatif", "inferno", "itertools 0.13.0", @@ -3544,7 +3554,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4123,7 +4133,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4282,7 +4292,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4331,7 +4341,7 @@ dependencies = [ "bytes", "chrono", "futures", - "hyper 1.5.0", + "hyper 1.5.1", "jsonwebtoken", "once_cell", "prost", @@ -4666,9 +4676,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -4816,7 +4826,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4935,14 +4945,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -4978,10 +4988,10 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.17", - "rustls-native-certs 0.8.0", + "rustls 0.23.18", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -4995,7 +5005,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", @@ -5010,7 +5020,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -5029,7 +5039,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", @@ -5175,7 +5185,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5269,13 +5279,13 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] @@ -5394,14 +5404,14 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] name = "interprocess" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" +checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb" dependencies = [ "doctest-file", "futures-core", @@ -5464,9 +5474,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "jiff" @@ -5683,9 +5693,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -5871,7 +5881,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5962,7 +5972,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5977,7 +5987,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -6236,7 +6246,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6284,9 +6294,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "op-alloy-consensus" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff54d1d790eca1f3aedbd666162e9c42eceff90b9f9d24b352ed9c2df1e901a" +checksum = "fce158d886815d419222daa67fcdf949a34f7950653a4498ebeb4963331f70ed" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6295,14 +6305,14 @@ dependencies = [ "alloy-serde", "derive_more", "serde", - "spin", + "thiserror 2.0.3", ] [[package]] name = "op-alloy-rpc-types" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981b7f8ab11fe85ba3c1723702f000429b8d0c16b5883c93d577895f262cbac6" +checksum = "060ebeaea8c772e396215f69bb86d231ec8b7f36aca0dd6ce367ceaa9a8c33e6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6376,7 +6386,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6457,7 +6467,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6540,7 +6550,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6599,7 +6609,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6683,7 +6693,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6741,7 +6751,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6774,9 +6784,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -6842,7 +6852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -6920,14 +6930,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -6940,7 +6950,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "version_check", "yansi", ] @@ -7004,7 +7014,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -7027,7 +7037,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -7085,16 +7095,16 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-junit" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ffd2f9a162cfae131bed6d9d1ed60adced33be340a94f96952897d7cb0c240" +checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" dependencies = [ "chrono", "indexmap 2.6.0", "newtype-uuid", - "quick-xml 0.36.2", + "quick-xml 0.37.1", "strip-ansi-escapes", - "thiserror 1.0.69", + "thiserror 2.0.3", "uuid 1.11.0", ] @@ -7118,9 +7128,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" dependencies = [ "memchr", ] @@ -7136,7 +7146,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.17", + "rustls 0.23.18", "socket2", "thiserror 2.0.3", "tokio", @@ -7154,7 +7164,7 @@ dependencies = [ "rand", "ring", "rustc-hash", - "rustls 0.23.17", + "rustls 0.23.18", "rustls-pki-types", "slab", "thiserror 2.0.3", @@ -7374,7 +7384,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", @@ -7388,14 +7398,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.17", - "rustls-native-certs 0.8.0", + "rustls 0.23.18", + "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", "tokio-rustls 0.26.0", @@ -7662,9 +7672,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -7687,9 +7697,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.17" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "log", "once_cell", @@ -7709,20 +7719,19 @@ dependencies = [ "openssl-probe", "rustls-pemfile 1.0.4", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.0.1", ] [[package]] @@ -7849,9 +7858,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", "derive_more", @@ -7861,14 +7870,14 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -7882,9 +7891,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -7910,7 +7919,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8017,7 +8026,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -8053,9 +8075,9 @@ dependencies = [ [[package]] name = "semver-parser" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" dependencies = [ "pest", ] @@ -8083,7 +8105,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8094,7 +8116,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8138,7 +8160,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8184,7 +8206,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8489,7 +8511,7 @@ checksum = "f0cc54b74e214647c1bbfc098d080cc5deac77f8dcb99aca91747276b01a15ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8564,9 +8586,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -8656,7 +8675,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8724,9 +8743,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -8742,7 +8761,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8753,9 +8772,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -8768,7 +8787,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8878,7 +8897,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -8889,7 +8908,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -9024,7 +9043,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -9053,7 +9072,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.17", + "rustls 0.23.18", "rustls-pki-types", "tokio", ] @@ -9090,7 +9109,7 @@ checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", - "rustls 0.23.17", + "rustls 0.23.18", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -9166,17 +9185,17 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.6", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project 1.1.7", "prost", - "rustls-native-certs 0.8.0", + "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "socket2", "tokio", @@ -9299,7 +9318,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -9416,7 +9435,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.23.17", + "rustls 0.23.18", "rustls-pki-types", "sha1", "thiserror 1.0.69", @@ -9482,9 +9501,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -9544,9 +9563,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -9715,7 +9734,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -9749,7 +9768,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9775,9 +9794,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4f099acbc1043cc752b91615b24b02d7f6fcd975bd781fed9f50b3c3e15bf7" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" dependencies = [ "futures", "js-sys", @@ -9872,9 +9891,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -9990,7 +10009,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -10001,7 +10020,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -10012,7 +10031,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -10023,7 +10042,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -10285,9 +10304,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -10297,13 +10316,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "synstructure", ] @@ -10325,27 +10344,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "synstructure", ] @@ -10366,7 +10385,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -10388,14 +10407,14 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] name = "zip" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" dependencies = [ "arbitrary", "bzip2", @@ -10405,7 +10424,7 @@ dependencies = [ "flate2", "indexmap 2.6.0", "memchr", - "thiserror 1.0.69", + "thiserror 2.0.3", "zopfli", ] From cca72aba47a675380a3c87199c7ed0406e3281c2 Mon Sep 17 00:00:00 2001 From: publicqi <56060664+publicqi@users.noreply.github.com> Date: Mon, 25 Nov 2024 03:07:34 -0800 Subject: [PATCH 012/107] fix: bail incomplete bytecode sequence disassemble (#9390) --- crates/cast/src/lib.rs | 21 ++++++++++++++++++++- crates/evm/core/src/ic.rs | 9 +++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 39e821dc9..b5b719e39 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -2017,7 +2017,7 @@ impl SimpleCast { pub fn disassemble(code: &[u8]) -> Result { let mut output = String::new(); - for step in decode_instructions(code) { + for step in decode_instructions(code)? { write!(output, "{:08x}: ", step.pc)?; if let Some(op) = step.op { @@ -2290,4 +2290,23 @@ mod tests { r#"["0x2b5df5f0757397573e8ff34a8b987b21680357de1f6c8d10273aa528a851eaca","0x","0x","0x2838ac1d2d2721ba883169179b48480b2ba4f43d70fcf806956746bd9e83f903","0x","0xe46fff283b0ab96a32a7cc375cecc3ed7b6303a43d64e0a12eceb0bc6bd87549","0x","0x1d818c1c414c665a9c9a0e0c0ef1ef87cacb380b8c1f6223cb2a68a4b2d023f5","0x","0x","0x","0x236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff0","0x","0x","0x","0x","0x"]"# ) } + + #[test] + fn disassemble_incomplete_sequence() { + let incomplete = &hex!("60"); // PUSH1 + let disassembled = Cast::disassemble(incomplete); + assert!(disassembled.is_err()); + + let complete = &hex!("6000"); // PUSH1 0x00 + let disassembled = Cast::disassemble(complete); + assert!(disassembled.is_ok()); + + let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes + let disassembled = Cast::disassemble(incomplete); + assert!(disassembled.is_err()); + + let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes + let disassembled = Cast::disassemble(complete); + assert!(disassembled.is_ok()); + } } diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index 2711f8933..fcabf2a18 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,4 +1,5 @@ use alloy_primitives::map::HashMap; +use eyre::Result; use revm::interpreter::{ opcode::{PUSH0, PUSH1, PUSH32}, OpCode, @@ -100,7 +101,7 @@ pub struct Instruction<'a> { } /// Decodes raw opcode bytes into [`Instruction`]s. -pub fn decode_instructions(code: &[u8]) -> Vec> { +pub fn decode_instructions(code: &[u8]) -> Result>> { let mut pc = 0; let mut steps = Vec::new(); @@ -108,10 +109,14 @@ pub fn decode_instructions(code: &[u8]) -> Vec> { let op = OpCode::new(code[pc]); let immediate_size = op.map(|op| immediate_size(op, &code[pc + 1..])).unwrap_or(0) as usize; + if pc + 1 + immediate_size > code.len() { + eyre::bail!("incomplete sequence of bytecode"); + } + steps.push(Instruction { op, pc, immediate: &code[pc + 1..pc + 1 + immediate_size] }); pc += 1 + immediate_size; } - steps + Ok(steps) } From 66228e443846127499374d997aa5df9c898d4f5d Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:22:15 +0200 Subject: [PATCH 013/107] fix(forge create): install missing deps if any (#9401) --- crates/forge/bin/cmd/create.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 9fc2629e5..71823416d 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -1,3 +1,4 @@ +use crate::cmd::install; use alloy_chains::Chain; use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::{Constructor, JsonAbi}; @@ -98,7 +99,14 @@ pub struct CreateArgs { impl CreateArgs { /// Executes the command to create a contract pub async fn run(mut self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let mut config = self.try_load_config_emit_warnings()?; + + // Install missing dependencies. + if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + // need to re-configure here to also catch additional remappings + config = self.load_config(); + } + // Find Project & Compile let project = config.project()?; From eae5fb489d39b4de0a611778b9ce82233399e73e Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:35:49 +0200 Subject: [PATCH 014/107] feat(forge): show additional details of contract to verify (#9403) --- crates/verify/src/verify.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index 9c32ee95f..5a8efe4c6 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -220,6 +220,17 @@ impl VerifyArgs { let verifier_url = self.verifier.verifier_url.clone(); sh_println!("Start verifying contract `{}` deployed on {chain}", self.address)?; + if let Some(version) = &self.compiler_version { + sh_println!("Compiler version: {version}")?; + } + if let Some(optimizations) = &self.num_of_optimizations { + sh_println!("Optimizations: {optimizations}")? + } + if let Some(args) = &self.constructor_args { + if !args.is_empty() { + sh_println!("Constructor args: {args}")? + } + } self.verifier.verifier.client(&self.etherscan.key())?.verify(self, context).await.map_err(|err| { if let Some(verifier_url) = verifier_url { match Url::parse(&verifier_url) { From de5e89cd117bb30f147c28862c51be6ef239f23f Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:16:57 +0100 Subject: [PATCH 015/107] fix: remove duplicate `gas_limit` / `block_gas_limit` field, declare as alias (#9406) remove duplicate gas_limit field, declare as alias --- crates/common/src/evm.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index 3eca0800e..c4dfccae1 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -196,11 +196,6 @@ impl Provider for EvmArgs { #[derive(Clone, Debug, Default, Serialize, Parser)] #[command(next_help_heading = "Executor environment config")] pub struct EnvArgs { - /// The block gas limit. - #[arg(long, value_name = "GAS_LIMIT")] - #[serde(skip_serializing_if = "Option::is_none")] - pub gas_limit: Option, - /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). #[arg(long, value_name = "CODE_SIZE")] @@ -253,7 +248,7 @@ pub struct EnvArgs { pub block_prevrandao: Option, /// The block gas limit. - #[arg(long, value_name = "GAS_LIMIT")] + #[arg(long, visible_alias = "gas-limit", value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub block_gas_limit: Option, From 672bdf60f01630d849f0bf7ffdb447965a53e4e2 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:33:36 +0200 Subject: [PATCH 016/107] fix(cheatcodes): use calldata in attachDelegation (#9407) --- crates/cheatcodes/assets/cheatcodes.json | 2 +- crates/cheatcodes/spec/src/vm.rs | 2 +- testdata/cheats/Vm.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index d19c776b9..ed842ddd8 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3111,7 +3111,7 @@ "func": { "id": "attachDelegation", "description": "Designate the next call as an EIP-7702 transaction", - "declaration": "function attachDelegation(SignedDelegation memory signedDelegation) external;", + "declaration": "function attachDelegation(SignedDelegation calldata signedDelegation) external;", "visibility": "external", "mutability": "", "signature": "attachDelegation((uint8,bytes32,bytes32,uint64,address))", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index bf0fe9781..e07543825 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2040,7 +2040,7 @@ interface Vm { /// Designate the next call as an EIP-7702 transaction #[cheatcode(group = Scripting)] - function attachDelegation(SignedDelegation memory signedDelegation) external; + function attachDelegation(SignedDelegation calldata signedDelegation) external; /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction #[cheatcode(group = Scripting)] diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 9d1d3efd1..682b58671 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -149,7 +149,7 @@ interface Vm { function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; function assumeNoRevert() external pure; - function attachDelegation(SignedDelegation memory signedDelegation) external; + function attachDelegation(SignedDelegation calldata signedDelegation) external; function blobBaseFee(uint256 newBlobBaseFee) external; function blobhashes(bytes32[] calldata hashes) external; function breakpoint(string calldata char) external pure; From 995fd9ea031d902b6dd550c7d8a1cf15379feb82 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:30:14 +0100 Subject: [PATCH 017/107] chore(cheatcodes): enforce `calldata` in declaration (#9408) --- crates/cheatcodes/assets/cheatcodes.json | 18 +++++++++--------- crates/cheatcodes/spec/src/vm.rs | 18 +++++++++--------- crates/macros/src/cheatcodes.rs | 11 +++++++++++ testdata/cheats/Vm.sol | 18 +++++++++--------- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index ed842ddd8..d8f8d21df 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -4615,7 +4615,7 @@ "func": { "id": "eth_getLogs", "description": "Gets all the logs according to specified filter.", - "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) external returns (EthGetLogs[] memory logs);", + "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs);", "visibility": "external", "mutability": "", "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", @@ -5355,7 +5355,7 @@ "func": { "id": "getBroadcast", "description": "Returns the most recent broadcast for the given contract on `chainId` matching `txType`.\nFor example:\nThe most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`.\nThe most recent call can be fetched by passing `txType` as `CALL`.", - "declaration": "function getBroadcast(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory);", + "declaration": "function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcast(string,uint64,uint8)", @@ -5375,7 +5375,7 @@ "func": { "id": "getBroadcasts_0", "description": "Returns all broadcasts for the given contract on `chainId` with the specified `txType`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", - "declaration": "function getBroadcasts(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory);", + "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcasts(string,uint64,uint8)", @@ -5395,7 +5395,7 @@ "func": { "id": "getBroadcasts_1", "description": "Returns all broadcasts for the given contract on `chainId`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", - "declaration": "function getBroadcasts(string memory contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory);", + "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory);", "visibility": "external", "mutability": "view", "signature": "getBroadcasts(string,uint64)", @@ -5455,7 +5455,7 @@ "func": { "id": "getDeployment_0", "description": "Returns the most recent deployment for the current `chainId`.", - "declaration": "function getDeployment(string memory contractName) external view returns (address deployedAddress);", + "declaration": "function getDeployment(string calldata contractName) external view returns (address deployedAddress);", "visibility": "external", "mutability": "view", "signature": "getDeployment(string)", @@ -5475,7 +5475,7 @@ "func": { "id": "getDeployment_1", "description": "Returns the most recent deployment for the given contract on `chainId`", - "declaration": "function getDeployment(string memory contractName, uint64 chainId) external view returns (address deployedAddress);", + "declaration": "function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress);", "visibility": "external", "mutability": "view", "signature": "getDeployment(string,uint64)", @@ -5495,7 +5495,7 @@ "func": { "id": "getDeployments", "description": "Returns all deployments for the given contract on `chainId`\nSorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber.\nThe most recent deployment is the first element, and the oldest is the last.", - "declaration": "function getDeployments(string memory contractName, uint64 chainId) external view returns (address[] memory deployedAddresses);", + "declaration": "function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses);", "visibility": "external", "mutability": "view", "signature": "getDeployments(string,uint64)", @@ -8621,7 +8621,7 @@ "func": { "id": "serializeJsonType_0", "description": "See `serializeJson`.", - "declaration": "function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json);", + "declaration": "function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json);", "visibility": "external", "mutability": "pure", "signature": "serializeJsonType(string,bytes)", @@ -8641,7 +8641,7 @@ "func": { "id": "serializeJsonType_1", "description": "See `serializeJson`.", - "declaration": "function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json);", + "declaration": "function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json);", "visibility": "external", "mutability": "", "signature": "serializeJsonType(string,string,string,bytes)", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index e07543825..6b66d31dd 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -808,7 +808,7 @@ interface Vm { /// Gets all the logs according to specified filter. #[cheatcode(group = Evm, safety = Safe)] - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); @@ -1764,27 +1764,27 @@ interface Vm { /// /// The most recent call can be fetched by passing `txType` as `CALL`. #[cheatcode(group = Filesystem)] - function getBroadcast(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); + function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); /// Returns all broadcasts for the given contract on `chainId` with the specified `txType`. /// /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. #[cheatcode(group = Filesystem)] - function getBroadcasts(string memory contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); + function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); /// Returns all broadcasts for the given contract on `chainId`. /// /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. #[cheatcode(group = Filesystem)] - function getBroadcasts(string memory contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); /// Returns the most recent deployment for the current `chainId`. #[cheatcode(group = Filesystem)] - function getDeployment(string memory contractName) external view returns (address deployedAddress); + function getDeployment(string calldata contractName) external view returns (address deployedAddress); /// Returns the most recent deployment for the given contract on `chainId` #[cheatcode(group = Filesystem)] - function getDeployment(string memory contractName, uint64 chainId) external view returns (address deployedAddress); + function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); /// Returns all deployments for the given contract on `chainId` /// @@ -1792,7 +1792,7 @@ interface Vm { /// /// The most recent deployment is the first element, and the oldest is the last. #[cheatcode(group = Filesystem)] - function getDeployments(string memory contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); // -------- Foreign Function Interface -------- @@ -2298,13 +2298,13 @@ interface Vm { returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] - function serializeJsonType(string calldata typeDescription, bytes memory value) + function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] - function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json); diff --git a/crates/macros/src/cheatcodes.rs b/crates/macros/src/cheatcodes.rs index d9c2d2c91..4d0f260c2 100644 --- a/crates/macros/src/cheatcodes.rs +++ b/crates/macros/src/cheatcodes.rs @@ -58,6 +58,17 @@ fn derive_call(name: &Ident, data: &DataStruct, attrs: &[Attribute]) -> Result Date: Tue, 26 Nov 2024 14:04:37 +0100 Subject: [PATCH 018/107] feat: remove ethers (#8826) Co-authored-by: grandizzy --- Cargo.lock | 216 ----------------------------------- Cargo.toml | 3 - README.md | 5 +- crates/forge/Cargo.toml | 2 - crates/forge/bin/cmd/bind.rs | 152 ++---------------------- 5 files changed, 14 insertions(+), 364 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e6cdd0f9..2aa261d17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1908,38 +1908,6 @@ dependencies = [ "serde", ] -[[package]] -name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.23", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "cassowary" version = "0.3.0" @@ -3068,106 +3036,6 @@ dependencies = [ "uuid 0.8.2", ] -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror 1.0.69", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" -dependencies = [ - "Inflector", - "const-hex", - "dunce", - "ethers-core", - "eyre", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "serde", - "serde_json", - "syn 2.0.89", - "toml 0.8.19", - "walkdir", -] - -[[package]] -name = "ethers-core" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" -dependencies = [ - "arrayvec", - "bytes", - "cargo_metadata", - "chrono", - "const-hex", - "elliptic-curve", - "ethabi", - "generic-array", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "rand", - "rlp", - "serde", - "serde_json", - "strum", - "syn 2.0.89", - "tempfile", - "thiserror 1.0.69", - "tiny-keccak", - "unicode-xid", -] - [[package]] name = "event-listener" version = "4.0.3" @@ -3375,7 +3243,6 @@ dependencies = [ "comfy-table", "dialoguer", "dunce", - "ethers-contract-abigen", "evm-disassembler", "eyre", "forge-doc", @@ -5259,24 +5126,6 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.3" @@ -6243,7 +6092,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.89", @@ -6326,31 +6174,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "opener" version = "0.7.2" @@ -6872,9 +6695,6 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", "uint", ] @@ -7554,21 +7374,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", - "rlp-derive", "rustc-hex", ] -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "rpassword" version = "7.3.1" @@ -7856,30 +7664,6 @@ dependencies = [ "regex", ] -[[package]] -name = "scale-info" -version = "2.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" -dependencies = [ - "cfg-if", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.89", -] - [[package]] name = "scc" version = "2.2.5" diff --git a/Cargo.toml b/Cargo.toml index 814c43aa5..42b033f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,9 +180,6 @@ revm = { version = "18.0.0", default-features = false } revm-primitives = { version = "14.0.0", default-features = false } revm-inspectors = { version = "0.11.0", features = ["serde"] } -## ethers -ethers-contract-abigen = { version = "2.0.14", default-features = false } - ## alloy alloy-consensus = { version = "0.6.4", default-features = false } alloy-contract = { version = "0.6.4", default-features = false } diff --git a/README.md b/README.md index ec0884aa2..bd9325191 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ If you're experiencing any issues while installing, check out [Getting Help](#ge ### How Fast? -Forge is quite fast at both compiling (leveraging [ethers-solc]) and testing. +Forge is quite fast at both compiling (leveraging [foundry-compilers]) and testing. See the benchmarks below. More benchmarks can be found in the [v0.2.0 announcement post][benchmark-post] and in the [Convex Shutdown Simulation][convex] repository. @@ -127,7 +127,7 @@ If you want to contribute, or follow along with contributor discussion, you can ## Acknowledgements - Foundry is a clean-room rewrite of the testing framework [DappTools](https://github.com/dapphub/dapptools). None of this would have been possible without the DappHub team's work over the years. -- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. +- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] (now [foundry-compilers]) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. - [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. - [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. - All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs) & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. @@ -135,6 +135,7 @@ If you want to contribute, or follow along with contributor discussion, you can [foundry-book]: https://book.getfoundry.sh [foundry-gha]: https://github.com/foundry-rs/foundry-toolchain [ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ +[foundry-compilers]: https://github.com/foundry-rs/foundry-compilers [solmate]: https://github.com/transmissions11/solmate/ [geb]: https://github.com/reflexer-labs/geb [vaults]: https://github.com/rari-capital/vaults diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 208ea8430..b01f2d3e1 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -35,8 +35,6 @@ foundry-wallets.workspace = true foundry-linking.workspace = true forge-script-sequence.workspace = true -ethers-contract-abigen = { workspace = true, features = ["providers"] } - revm-inspectors.workspace = true comfy-table.workspace = true diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index ec6b13dfd..d541a530d 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -1,9 +1,6 @@ use alloy_primitives::map::HashSet; use clap::{Parser, ValueHint}; -use ethers_contract_abigen::{ - Abigen, ContractFilter, ExcludeContracts, MultiAbigen, SelectContracts, -}; -use eyre::{Result, WrapErr}; +use eyre::Result; use forge_sol_macro_gen::{MultiSolMacroGen, SolMacroGen}; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; use foundry_common::{compile::ProjectCompiler, fs::json_files}; @@ -83,15 +80,15 @@ pub struct BindArgs { skip_extra_derives: bool, /// Generate bindings for the `alloy` library, instead of `ethers`. - #[arg(long, conflicts_with = "ethers")] + #[arg(long, hide = true)] alloy: bool, /// Specify the alloy version. - #[arg(long, value_name = "ALLOY_VERSION")] + #[arg(long)] alloy_version: Option, - /// Generate bindings for the `ethers` library, instead of `alloy` (default, deprecated). - #[arg(long)] + /// Generate bindings for the `ethers` library, instead of `alloy` (removed). + #[arg(long, hide = true)] ethers: bool, #[command(flatten)] @@ -100,17 +97,15 @@ pub struct BindArgs { impl BindArgs { pub fn run(self) -> Result<()> { + if self.ethers { + eyre::bail!("`--ethers` bindings have been removed. Use `--alloy` (default) instead."); + } + if !self.skip_build { let project = self.build_args.project()?; let _ = ProjectCompiler::new().compile(&project)?; } - if self.ethers { - sh_warn!( - "`--ethers` bindings are deprecated and will be removed in the future. Consider using `--alloy` (default) instead." - )?; - } - let config = self.try_load_config_emit_warnings()?; let artifacts = config.out; let bindings_root = self.bindings.clone().unwrap_or_else(|| artifacts.join("bindings")); @@ -131,40 +126,7 @@ impl BindArgs { Ok(()) } - /// Returns the filter to use for `MultiAbigen` - fn get_filter(&self) -> Result { - if self.select_all { - return Ok(ContractFilter::All) - } - if !self.select.is_empty() { - return Ok(SelectContracts::default().extend_regex(self.select.clone()).into()) - } - if let Some(skip) = self.build_args.skip.as_ref().filter(|s| !s.is_empty()) { - return Ok(ExcludeContracts::default() - .extend_regex( - skip.clone() - .into_iter() - .map(|s| Regex::new(s.file_pattern())) - .collect::, _>>()?, - ) - .into()) - } - // This excludes all Test/Script and forge-std contracts - Ok(ExcludeContracts::default() - .extend_pattern([ - ".*Test.*", - ".*Script", - "console[2]?", - "CommonBase", - "Components", - "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Toml|Storage(Safe)?)", - "[Vv]m.*", - ]) - .extend_names(["IMulticall3"]) - .into()) - } - - fn get_alloy_filter(&self) -> Result { + fn get_filter(&self) -> Result { if self.select_all { // Select all json files return Ok(Filter::All); @@ -190,8 +152,6 @@ impl BindArgs { /// Returns an iterator over the JSON files and the contract name in the `artifacts` directory. fn get_json_files(&self, artifacts: &Path) -> Result> { let filter = self.get_filter()?; - let alloy_filter = self.get_alloy_filter()?; - let is_alloy = !self.ethers; Ok(json_files(artifacts) .filter_map(|path| { // Ignore the build info JSON. @@ -212,35 +172,7 @@ impl BindArgs { Some((name, path)) }) - .filter( - move |(name, _path)| { - if is_alloy { - alloy_filter.is_match(name) - } else { - filter.is_match(name) - } - }, - )) - } - - /// Instantiate the multi-abigen - fn get_multi(&self, artifacts: &Path) -> Result { - let abigens = self - .get_json_files(artifacts)? - .map(|(name, path)| { - trace!(?path, "parsing Abigen from file"); - let abi = Abigen::new(name, path.to_str().unwrap()) - .wrap_err_with(|| format!("failed to parse Abigen from file: {path:?}")); - if !self.skip_extra_derives { - abi?.add_derive("serde::Serialize")?.add_derive("serde::Deserialize") - } else { - abi - } - }) - .collect::, _>>()?; - let multi = MultiAbigen::from_abigens(abigens); - eyre::ensure!(!multi.is_empty(), "No contract artifacts found"); - Ok(multi) + .filter(move |(name, _path)| filter.is_match(name))) } fn get_solmacrogen(&self, artifacts: &Path) -> Result { @@ -264,40 +196,6 @@ impl BindArgs { /// Check that the existing bindings match the expected abigen output fn check_existing_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { - if self.ethers { - return self.check_ethers(artifacts, bindings_root); - } - - self.check_alloy(artifacts, bindings_root) - } - - fn check_ethers(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { - let bindings = self.get_multi(artifacts)?.build()?; - sh_println!("Checking bindings for {} contracts.", bindings.len())?; - if !self.module { - bindings - .ensure_consistent_crate( - &self.crate_name, - &self.crate_version, - bindings_root, - self.single_file, - !self.skip_cargo_toml, - ) - .map_err(|err| { - if !self.skip_cargo_toml && err.to_string().contains("Cargo.toml") { - err.wrap_err("To skip Cargo.toml consistency check, pass --skip-cargo-toml") - } else { - err - } - })?; - } else { - bindings.ensure_consistent_module(bindings_root, self.single_file)?; - } - sh_println!("OK.")?; - Ok(()) - } - - fn check_alloy(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { let mut bindings = self.get_solmacrogen(artifacts)?; bindings.generate_bindings()?; sh_println!("Checking bindings for {} contracts", bindings.instances.len())?; @@ -316,34 +214,6 @@ impl BindArgs { /// Generate the bindings fn generate_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { - if self.ethers { - return self.generate_ethers(artifacts, bindings_root); - } - - self.generate_alloy(artifacts, bindings_root) - } - - fn generate_ethers(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { - let mut bindings = self.get_multi(artifacts)?.build()?; - sh_println!("Generating bindings for {} contracts", bindings.len())?; - if !self.module { - trace!(single_file = self.single_file, "generating crate"); - if !self.skip_extra_derives { - bindings = bindings.dependencies([r#"serde = "1""#]) - } - bindings.write_to_crate( - &self.crate_name, - &self.crate_version, - bindings_root, - self.single_file, - ) - } else { - trace!(single_file = self.single_file, "generating module"); - bindings.write_to_module(bindings_root, self.single_file) - } - } - - fn generate_alloy(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { let mut solmacrogen = self.get_solmacrogen(artifacts)?; sh_println!("Generating bindings for {} contracts", solmacrogen.instances.len())?; From 958c713e2fd343c0e84d3f7adda6b8ef9aa42eeb Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:37:29 +0200 Subject: [PATCH 019/107] Revert "feat: remove ethers" (#9411) Revert "feat: remove ethers (#8826)" This reverts commit d7397043e17e8d88a0c21cffa9d300377aed27c5. --- Cargo.lock | 216 +++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + README.md | 5 +- crates/forge/Cargo.toml | 2 + crates/forge/bin/cmd/bind.rs | 152 ++++++++++++++++++++++-- 5 files changed, 364 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2aa261d17..0e6cdd0f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1908,6 +1908,38 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -3036,6 +3068,106 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror 1.0.69", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers-contract-abigen" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" +dependencies = [ + "Inflector", + "const-hex", + "dunce", + "ethers-core", + "eyre", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "serde", + "serde_json", + "syn 2.0.89", + "toml 0.8.19", + "walkdir", +] + +[[package]] +name = "ethers-core" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" +dependencies = [ + "arrayvec", + "bytes", + "cargo_metadata", + "chrono", + "const-hex", + "elliptic-curve", + "ethabi", + "generic-array", + "k256", + "num_enum", + "once_cell", + "open-fastrlp", + "rand", + "rlp", + "serde", + "serde_json", + "strum", + "syn 2.0.89", + "tempfile", + "thiserror 1.0.69", + "tiny-keccak", + "unicode-xid", +] + [[package]] name = "event-listener" version = "4.0.3" @@ -3243,6 +3375,7 @@ dependencies = [ "comfy-table", "dialoguer", "dunce", + "ethers-contract-abigen", "evm-disassembler", "eyre", "forge-doc", @@ -5126,6 +5259,24 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.3" @@ -6092,6 +6243,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.89", @@ -6174,6 +6326,31 @@ dependencies = [ "serde_json", ] +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "opener" version = "0.7.2" @@ -6695,6 +6872,9 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", "uint", ] @@ -7374,9 +7554,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", + "rlp-derive", "rustc-hex", ] +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rpassword" version = "7.3.1" @@ -7664,6 +7856,30 @@ dependencies = [ "regex", ] +[[package]] +name = "scale-info" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "scc" version = "2.2.5" diff --git a/Cargo.toml b/Cargo.toml index 42b033f26..814c43aa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,6 +180,9 @@ revm = { version = "18.0.0", default-features = false } revm-primitives = { version = "14.0.0", default-features = false } revm-inspectors = { version = "0.11.0", features = ["serde"] } +## ethers +ethers-contract-abigen = { version = "2.0.14", default-features = false } + ## alloy alloy-consensus = { version = "0.6.4", default-features = false } alloy-contract = { version = "0.6.4", default-features = false } diff --git a/README.md b/README.md index bd9325191..ec0884aa2 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ If you're experiencing any issues while installing, check out [Getting Help](#ge ### How Fast? -Forge is quite fast at both compiling (leveraging [foundry-compilers]) and testing. +Forge is quite fast at both compiling (leveraging [ethers-solc]) and testing. See the benchmarks below. More benchmarks can be found in the [v0.2.0 announcement post][benchmark-post] and in the [Convex Shutdown Simulation][convex] repository. @@ -127,7 +127,7 @@ If you want to contribute, or follow along with contributor discussion, you can ## Acknowledgements - Foundry is a clean-room rewrite of the testing framework [DappTools](https://github.com/dapphub/dapptools). None of this would have been possible without the DappHub team's work over the years. -- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] (now [foundry-compilers]) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. +- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. - [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. - [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. - All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs) & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. @@ -135,7 +135,6 @@ If you want to contribute, or follow along with contributor discussion, you can [foundry-book]: https://book.getfoundry.sh [foundry-gha]: https://github.com/foundry-rs/foundry-toolchain [ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ -[foundry-compilers]: https://github.com/foundry-rs/foundry-compilers [solmate]: https://github.com/transmissions11/solmate/ [geb]: https://github.com/reflexer-labs/geb [vaults]: https://github.com/rari-capital/vaults diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index b01f2d3e1..208ea8430 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -35,6 +35,8 @@ foundry-wallets.workspace = true foundry-linking.workspace = true forge-script-sequence.workspace = true +ethers-contract-abigen = { workspace = true, features = ["providers"] } + revm-inspectors.workspace = true comfy-table.workspace = true diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index d541a530d..ec6b13dfd 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -1,6 +1,9 @@ use alloy_primitives::map::HashSet; use clap::{Parser, ValueHint}; -use eyre::Result; +use ethers_contract_abigen::{ + Abigen, ContractFilter, ExcludeContracts, MultiAbigen, SelectContracts, +}; +use eyre::{Result, WrapErr}; use forge_sol_macro_gen::{MultiSolMacroGen, SolMacroGen}; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; use foundry_common::{compile::ProjectCompiler, fs::json_files}; @@ -80,15 +83,15 @@ pub struct BindArgs { skip_extra_derives: bool, /// Generate bindings for the `alloy` library, instead of `ethers`. - #[arg(long, hide = true)] + #[arg(long, conflicts_with = "ethers")] alloy: bool, /// Specify the alloy version. - #[arg(long)] + #[arg(long, value_name = "ALLOY_VERSION")] alloy_version: Option, - /// Generate bindings for the `ethers` library, instead of `alloy` (removed). - #[arg(long, hide = true)] + /// Generate bindings for the `ethers` library, instead of `alloy` (default, deprecated). + #[arg(long)] ethers: bool, #[command(flatten)] @@ -97,15 +100,17 @@ pub struct BindArgs { impl BindArgs { pub fn run(self) -> Result<()> { - if self.ethers { - eyre::bail!("`--ethers` bindings have been removed. Use `--alloy` (default) instead."); - } - if !self.skip_build { let project = self.build_args.project()?; let _ = ProjectCompiler::new().compile(&project)?; } + if self.ethers { + sh_warn!( + "`--ethers` bindings are deprecated and will be removed in the future. Consider using `--alloy` (default) instead." + )?; + } + let config = self.try_load_config_emit_warnings()?; let artifacts = config.out; let bindings_root = self.bindings.clone().unwrap_or_else(|| artifacts.join("bindings")); @@ -126,7 +131,40 @@ impl BindArgs { Ok(()) } - fn get_filter(&self) -> Result { + /// Returns the filter to use for `MultiAbigen` + fn get_filter(&self) -> Result { + if self.select_all { + return Ok(ContractFilter::All) + } + if !self.select.is_empty() { + return Ok(SelectContracts::default().extend_regex(self.select.clone()).into()) + } + if let Some(skip) = self.build_args.skip.as_ref().filter(|s| !s.is_empty()) { + return Ok(ExcludeContracts::default() + .extend_regex( + skip.clone() + .into_iter() + .map(|s| Regex::new(s.file_pattern())) + .collect::, _>>()?, + ) + .into()) + } + // This excludes all Test/Script and forge-std contracts + Ok(ExcludeContracts::default() + .extend_pattern([ + ".*Test.*", + ".*Script", + "console[2]?", + "CommonBase", + "Components", + "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Toml|Storage(Safe)?)", + "[Vv]m.*", + ]) + .extend_names(["IMulticall3"]) + .into()) + } + + fn get_alloy_filter(&self) -> Result { if self.select_all { // Select all json files return Ok(Filter::All); @@ -152,6 +190,8 @@ impl BindArgs { /// Returns an iterator over the JSON files and the contract name in the `artifacts` directory. fn get_json_files(&self, artifacts: &Path) -> Result> { let filter = self.get_filter()?; + let alloy_filter = self.get_alloy_filter()?; + let is_alloy = !self.ethers; Ok(json_files(artifacts) .filter_map(|path| { // Ignore the build info JSON. @@ -172,7 +212,35 @@ impl BindArgs { Some((name, path)) }) - .filter(move |(name, _path)| filter.is_match(name))) + .filter( + move |(name, _path)| { + if is_alloy { + alloy_filter.is_match(name) + } else { + filter.is_match(name) + } + }, + )) + } + + /// Instantiate the multi-abigen + fn get_multi(&self, artifacts: &Path) -> Result { + let abigens = self + .get_json_files(artifacts)? + .map(|(name, path)| { + trace!(?path, "parsing Abigen from file"); + let abi = Abigen::new(name, path.to_str().unwrap()) + .wrap_err_with(|| format!("failed to parse Abigen from file: {path:?}")); + if !self.skip_extra_derives { + abi?.add_derive("serde::Serialize")?.add_derive("serde::Deserialize") + } else { + abi + } + }) + .collect::, _>>()?; + let multi = MultiAbigen::from_abigens(abigens); + eyre::ensure!(!multi.is_empty(), "No contract artifacts found"); + Ok(multi) } fn get_solmacrogen(&self, artifacts: &Path) -> Result { @@ -196,6 +264,40 @@ impl BindArgs { /// Check that the existing bindings match the expected abigen output fn check_existing_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + if self.ethers { + return self.check_ethers(artifacts, bindings_root); + } + + self.check_alloy(artifacts, bindings_root) + } + + fn check_ethers(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let bindings = self.get_multi(artifacts)?.build()?; + sh_println!("Checking bindings for {} contracts.", bindings.len())?; + if !self.module { + bindings + .ensure_consistent_crate( + &self.crate_name, + &self.crate_version, + bindings_root, + self.single_file, + !self.skip_cargo_toml, + ) + .map_err(|err| { + if !self.skip_cargo_toml && err.to_string().contains("Cargo.toml") { + err.wrap_err("To skip Cargo.toml consistency check, pass --skip-cargo-toml") + } else { + err + } + })?; + } else { + bindings.ensure_consistent_module(bindings_root, self.single_file)?; + } + sh_println!("OK.")?; + Ok(()) + } + + fn check_alloy(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { let mut bindings = self.get_solmacrogen(artifacts)?; bindings.generate_bindings()?; sh_println!("Checking bindings for {} contracts", bindings.instances.len())?; @@ -214,6 +316,34 @@ impl BindArgs { /// Generate the bindings fn generate_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + if self.ethers { + return self.generate_ethers(artifacts, bindings_root); + } + + self.generate_alloy(artifacts, bindings_root) + } + + fn generate_ethers(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let mut bindings = self.get_multi(artifacts)?.build()?; + sh_println!("Generating bindings for {} contracts", bindings.len())?; + if !self.module { + trace!(single_file = self.single_file, "generating crate"); + if !self.skip_extra_derives { + bindings = bindings.dependencies([r#"serde = "1""#]) + } + bindings.write_to_crate( + &self.crate_name, + &self.crate_version, + bindings_root, + self.single_file, + ) + } else { + trace!(single_file = self.single_file, "generating module"); + bindings.write_to_module(bindings_root, self.single_file) + } + } + + fn generate_alloy(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { let mut solmacrogen = self.get_solmacrogen(artifacts)?; sh_println!("Generating bindings for {} contracts", solmacrogen.instances.len())?; From 0045384f1087897b2665506e95808f022776a5a7 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:32:35 +0100 Subject: [PATCH 020/107] fix: forge script should adhere to `--json` flag (#9404) * adhere to --quiet flag * revert case-specific handling of writing to progress, redundant * handle writing to multiprogress, previously panic * make verification process compatible with --json flag * revert verifaction --json flow, too messy * clean up * revert * handle json correctly for script deployment logs, incl. receipts * avoid incompatible lines with json output * revert unnecessary change * add json and quiet test * address feedback * fix incorrect ordering --- crates/forge/tests/cli/script.rs | 78 +++++++++++++++++++++ crates/script-sequence/src/sequence.rs | 17 ++++- crates/script/src/broadcast.rs | 8 ++- crates/script/src/lib.rs | 9 ++- crates/script/src/multi_sequence.rs | 17 ++++- crates/script/src/progress.rs | 95 +++++++++++++++----------- crates/script/src/receipts.rs | 76 +++++++++++++-------- crates/script/src/simulate.rs | 58 +++++++++------- crates/verify/src/etherscan/mod.rs | 1 - 9 files changed, 257 insertions(+), 102 deletions(-) diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index df2a59bdc..d7776eee2 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -6,6 +6,7 @@ use anvil::{spawn, NodeConfig}; use forge_script_sequence::ScriptSequence; use foundry_test_utils::{ rpc, + snapbox::IntoData, util::{OTHER_SOLC_VERSION, SOLC_VERSION}, ScriptOutcome, ScriptTester, }; @@ -1821,6 +1822,83 @@ Warning: Script contains a transaction to 0x000000000000000000000000000000000000 "#]]); }); +// Asserts that the script runs with expected non-output using `--quiet` flag +forgetest_async!(adheres_to_quiet_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--broadcast", + "--unlocked", + "--non-interactive", + "--quiet", + ]) + .assert_empty_stdout(); +}); + +// Asserts that the script runs with expected non-output using `--quiet` flag +forgetest_async!(adheres_to_json_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--broadcast", + "--unlocked", + "--non-interactive", + "--json", + ]) + .assert_success() + .stdout_eq(str![[r#" +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"0x6080604052600c805462ff00ff191662010001179055348015601f575f5ffd5b506101568061002d5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063c040622614610038578063f8ccbf4714610054575b5f5ffd5b610040610067565b604051901515815260200160405180910390f35b600c546100409062010000900460ff1681565b5f7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d5f1c6001600160a01b0316637fb5297f6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156100c2575f5ffd5b505af11580156100d4573d5f5f3e3d5ffd5b50506040515f925090508181818181805af19150503d805f8114610113576040519150601f19603f3d011682016040523d82523d5f602084013e610118565b606091505b50909291505056fea264697066735822122060ba6332e526de9b6bc731fb4682b44e42845196324ec33068982984d700cdd964736f6c634300081b0033","output":"0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063c040622614610038578063f8ccbf4714610054575b5f5ffd5b610040610067565b604051901515815260200160405180910390f35b600c546100409062010000900460ff1681565b5f7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d5f1c6001600160a01b0316637fb5297f6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156100c2575f5ffd5b505af11580156100d4573d5f5f3e3d5ffd5b50506040515f925090508181818181805af19150503d805f8114610113576040519150601f19603f3d011682016040523d82523d5f602084013e610118565b606091505b50909291505056fea264697066735822122060ba6332e526de9b6bc731fb4682b44e42845196324ec33068982984d700cdd964736f6c634300081b0033","gas_used":90639,"gas_limit":1073682810,"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":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":3214,"gas_limit":1073720760,"status":"Return","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":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":0,"gas_limit":1056940983,"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":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1056940820,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":24278,"labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"chain":31337,"estimated_gas_price":"2.000000001","estimated_total_gas_used":29005,"estimated_amount_required":"0.000058010000029005"} +{"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":21000,"gas_price":1000000001} +{"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} + +"#]].is_jsonlines()); +}); + // https://github.com/foundry-rs/foundry/pull/7742 forgetest_async!(unlocked_no_sender, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index e34b6d06a..235f28f2c 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -2,7 +2,7 @@ use crate::transaction::TransactionWithMetadata; use alloy_primitives::{hex, map::HashMap, TxHash}; use alloy_rpc_types::AnyTransactionReceipt; use eyre::{ContextCompat, Result, WrapErr}; -use foundry_common::{fs, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_common::{fs, shell, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; @@ -127,8 +127,19 @@ impl ScriptSequence { } if !silent { - sh_println!("\nTransactions saved to: {}\n", path.display())?; - sh_println!("Sensitive values saved to: {}\n", sensitive_path.display())?; + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ + "status": "success", + "transactions": path.display().to_string(), + "sensitive": sensitive_path.display().to_string(), + }) + )?; + } else { + sh_println!("\nTransactions saved to: {}\n", path.display())?; + sh_println!("Sensitive values saved to: {}\n", sensitive_path.display())?; + } } Ok(()) diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 51e8baf5b..207754e5c 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -21,7 +21,7 @@ use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; use foundry_common::{ provider::{get_http_provider, try_get_http_provider, RetryProvider}, - TransactionMaybeSigned, + shell, TransactionMaybeSigned, }; use foundry_config::Config; use futures::{future::join_all, StreamExt}; @@ -429,8 +429,10 @@ impl BundledState { seq_progress.inner.write().finish(); } - sh_println!("\n\n==========================")?; - sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; + if !shell::is_json() { + sh_println!("\n\n==========================")?; + sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; + } Ok(BroadcastedState { args: self.args, diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index aeea4940a..517eff552 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -284,7 +284,10 @@ impl ScriptArgs { // Check if there are any missing RPCs and exit early to avoid hard error. if pre_simulation.execution_artifacts.rpc_data.missing_rpc { - sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; + if !shell::is_json() { + sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; + } + return Ok(()); } @@ -298,7 +301,9 @@ impl ScriptArgs { // Exit early in case user didn't provide any broadcast/verify related flags. if !bundled.args.should_broadcast() { - sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; + if !shell::is_json() { + sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; + } return Ok(()); } diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs index ec2f03ae9..e0fd4d1bc 100644 --- a/crates/script/src/multi_sequence.rs +++ b/crates/script/src/multi_sequence.rs @@ -2,7 +2,7 @@ use eyre::{ContextCompat, Result, WrapErr}; use forge_script_sequence::{ now, sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR, }; -use foundry_common::fs; +use foundry_common::{fs, shell}; use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; @@ -146,8 +146,19 @@ impl MultiChainSequence { } if !silent { - sh_println!("\nTransactions saved to: {}\n", self.path.display())?; - sh_println!("Sensitive details saved to: {}\n", self.sensitive_path.display())?; + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ + "status": "success", + "transactions": self.path.display().to_string(), + "sensitive": self.sensitive_path.display().to_string(), + }) + )?; + } else { + sh_println!("\nTransactions saved to: {}\n", self.path.display())?; + sh_println!("Sensitive details saved to: {}\n", self.sensitive_path.display())?; + } } Ok(()) diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs index d9fa53bb3..bc23fcf67 100644 --- a/crates/script/src/progress.rs +++ b/crates/script/src/progress.rs @@ -7,7 +7,7 @@ use alloy_primitives::{ use eyre::Result; use forge_script_sequence::ScriptSequence; use foundry_cli::utils::init_progress; -use foundry_common::provider::RetryProvider; +use foundry_common::{provider::RetryProvider, shell}; use futures::StreamExt; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use parking_lot::RwLock; @@ -31,33 +31,42 @@ pub struct SequenceProgressState { impl SequenceProgressState { pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { - let mut template = "{spinner:.green}".to_string(); - write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain)) - .unwrap(); - template.push_str("{msg}"); - - let top_spinner = ProgressBar::new_spinner() - .with_style(ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅")); - let top_spinner = multi.add(top_spinner); - - let txs = multi.insert_after( - &top_spinner, - init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "), - ); - - let receipts = multi.insert_after( - &txs, - init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "), - ); - - top_spinner.enable_steady_tick(Duration::from_millis(100)); - txs.enable_steady_tick(Duration::from_millis(1000)); - receipts.enable_steady_tick(Duration::from_millis(1000)); - - txs.set_position(sequence.receipts.len() as u64); - receipts.set_position(sequence.receipts.len() as u64); - - let mut state = Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi }; + let mut state = if shell::is_quiet() || shell::is_json() { + let top_spinner = ProgressBar::hidden(); + let txs = ProgressBar::hidden(); + let receipts = ProgressBar::hidden(); + + Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi } + } else { + let mut template = "{spinner:.green}".to_string(); + write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain)) + .unwrap(); + template.push_str("{msg}"); + + let top_spinner = ProgressBar::new_spinner().with_style( + ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅"), + ); + let top_spinner = multi.add(top_spinner); + + let txs = multi.insert_after( + &top_spinner, + init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "), + ); + + let receipts = multi.insert_after( + &txs, + init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "), + ); + + top_spinner.enable_steady_tick(Duration::from_millis(100)); + txs.enable_steady_tick(Duration::from_millis(1000)); + receipts.enable_steady_tick(Duration::from_millis(1000)); + + txs.set_position(sequence.receipts.len() as u64); + receipts.set_position(sequence.receipts.len() as u64); + + Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi } + }; for tx_hash in sequence.pending.iter() { state.tx_sent(*tx_hash); @@ -71,16 +80,21 @@ impl SequenceProgressState { pub fn tx_sent(&mut self, tx_hash: B256) { // Avoid showing more than 10 spinners. if self.tx_spinners.len() < 10 { - let spinner = ProgressBar::new_spinner() - .with_style( - ProgressStyle::with_template(" {spinner:.green} {msg}") - .unwrap() - .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), - ) - .with_message(format!("{} {}", "[Pending]".yellow(), tx_hash)); - - let spinner = self.multi.insert_before(&self.txs, spinner); - spinner.enable_steady_tick(Duration::from_millis(100)); + let spinner = if shell::is_quiet() || shell::is_json() { + ProgressBar::hidden() + } else { + let spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template(" {spinner:.green} {msg}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), + ) + .with_message(format!("{} {}", "[Pending]".yellow(), tx_hash)); + + let spinner = self.multi.insert_before(&self.txs, spinner); + spinner.enable_steady_tick(Duration::from_millis(100)); + spinner + }; self.tx_spinners.insert(tx_hash, spinner); } @@ -98,7 +112,10 @@ impl SequenceProgressState { /// Same as finish_tx_spinner but also prints a message to stdout above all other progress bars. pub fn finish_tx_spinner_with_msg(&mut self, tx_hash: B256, msg: &str) -> std::io::Result<()> { self.finish_tx_spinner(tx_hash); - self.multi.println(msg)?; + + if !(shell::is_quiet() || shell::is_json()) { + self.multi.println(msg)?; + } Ok(()) } diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index c11fdd71a..f073e38e6 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -3,7 +3,7 @@ use alloy_primitives::{utils::format_units, TxHash, U256}; use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; use alloy_rpc_types::AnyTransactionReceipt; use eyre::Result; -use foundry_common::provider::RetryProvider; +use foundry_common::{provider::RetryProvider, shell}; use std::time::Duration; /// Convenience enum for internal signalling of transaction status @@ -71,31 +71,51 @@ pub async fn check_tx_status( pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { let gas_used = receipt.gas_used; let gas_price = receipt.effective_gas_price; - format!( - "\n##### {chain}\n{status} Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n\n", - status = if !receipt.inner.inner.inner.receipt.status.coerce_status() { - "❌ [Failed]" - } else { - "✅ [Success]" - }, - tx_hash = receipt.transaction_hash, - caddr = if let Some(addr) = &receipt.contract_address { - format!("\nContract Address: {}", addr.to_checksum(None)) - } else { - String::new() - }, - bn = receipt.block_number.unwrap_or_default(), - gas = if gas_price == 0 { - format!("Gas Used: {gas_used}") - } else { - let paid = format_units(gas_used.saturating_mul(gas_price), 18) - .unwrap_or_else(|_| "N/A".into()); - let gas_price = format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); - format!( - "Paid: {} ETH ({gas_used} gas * {} gwei)", - paid.trim_end_matches('0'), - gas_price.trim_end_matches('0').trim_end_matches('.') - ) - }, - ) + let block_number = receipt.block_number.unwrap_or_default(); + let success = receipt.inner.inner.inner.receipt.status.coerce_status(); + + if shell::is_json() { + let _ = sh_println!( + "{}", + serde_json::json!({ + "chain": chain, + "status": if success { + "success" + } else { + "failed" + }, + "tx_hash": receipt.transaction_hash, + "contract_address": receipt.contract_address.map(|addr| addr.to_string()), + "block_number": block_number, + "gas_used": gas_used, + "gas_price": gas_price, + }) + ); + + String::new() + } else { + format!( + "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n", + status = if success { "✅ [Success]" } else { "❌ [Failed]" }, + tx_hash = receipt.transaction_hash, + contract_address = if let Some(addr) = &receipt.contract_address { + format!("\nContract Address: {}", addr.to_checksum(None)) + } else { + String::new() + }, + gas = if gas_price == 0 { + format!("Gas Used: {gas_used}") + } else { + let paid = format_units(gas_used.saturating_mul(gas_price), 18) + .unwrap_or_else(|_| "N/A".into()); + let gas_price = + format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); + format!( + "Paid: {} ETH ({gas_used} gas * {} gwei)", + paid.trim_end_matches('0'), + gas_price.trim_end_matches('0').trim_end_matches('.') + ) + }, + ) + } } diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 95427225f..e833073ac 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -16,7 +16,7 @@ use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; -use foundry_common::ContractData; +use foundry_common::{shell, ContractData}; use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; @@ -151,7 +151,7 @@ impl PreSimulationState { }) .collect::>(); - if self.script_config.evm_opts.verbosity > 3 { + if !shell::is_json() && self.script_config.evm_opts.verbosity > 3 { sh_println!("==========================")?; sh_println!("Simulated On-chain Traces:\n")?; } @@ -220,9 +220,11 @@ impl PreSimulationState { async fn build_runners(&self) -> Result> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); - let n = rpcs.len(); - let s = if n != 1 { "s" } else { "" }; - sh_println!("\n## Setting up {n} EVM{s}.")?; + if !shell::is_json() { + let n = rpcs.len(); + let s = if n != 1 { "s" } else { "" }; + sh_println!("\n## Setting up {n} EVM{s}.")?; + } let futs = rpcs.into_iter().map(|rpc| async move { let mut script_config = self.script_config.clone(); @@ -348,24 +350,34 @@ impl FilledTransactionsState { provider_info.gas_price()? }; - sh_println!("\n==========================")?; - sh_println!("\nChain {}", provider_info.chain)?; - - sh_println!( - "\nEstimated gas price: {} gwei", - format_units(per_gas, 9) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - .trim_end_matches('.') - )?; - sh_println!("\nEstimated total gas used for script: {total_gas}")?; - sh_println!( - "\nEstimated amount required: {} ETH", - format_units(total_gas.saturating_mul(per_gas), 18) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - )?; - sh_println!("\n==========================")?; + let estimated_gas_price_raw = format_units(per_gas, 9) + .unwrap_or_else(|_| "[Could not calculate]".to_string()); + let estimated_gas_price = + estimated_gas_price_raw.trim_end_matches('0').trim_end_matches('.'); + + let estimated_amount_raw = format_units(total_gas.saturating_mul(per_gas), 18) + .unwrap_or_else(|_| "[Could not calculate]".to_string()); + let estimated_amount = estimated_amount_raw.trim_end_matches('0'); + + if !shell::is_json() { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; + sh_println!("\n==========================")?; + } else { + sh_println!( + "{}", + serde_json::json!({ + "chain": provider_info.chain, + "estimated_gas_price": estimated_gas_price, + "estimated_total_gas_used": total_gas, + "estimated_amount_required": estimated_amount, + }) + )?; + } } } diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index ca90129d5..78f39fe96 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -139,7 +139,6 @@ impl VerificationProvider for EtherscanVerificationProvider { retry: RETRY_CHECK_ON_VERIFY, verifier: args.verifier, }; - // return check_args.run().await return self.check(check_args).await } } else { From 31dd1f77fd9156d09836486d97963cec7f555343 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:33:30 +0200 Subject: [PATCH 021/107] feat(cast): add decode-event sig data (#9413) --- crates/cast/bin/args.rs | 21 +++++++++++++++------ crates/cast/bin/main.rs | 13 +++++++++---- crates/cast/tests/cli/main.rs | 8 ++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index f010c279e..7078810e4 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -512,8 +512,8 @@ pub enum CastSubcommand { /// /// Similar to `abi-decode --input`, but function selector MUST be prefixed in `calldata` /// string - #[command(visible_aliases = &["--calldata-decode", "cdd"])] - CalldataDecode { + #[command(visible_aliases = &["calldata-decode", "--calldata-decode", "cdd"])] + DecodeCalldata { /// The function signature in the format `()()`. sig: String, @@ -524,19 +524,28 @@ pub enum CastSubcommand { /// Decode ABI-encoded string. /// /// Similar to `calldata-decode --input`, but the function argument is a `string` - #[command(visible_aliases = &["--string-decode", "sd"])] - StringDecode { + #[command(visible_aliases = &["string-decode", "--string-decode", "sd"])] + DecodeString { /// The ABI-encoded string. data: String, }, + /// Decode event data. + #[command(visible_aliases = &["event-decode", "--event-decode", "ed"])] + DecodeEvent { + /// The event signature. + sig: String, + /// The event data to decode. + data: String, + }, + /// Decode ABI-encoded input or output data. /// /// Defaults to decoding output data. To decode input data pass --input. /// /// When passing `--input`, function selector must NOT be prefixed in `calldata` string - #[command(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] - AbiDecode { + #[command(name = "decode-abi", visible_aliases = &["abi-decode", "--abi-decode", "ad"])] + DecodeAbi { /// The function signature in the format `()()`. sig: String, diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index ffce79099..21b1df36d 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate tracing; -use alloy_dyn_abi::DynSolValue; +use alloy_dyn_abi::{DynSolValue, EventExt}; use alloy_primitives::{eip191_hash_message, hex, keccak256, Address, B256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; @@ -189,7 +189,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } // ABI encoding & decoding - CastSubcommand::AbiDecode { sig, calldata, input } => { + CastSubcommand::DecodeAbi { sig, calldata, input } => { let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?; print_tokens(&tokens); } @@ -200,17 +200,22 @@ async fn main_args(args: CastArgs) -> Result<()> { sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)? } } - CastSubcommand::CalldataDecode { sig, calldata } => { + CastSubcommand::DecodeCalldata { sig, calldata } => { let tokens = SimpleCast::calldata_decode(&sig, &calldata, true)?; print_tokens(&tokens); } CastSubcommand::CalldataEncode { sig, args } => { sh_println!("{}", SimpleCast::calldata_encode(sig, &args)?)?; } - CastSubcommand::StringDecode { data } => { + CastSubcommand::DecodeString { data } => { let tokens = SimpleCast::calldata_decode("Any(string)", &data, true)?; print_tokens(&tokens); } + CastSubcommand::DecodeEvent { sig, data } => { + let event = get_event(sig.as_str())?; + let decoded_event = event.decode_log_parts(None, &hex::decode(data)?, false)?; + print_tokens(&decoded_event.body); + } CastSubcommand::Interface(cmd) => cmd.run().await?, CastSubcommand::CreationCode(cmd) => cmd.run().await?, CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 332f0f99f..92f23a457 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1474,6 +1474,14 @@ casttest!(string_decode, |_prj, cmd| { "#]]); }); +casttest!(event_decode, |_prj, cmd| { + cmd.args(["decode-event", "MyEvent(uint256,address)", "0x000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000d0004f"]).assert_success().stdout_eq(str![[r#" +78 +0x0000000000000000000000000000000000D0004F + +"#]]); +}); + casttest!(format_units, |_prj, cmd| { cmd.args(["format-units", "1000000", "6"]).assert_success().stdout_eq(str![[r#" 1 From 735b5ebdbbe8fe05c93af930c53e0fba6d3aa4f9 Mon Sep 17 00:00:00 2001 From: wangjingcun Date: Wed, 27 Nov 2024 17:06:47 +0800 Subject: [PATCH 022/107] chore: fix 404 status URL (#9417) Signed-off-by: wangjingcun --- crates/anvil/src/eth/otterscan/api.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 617655444..f9a7334e0 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -46,7 +46,7 @@ pub fn mentions_address(trace: LocalizedTransactionTrace, address: Address) -> O /// Converts the list of traces for a transaction into the expected Otterscan format. /// -/// Follows format specified in the [`ots_traceTransaction`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_tracetransaction) spec. +/// Follows format specified in the [`ots_traceTransaction`](https://github.com/otterscan/otterscan/blob/main/docs/custom-jsonrpc.md#ots_tracetransaction) spec. pub fn batch_build_ots_traces(traces: Vec) -> Vec { traces .into_iter() @@ -350,7 +350,7 @@ impl EthApi { /// their `gas_used`. This would be extremely inefficient in a real blockchain RPC, but we can /// get away with that in this context. /// - /// The [original spec](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails) + /// The [original spec](https://github.com/otterscan/otterscan/blob/main/docs/custom-jsonrpc.md#ots_getblockdetails) /// also mentions we can hardcode `transactions` and `logsBloom` to an empty array to save /// bandwidth, because fields weren't intended to be used in the Otterscan UI at this point. /// @@ -402,7 +402,7 @@ impl EthApi { /// Fetches all receipts for the blocks's transactions, as required by the /// [`ots_getBlockTransactions`] endpoint spec, and returns the final response object. /// - /// [`ots_getBlockTransactions`]: https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails + /// [`ots_getBlockTransactions`]: https://github.com/otterscan/otterscan/blob/main/docs/custom-jsonrpc.md#ots_getblockdetails pub async fn build_ots_block_tx( &self, mut block: AnyRpcBlock, From 2c3114c4d9cbe66a897e634b11b8771a56f91bec Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:06:26 +0100 Subject: [PATCH 023/107] feat: add `--broadcast` flag to forge create, default to dry run mode (#9420) * add --broadcast flag to forge create, default to dry run * nits * fix tests * add dry run tests incl --json * minor fixes, failing test due to minor bytecode difference --- crates/forge/bin/cmd/create.rs | 37 ++++++++- crates/forge/tests/cli/create.rs | 129 +++++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 6 deletions(-) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 71823416d..6c2fbb0cf 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -61,6 +61,10 @@ pub struct CreateArgs { )] constructor_args_path: Option, + /// Broadcast the transaction. + #[arg(long)] + pub broadcast: bool, + /// Verify contract after creation. #[arg(long)] verify: bool, @@ -155,6 +159,10 @@ impl CreateArgs { } else { provider.get_chain_id().await? }; + + // Whether to broadcast the transaction or not + let dry_run = !self.broadcast; + if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); @@ -167,6 +175,7 @@ impl CreateArgs { sender, config.transaction_timeout, id, + dry_run, ) .await } else { @@ -185,6 +194,7 @@ impl CreateArgs { deployer, config.transaction_timeout, id, + dry_run, ) .await } @@ -260,6 +270,7 @@ impl CreateArgs { deployer_address: Address, timeout: u64, id: ArtifactId, + dry_run: bool, ) -> Result<()> { let bin = bin.into_bytes().unwrap_or_else(|| { panic!("no bytecode found in bin object for {}", self.contract.name) @@ -339,6 +350,30 @@ impl CreateArgs { self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; } + if dry_run { + if !shell::is_json() { + sh_warn!("Dry run enabled, not broadcasting transaction\n")?; + + sh_println!("Contract: {}", self.contract.name)?; + sh_println!( + "Transaction: {}", + serde_json::to_string_pretty(&deployer.tx.clone())? + )?; + sh_println!("ABI: {}\n", serde_json::to_string_pretty(&abi)?)?; + + sh_warn!("To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more.")?; + } else { + let output = json!({ + "contract": self.contract.name, + "transaction": &deployer.tx, + "abi":&abi + }); + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; + } + + return Ok(()); + } + // Deploy the actual contract let (deployed_contract, receipt) = deployer.send_with_receipt().await?; @@ -349,7 +384,7 @@ impl CreateArgs { "deployedTo": address.to_string(), "transactionHash": receipt.transaction_hash }); - sh_println!("{output}")?; + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; } else { sh_println!("Deployer: {deployer_address}")?; sh_println!("Deployed to: {address}")?; diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index ebf8c81db..6a78f8323 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -9,7 +9,9 @@ use anvil::{spawn, NodeConfig}; use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; use foundry_config::Config; use foundry_test_utils::{ - forgetest, forgetest_async, str, + forgetest, forgetest_async, + snapbox::IntoData, + str, util::{OutputExt, TestCommand, TestProject}, }; use std::str::FromStr; @@ -145,6 +147,7 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; prj.write_config(config); + // Dry-run without the `--broadcast` flag cmd.forge_fuse().args([ "create", format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), @@ -154,20 +157,131 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { pk.as_str(), ]); + // Dry-run cmd.assert().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -[TX_HASH] +Contract: Counter +Transaction: { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "gas": "0x17575", + "input": "[..]", + "nonce": "0x0", + "chainId": "0x7a69" +} +ABI: [ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setNumber", + "inputs": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] + "#]]); + // Dry-run with `--json` flag + cmd.arg("--json").assert().stdout_eq( + str![[r#" +{ + "contract": "Counter", + "transaction": { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "gas": "0x17575", + "input": "[..]", + "nonce": "0x0", + "chainId": "0x7a69" + }, + "abi": [ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setNumber", + "inputs": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } + ] +} + +"#]] + .is_json(), + ); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--broadcast", + ]); + cmd.assert().stdout_eq(str![[r#" No files changed, compilation skipped Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 [TX_HASH] "#]]); @@ -193,6 +307,7 @@ forgetest_async!(can_create_using_unlocked, |prj, cmd| { "--from", format!("{dev:?}").as_str(), "--unlocked", + "--broadcast", ]); cmd.assert().stdout_eq(str![[r#" @@ -204,6 +319,7 @@ Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 [TX_HASH] "#]]); + cmd.assert().stdout_eq(str![[r#" No files changed, compilation skipped Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 @@ -248,6 +364,7 @@ contract ConstructorContract { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", "--constructor-args", "My Constructor", ]) @@ -285,6 +402,7 @@ contract TupleArrayConstructorContract { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", "--constructor-args", "[(1,2), (2,3), (3,4)]", ]) @@ -335,6 +453,7 @@ contract UniswapV2Swap { rpc.as_str(), "--private-key", pk.as_str(), + "--broadcast", ]) .assert_success() .stdout_eq(str![[r#" From c63aba816b76f9bad103b1275cc662a063919403 Mon Sep 17 00:00:00 2001 From: cl Date: Thu, 28 Nov 2024 03:36:30 +0800 Subject: [PATCH 024/107] feat(`traces`): show state changes in `cast run` and `forge test` on `-vvvvv` (#9013) * Add options for state changes output and json output in cast run command * fix test * add back serde_json in Cargo.lock * format using nightly * rename parameter * update revm-inspectors * supress clippy warning and merge master * add serde_json * disable some stdout print when --json option is used * remove unnecessary check * replace with sh_println * replace with shell::is_json * Show storage for verbosity > 1, add test * Change verbosity to > 4 for both cast and forge test, add test, fix ci --------- Co-authored-by: grandizzy --- Cargo.lock | 1 + crates/cast/bin/cmd/call.rs | 13 ++++-- crates/cast/bin/cmd/run.rs | 7 ++- crates/cast/tests/cli/main.rs | 65 ++++++++++++++++++++++++++- crates/cli/src/opts/global.rs | 2 +- crates/cli/src/utils/cmd.rs | 21 ++++++--- crates/evm/evm/src/executors/trace.rs | 9 ++-- crates/evm/traces/Cargo.toml | 1 + crates/evm/traces/src/lib.rs | 37 ++++++++++++--- crates/forge/bin/cmd/test/mod.rs | 8 ++-- crates/forge/src/multi_runner.rs | 5 ++- crates/forge/tests/cli/cmd.rs | 2 +- crates/forge/tests/cli/test_cmd.rs | 34 ++++++++++++++ crates/verify/src/utils.rs | 1 + 14 files changed, 176 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e6cdd0f9..d94d710ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4086,6 +4086,7 @@ dependencies = [ "revm", "revm-inspectors", "serde", + "serde_json", "solar-parse", "tempfile", "tokio", diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index cdc3bd4bc..2d5692efe 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -8,7 +8,7 @@ use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, handle_traces, parse_ether_value, TraceResult}, }; -use foundry_common::ens::NameOrAddress; +use foundry_common::{ens::NameOrAddress, shell}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ figment::{ @@ -182,8 +182,15 @@ impl CallArgs { env.cfg.disable_block_gas_limit = true; env.block.gas_limit = U256::MAX; - let mut executor = - TracingExecutor::new(env, fork, evm_version, debug, decode_internal, alphanet); + let mut executor = TracingExecutor::new( + env, + fork, + evm_version, + debug, + decode_internal, + shell::verbosity() > 4, + alphanet, + ); let value = tx.value.unwrap_or_default(); let input = tx.inner.input.into_input().unwrap_or_default(); diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 0b85d14fe..cfad7263a 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -10,7 +10,7 @@ use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, utils::{handle_traces, init_progress, TraceResult}, }; -use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; +use foundry_common::{is_known_system_sender, shell, SYSTEM_TRANSACTION_TYPE}; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{ figment::{ @@ -169,6 +169,7 @@ impl RunArgs { evm_version, self.debug, self.decode_internal, + shell::verbosity() > 4, alphanet, ); let mut env = @@ -176,7 +177,9 @@ impl RunArgs { // Set the state to the moment right before the transaction if !self.quick { - sh_println!("Executing previous transactions from the block.")?; + if !shell::is_json() { + sh_println!("Executing previous transactions from the block.")?; + } if let Some(block) = block { let pb = init_progress(block.transactions.len() as u64, "tx"); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 92f23a457..aa70fef92 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -64,7 +64,7 @@ Display options: - 2 (-vv): Print logs for all tests. - 3 (-vvv): Print execution traces for failing tests. - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - - 5 (-vvvvv): Print execution and setup traces for all tests. + - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html @@ -1713,3 +1713,66 @@ Transaction successfully executed. "#]]); }); + +// tests cast can decode traces when running with verbosity level > 4 +forgetest_async!(show_state_changes_in_traces, |prj, cmd| { + let (api, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + // Deploy counter contract. + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "CounterScript", + ]) + .assert_success(); + + // Send tx to change counter storage value. + cmd.cast_fuse() + .args([ + "send", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "setNumber(uint256)", + "111", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success(); + + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + // Assert cast with verbosity displays storage changes. + cmd.cast_fuse() + .args([ + "run", + format!("{tx_hash}").as_str(), + "-vvvvv", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [22287] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) + ├─ storage changes: + │ @ 0: 0 → 111 + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index ad715f241..c820ca2cf 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -15,7 +15,7 @@ pub struct GlobalOpts { /// - 2 (-vv): Print logs for all tests. /// - 3 (-vvv): Print execution traces for failing tests. /// - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - /// - 5 (-vvvvv): Print execution and setup traces for all tests. + /// - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. #[arg(help_heading = "Display options", global = true, short, long, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count)] verbosity: Verbosity, diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 523c10478..505634996 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -17,8 +17,7 @@ use foundry_evm::{ debug::{ContractSources, DebugTraceIdentifier}, decode_trace_arena, identifier::{CachedSignatures, SignaturesIdentifier, TraceIdentifiers}, - render_trace_arena_with_bytecodes, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, - Traces, + render_trace_arena_inner, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, }; use std::{ @@ -450,7 +449,7 @@ pub async fn handle_traces( decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); } - print_traces(&mut result, &decoder, shell::verbosity() > 0).await?; + print_traces(&mut result, &decoder, shell::verbosity() > 0, shell::verbosity() > 4).await?; Ok(()) } @@ -459,23 +458,31 @@ pub async fn print_traces( result: &mut TraceResult, decoder: &CallTraceDecoder, verbose: bool, + state_changes: bool, ) -> Result<()> { let traces = result.traces.as_mut().expect("No traces found"); - sh_println!("Traces:")?; + if !shell::is_json() { + sh_println!("Traces:")?; + } + for (_, arena) in traces { decode_trace_arena(arena, decoder).await?; - sh_println!("{}", render_trace_arena_with_bytecodes(arena, verbose))?; + sh_println!("{}", render_trace_arena_inner(arena, verbose, state_changes))?; + } + + if shell::is_json() { + return Ok(()); } - sh_println!()?; + sh_println!()?; if result.success { sh_println!("{}", "Transaction successfully executed.".green())?; } else { sh_err!("Transaction failed.")?; } - sh_println!("Gas used: {}", result.gas_used)?; + Ok(()) } diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 69c68442b..ceea6e672 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -18,15 +18,18 @@ impl TracingExecutor { version: Option, debug: bool, decode_internal: bool, + with_state_changes: bool, alphanet: bool, ) -> Self { let db = Backend::spawn(fork); - let trace_mode = - TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal { + let trace_mode = TraceMode::Call + .with_debug(debug) + .with_decode_internal(if decode_internal { InternalTraceMode::Full } else { InternalTraceMode::None - }); + }) + .with_state_changes(with_state_changes); Self { // configures a bare version of the evm executor: no cheatcode inspector is enabled, // tracing will be enabled only for the targeted transaction diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 53bf8b3bb..f555d619f 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -36,6 +36,7 @@ eyre.workspace = true futures.workspace = true itertools.workspace = true serde.workspace = true +serde_json.workspace = true tokio = { workspace = true, features = ["time", "macros"] } tracing.workspace = true tempfile.workspace = true diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 18136c481..a0fa7e1fc 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -11,7 +11,10 @@ extern crate foundry_common; #[macro_use] extern crate tracing; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; +use foundry_common::{ + contracts::{ContractsByAddress, ContractsByArtifact}, + shell, +}; use revm::interpreter::OpCode; use revm_inspectors::tracing::{ types::{DecodedTraceStep, TraceMemberOrder}, @@ -183,15 +186,23 @@ pub async fn decode_trace_arena( /// Render a collection of call traces to a string. pub fn render_trace_arena(arena: &SparsedTraceArena) -> String { - render_trace_arena_with_bytecodes(arena, false) + render_trace_arena_inner(arena, false, false) } -/// Render a collection of call traces to a string optionally including contract creation bytecodes. -pub fn render_trace_arena_with_bytecodes( +/// Render a collection of call traces to a string optionally including contract creation bytecodes +/// and in JSON format. +pub fn render_trace_arena_inner( arena: &SparsedTraceArena, with_bytecodes: bool, + with_storage_changes: bool, ) -> String { - let mut w = TraceWriter::new(Vec::::new()).write_bytecodes(with_bytecodes); + if shell::is_json() { + return serde_json::to_string(&arena.resolve_arena()).expect("Failed to write traces"); + } + + let mut w = TraceWriter::new(Vec::::new()) + .write_bytecodes(with_bytecodes) + .with_storage_changes(with_storage_changes); w.write_arena(&arena.resolve_arena()).expect("Failed to write traces"); String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } @@ -289,6 +300,8 @@ pub enum TraceMode { /// /// Used by debugger. Debug, + /// Debug trace with storage changes. + RecordStateDiff, } impl TraceMode { @@ -308,6 +321,10 @@ impl TraceMode { matches!(self, Self::Jump) } + pub const fn record_state_diff(self) -> bool { + matches!(self, Self::RecordStateDiff) + } + pub const fn is_debug(self) -> bool { matches!(self, Self::Debug) } @@ -324,6 +341,14 @@ impl TraceMode { std::cmp::max(self, mode.into()) } + pub fn with_state_changes(self, yes: bool) -> Self { + if yes { + std::cmp::max(self, Self::RecordStateDiff) + } else { + self + } + } + pub fn with_verbosity(self, verbosiy: u8) -> Self { if verbosiy >= 3 { std::cmp::max(self, Self::Call) @@ -345,7 +370,7 @@ impl TraceMode { StackSnapshotType::None }, record_logs: true, - record_state_diff: false, + record_state_diff: self.record_state_diff(), record_returndata_snapshots: self.is_debug(), record_opcodes_filter: (self.is_jump() || self.is_jump_simple()) .then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)), diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 1a409b33a..ad2e52df6 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -12,7 +12,7 @@ use forge::{ debug::{ContractSources, DebugTraceIdentifier}, decode_trace_arena, folded_stack_trace, identifier::SignaturesIdentifier, - render_trace_arena, CallTraceDecoderBuilder, InternalTraceMode, TraceKind, + CallTraceDecoderBuilder, InternalTraceMode, TraceKind, }, MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder, }; @@ -56,7 +56,7 @@ use summary::TestSummaryReporter; use crate::cmd::test::summary::print_invariant_metrics; pub use filter::FilterArgs; -use forge::result::TestKind; +use forge::{result::TestKind, traces::render_trace_arena_inner}; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); @@ -652,7 +652,7 @@ impl TestArgs { // - 0..3: nothing // - 3: only display traces for failed tests // - 4: also display the setup trace for failed tests - // - 5..: display all traces for all tests + // - 5..: display all traces for all tests, including storage changes let should_include = match kind { TraceKind::Execution => { (verbosity == 3 && result.status.is_failure()) || verbosity >= 4 @@ -665,7 +665,7 @@ impl TestArgs { if should_include { decode_trace_arena(arena, &decoder).await?; - decoded_traces.push(render_trace_arena(arena)); + decoded_traces.push(render_trace_arena_inner(arena, false, verbosity > 4)); } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index a9f2a93eb..dfb498c06 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -7,7 +7,7 @@ use crate::{ use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; -use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt}; +use foundry_common::{get_contract_name, shell::verbosity, ContractsByArtifact, TestFunctionExt}; use foundry_compilers::{ artifacts::Libraries, compilers::Compiler, Artifact, ArtifactId, ProjectCompileOutput, }; @@ -249,7 +249,8 @@ impl MultiContractRunner { let trace_mode = TraceMode::default() .with_debug(self.debug) .with_decode_internal(self.decode_internal) - .with_verbosity(self.evm_opts.verbosity); + .with_verbosity(self.evm_opts.verbosity) + .with_state_changes(verbosity() > 4); let executor = ExecutorBuilder::new() .inspectors(|stack| { diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index e0000e01b..35f5c2314 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -67,7 +67,7 @@ Display options: - 2 (-vv): Print logs for all tests. - 3 (-vvv): Print execution traces for failing tests. - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. - - 5 (-vvvvv): Print execution and setup traces for all tests. + - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 8e064c63c..819c3e940 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2665,3 +2665,37 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] "#]]); }); + +// Tests that test traces display state changes when running with verbosity. +forgetest_init!(should_show_state_changes, |prj, cmd| { + cmd.args(["test", "--mt", "test_Increment", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] test_Increment() ([GAS]) +Traces: + [87464] CounterTest::setUp() + ├─ [47297] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 236 bytes of code + ├─ [2387] Counter::setNumber(0) + │ └─ ← [Stop] + └─ ← [Stop] + + [31293] CounterTest::test_Increment() + ├─ [22337] Counter::increment() + │ ├─ storage changes: + │ │ @ 0: 0 → 1 + │ └─ ← [Stop] + ├─ [281] Counter::number() [staticcall] + │ └─ ← [Return] 1 + ├─ [0] VM::assertEq(1, 1) [staticcall] + │ └─ ← [Return] + ├─ storage changes: + │ @ 0: 0 → 1 + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index a14d6af6d..56ec035ab 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -334,6 +334,7 @@ pub async fn get_tracing_executor( Some(fork_config.evm_version), false, false, + false, is_alphanet, ); From 16a013fafb519395dc1aca810dabc3fffb7d02a0 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Thu, 28 Nov 2024 08:35:24 +0200 Subject: [PATCH 025/107] feat(cast): decode external lib sigs from cached selectors (#9399) --- crates/cast/tests/cli/main.rs | 91 ++++++++++++++++++++++++++++ crates/cli/src/utils/cmd.rs | 9 +++ crates/evm/traces/src/decoder/mod.rs | 7 ++- 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index aa70fef92..d2c70a779 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1776,3 +1776,94 @@ Transaction successfully executed. "#]]); }); + +// tests cast can decode external libraries traces with project cached selectors +forgetest_async!(decode_external_libraries_with_cached_selectors, |prj, cmd| { + let (api, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "ExternalLib", + r#" +import "./CounterInExternalLib.sol"; +library ExternalLib { + function updateCounterInExternalLib(CounterInExternalLib.Info storage counterInfo, uint256 counter) public { + counterInfo.counter = counter + 1; + } +} + "#, + ) + .unwrap(); + prj.add_source( + "CounterInExternalLib", + r#" +import "./ExternalLib.sol"; +contract CounterInExternalLib { + struct Info { + uint256 counter; + } + Info info; + constructor() { + ExternalLib.updateCounterInExternalLib(info, 100); + } +} + "#, + ) + .unwrap(); + prj.add_script( + "CounterInExternalLibScript", + r#" +import "forge-std/Script.sol"; +import {CounterInExternalLib} from "../src/CounterInExternalLib.sol"; +contract CounterInExternalLibScript is Script { + function run() public { + vm.startBroadcast(); + new CounterInExternalLib(); + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "CounterInExternalLibScript", + ]) + .assert_success(); + + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + // Cache project selectors. + cmd.forge_fuse().set_current_dir(prj.root()); + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); + + // Assert cast with local artifacts can decode external lib signature. + cmd.cast_fuse().set_current_dir(prj.root()); + cmd.cast_fuse() + .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +... +Traces: + [37739] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ├─ [22411] 0xfAb06527117d29EA121998AC4fAB9Fc88bF5f979::updateCounterInExternalLib(0, 100) [delegatecall] + │ └─ ← [Stop] + └─ ← [Return] 62 bytes of code + + +Transaction successfully executed. +[GAS] + +"#]]); +}); diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 505634996..67aa65073 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -501,6 +501,15 @@ pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf .events .insert(event.selector().to_string(), event.full_signature()); } + // External libraries doesn't have functions included in abi, but `methodIdentifiers`. + if let Some(method_identifiers) = &artifact.method_identifiers { + method_identifiers.iter().for_each(|(signature, selector)| { + cached_signatures + .functions + .entry(format!("0x{selector}")) + .or_insert(signature.to_string()); + }); + } } }); diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index b63c7f1d7..68ecbac29 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -689,10 +689,13 @@ fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec let mut unindexed = 0; let mut inputs = vec![]; for input in event.inputs.iter() { - if input.indexed { + // Prevent panic of event `Transfer(from, to)` decoded with a signature + // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making + // sure the event inputs is not higher than decoded indexed / un-indexed values. + if input.indexed && indexed < decoded.indexed.len() { inputs.push(decoded.indexed[indexed].clone()); indexed += 1; - } else { + } else if unindexed < decoded.body.len() { inputs.push(decoded.body[unindexed].clone()); unindexed += 1; } From 56d0dd8745248e9cd029472eb0a8697d12677246 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:59:27 +0100 Subject: [PATCH 026/107] feat: rewrite inline config using figment (#9414) * feat: rewrite inline config using figment * wip * wip * fix: use same GasLimit type * wip * fixes * tests * test fixes * fmt * test update --- Cargo.lock | 1 + crates/config/Cargo.toml | 5 +- crates/config/src/fuzz.rs | 108 +--------- crates/config/src/inline/conf_parser.rs | 169 --------------- crates/config/src/inline/error.rs | 44 ---- crates/config/src/inline/mod.rs | 185 +++++++++++++---- crates/config/src/inline/natspec.rs | 146 +++++++------ crates/config/src/invariant.rs | 93 +-------- crates/config/src/lib.rs | 194 ++++++++++++++---- crates/config/src/providers/mod.rs | 17 +- crates/config/src/providers/remappings.rs | 2 +- crates/config/src/utils.rs | 78 ------- crates/evm/core/src/opts.rs | 50 +---- crates/forge/bin/cmd/coverage.rs | 6 +- crates/forge/bin/cmd/test/mod.rs | 20 +- crates/forge/src/lib.rs | 239 +++++++--------------- crates/forge/src/runner.rs | 34 +-- crates/forge/tests/cli/alphanet.rs | 15 +- crates/forge/tests/cli/build.rs | 15 +- crates/forge/tests/cli/config.rs | 2 + crates/forge/tests/cli/inline_config.rs | 194 ++++++++++++++++++ crates/forge/tests/cli/main.rs | 1 + crates/forge/tests/it/cheats.rs | 18 +- crates/forge/tests/it/core.rs | 12 +- crates/forge/tests/it/fork.rs | 42 ++-- crates/forge/tests/it/fs.rs | 12 +- crates/forge/tests/it/fuzz.rs | 33 +-- crates/forge/tests/it/inline.rs | 36 +--- crates/forge/tests/it/invariant.rs | 131 +++++++----- crates/forge/tests/it/repros.rs | 38 ++-- crates/forge/tests/it/test_helpers.rs | 190 ++++++++--------- crates/test-utils/src/filter.rs | 1 + 32 files changed, 969 insertions(+), 1162 deletions(-) delete mode 100644 crates/config/src/inline/conf_parser.rs delete mode 100644 crates/config/src/inline/error.rs create mode 100644 crates/forge/tests/cli/inline_config.rs diff --git a/Cargo.lock b/Cargo.lock index d94d710ac..09fab74b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3910,6 +3910,7 @@ dependencies = [ "foundry-compilers", "glob", "globset", + "itertools 0.13.0", "mesc", "number_prefix", "path-slash", diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 3e6815d78..ba01a1afb 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -27,11 +27,12 @@ dirs-next = "2" dunce.workspace = true eyre.workspace = true figment = { workspace = true, features = ["toml", "env"] } -globset = "0.4" glob = "0.3" +globset = "0.4" Inflector = "0.11" -number_prefix = "0.4" +itertools.workspace = true mesc.workspace = true +number_prefix = "0.4" regex.workspace = true reqwest.workspace = true semver = { workspace = true, features = ["serde"] } diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index fb0438322..ae3d3c796 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -1,9 +1,5 @@ //! Configuration for fuzz testing. -use crate::inline::{ - parse_config_bool, parse_config_u32, InlineConfigParser, InlineConfigParserError, - INLINE_CONFIG_FUZZ_KEY, -}; use alloy_primitives::U256; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -53,50 +49,13 @@ impl FuzzConfig { /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. pub fn new(cache_dir: PathBuf) -> Self { Self { - runs: 256, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig::default(), - gas_report_samples: 256, failure_persist_dir: Some(cache_dir), failure_persist_file: Some("failures".to_string()), - show_logs: false, + ..Default::default() } } } -impl InlineConfigParser for FuzzConfig { - fn config_key() -> String { - INLINE_CONFIG_FUZZ_KEY.into() - } - - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { - let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); - - if overrides.is_empty() { - return Ok(None) - } - - let mut conf_clone = self.clone(); - - for pair in overrides { - let key = pair.0; - let value = pair.1; - match key.as_str() { - "runs" => conf_clone.runs = parse_config_u32(key, value)?, - "max-test-rejects" => conf_clone.max_test_rejects = parse_config_u32(key, value)?, - "dictionary-weight" => { - conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)? - } - "failure-persist-file" => conf_clone.failure_persist_file = Some(value), - "show-logs" => conf_clone.show_logs = parse_config_bool(key, value)?, - _ => Err(InlineConfigParserError::InvalidConfigProperty(key))?, - } - } - Ok(Some(conf_clone)) - } -} - /// Contains for fuzz testing #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FuzzDictionaryConfig { @@ -132,68 +91,3 @@ impl Default for FuzzDictionaryConfig { } } } - -#[cfg(test)] -mod tests { - use crate::{inline::InlineConfigParser, FuzzConfig}; - - #[test] - fn unrecognized_property() { - let configs = &["forge-config: default.fuzz.unknownprop = 200".to_string()]; - let base_config = FuzzConfig::default(); - if let Err(e) = base_config.try_merge(configs) { - assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); - } else { - unreachable!() - } - } - - #[test] - fn successful_merge() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: default.fuzz.dictionary-weight = 42".to_string(), - "forge-config: default.fuzz.failure-persist-file = fuzz-failure".to_string(), - ]; - let base_config = FuzzConfig::default(); - let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap(); - assert_eq!(merged.runs, 42424242); - assert_eq!(merged.dictionary.dictionary_weight, 42); - assert_eq!(merged.failure_persist_file, Some("fuzz-failure".to_string())); - } - - #[test] - fn merge_is_none() { - let empty_config = &[]; - let base_config = FuzzConfig::default(); - let merged = base_config.try_merge(empty_config).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn merge_is_none_unrelated_property() { - let unrelated_configs = &["forge-config: default.invariant.runs = 2".to_string()]; - let base_config = FuzzConfig::default(); - let merged = base_config.try_merge(unrelated_configs).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn override_detection() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: ci.fuzz.runs = 666666".to_string(), - "forge-config: default.invariant.runs = 2".to_string(), - "forge-config: default.fuzz.dictionary-weight = 42".to_string(), - ]; - let variables = FuzzConfig::get_config_overrides(configs); - assert_eq!( - variables, - vec![ - ("runs".into(), "42424242".into()), - ("runs".into(), "666666".into()), - ("dictionary-weight".into(), "42".into()) - ] - ); - } -} diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs deleted file mode 100644 index e69449654..000000000 --- a/crates/config/src/inline/conf_parser.rs +++ /dev/null @@ -1,169 +0,0 @@ -use super::{remove_whitespaces, InlineConfigParserError}; -use crate::{inline::INLINE_CONFIG_PREFIX, InlineConfigError, NatSpec}; -use regex::Regex; - -/// This trait is intended to parse configurations from -/// structured text. Foundry users can annotate Solidity test functions, -/// providing special configs just for the execution of a specific test. -/// -/// An example: -/// -/// ```solidity -/// contract MyTest is Test { -/// /// forge-config: default.fuzz.runs = 100 -/// /// forge-config: ci.fuzz.runs = 500 -/// function test_SimpleFuzzTest(uint256 x) public {...} -/// -/// /// forge-config: default.fuzz.runs = 500 -/// /// forge-config: ci.fuzz.runs = 10000 -/// function test_ImportantFuzzTest(uint256 x) public {...} -/// } -/// ``` -pub trait InlineConfigParser -where - Self: Clone + Default + Sized + 'static, -{ - /// Returns a config key that is common to all valid configuration lines - /// for the current impl. This helps to extract correct values out of a text. - /// - /// An example key would be `fuzz` of `invariant`. - fn config_key() -> String; - - /// Tries to override `self` properties with values specified in the `configs` parameter. - /// - /// Returns - /// - `Some(Self)` in case some configurations are merged into self. - /// - `None` in case there are no configurations that can be applied to self. - /// - `Err(InlineConfigParserError)` in case of wrong configuration. - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError>; - - /// Validates and merges the natspec configs for current profile into the current config. - fn merge(&self, natspec: &NatSpec) -> Result, InlineConfigError> { - let config_key = Self::config_key(); - - let configs = natspec - .current_profile_configs() - .filter(|l| l.contains(&config_key)) - .collect::>(); - - self.try_merge(&configs).map_err(|e| { - let line = natspec.debug_context(); - InlineConfigError { line, source: e } - }) - } - - /// Given a list of config lines, returns all available pairs (key, value) matching the current - /// config key. - /// - /// # Examples - /// - /// ```ignore - /// assert_eq!( - /// get_config_overrides(&[ - /// "forge-config: default.invariant.runs = 500", - /// "forge-config: default.invariant.depth = 500", - /// "forge-config: ci.invariant.depth = 500", - /// "forge-config: ci.fuzz.runs = 10", - /// ]), - /// [("runs", "500"), ("depth", "500"), ("depth", "500")] - /// ); - /// ``` - fn get_config_overrides(config_lines: &[String]) -> Vec<(String, String)> { - let mut result: Vec<(String, String)> = vec![]; - let config_key = Self::config_key(); - let profile = ".*"; - let prefix = format!("^{INLINE_CONFIG_PREFIX}:{profile}{config_key}\\."); - let re = Regex::new(&prefix).unwrap(); - - config_lines - .iter() - .map(|l| remove_whitespaces(l)) - .filter(|l| re.is_match(l)) - .map(|l| re.replace(&l, "").to_string()) - .for_each(|line| { - let key_value = line.split('=').collect::>(); // i.e. "['runs', '500']" - if let Some(key) = key_value.first() { - if let Some(value) = key_value.last() { - result.push((key.to_string(), value.to_string())); - } - } - }); - - result - } -} - -/// Checks if all configuration lines specified in `natspec` use a valid profile. -/// -/// i.e. Given available profiles -/// ```rust -/// let _profiles = vec!["ci", "default"]; -/// ``` -/// A configuration like `forge-config: ciii.invariant.depth = 1` would result -/// in an error. -pub fn validate_profiles(natspec: &NatSpec, profiles: &[String]) -> Result<(), InlineConfigError> { - for config in natspec.config_lines() { - if !profiles.iter().any(|p| config.starts_with(&format!("{INLINE_CONFIG_PREFIX}:{p}."))) { - let err_line: String = natspec.debug_context(); - let profiles = format!("{profiles:?}"); - Err(InlineConfigError { - source: InlineConfigParserError::InvalidProfile(config, profiles), - line: err_line, - })? - } - } - Ok(()) -} - -/// Tries to parse a `u32` from `value`. The `key` argument is used to give details -/// in the case of an error. -pub fn parse_config_u32(key: String, value: String) -> Result { - value.parse().map_err(|_| InlineConfigParserError::ParseInt(key, value)) -} - -/// Tries to parse a `bool` from `value`. The `key` argument is used to give details -/// in the case of an error. -pub fn parse_config_bool(key: String, value: String) -> Result { - value.parse().map_err(|_| InlineConfigParserError::ParseBool(key, value)) -} - -#[cfg(test)] -mod tests { - use crate::{inline::conf_parser::validate_profiles, NatSpec}; - - #[test] - fn can_reject_invalid_profiles() { - let profiles = ["ci".to_string(), "default".to_string()]; - let natspec = NatSpec { - contract: Default::default(), - function: Default::default(), - line: Default::default(), - docs: r" - forge-config: ciii.invariant.depth = 1 - forge-config: default.invariant.depth = 1 - " - .into(), - }; - - let result = validate_profiles(&natspec, &profiles); - assert!(result.is_err()); - } - - #[test] - fn can_accept_valid_profiles() { - let profiles = ["ci".to_string(), "default".to_string()]; - let natspec = NatSpec { - contract: Default::default(), - function: Default::default(), - line: Default::default(), - docs: r" - forge-config: ci.invariant.depth = 1 - forge-config: default.invariant.depth = 1 - " - .into(), - }; - - let result = validate_profiles(&natspec, &profiles); - assert!(result.is_ok()); - } -} diff --git a/crates/config/src/inline/error.rs b/crates/config/src/inline/error.rs deleted file mode 100644 index ddcb6a61b..000000000 --- a/crates/config/src/inline/error.rs +++ /dev/null @@ -1,44 +0,0 @@ -/// Errors returned by the [`InlineConfigParser`](crate::InlineConfigParser) trait. -#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] -pub enum InlineConfigParserError { - /// An invalid configuration property has been provided. - /// The property cannot be mapped to the configuration object - #[error("'{0}' is an invalid config property")] - InvalidConfigProperty(String), - /// An invalid profile has been provided - #[error("'{0}' specifies an invalid profile. Available profiles are: {1}")] - InvalidProfile(String, String), - /// An error occurred while trying to parse an integer configuration value - #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into an integer value")] - ParseInt(String, String), - /// An error occurred while trying to parse a boolean configuration value - #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into a boolean value")] - ParseBool(String, String), -} - -/// Wrapper error struct that catches config parsing errors, enriching them with context information -/// reporting the misconfigured line. -#[derive(Debug, thiserror::Error)] -#[error("Inline config error detected at {line}")] -pub struct InlineConfigError { - /// Specifies the misconfigured line. This is something of the form - /// `dir/TestContract.t.sol:FuzzContract:10:12:111` - pub line: String, - /// The inner error - pub source: InlineConfigParserError, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_format_inline_config_errors() { - let source = InlineConfigParserError::ParseBool("key".into(), "invalid-bool-value".into()); - let line = "dir/TestContract.t.sol:FuzzContract".to_string(); - let error = InlineConfigError { line: line.clone(), source }; - - let expected = format!("Inline config error detected at {line}"); - assert_eq!(error.to_string(), expected); - } -} diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 8b5616a21..30b1c820e 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,61 +1,168 @@ use crate::Config; use alloy_primitives::map::HashMap; -use std::sync::LazyLock; -mod conf_parser; -pub use conf_parser::*; - -mod error; -pub use error::*; +use figment::{ + value::{Dict, Map, Value}, + Figment, Profile, Provider, +}; +use itertools::Itertools; mod natspec; pub use natspec::*; -pub const INLINE_CONFIG_FUZZ_KEY: &str = "fuzz"; -pub const INLINE_CONFIG_INVARIANT_KEY: &str = "invariant"; -const INLINE_CONFIG_PREFIX: &str = "forge-config"; +const INLINE_CONFIG_PREFIX: &str = "forge-config:"; + +type DataMap = Map; -static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: LazyLock = LazyLock::new(|| { - let selected_profile = Config::selected_profile().to_string(); - format!("{INLINE_CONFIG_PREFIX}:{selected_profile}.") -}); +/// Errors returned when parsing inline config. +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +pub enum InlineConfigErrorKind { + /// Failed to parse inline config as TOML. + #[error(transparent)] + Parse(#[from] toml::de::Error), + /// An invalid profile has been provided. + #[error("invalid profile `{0}`; valid profiles: {1}")] + InvalidProfile(String, String), +} + +/// Wrapper error struct that catches config parsing errors, enriching them with context information +/// reporting the misconfigured line. +#[derive(Debug, thiserror::Error)] +#[error("Inline config error at {location}: {kind}")] +pub struct InlineConfigError { + /// The span of the error in the format: + /// `dir/TestContract.t.sol:FuzzContract:10:12:111` + pub location: String, + /// The inner error + pub kind: InlineConfigErrorKind, +} /// Represents per-test configurations, declared inline /// as structured comments in Solidity test files. This allows /// to create configs directly bound to a solidity test. #[derive(Clone, Debug, Default)] -pub struct InlineConfig { - /// Contract-level configurations, used for functions that do not have a specific - /// configuration. - contract_level: HashMap, - /// Maps a (test-contract, test-function) pair - /// to a specific configuration provided by the user. - fn_level: HashMap<(String, String), T>, +pub struct InlineConfig { + /// Contract-level configuration. + contract_level: HashMap, + /// Function-level configuration. + fn_level: HashMap<(String, String), DataMap>, } -impl InlineConfig { - /// Returns an inline configuration, if any, for a test function. - /// Configuration is identified by the pair "contract", "function". - pub fn get(&self, contract_id: &str, fn_name: &str) -> Option<&T> { - let key = (contract_id.to_string(), fn_name.to_string()); - self.fn_level.get(&key).or_else(|| self.contract_level.get(contract_id)) +impl InlineConfig { + /// Creates a new, empty [`InlineConfig`]. + pub fn new() -> Self { + Self::default() + } + + /// Inserts a new [`NatSpec`] into the [`InlineConfig`]. + pub fn insert(&mut self, natspec: &NatSpec) -> Result<(), InlineConfigError> { + let map = if let Some(function) = &natspec.function { + self.fn_level.entry((natspec.contract.clone(), function.clone())).or_default() + } else { + self.contract_level.entry(natspec.contract.clone()).or_default() + }; + let joined = natspec + .config_values() + .map(|s| { + // Replace `-` with `_` for backwards compatibility with the old parser. + if let Some(idx) = s.find('=') { + s[..idx].replace('-', "_") + &s[idx..] + } else { + s.to_string() + } + }) + .format("\n") + .to_string(); + let data = toml::from_str::(&joined).map_err(|e| InlineConfigError { + location: natspec.location_string(), + kind: InlineConfigErrorKind::Parse(e), + })?; + extend_data_map(map, &data); + Ok(()) } - pub fn insert_contract(&mut self, contract_id: impl Into, config: T) { - self.contract_level.insert(contract_id.into(), config); + /// Returns a [`figment::Provider`] for this [`InlineConfig`] at the given contract and function + /// level. + pub fn provide<'a>(&'a self, contract: &'a str, function: &'a str) -> InlineConfigProvider<'a> { + InlineConfigProvider { inline: self, contract, function } } - /// Inserts an inline configuration, for a test function. - /// Configuration is identified by the pair "contract", "function". - pub fn insert_fn(&mut self, contract_id: C, fn_name: F, config: T) - where - C: Into, - F: Into, - { - let key = (contract_id.into(), fn_name.into()); - self.fn_level.insert(key, config); + /// Merges the inline configuration at the given contract and function level with the provided + /// base configuration. + pub fn merge(&self, contract: &str, function: &str, base: &Config) -> Figment { + Figment::from(base).merge(self.provide(contract, function)) + } + + /// Returns `true` if a configuration is present at the given contract and function level. + pub fn contains(&self, contract: &str, function: &str) -> bool { + // Order swapped to avoid allocation in `get_function` since order doesn't matter here. + self.get_contract(contract) + .filter(|map| !map.is_empty()) + .or_else(|| self.get_function(contract, function)) + .is_some_and(|map| !map.is_empty()) + } + + fn get_contract(&self, contract: &str) -> Option<&DataMap> { + self.contract_level.get(contract) + } + + fn get_function(&self, contract: &str, function: &str) -> Option<&DataMap> { + let key = (contract.to_string(), function.to_string()); + self.fn_level.get(&key) } } -pub(crate) fn remove_whitespaces(s: &str) -> String { - s.chars().filter(|c| !c.is_whitespace()).collect() +/// [`figment::Provider`] for [`InlineConfig`] at a given contract and function level. +/// +/// Created by [`InlineConfig::provide`]. +#[derive(Clone, Debug)] +pub struct InlineConfigProvider<'a> { + inline: &'a InlineConfig, + contract: &'a str, + function: &'a str, +} + +impl Provider for InlineConfigProvider<'_> { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("inline config") + } + + fn data(&self) -> figment::Result { + let mut map = DataMap::new(); + if let Some(new) = self.inline.get_contract(self.contract) { + extend_data_map(&mut map, new); + } + if let Some(new) = self.inline.get_function(self.contract, self.function) { + extend_data_map(&mut map, new); + } + Ok(map) + } +} + +fn extend_data_map(map: &mut DataMap, new: &DataMap) { + for (profile, data) in new { + extend_dict(map.entry(profile.clone()).or_default(), data); + } +} + +fn extend_dict(dict: &mut Dict, new: &Dict) { + for (k, v) in new { + match dict.entry(k.clone()) { + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(v.clone()); + } + std::collections::btree_map::Entry::Occupied(entry) => { + extend_value(entry.into_mut(), v); + } + } + } +} + +fn extend_value(value: &mut Value, new: &Value) { + match (value, new) { + (Value::Dict(tag, dict), Value::Dict(new_tag, new_dict)) => { + *tag = *new_tag; + extend_dict(dict, new_dict); + } + (value, new) => *value = new.clone(), + } } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 6dd6b696c..5774d9e19 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -1,8 +1,10 @@ -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; +use super::{InlineConfigError, InlineConfigErrorKind, INLINE_CONFIG_PREFIX}; +use figment::Profile; use foundry_compilers::{ artifacts::{ast::NodeType, Node}, ProjectCompileOutput, }; +use itertools::Itertools; use serde_json::Value; use solang_parser::{helpers::CodeLocation, pt}; use std::{collections::BTreeMap, path::Path}; @@ -10,15 +12,13 @@ use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations #[derive(Clone, Debug, PartialEq, Eq)] pub struct NatSpec { - /// The parent contract of the natspec + /// The parent contract of the natspec. pub contract: String, - /// The function annotated with the natspec. None if the natspec is contract-level + /// The function annotated with the natspec. None if the natspec is contract-level. pub function: Option, - /// The line the natspec appears, in the form - /// `row:col:length` i.e. `10:21:122` + /// The line the natspec appears, in the form `row:col:length`, i.e. `10:21:122`. pub line: String, - /// The actual natspec comment, without slashes or block - /// punctuation + /// The actual natspec comment, without slashes or block punctuation. pub docs: String, } @@ -56,29 +56,52 @@ impl NatSpec { natspecs } - /// Returns a string describing the natspec - /// context, for debugging purposes 🐞 - /// i.e. `test/Counter.t.sol:CounterTest:testFuzz_SetNumber` - pub fn debug_context(&self) -> String { - format!("{}:{}", self.contract, self.function.as_deref().unwrap_or_default()) + /// Checks if all configuration lines use a valid profile. + /// + /// i.e. Given available profiles + /// ```rust + /// let _profiles = vec!["ci", "default"]; + /// ``` + /// A configuration like `forge-config: ciii.invariant.depth = 1` would result + /// in an error. + pub fn validate_profiles(&self, profiles: &[Profile]) -> eyre::Result<()> { + for config in self.config_values() { + if !profiles.iter().any(|p| { + config + .strip_prefix(p.as_str().as_str()) + .is_some_and(|rest| rest.trim_start().starts_with('.')) + }) { + Err(InlineConfigError { + location: self.location_string(), + kind: InlineConfigErrorKind::InvalidProfile( + config.to_string(), + profiles.iter().format(", ").to_string(), + ), + })? + } + } + Ok(()) } - /// Returns a list of configuration lines that match the current profile - pub fn current_profile_configs(&self) -> impl Iterator + '_ { - self.config_lines_with_prefix(INLINE_CONFIG_PREFIX_SELECTED_PROFILE.as_str()) + /// Returns the path of the contract. + pub fn path(&self) -> &str { + match self.contract.split_once(':') { + Some((path, _)) => path, + None => self.contract.as_str(), + } } - /// Returns a list of configuration lines that match a specific string prefix - pub fn config_lines_with_prefix<'a>( - &'a self, - prefix: &'a str, - ) -> impl Iterator + 'a { - self.config_lines().filter(move |l| l.starts_with(prefix)) + /// Returns the location of the natspec as a string. + pub fn location_string(&self) -> String { + format!("{}:{}", self.path(), self.line) } - /// Returns a list of all the configuration lines available in the natspec - pub fn config_lines(&self) -> impl Iterator + '_ { - self.docs.lines().filter(|line| line.contains(INLINE_CONFIG_PREFIX)).map(remove_whitespaces) + /// Returns a list of all the configuration values available in the natspec. + pub fn config_values(&self) -> impl Iterator { + self.docs.lines().filter_map(|line| { + line.find(INLINE_CONFIG_PREFIX) + .map(|idx| line[idx + INLINE_CONFIG_PREFIX.len()..].trim()) + }) } } @@ -258,6 +281,42 @@ mod tests { use super::*; use serde_json::json; + #[test] + fn can_reject_invalid_profiles() { + let profiles = ["ci".into(), "default".into()]; + let natspec = NatSpec { + contract: Default::default(), + function: Default::default(), + line: Default::default(), + docs: r" + forge-config: ciii.invariant.depth = 1 + forge-config: default.invariant.depth = 1 + " + .into(), + }; + + let result = natspec.validate_profiles(&profiles); + assert!(result.is_err()); + } + + #[test] + fn can_accept_valid_profiles() { + let profiles = ["ci".into(), "default".into()]; + let natspec = NatSpec { + contract: Default::default(), + function: Default::default(), + line: Default::default(), + docs: r" + forge-config: ci.invariant.depth = 1 + forge-config: default.invariant.depth = 1 + " + .into(), + }; + + let result = natspec.validate_profiles(&profiles); + assert!(result.is_ok()); + } + #[test] fn parse_solang() { let src = " @@ -355,42 +414,13 @@ contract FuzzInlineConf is DSTest { #[test] fn config_lines() { let natspec = natspec(); - let config_lines = natspec.config_lines(); - assert_eq!( - config_lines.collect::>(), - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:ci.fuzz.runs=500".to_string(), - "forge-config:default.invariant.runs=1".to_string() - ] - ) - } - - #[test] - fn current_profile_configs() { - let natspec = natspec(); - let config_lines = natspec.current_profile_configs(); - - assert_eq!( - config_lines.collect::>(), - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:default.invariant.runs=1".to_string() - ] - ); - } - - #[test] - fn config_lines_with_prefix() { - use super::INLINE_CONFIG_PREFIX; - let natspec = natspec(); - let prefix = format!("{INLINE_CONFIG_PREFIX}:default"); - let config_lines = natspec.config_lines_with_prefix(&prefix); + let config_lines = natspec.config_values(); assert_eq!( config_lines.collect::>(), - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:default.invariant.runs=1".to_string() + [ + "default.fuzz.runs = 600".to_string(), + "ci.fuzz.runs = 500".to_string(), + "default.invariant.runs = 1".to_string() ] ) } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 70e9a2b85..97f189b36 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -1,12 +1,6 @@ //! Configuration for invariant testing -use crate::{ - fuzz::FuzzDictionaryConfig, - inline::{ - parse_config_bool, parse_config_u32, InlineConfigParser, InlineConfigParserError, - INLINE_CONFIG_INVARIANT_KEY, - }, -}; +use crate::fuzz::FuzzDictionaryConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -80,88 +74,3 @@ impl InvariantConfig { .join(contract_name.split(':').last().unwrap()) } } - -impl InlineConfigParser for InvariantConfig { - fn config_key() -> String { - INLINE_CONFIG_INVARIANT_KEY.into() - } - - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { - let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); - - if overrides.is_empty() { - return Ok(None) - } - - let mut conf_clone = self.clone(); - - for pair in overrides { - let key = pair.0; - let value = pair.1; - match key.as_str() { - "runs" => conf_clone.runs = parse_config_u32(key, value)?, - "depth" => conf_clone.depth = parse_config_u32(key, value)?, - "fail-on-revert" => conf_clone.fail_on_revert = parse_config_bool(key, value)?, - "call-override" => conf_clone.call_override = parse_config_bool(key, value)?, - "failure-persist-dir" => { - conf_clone.failure_persist_dir = Some(PathBuf::from(value)) - } - "shrink-run-limit" => conf_clone.shrink_run_limit = parse_config_u32(key, value)?, - "show-metrics" => conf_clone.show_metrics = parse_config_bool(key, value)?, - _ => Err(InlineConfigParserError::InvalidConfigProperty(key.to_string()))?, - } - } - Ok(Some(conf_clone)) - } -} - -#[cfg(test)] -mod tests { - use crate::{inline::InlineConfigParser, InvariantConfig}; - - #[test] - fn unrecognized_property() { - let configs = &["forge-config: default.invariant.unknownprop = 200".to_string()]; - let base_config = InvariantConfig::default(); - if let Err(e) = base_config.try_merge(configs) { - assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); - } else { - unreachable!() - } - } - - #[test] - fn successful_merge() { - let configs = &["forge-config: default.invariant.runs = 42424242".to_string()]; - let base_config = InvariantConfig::default(); - let merged: InvariantConfig = base_config.try_merge(configs).expect("No errors").unwrap(); - assert_eq!(merged.runs, 42424242); - } - - #[test] - fn merge_is_none() { - let empty_config = &[]; - let base_config = InvariantConfig::default(); - let merged = base_config.try_merge(empty_config).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn can_merge_unrelated_properties_into_config() { - let unrelated_configs = &["forge-config: default.fuzz.runs = 2".to_string()]; - let base_config = InvariantConfig::default(); - let merged = base_config.try_merge(unrelated_configs).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn override_detection() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: ci.fuzz.runs = 666666".to_string(), - "forge-config: default.invariant.runs = 2".to_string(), - ]; - let variables = InvariantConfig::get_config_overrides(configs); - assert_eq!(variables, vec![("runs".into(), "2".into())]); - } -} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 5a159c392..72be43ab1 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -108,7 +108,7 @@ mod invariant; pub use invariant::InvariantConfig; mod inline; -pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; +pub use inline::{InlineConfig, InlineConfigError, NatSpec}; pub mod soldeer; use soldeer::{SoldeerConfig, SoldeerDependencyConfig}; @@ -163,6 +163,11 @@ pub struct Config { /// set to the extracting Figment's selected `Profile`. #[serde(skip)] pub profile: Profile, + /// The list of all profiles defined in the config. + /// + /// See `profile`. + #[serde(skip)] + pub profiles: Vec, /// path of the source contracts dir, like `src` or `contracts` pub src: PathBuf, /// path of the test dir @@ -481,7 +486,7 @@ pub struct Config { /// Use EOF-enabled solc for compilation. pub eof: bool, - /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information + /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information. #[serde(rename = "__warnings", default, skip_serializing)] pub warnings: Vec, @@ -516,7 +521,7 @@ pub const DEPRECATIONS: &[(&str, &str)] = &[("cancun", "evm_version = Cancun")]; impl Config { /// The default profile: "default" - pub const DEFAULT_PROFILE: Profile = Profile::const_new("default"); + pub const DEFAULT_PROFILE: Profile = Profile::Default; /// The hardhat profile: "hardhat" pub const HARDHAT_PROFILE: Profile = Profile::const_new("hardhat"); @@ -569,7 +574,7 @@ impl Config { /// See [`figment`](Self::figment) for more details. #[track_caller] pub fn load_with_providers(providers: FigmentProviders) -> Self { - Self::default().to_figment(providers).extract().unwrap() + Self::from_provider(Self::default().to_figment(providers)) } /// Returns the current `Config` @@ -620,19 +625,47 @@ impl Config { /// let config = Config::try_from(figment); /// ``` pub fn try_from(provider: T) -> Result { - let figment = Figment::from(provider); + Self::try_from_figment(Figment::from(provider)) + } + + fn try_from_figment(figment: Figment) -> Result { let mut config = figment.extract::().map_err(ExtractConfigError::new)?; config.profile = figment.profile().clone(); + + // The `"profile"` profile contains all the profiles as keys. + let mut add_profile = |profile: &Profile| { + if !config.profiles.contains(profile) { + config.profiles.push(profile.clone()); + } + }; + let figment = figment.select(Self::PROFILE_SECTION); + if let Ok(data) = figment.data() { + if let Some(profiles) = data.get(&Profile::new(Self::PROFILE_SECTION)) { + for profile in profiles.keys() { + add_profile(&Profile::new(profile)); + } + } + } + add_profile(&Self::DEFAULT_PROFILE); + add_profile(&config.profile); + Ok(config) } /// Returns the populated [Figment] using the requested [FigmentProviders] preset. /// - /// This will merge various providers, such as env,toml,remappings into the figment. - pub fn to_figment(self, providers: FigmentProviders) -> Figment { - let mut c = self; + /// This will merge various providers, such as env,toml,remappings into the figment if + /// requested. + pub fn to_figment(&self, providers: FigmentProviders) -> Figment { + // Note that `Figment::from` here is a method on `Figment` rather than the `From` impl below + + if providers.is_none() { + return Figment::from(self); + } + + let root = self.root.0.as_path(); let profile = Self::selected_profile(); - let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.root.0)); + let mut figment = Figment::default().merge(DappHardhatDirProvider(root)); // merge global foundry.toml file if let Some(global_toml) = Self::foundry_dir_toml().filter(|p| p.exists()) { @@ -645,7 +678,7 @@ impl Config { // merge local foundry.toml file figment = Self::merge_toml_provider( figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.root.0.join(Self::FILE_NAME)).cached(), + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), root.join(Self::FILE_NAME)).cached(), profile.clone(), ); @@ -692,17 +725,17 @@ impl Config { lib_paths: figment .extract_inner::>("libs") .map(Cow::Owned) - .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), - root: &c.root.0, + .unwrap_or_else(|_| Cow::Borrowed(&self.libs)), + root, remappings: figment.extract_inner::>("remappings"), }; figment = figment.merge(remappings); } // normalize defaults - figment = c.normalize_defaults(figment); + figment = self.normalize_defaults(figment); - Figment::from(c).merge(figment).select(profile) + Figment::from(self).merge(figment).select(profile) } /// The config supports relative paths and tracks the root path separately see @@ -1722,6 +1755,19 @@ impl Config { /// /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_PROFILE`. pub fn selected_profile() -> Profile { + // Can't cache in tests because the env var can change. + #[cfg(test)] + { + Self::force_selected_profile() + } + #[cfg(not(test))] + { + static CACHE: std::sync::OnceLock = std::sync::OnceLock::new(); + CACHE.get_or_init(Self::force_selected_profile).clone() + } + } + + fn force_selected_profile() -> Profile { Profile::from_env_or("FOUNDRY_PROFILE", Self::DEFAULT_PROFILE) } @@ -2017,19 +2063,20 @@ impl Config { /// This normalizes the default `evm_version` if a `solc` was provided in the config. /// /// See also - fn normalize_defaults(&mut self, figment: Figment) -> Figment { + fn normalize_defaults(&self, mut figment: Figment) -> Figment { + // TODO: add a warning if evm_version is provided but incompatible + if figment.contains("evm_version") { + return figment; + } + + // Normalize `evm_version` based on the provided solc version. if let Ok(solc) = figment.extract_inner::("solc") { - // check if evm_version is set - // TODO: add a warning if evm_version is provided but incompatible - if figment.find_value("evm_version").is_err() { - if let Some(version) = solc - .try_version() - .ok() - .and_then(|version| self.evm_version.normalize_version_solc(&version)) - { - // normalize evm_version based on the provided solc version - self.evm_version = version; - } + if let Some(version) = solc + .try_version() + .ok() + .and_then(|version| self.evm_version.normalize_version_solc(&version)) + { + figment = figment.merge(("evm_version", version)); } } @@ -2039,36 +2086,53 @@ impl Config { impl From for Figment { fn from(c: Config) -> Self { + (&c).into() + } +} +impl From<&Config> for Figment { + fn from(c: &Config) -> Self { c.to_figment(FigmentProviders::All) } } -/// Determines what providers should be used when loading the [Figment] for a [Config] +/// Determines what providers should be used when loading the [`Figment`] for a [`Config`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum FigmentProviders { - /// Include all providers + /// Include all providers. #[default] All, - /// Only include necessary providers that are useful for cast commands + /// Only include necessary providers that are useful for cast commands. /// - /// This will exclude more expensive providers such as remappings + /// This will exclude more expensive providers such as remappings. Cast, - /// Only include necessary providers that are useful for anvil + /// Only include necessary providers that are useful for anvil. /// - /// This will exclude more expensive providers such as remappings + /// This will exclude more expensive providers such as remappings. Anvil, + /// Don't include any providers. + None, } impl FigmentProviders { - /// Returns true if all providers should be included + /// Returns true if all providers should be included. pub const fn is_all(&self) -> bool { matches!(self, Self::All) } - /// Returns true if this is the cast preset + /// Returns true if this is the cast preset. pub const fn is_cast(&self) -> bool { matches!(self, Self::Cast) } + + /// Returns true if this is the anvil preset. + pub const fn is_anvil(&self) -> bool { + matches!(self, Self::Anvil) + } + + /// Returns true if no providers should be included. + pub const fn is_none(&self) -> bool { + matches!(self, Self::None) + } } /// Wrapper type for `regex::Regex` that implements `PartialEq` @@ -2154,6 +2218,20 @@ impl AsRef for RootPath { } } +impl std::ops::Deref for RootPath { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for RootPath { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + /// Parses a config profile /// /// All `Profile` date is ignored by serde, however the `Config::to_string_pretty` includes it and @@ -2202,11 +2280,9 @@ impl Default for Config { fn default() -> Self { Self { profile: Self::DEFAULT_PROFILE, + profiles: vec![Self::DEFAULT_PROFILE], fs_permissions: FsPermissions::new([PathPermission::read("out")]), - #[cfg(not(feature = "isolate-by-default"))] - isolate: false, - #[cfg(feature = "isolate-by-default")] - isolate: true, + isolate: cfg!(feature = "isolate-by-default"), root: Default::default(), src: "src".into(), test: "test".into(), @@ -2322,11 +2398,12 @@ impl Default for Config { } } -/// Wrapper for the config's `gas_limit` value necessary because toml-rs can't handle larger number because integers are stored signed: +/// Wrapper for the config's `gas_limit` value necessary because toml-rs can't handle larger number +/// because integers are stored signed: /// /// Due to this limitation this type will be serialized/deserialized as String if it's larger than /// `i64` -#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize)] pub struct GasLimit(#[serde(deserialize_with = "crate::deserialize_u64_or_max")] pub u64); impl From for GasLimit { @@ -3052,9 +3129,39 @@ mod tests { #[test] fn test_figment_is_default() { figment::Jail::expect_with(|_| { - let mut default: Config = Config::figment().extract().unwrap(); - default.profile = Config::default().profile; - assert_eq!(default, Config::default()); + let mut default: Config = Config::figment().extract()?; + let default2 = Config::default(); + default.profile = default2.profile.clone(); + default.profiles = default2.profiles.clone(); + assert_eq!(default, default2); + Ok(()) + }); + } + + #[test] + fn figment_profiles() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [foo.baz] + libs = ['node_modules', 'lib'] + + [profile.default] + libs = ['node_modules', 'lib'] + + [profile.ci] + libs = ['node_modules', 'lib'] + + [profile.local] + libs = ['node_modules', 'lib'] + ", + )?; + + let config = crate::Config::load(); + let expected: &[figment::Profile] = &["ci".into(), "default".into(), "local".into()]; + assert_eq!(config.profiles, expected); + Ok(()) }); } @@ -3163,7 +3270,6 @@ mod tests { jail.set_env("FOUNDRY_PROFILE", "custom"); let config = Config::load(); - assert_eq!(config.src, PathBuf::from("customsrc")); assert_eq!(config.test, PathBuf::from("defaulttest")); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); diff --git a/crates/config/src/providers/mod.rs b/crates/config/src/providers/mod.rs index 1f9f5c88e..9bd8a014f 100644 --- a/crates/config/src/providers/mod.rs +++ b/crates/config/src/providers/mod.rs @@ -17,7 +17,7 @@ pub struct WarningsProvider

{ old_warnings: Result, Error>, } -impl

WarningsProvider

{ +impl WarningsProvider

{ const WARNINGS_KEY: &'static str = "__warnings"; /// Creates a new warnings provider. @@ -41,9 +41,7 @@ impl

WarningsProvider

{ }; Self::new(provider, figment.profile().clone(), old_warnings) } -} -impl WarningsProvider

{ /// Collects all warnings. pub fn collect_warnings(&self) -> Result, Error> { let data = self.provider.data().unwrap_or_default(); @@ -103,12 +101,10 @@ impl Provider for WarningsProvider

{ } fn data(&self) -> Result, Error> { + let warnings = self.collect_warnings()?; Ok(Map::from([( self.profile.clone(), - Dict::from([( - Self::WARNINGS_KEY.to_string(), - Value::serialize(self.collect_warnings()?)?, - )]), + Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]), )])) } @@ -138,8 +134,9 @@ impl Provider for FallbackProfileProvider

{ } fn data(&self) -> Result, Error> { - if let Some(fallback) = self.provider.data()?.get(&self.fallback) { - let mut inner = self.provider.data()?.remove(&self.profile).unwrap_or_default(); + let data = self.provider.data()?; + if let Some(fallback) = data.get(&self.fallback) { + let mut inner = data.get(&self.profile).cloned().unwrap_or_default(); for (k, v) in fallback.iter() { if !inner.contains_key(k) { inner.insert(k.to_owned(), v.clone()); @@ -147,7 +144,7 @@ impl Provider for FallbackProfileProvider

{ } Ok(self.profile.collect(inner)) } else { - self.provider.data() + Ok(data) } } diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 623234f94..343fde697 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -112,7 +112,7 @@ pub struct RemappingsProvider<'a> { pub lib_paths: Cow<'a, Vec>, /// the root path used to turn an absolute `Remapping`, as we're getting it from /// `Remapping::find_many` into a relative one. - pub root: &'a PathBuf, + pub root: &'a Path, /// This contains either: /// - previously set remappings /// - a `MissingField` error, which means previous provider didn't set the "remappings" field diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 2117834f4..e07d7dfbc 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -14,7 +14,6 @@ use std::{ path::{Path, PathBuf}, str::FromStr, }; -use toml_edit::{DocumentMut, Item}; /// Loads the config for the current project workspace pub fn load_config() -> Config { @@ -186,45 +185,6 @@ pub(crate) fn get_dir_remapping(dir: impl AsRef) -> Option { } } -/// Returns all available `profile` keys in a given `.toml` file -/// -/// i.e. The toml below would return would return `["default", "ci", "local"]` -/// ```toml -/// [profile.default] -/// ... -/// [profile.ci] -/// ... -/// [profile.local] -/// ``` -pub fn get_available_profiles(toml_path: impl AsRef) -> eyre::Result> { - let mut result = vec![Config::DEFAULT_PROFILE.to_string()]; - - if !toml_path.as_ref().exists() { - return Ok(result) - } - - let doc = read_toml(toml_path)?; - - if let Some(Item::Table(profiles)) = doc.as_table().get(Config::PROFILE_SECTION) { - for (profile, _) in profiles { - let p = profile.to_string(); - if !result.contains(&p) { - result.push(p); - } - } - } - - Ok(result) -} - -/// Returns a [`toml_edit::Document`] loaded from the provided `path`. -/// Can raise an error in case of I/O or parsing errors. -fn read_toml(path: impl AsRef) -> eyre::Result { - let path = path.as_ref().to_owned(); - let doc: DocumentMut = std::fs::read_to_string(path)?.parse()?; - Ok(doc) -} - /// Deserialize stringified percent. The value must be between 0 and 100 inclusive. pub(crate) fn deserialize_stringified_percent<'de, D>(deserializer: D) -> Result where @@ -319,41 +279,3 @@ pub fn evm_spec_id(evm_version: &EvmVersion, alphanet: bool) -> SpecId { EvmVersion::Prague => SpecId::OSAKA, // Osaka enables EOF } } - -#[cfg(test)] -mod tests { - use crate::get_available_profiles; - use std::path::Path; - - #[test] - fn get_profiles_from_toml() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r" - [foo.baz] - libs = ['node_modules', 'lib'] - - [profile.default] - libs = ['node_modules', 'lib'] - - [profile.ci] - libs = ['node_modules', 'lib'] - - [profile.local] - libs = ['node_modules', 'lib'] - ", - )?; - - let path = Path::new("./foundry.toml"); - let profiles = get_available_profiles(path).unwrap(); - - assert_eq!( - profiles, - vec!["default".to_string(), "ci".to_string(), "local".to_string()] - ); - - Ok(()) - }); - } -} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 9849fd1ce..6f4448ae4 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -4,9 +4,9 @@ use alloy_primitives::{Address, B256, U256}; use alloy_provider::{network::AnyRpcBlock, Provider}; use eyre::WrapErr; use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; -use foundry_config::{Chain, Config}; +use foundry_config::{Chain, Config, GasLimit}; use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use url::Url; #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -166,7 +166,7 @@ impl EvmOpts { /// Returns the gas limit to use pub fn gas_limit(&self) -> u64 { - self.env.block_gas_limit.unwrap_or(self.env.gas_limit) + self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0 } /// Returns the configured chain id, which will be @@ -225,8 +225,7 @@ impl EvmOpts { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Env { /// The block gas limit. - #[serde(deserialize_with = "string_or_number")] - pub gas_limit: u64, + pub gas_limit: GasLimit, /// The `CHAINID` opcode value. pub chain_id: Option, @@ -260,47 +259,10 @@ pub struct Env { pub block_prevrandao: B256, /// the block.gaslimit value during EVM execution - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "string_or_number_opt" - )] - pub block_gas_limit: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_gas_limit: Option, /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. #[serde(default, skip_serializing_if = "Option::is_none")] pub code_size_limit: Option, } - -#[derive(Deserialize)] -#[serde(untagged)] -enum Gas { - Number(u64), - Text(String), -} - -fn string_or_number<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - use serde::de::Error; - match Gas::deserialize(deserializer)? { - Gas::Number(num) => Ok(num), - Gas::Text(s) => s.parse().map_err(D::Error::custom), - } -} - -fn string_or_number_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - use serde::de::Error; - - match Option::::deserialize(deserializer)? { - Some(gas) => match gas { - Gas::Number(num) => Ok(Some(num)), - Gas::Text(s) => s.parse().map(Some).map_err(D::Error::custom), - }, - _ => Ok(None), - } -} diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 7e60a5451..4ca111a56 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -230,11 +230,7 @@ impl CoverageArgs { .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_test_options(TestOptions { - fuzz: config.fuzz.clone(), - invariant: config.invariant.clone(), - ..Default::default() - }) + .with_test_options(TestOptions::new(output, config.clone())?) .set_coverage(true) .build(&root, output, env, evm_opts)?; diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index ad2e52df6..6e19c96d0 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -14,7 +14,7 @@ use forge::{ identifier::SignaturesIdentifier, CallTraceDecoderBuilder, InternalTraceMode, TraceKind, }, - MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder, + MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, }; use foundry_cli::{ opts::{CoreBuildArgs, GlobalOpts}, @@ -34,7 +34,7 @@ use foundry_config::{ Metadata, Profile, Provider, }, filter::GlobMatcher, - get_available_profiles, Config, + Config, }; use foundry_debugger::Debugger; use foundry_evm::traces::identifier::TraceIdentifiers; @@ -301,25 +301,20 @@ impl TestArgs { // Create test options from general project settings and compiler output. let project_root = &project.paths.root; - let toml = config.get_config_path(); - let profiles = get_available_profiles(toml)?; // Remove the snapshots directory if it exists. // This is to ensure that we don't have any stale snapshots. // If `FORGE_SNAPSHOT_CHECK` is set, we don't remove the snapshots directory as it is // required for comparison. - if std::env::var("FORGE_SNAPSHOT_CHECK").is_err() { + if std::env::var_os("FORGE_SNAPSHOT_CHECK").is_none() { let snapshot_dir = project_root.join(&config.snapshots); if snapshot_dir.exists() { let _ = fs::remove_dir_all(project_root.join(&config.snapshots)); } } - let test_options: TestOptions = TestOptionsBuilder::default() - .fuzz(config.fuzz.clone()) - .invariant(config.invariant.clone()) - .profiles(profiles) - .build(&output, project_root)?; + let config = Arc::new(config); + let test_options = TestOptions::new(&output, config.clone())?; let should_debug = self.debug.is_some(); let should_draw = self.flamegraph || self.flamechart; @@ -347,7 +342,6 @@ impl TestArgs { }; // Prepare the test builder. - let config = Arc::new(config); let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) .set_decode_internal(decode_internal) @@ -1067,9 +1061,9 @@ contract FooBarTest is DSTest { &prj.root().to_string_lossy(), ]); let outcome = args.run().await.unwrap(); - let gas_report = outcome.gas_report.unwrap(); + let gas_report = outcome.gas_report.as_ref().unwrap(); - assert_eq!(gas_report.contracts.len(), 3); + assert_eq!(gas_report.contracts.len(), 3, "{}", outcome.summary(Default::default())); let call_cnts = gas_report .contracts .values() diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 0bec55153..257760c4e 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -7,15 +7,16 @@ extern crate foundry_common; #[macro_use] extern crate tracing; +use alloy_primitives::U256; +use eyre::Result; use foundry_compilers::ProjectCompileOutput; use foundry_config::{ - validate_profiles, Config, FuzzConfig, InlineConfig, InlineConfigError, InlineConfigParser, - InvariantConfig, NatSpec, + figment::Figment, Config, FuzzConfig, InlineConfig, InvariantConfig, NatSpec, }; use proptest::test_runner::{ FailurePersistence, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner, }; -use std::path::Path; +use std::sync::Arc; pub mod coverage; @@ -34,200 +35,108 @@ pub mod result; pub use foundry_common::traits::TestFilter; pub use foundry_evm::*; -/// Metadata on how to run fuzz/invariant tests +/// Test configuration. #[derive(Clone, Debug, Default)] pub struct TestOptions { - /// The base "fuzz" test configuration. To be used as a fallback in case - /// no more specific configs are found for a given run. - pub fuzz: FuzzConfig, - /// The base "invariant" test configuration. To be used as a fallback in case - /// no more specific configs are found for a given run. - pub invariant: InvariantConfig, - /// Contains per-test specific "fuzz" configurations. - pub inline_fuzz: InlineConfig, - /// Contains per-test specific "invariant" configurations. - pub inline_invariant: InlineConfig, + /// The base configuration. + pub config: Arc, + /// Per-test configuration. Merged onto `base_config`. + pub inline: InlineConfig, } impl TestOptions { /// Tries to create a new instance by detecting inline configurations from the project compile /// output. - pub fn new( - output: &ProjectCompileOutput, - root: &Path, - profiles: Vec, - base_fuzz: FuzzConfig, - base_invariant: InvariantConfig, - ) -> Result { - let natspecs: Vec = NatSpec::parse(output, root); - let mut inline_invariant = InlineConfig::::default(); - let mut inline_fuzz = InlineConfig::::default(); - - // Validate all natspecs + pub fn new(output: &ProjectCompileOutput, base_config: Arc) -> eyre::Result { + let natspecs: Vec = NatSpec::parse(output, &base_config.root); + let profiles = &base_config.profiles; + let mut inline = InlineConfig::new(); for natspec in &natspecs { - validate_profiles(natspec, &profiles)?; + inline.insert(natspec)?; + // Validate after parsing as TOML. + natspec.validate_profiles(profiles)?; } + Ok(Self { config: base_config, inline }) + } - // Firstly, apply contract-level configurations - for natspec in natspecs.iter().filter(|n| n.function.is_none()) { - if let Some(fuzz) = base_fuzz.merge(natspec)? { - inline_fuzz.insert_contract(&natspec.contract, fuzz); - } - - if let Some(invariant) = base_invariant.merge(natspec)? { - inline_invariant.insert_contract(&natspec.contract, invariant); - } - } - - for (natspec, f) in natspecs.iter().filter_map(|n| n.function.as_ref().map(|f| (n, f))) { - // Apply in-line configurations for the current profile - let c = &natspec.contract; - - // We might already have inserted contract-level configs above, so respect data already - // present in inline configs. - let base_fuzz = inline_fuzz.get(c, f).unwrap_or(&base_fuzz); - let base_invariant = inline_invariant.get(c, f).unwrap_or(&base_invariant); - - if let Some(fuzz) = base_fuzz.merge(natspec)? { - inline_fuzz.insert_fn(c, f, fuzz); - } - - if let Some(invariant) = base_invariant.merge(natspec)? { - inline_invariant.insert_fn(c, f, invariant); - } - } + /// Creates a new instance without parsing inline configuration. + pub fn new_unparsed(base_config: Arc) -> Self { + Self { config: base_config, inline: InlineConfig::new() } + } - Ok(Self { fuzz: base_fuzz, invariant: base_invariant, inline_fuzz, inline_invariant }) + /// Returns the [`Figment`] for the configuration. + pub fn figment(&self, contract: &str, function: &str) -> Result { + Ok(self.inline.merge(contract, function, &self.config)) } /// Returns a "fuzz" test runner instance. Parameters are used to select tight scoped fuzz /// configs that apply for a contract-function pair. A fallback configuration is applied /// if no specific setup is found for a given input. /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { - let fuzz_config = self.fuzz_config(contract_id, test_fn).clone(); - let failure_persist_path = fuzz_config + /// - `contract` is the id of the test contract, expressed as a relative path from the project + /// root. + /// - `function` is the name of the test function declared inside the test contract. + pub fn fuzz_runner(&self, contract: &str, function: &str) -> Result<(FuzzConfig, TestRunner)> { + let config: FuzzConfig = self.figment(contract, function)?.extract_inner("fuzz")?; + let failure_persist_path = config .failure_persist_dir + .as_ref() .unwrap() - .join(fuzz_config.failure_persist_file.unwrap()) + .join(config.failure_persist_file.as_ref().unwrap()) .into_os_string() .into_string() .unwrap(); - self.fuzzer_with_cases( - fuzz_config.runs, - fuzz_config.max_test_rejects, + let runner = fuzzer_with_cases( + config.seed, + config.runs, + config.max_test_rejects, Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), - ) + ); + Ok((config, runner)) } /// Returns an "invariant" test runner instance. Parameters are used to select tight scoped fuzz /// configs that apply for a contract-function pair. A fallback configuration is applied /// if no specific setup is found for a given input. /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { - let invariant = self.invariant_config(contract_id, test_fn); - self.fuzzer_with_cases(invariant.runs, invariant.max_assume_rejects, None) - } - - /// Returns a "fuzz" configuration setup. Parameters are used to select tight scoped fuzz - /// configs that apply for a contract-function pair. A fallback configuration is applied - /// if no specific setup is found for a given input. - /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_config(&self, contract_id: &str, test_fn: &str) -> &FuzzConfig { - self.inline_fuzz.get(contract_id, test_fn).unwrap_or(&self.fuzz) - } - - /// Returns an "invariant" configuration setup. Parameters are used to select tight scoped - /// invariant configs that apply for a contract-function pair. A fallback configuration is - /// applied if no specific setup is found for a given input. - /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_config(&self, contract_id: &str, test_fn: &str) -> &InvariantConfig { - self.inline_invariant.get(contract_id, test_fn).unwrap_or(&self.invariant) - } - - pub fn fuzzer_with_cases( + /// - `contract` is the id of the test contract, expressed as a relative path from the project + /// root. + /// - `function` is the name of the test function declared inside the test contract. + pub fn invariant_runner( &self, - cases: u32, - max_global_rejects: u32, - file_failure_persistence: Option>, - ) -> TestRunner { - let config = proptest::test_runner::Config { - failure_persistence: file_failure_persistence, - cases, - max_global_rejects, - // Disable proptest shrink: for fuzz tests we provide single counterexample, - // for invariant tests we shrink outside proptest. - max_shrink_iters: 0, - ..Default::default() - }; - - if let Some(seed) = &self.fuzz.seed { - trace!(target: "forge::test", %seed, "building deterministic fuzzer"); - let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); - TestRunner::new_with_rng(config, rng) - } else { - trace!(target: "forge::test", "building stochastic fuzzer"); - TestRunner::new(config) - } + contract: &str, + function: &str, + ) -> Result<(InvariantConfig, TestRunner)> { + let figment = self.figment(contract, function)?; + let config: InvariantConfig = figment.extract_inner("invariant")?; + let seed: Option = figment.extract_inner("fuzz.seed").ok(); + let runner = fuzzer_with_cases(seed, config.runs, config.max_assume_rejects, None); + Ok((config, runner)) } } -/// Builder utility to create a [`TestOptions`] instance. -#[derive(Default)] -#[must_use = "builders do nothing unless you call `build` on them"] -pub struct TestOptionsBuilder { - fuzz: Option, - invariant: Option, - profiles: Option>, -} - -impl TestOptionsBuilder { - /// Sets a [`FuzzConfig`] to be used as base "fuzz" configuration. - pub fn fuzz(mut self, conf: FuzzConfig) -> Self { - self.fuzz = Some(conf); - self - } - - /// Sets a [`InvariantConfig`] to be used as base "invariant" configuration. - pub fn invariant(mut self, conf: InvariantConfig) -> Self { - self.invariant = Some(conf); - self - } - - /// Sets available configuration profiles. Profiles are useful to validate existing in-line - /// configurations. This argument is necessary in case a `compile_output`is provided. - pub fn profiles(mut self, p: Vec) -> Self { - self.profiles = Some(p); - self - } - - /// Creates an instance of [`TestOptions`]. This takes care of creating "fuzz" and - /// "invariant" fallbacks, and extracting all inline test configs, if available. - /// - /// `root` is a reference to the user's project root dir. This is essential - /// to determine the base path of generated contract identifiers. This is to provide correct - /// matchers for inline test configs. - pub fn build( - self, - output: &ProjectCompileOutput, - root: &Path, - ) -> Result { - let profiles: Vec = - self.profiles.unwrap_or_else(|| vec![Config::selected_profile().into()]); - let base_fuzz = self.fuzz.unwrap_or_default(); - let base_invariant = self.invariant.unwrap_or_default(); - TestOptions::new(output, root, profiles, base_fuzz, base_invariant) +fn fuzzer_with_cases( + seed: Option, + cases: u32, + max_global_rejects: u32, + file_failure_persistence: Option>, +) -> TestRunner { + let config = proptest::test_runner::Config { + failure_persistence: file_failure_persistence, + cases, + max_global_rejects, + // Disable proptest shrink: for fuzz tests we provide single counterexample, + // for invariant tests we shrink outside proptest. + max_shrink_iters: 0, + ..Default::default() + }; + + if let Some(seed) = seed { + trace!(target: "forge::test", %seed, "building deterministic fuzzer"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(config, rng) + } else { + trace!(target: "forge::test", "building stochastic fuzzer"); + TestRunner::new(config) } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 4b66a482c..fc2b89cb0 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -341,24 +341,26 @@ impl ContractRunner<'_> { self.run_unit_test(func, should_fail, setup) } TestFunctionKind::FuzzTest { should_fail } => { - let runner = test_options.fuzz_runner(self.name, &func.name); - let fuzz_config = test_options.fuzz_config(self.name, &func.name); - - self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config.clone()) + match test_options.fuzz_runner(self.name, &func.name) { + Ok((fuzz_config, runner)) => { + self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config) + } + Err(err) => TestResult::fail(err.to_string()), + } } TestFunctionKind::InvariantTest => { - let runner = test_options.invariant_runner(self.name, &func.name); - let invariant_config = test_options.invariant_config(self.name, &func.name); - - self.run_invariant_test( - runner, - setup, - invariant_config.clone(), - func, - call_after_invariant, - &known_contracts, - identified_contracts.as_ref().unwrap(), - ) + match test_options.invariant_runner(self.name, &func.name) { + Ok((invariant_config, runner)) => self.run_invariant_test( + runner, + setup, + invariant_config, + func, + call_after_invariant, + &known_contracts, + identified_contracts.as_ref().unwrap(), + ), + Err(err) => TestResult::fail(err.to_string()), + } } _ => unreachable!(), }; diff --git a/crates/forge/tests/cli/alphanet.rs b/crates/forge/tests/cli/alphanet.rs index 6e41551ac..49b8c01fc 100644 --- a/crates/forge/tests/cli/alphanet.rs +++ b/crates/forge/tests/cli/alphanet.rs @@ -1,6 +1,10 @@ // Ensure we can run basic counter tests with EOF support. -#[cfg(target_os = "linux")] forgetest_init!(test_eof_flag, |prj, cmd| { + if !has_docker() { + println!("skipping because no docker is available"); + return; + } + cmd.forge_fuse().args(["test", "--eof"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -17,3 +21,12 @@ Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) "#]]); }); + +fn has_docker() -> bool { + if !cfg!(target_os = "linux") { + return false; + } + + // `images` will also check for the daemon. + std::process::Command::new("docker").arg("images").output().is_ok_and(|o| o.status.success()) +} diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 9585b216b..ca54ae0d0 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -140,6 +140,11 @@ Compiler run successful! // tests build output is as expected forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { + prj.write_config(Config { + solc: Some(foundry_config::SolcReq::Version(semver::Version::new(0, 8, 27))), + ..Default::default() + }); + cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![ r#" ... @@ -154,12 +159,12 @@ forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { str![[r#" { "Counter": { - "runtime_size": 247, - "init_size": 277, - "runtime_margin": 24329, - "init_margin": 48875 + "runtime_size": 236, + "init_size": 263, + "runtime_margin": 24340, + "init_margin": 48889 } -} +} "#]] .is_json(), ); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index db87a85ba..72aacff49 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -29,6 +29,8 @@ forgetest!(can_extract_config_values, |prj, cmd| { // explicitly set all values let input = Config { profile: Config::DEFAULT_PROFILE, + // `profiles` is not serialized. + profiles: vec![], root: Default::default(), src: "test-src".into(), test: "test-test".into(), diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs new file mode 100644 index 000000000..de585a48c --- /dev/null +++ b/crates/forge/tests/cli/inline_config.rs @@ -0,0 +1,194 @@ +forgetest!(runs, |prj, cmd| { + prj.add_test( + "inline.sol", + " + contract Inline { + /** forge-config: default.fuzz.runs = 2 */ + function test1(bool) public {} + + \t///\t forge-config:\tdefault.fuzz.runs=\t3 \t + + function test2(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/inline.sol:Inline +[PASS] test1(bool) (runs: 2, [AVG_GAS]) +[PASS] test2(bool) (runs: 3, [AVG_GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + + // Make sure inline config is parsed in coverage too. + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/inline.sol:Inline +[PASS] test1(bool) (runs: 2, [AVG_GAS]) +[PASS] test2(bool) (runs: 3, [AVG_GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) +| File | % Lines | % Statements | % Branches | % Funcs | +|-------|---------------|---------------|---------------|---------------| +| Total | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | + +"#]]); +}); + +forgetest!(invalid_profile, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: unknown.fuzz.runs = 2 */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[r#" +Error: Inline config error at test/inline.sol:0:0:0: invalid profile `unknown.fuzz.runs = 2`; valid profiles: default + +"#]]); +}); + +// TODO: Uncomment once this done for normal config too. +/* +forgetest!(invalid_key, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: default.fuzzz.runs = 2 */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest!(invalid_key_2, |prj, cmd| { + prj.add_test( + "inline.sol", + " +/** forge-config: default.fuzz.runss = 2 */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); +*/ + +forgetest!(invalid_value, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: default.fuzz.runs = [2] */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: invalid type: found sequence, expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: invalid type: found sequence, expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest!(invalid_value_2, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: default.fuzz.runs = '2' */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: invalid type: found string "2", expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: invalid type: found string "2", expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index 5838fa853..d59dbc6be 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -18,6 +18,7 @@ mod debug; mod doc; mod eip712; mod geiger; +mod inline_config; mod multi_script; mod script; mod soldeer; diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 871cda045..11fcdbcfd 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -27,9 +27,9 @@ async fn test_cheats_local(test_data: &ForgeTestData) { filter = filter.exclude_contracts("(LastCallGasDefaultTest|MockFunctionTest|WithSeed)"); } - let mut config = test_data.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = test_data.runner_with_config(config); + let runner = test_data.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); + }); TestConfig::with_filter(runner, filter).run().await; } @@ -38,9 +38,9 @@ async fn test_cheats_local(test_data: &ForgeTestData) { async fn test_cheats_local_isolated(test_data: &ForgeTestData) { let filter = Filter::new(".*", ".*(Isolated)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); - let mut config = test_data.config.clone(); - config.isolate = true; - let runner = test_data.runner_with_config(config); + let runner = test_data.runner_with(|config| { + config.isolate = true; + }); TestConfig::with_filter(runner, filter).run().await; } @@ -49,9 +49,9 @@ async fn test_cheats_local_isolated(test_data: &ForgeTestData) { async fn test_cheats_local_with_seed(test_data: &ForgeTestData) { let filter = Filter::new(".*", ".*(WithSeed)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); - let mut config = test_data.config.clone(); - config.fuzz.seed = Some(U256::from(100)); - let runner = test_data.runner_with_config(config); + let runner = test_data.runner_with(|config| { + config.fuzz.seed = Some(U256::from(100)); + }); TestConfig::with_filter(runner, filter).run().await; } diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs index c8a599195..a2b4916d3 100644 --- a/crates/forge/tests/it/core.rs +++ b/crates/forge/tests/it/core.rs @@ -758,9 +758,9 @@ async fn test_trace() { #[tokio::test(flavor = "multi_thread")] async fn test_assertions_revert_false() { let filter = Filter::new(".*", ".*NoAssertionsRevertTest", ".*"); - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.assertions_revert = false; - let mut runner = TEST_DATA_DEFAULT.runner_with_config(config); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.assertions_revert = false; + }); let results = runner.test_collect(&filter); assert_multiple( @@ -784,9 +784,9 @@ async fn test_assertions_revert_false() { #[tokio::test(flavor = "multi_thread")] async fn test_legacy_assertions() { let filter = Filter::new(".*", ".*LegacyAssertions", ".*"); - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.legacy_assertions = true; - let mut runner = TEST_DATA_DEFAULT.runner_with_config(config); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.legacy_assertions = true; + }); let results = runner.test_collect(&filter); assert_multiple( diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs index 8dc637528..5974a12ed 100644 --- a/crates/forge/tests/it/fork.rs +++ b/crates/forge/tests/it/fork.rs @@ -35,9 +35,9 @@ async fn test_cheats_fork_revert() { /// Executes all non-reverting fork cheatcodes #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork() { - let mut config = TEST_DATA_PARIS.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = TEST_DATA_PARIS.runner_with_config(config); + let runner = TEST_DATA_PARIS.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); let filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); TestConfig::with_filter(runner, filter).run().await; @@ -46,9 +46,9 @@ async fn test_cheats_fork() { /// Executes eth_getLogs cheatcode #[tokio::test(flavor = "multi_thread")] async fn test_get_logs_fork() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); let filter = Filter::new("testEthGetLogs", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); TestConfig::with_filter(runner, filter).run().await; @@ -57,9 +57,9 @@ async fn test_get_logs_fork() { /// Executes rpc cheatcode #[tokio::test(flavor = "multi_thread")] async fn test_rpc_fork() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); let filter = Filter::new("testRpc", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); TestConfig::with_filter(runner, filter).run().await; @@ -102,25 +102,25 @@ async fn test_create_same_fork() { /// Test that `no_storage_caching` config is properly applied #[tokio::test(flavor = "multi_thread")] async fn test_storage_caching_config() { - // no_storage_caching set to true: storage should not be cached - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.no_storage_caching = true; - let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new("testStorageCaching", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner, filter).run().await; + + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.no_storage_caching = true; + }); + + // no_storage_caching set to true: storage should not be cached + TestConfig::with_filter(runner, filter.clone()).run().await; let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); let _ = fs::remove_file(cache_dir); - // no_storage_caching set to false: storage should be cached - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.no_storage_caching = false; - let runner = TEST_DATA_DEFAULT.runner_with_config(config); - let filter = - Filter::new("testStorageCaching", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) - .exclude_tests(".*Revert"); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.no_storage_caching = false; + }); TestConfig::with_filter(runner, filter).run().await; + + // no_storage_caching set to false: storage should be cached let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); assert!(cache_dir.exists()); diff --git a/crates/forge/tests/it/fs.rs b/crates/forge/tests/it/fs.rs index 5bb0b59fb..5733ec584 100644 --- a/crates/forge/tests/it/fs.rs +++ b/crates/forge/tests/it/fs.rs @@ -6,18 +6,18 @@ use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_fs_disabled() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); - let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); + }); let filter = Filter::new(".*", ".*", ".*fs/Disabled"); TestConfig::with_filter(runner, filter).run().await; } #[tokio::test(flavor = "multi_thread")] async fn test_fs_default() { - let mut config = TEST_DATA_DEFAULT.config.clone(); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); let filter = Filter::new(".*", ".*", ".*fs/Default"); TestConfig::with_filter(runner, filter).run().await; } diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 8972c9bd9..eaa627b96 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -82,11 +82,12 @@ async fn test_successful_fuzz_cases() { #[ignore] async fn test_fuzz_collection() { let filter = Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.depth = 100; - runner.test_options.invariant.runs = 1000; - runner.test_options.fuzz.runs = 1000; - runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.depth = 100; + config.invariant.runs = 1000; + config.fuzz.runs = 1000; + config.fuzz.seed = Some(U256::from(6u32)); + }); let results = runner.test_collect(&filter); assert_multiple( @@ -111,11 +112,14 @@ async fn test_fuzz_collection() { #[tokio::test(flavor = "multi_thread")] async fn test_persist_fuzz_failure() { let filter = Filter::new(".*", ".*", ".*fuzz/FuzzFailurePersist.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.runs = 1000; - macro_rules! get_failure_result { - () => { + macro_rules! run_fail { + () => { run_fail!(|config| {}) }; + (|$config:ident| $e:expr) => {{ + let mut runner = TEST_DATA_DEFAULT.runner_with(|$config| { + $config.fuzz.runs = 1000; + $e + }); runner .test_collect(&filter) .get("default/fuzz/FuzzFailurePersist.t.sol:FuzzFailurePersistTest") @@ -125,11 +129,11 @@ async fn test_persist_fuzz_failure() { .unwrap() .counterexample .clone() - }; + }}; } // record initial counterexample calldata - let initial_counterexample = get_failure_result!(); + let initial_counterexample = run_fail!(); let initial_calldata = match initial_counterexample { Some(CounterExample::Single(counterexample)) => counterexample.calldata, _ => Bytes::new(), @@ -137,7 +141,7 @@ async fn test_persist_fuzz_failure() { // run several times and compare counterexamples calldata for i in 0..10 { - let new_calldata = match get_failure_result!() { + let new_calldata = match run_fail!() { Some(CounterExample::Single(counterexample)) => counterexample.calldata, _ => Bytes::new(), }; @@ -146,8 +150,9 @@ async fn test_persist_fuzz_failure() { } // write new failure in different file - runner.test_options.fuzz.failure_persist_file = Some("failure1".to_string()); - let new_calldata = match get_failure_result!() { + let new_calldata = match run_fail!(|config| { + config.fuzz.failure_persist_file = Some("failure1".to_string()); + }) { Some(CounterExample::Single(counterexample)) => counterexample.calldata, _ => Bytes::new(), }; diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 4448f982d..eab7f9ec1 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -1,15 +1,13 @@ //! Inline configuration tests. -use crate::test_helpers::{ForgeTestData, ForgeTestProfile, TEST_DATA_DEFAULT}; -use forge::{result::TestKind, TestOptionsBuilder}; -use foundry_config::{FuzzConfig, InvariantConfig}; +use crate::test_helpers::TEST_DATA_DEFAULT; +use forge::result::TestKind; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_fuzz() { let filter = Filter::new(".*", ".*", ".*inline/FuzzInlineConf.t.sol"); - // Fresh runner to make sure there's no persisted failure from previous tests. - let mut runner = ForgeTestData::new(ForgeTestProfile::Default).runner(); + let mut runner = TEST_DATA_DEFAULT.runner(); let result = runner.test_collect(&filter); let results = result .into_iter() @@ -70,31 +68,3 @@ async fn inline_config_run_invariant() { _ => unreachable!(), } } - -#[test] -fn build_test_options() { - let root = &TEST_DATA_DEFAULT.project.paths.root; - let profiles = vec!["default".to_string(), "ci".to_string()]; - let build_result = TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .profiles(profiles) - .build(&TEST_DATA_DEFAULT.output, root); - - assert!(build_result.is_ok()); -} - -#[test] -fn build_test_options_just_one_valid_profile() { - let root = &TEST_DATA_DEFAULT.project.root(); - let valid_profiles = vec!["profile-sheldon-cooper".to_string()]; - let build_result = TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .profiles(valid_profiles) - .build(&TEST_DATA_DEFAULT.output, root); - - // We expect an error, since COMPILED contains in-line - // per-test configs for "default" and "ci" profiles - assert!(build_result.is_err()); -} diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 3e09cd465..2f4da4054 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -48,8 +48,9 @@ async fn test_invariant_with_alias() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_filters() { - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.runs = 10; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.runs = 10; + }); // Contracts filter tests. assert_multiple( @@ -173,9 +174,10 @@ async fn test_invariant_filters() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_override() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = false; - runner.test_options.invariant.call_override = true; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = false; + config.invariant.call_override = true; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -189,10 +191,11 @@ async fn test_invariant_override() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_fail_on_revert() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = true; - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 10; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 10; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -213,9 +216,13 @@ async fn test_invariant_fail_on_revert() { #[ignore] async fn test_invariant_storage() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.depth = 100 + (50 * cfg!(windows) as u32); - runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.depth = 100; + if cfg!(windows) { + config.invariant.depth += 50; + } + config.fuzz.seed = Some(U256::from(6u32)); + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -254,8 +261,9 @@ async fn test_invariant_inner_contract() { #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_invariant_shrink() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.seed = Some(U256::from(119u32)); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + }); match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), @@ -300,10 +308,11 @@ async fn test_invariant_require_shrink() { async fn check_shrink_sequence(test_pattern: &str, expected_len: usize) { let filter = Filter::new(test_pattern, ".*", ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.seed = Some(U256::from(100u32)); - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 15; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 1; + config.invariant.depth = 15; + }); match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), @@ -318,10 +327,11 @@ async fn check_shrink_sequence(test_pattern: &str, expected_len: usize) { async fn test_shrink_big_sequence() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkBigSequence.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.seed = Some(U256::from(119u32)); - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 1000; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.runs = 1; + config.invariant.depth = 1000; + }); let initial_counterexample = runner .test_collect(&filter) @@ -390,11 +400,12 @@ async fn test_shrink_big_sequence() { async fn test_shrink_fail_on_revert() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.seed = Some(U256::from(119u32)); - runner.test_options.invariant.fail_on_revert = true; - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 200; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 200; + }); match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), @@ -408,8 +419,9 @@ async fn test_shrink_fail_on_revert() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_preserve_state() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantPreserveState.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = true; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -452,9 +464,10 @@ async fn test_invariant_with_address_fixture() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_assume_does_not_revert() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - // Should not treat vm.assume as revert. - runner.test_options.invariant.fail_on_revert = true; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + // Should not treat vm.assume as revert. + config.invariant.fail_on_revert = true; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -468,10 +481,11 @@ async fn test_invariant_assume_does_not_revert() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_assume_respects_restrictions() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 10; - runner.test_options.invariant.max_assume_rejects = 1; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.max_assume_rejects = 1; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -491,8 +505,9 @@ async fn test_invariant_assume_respects_restrictions() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_decode_custom_error() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantCustomError.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = true; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -512,8 +527,9 @@ async fn test_invariant_decode_custom_error() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_fuzzed_selected_targets() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/target/FuzzedTargetContracts.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = true; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -539,9 +555,10 @@ async fn test_invariant_fuzzed_selected_targets() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_fixtures() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantFixtures.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 100; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.runs = 1; + config.invariant.depth = 100; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -592,8 +609,9 @@ async fn test_invariant_scrape_values() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_roll_fork_handler() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantRollFork.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.seed = Some(U256::from(119u32)); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -625,8 +643,9 @@ async fn test_invariant_roll_fork_handler() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_excluded_senders() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantExcludedSenders.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = true; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -670,10 +689,11 @@ async fn test_invariant_after_invariant() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_selectors_weight() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSelectorsWeight.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.fuzz.seed = Some(U256::from(119u32)); - runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 10; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.runs = 1; + config.invariant.depth = 10; + }); let results = runner.test_collect(&filter); assert_multiple( &results, @@ -688,10 +708,11 @@ async fn test_invariant_selectors_weight() { async fn test_no_reverts_in_counterexample() { let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSequenceNoReverts.t.sol"); - let mut runner = TEST_DATA_DEFAULT.runner(); - runner.test_options.invariant.fail_on_revert = false; - // Use original counterexample to test sequence len. - runner.test_options.invariant.shrink_run_limit = 0; + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = false; + // Use original counterexample to test sequence len. + config.invariant.shrink_run_limit = 0; + }); match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 53185cf97..69c3a0fb3 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -1,11 +1,6 @@ //! Regression tests for previous issues. -use std::sync::Arc; - -use crate::{ - config::*, - test_helpers::{ForgeTestData, TEST_DATA_DEFAULT}, -}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; use alloy_json_abi::Event; use alloy_primitives::{address, b256, Address, U256}; @@ -19,6 +14,7 @@ use foundry_evm::{ traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}, }; use foundry_test_utils::Filter; +use std::sync::Arc; /// Creates a test that runs `testdata/repros/Issue{issue}.t.sol`. macro_rules! test_repro { @@ -33,7 +29,7 @@ macro_rules! test_repro { #[tokio::test(flavor = "multi_thread")] $(#[$attr])* async fn [< issue_ $issue_number >]() { - repro_config($issue_number, $should_fail, $sender.into(), &*TEST_DATA_DEFAULT).await.run().await; + repro_config($issue_number, $should_fail, $sender.into()).await.run().await; } } }; @@ -42,7 +38,7 @@ macro_rules! test_repro { #[tokio::test(flavor = "multi_thread")] $(#[$attr])* async fn [< issue_ $issue_number >]() { - let mut $res = repro_config($issue_number, $should_fail, $sender.into(), &*TEST_DATA_DEFAULT).await.test(); + let mut $res = repro_config($issue_number, $should_fail, $sender.into()).await.test(); $e } } @@ -52,7 +48,7 @@ macro_rules! test_repro { #[tokio::test(flavor = "multi_thread")] $(#[$attr])* async fn [< issue_ $issue_number >]() { - let mut $config = repro_config($issue_number, false, None, &*TEST_DATA_DEFAULT).await; + let mut $config = repro_config($issue_number, false, None).await; $e $config.run().await; } @@ -60,23 +56,19 @@ macro_rules! test_repro { }; } -async fn repro_config( - issue: usize, - should_fail: bool, - sender: Option

, - test_data: &ForgeTestData, -) -> TestConfig { +async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { foundry_test_utils::init_tracing(); let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); - let mut config = test_data.config.clone(); - config.fs_permissions = - FsPermissions::new(vec![PathPermission::read("./fixtures"), PathPermission::read("out")]); - if let Some(sender) = sender { - config.sender = sender; - } - - let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![ + PathPermission::read("./fixtures"), + PathPermission::read("out"), + ]); + if let Some(sender) = sender { + config.sender = sender; + } + }); TestConfig::with_filter(runner, filter).set_should_fail(should_fail) } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 5e540d8c6..298bbae29 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -4,7 +4,6 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; use forge::{ revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder, TestOptions, - TestOptionsBuilder, }; use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, @@ -15,10 +14,7 @@ use foundry_config::{ fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, InvariantConfig, RpcEndpoint, RpcEndpoints, }; -use foundry_evm::{ - constants::CALLER, - opts::{Env, EvmOpts}, -}; +use foundry_evm::{constants::CALLER, opts::EvmOpts}; use foundry_test_utils::{fd_lock, init_tracing, rpc::next_rpc_endpoint}; use std::{ env, fmt, @@ -74,69 +70,6 @@ impl ForgeTestProfile { SolcConfig { settings } } - pub fn project(&self) -> Project { - self.config().project().expect("Failed to build project") - } - - pub fn test_opts(&self, output: &ProjectCompileOutput) -> TestOptions { - TestOptionsBuilder::default() - .fuzz(FuzzConfig { - runs: 256, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig { - include_storage: true, - include_push_bytes: true, - dictionary_weight: 40, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - gas_report_samples: 256, - failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), - failure_persist_file: Some("testfailure".to_string()), - show_logs: false, - }) - .invariant(InvariantConfig { - runs: 256, - depth: 15, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { - dictionary_weight: 80, - include_storage: true, - include_push_bytes: true, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - shrink_run_limit: 5000, - max_assume_rejects: 65536, - gas_report_samples: 256, - failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), - show_metrics: false, - }) - .build(output, Path::new(self.project().root())) - .expect("Config loaded") - } - - pub fn evm_opts(&self) -> EvmOpts { - EvmOpts { - env: Env { - gas_limit: u64::MAX, - chain_id: None, - tx_origin: CALLER, - block_number: 1, - block_timestamp: 1, - ..Default::default() - }, - sender: CALLER, - initial_balance: U256::MAX, - ffi: true, - verbosity: 3, - memory_limit: 1 << 26, - ..Default::default() - } - } - /// Build [Config] for test profile. /// /// Project source files are read from testdata/{profile_name} @@ -155,11 +88,66 @@ impl ForgeTestProfile { "fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(), ]; + config.prompt_timeout = 0; + + config.gas_limit = u64::MAX.into(); + config.chain = None; + config.tx_origin = CALLER; + config.block_number = 1; + config.block_timestamp = 1; + + config.sender = CALLER; + config.initial_balance = U256::MAX; + config.ffi = true; + config.verbosity = 3; + config.memory_limit = 1 << 26; + if self.is_paris() { config.evm_version = EvmVersion::Paris; } - config + config.fuzz = FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig { + include_storage: true, + include_push_bytes: true, + dictionary_weight: 40, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + gas_report_samples: 256, + failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), + failure_persist_file: Some("testfailure".to_string()), + show_logs: false, + }; + config.invariant = InvariantConfig { + runs: 256, + depth: 15, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { + dictionary_weight: 80, + include_storage: true, + include_push_bytes: true, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: Some( + tempfile::Builder::new() + .prefix(&format!("foundry-{self}")) + .tempdir() + .unwrap() + .into_path(), + ), + show_metrics: false, + }; + + config.sanitized() } } @@ -167,9 +155,7 @@ impl ForgeTestProfile { pub struct ForgeTestData { pub project: Project, pub output: ProjectCompileOutput, - pub test_opts: TestOptions, - pub evm_opts: EvmOpts, - pub config: Config, + pub config: Arc, pub profile: ForgeTestProfile, } @@ -179,67 +165,63 @@ impl ForgeTestData { /// Uses [get_compiled] to lazily compile the project. pub fn new(profile: ForgeTestProfile) -> Self { init_tracing(); - - let mut project = profile.project(); + let config = Arc::new(profile.config()); + let mut project = config.project().unwrap(); let output = get_compiled(&mut project); - let test_opts = profile.test_opts(&output); - let config = profile.config(); - let evm_opts = profile.evm_opts(); - - Self { project, output, test_opts, evm_opts, config, profile } + Self { project, output, config, profile } } /// Builds a base runner pub fn base_runner(&self) -> MultiContractRunnerBuilder { init_tracing(); - let mut runner = MultiContractRunnerBuilder::new(Arc::new(self.config.clone())) - .sender(self.evm_opts.sender) - .with_test_options(self.test_opts.clone()); + let config = self.config.clone(); + let mut runner = MultiContractRunnerBuilder::new(config.clone()) + .sender(self.config.sender) + .with_test_options(TestOptions::new_unparsed(config)); if self.profile.is_paris() { runner = runner.evm_spec(SpecId::MERGE); } - runner } /// Builds a non-tracing runner pub fn runner(&self) -> MultiContractRunner { - let mut config = self.config.clone(); - config.fs_permissions = - FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); - self.runner_with_config(config) + self.runner_with(|_| {}) } /// Builds a non-tracing runner - pub fn runner_with_config(&self, mut config: Config) -> MultiContractRunner { + pub fn runner_with(&self, modify: impl FnOnce(&mut Config)) -> MultiContractRunner { + let mut config = (*self.config).clone(); + modify(&mut config); + self.runner_with_config(config) + } + + fn runner_with_config(&self, mut config: Config) -> MultiContractRunner { config.rpc_endpoints = rpc_endpoints(); config.allow_paths.push(manifest_root().to_path_buf()); - // no prompt testing - config.prompt_timeout = 0; - - let root = self.project.root(); - let mut opts = self.evm_opts.clone(); - - if config.isolate { - opts.isolate = true; + if config.fs_permissions.is_empty() { + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); } - let sender = config.sender; + let opts = config_evm_opts(&config); let mut builder = self.base_runner(); - builder.config = Arc::new(config); + let config = Arc::new(config); + let root = self.project.root(); + builder.config = config.clone(); builder .enable_isolation(opts.isolate) - .sender(sender) - .with_test_options(self.test_opts.clone()) + .sender(config.sender) + .with_test_options(TestOptions::new(&self.output, config.clone()).unwrap()) .build(root, &self.output, opts.local_evm_env(), opts) .unwrap() } /// Builds a tracing runner pub fn tracing_runner(&self) -> MultiContractRunner { - let mut opts = self.evm_opts.clone(); + let mut opts = config_evm_opts(&self.config); opts.verbosity = 5; self.base_runner() .build(self.project.root(), &self.output, opts.local_evm_env(), opts) @@ -248,7 +230,7 @@ impl ForgeTestData { /// Builds a runner that runs against forked state pub async fn forked_runner(&self, rpc: &str) -> MultiContractRunner { - let mut opts = self.evm_opts.clone(); + let mut opts = config_evm_opts(&self.config); opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC opts.fork_url = Some(rpc.to_string()); @@ -369,3 +351,7 @@ pub fn rpc_endpoints() -> RpcEndpoints { ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".into())), ]) } + +fn config_evm_opts(config: &Config) -> EvmOpts { + config.to_figment(foundry_config::FigmentProviders::None).extract().unwrap() +} diff --git a/crates/test-utils/src/filter.rs b/crates/test-utils/src/filter.rs index 003b0170f..1ba905d27 100644 --- a/crates/test-utils/src/filter.rs +++ b/crates/test-utils/src/filter.rs @@ -2,6 +2,7 @@ use foundry_common::TestFilter; use regex::Regex; use std::path::Path; +#[derive(Clone, Debug)] pub struct Filter { test_regex: Regex, contract_regex: Regex, From 20905ef9491f86c45415bf8ec764fbda31b83f54 Mon Sep 17 00:00:00 2001 From: Delweng Date: Fri, 29 Nov 2024 03:56:28 +0800 Subject: [PATCH 027/107] chore: rename the arg name of EvmOpts from evm_opts to evm_args (#9424) * script: evm_opts -> evm_args Signed-off-by: jsvisa * forge: evm_opts -> evm_args Signed-off-by: jsvisa * chisel: evm_opts -> evm_args Signed-off-by: jsvisa * forge: evm_opts -> evm_args Signed-off-by: jsvisa --------- Signed-off-by: jsvisa --- crates/chisel/bin/main.rs | 4 ++-- crates/forge/bin/cmd/config.rs | 4 ++-- crates/forge/bin/cmd/debug.rs | 6 +++--- crates/forge/bin/cmd/mod.rs | 2 +- crates/forge/bin/cmd/test/mod.rs | 6 +++--- crates/script/src/execute.rs | 2 +- crates/script/src/lib.rs | 10 +++++----- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index acc2c6f17..ca3fc1ff5 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -35,7 +35,7 @@ extern crate foundry_common; static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; // Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(Chisel, opts, evm_opts); +foundry_config::merge_impl_figment_convert!(Chisel, opts, evm_args); const VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), @@ -76,7 +76,7 @@ pub struct Chisel { pub opts: CoreBuildArgs, #[command(flatten)] - pub evm_opts: EvmArgs, + pub evm_args: EvmArgs, } /// Chisel binary subcommands diff --git a/crates/forge/bin/cmd/config.rs b/crates/forge/bin/cmd/config.rs index 36f4d1731..0aa1fdb63 100644 --- a/crates/forge/bin/cmd/config.rs +++ b/crates/forge/bin/cmd/config.rs @@ -5,7 +5,7 @@ use foundry_cli::utils::LoadConfig; use foundry_common::{evm::EvmArgs, shell}; use foundry_config::fix::fix_tomls; -foundry_config::impl_figment_convert!(ConfigArgs, opts, evm_opts); +foundry_config::impl_figment_convert!(ConfigArgs, opts, evm_args); /// CLI arguments for `forge config`. #[derive(Clone, Debug, Parser)] @@ -23,7 +23,7 @@ pub struct ConfigArgs { opts: BuildArgs, #[command(flatten)] - evm_opts: EvmArgs, + evm_args: EvmArgs, } impl ConfigArgs { diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs index 421478bd5..5ccfc13d5 100644 --- a/crates/forge/bin/cmd/debug.rs +++ b/crates/forge/bin/cmd/debug.rs @@ -6,7 +6,7 @@ use foundry_common::evm::EvmArgs; use std::path::PathBuf; // Loads project's figment and merges the build cli arguments into it -foundry_config::impl_figment_convert!(DebugArgs, opts, evm_opts); +foundry_config::impl_figment_convert!(DebugArgs, opts, evm_args); /// CLI arguments for `forge debug`. #[derive(Clone, Debug, Parser)] @@ -46,7 +46,7 @@ pub struct DebugArgs { pub opts: CoreBuildArgs, #[command(flatten)] - pub evm_opts: EvmArgs, + pub evm_args: EvmArgs, } impl DebugArgs { @@ -58,7 +58,7 @@ impl DebugArgs { sig: self.sig, gas_estimate_multiplier: 130, opts: self.opts, - evm_opts: self.evm_opts, + evm_args: self.evm_args, debug: true, dump: self.dump, retry: RETRY_VERIFY_ON_CREATE, diff --git a/crates/forge/bin/cmd/mod.rs b/crates/forge/bin/cmd/mod.rs index f2de1d632..427b25fb0 100644 --- a/crates/forge/bin/cmd/mod.rs +++ b/crates/forge/bin/cmd/mod.rs @@ -24,7 +24,7 @@ //! #[derive(Clone, Debug, Parser)] //! pub struct MyArgs { //! #[command(flatten)] -//! evm_opts: EvmArgs, +//! evm_args: EvmArgs, //! #[command(flatten)] //! opts: BuildArgs, //! } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6e19c96d0..982a8f0a3 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -59,7 +59,7 @@ pub use filter::FilterArgs; use forge::{result::TestKind, traces::render_trace_arena_inner}; // Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); +foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_args); /// CLI arguments for `forge test`. #[derive(Clone, Debug, Parser)] @@ -162,7 +162,7 @@ pub struct TestArgs { pub rerun: bool, #[command(flatten)] - evm_opts: EvmArgs, + evm_args: EvmArgs, #[command(flatten)] opts: CoreBuildArgs, @@ -1001,7 +1001,7 @@ mod tests { fn extract_chain() { let test = |arg: &str, expected: Chain| { let args = TestArgs::parse_from(["foundry-cli", arg]); - assert_eq!(args.evm_opts.env.chain, Some(expected)); + assert_eq!(args.evm_args.env.chain, Some(expected)); let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); assert_eq!(config.chain, Some(expected)); assert_eq!(evm_opts.env.chain_id, Some(expected.id())); diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 51ab01414..3e6cc30a1 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -189,7 +189,7 @@ impl PreExecutionState { if let Some(txs) = transactions { // If the user passed a `--sender` don't check anything. if self.build_data.predeploy_libraries.libraries_count() > 0 && - self.args.evm_opts.sender.is_none() + self.args.evm_args.sender.is_none() { for tx in txs.iter() { if tx.transaction.to().is_none() { diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 517eff552..6ccc81841 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -73,7 +73,7 @@ mod transaction; mod verify; // Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(ScriptArgs, opts, evm_opts); +foundry_config::merge_impl_figment_convert!(ScriptArgs, opts, evm_args); /// CLI arguments for `forge script`. #[derive(Clone, Debug, Default, Parser)] @@ -210,7 +210,7 @@ pub struct ScriptArgs { pub wallets: MultiWalletOpts, #[command(flatten)] - pub evm_opts: EvmArgs, + pub evm_args: EvmArgs, #[command(flatten)] pub verifier: forge_verify::VerifierArgs, @@ -222,7 +222,7 @@ pub struct ScriptArgs { impl ScriptArgs { pub async fn preprocess(self) -> Result { let script_wallets = - Wallets::new(self.wallets.get_multi_wallet().await?, self.evm_opts.sender); + Wallets::new(self.wallets.get_multi_wallet().await?, self.evm_args.sender); let (config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; @@ -411,7 +411,7 @@ impl ScriptArgs { } let mut prompt_user = false; - let max_size = match self.evm_opts.env.code_size_limit { + let max_size = match self.evm_args.env.code_size_limit { Some(size) => size, None => CONTRACT_MAX_SIZE, }; @@ -723,7 +723,7 @@ mod tests { "--code-size-limit", "50000", ]); - assert_eq!(args.evm_opts.env.code_size_limit, Some(50000)); + assert_eq!(args.evm_args.env.code_size_limit, Some(50000)); } #[test] From 27cabbd6c905b1273a5ed3ba7c10acce90833d76 Mon Sep 17 00:00:00 2001 From: Voronor <129545215+voronor@users.noreply.github.com> Date: Thu, 28 Nov 2024 20:57:46 +0100 Subject: [PATCH 028/107] Fix conditional syntax issue in macOS libusb check (#9384) This pull request addresses a minor but important syntax issue in the conditional statement used to check for the presence of libusb on macOS. --- foundryup/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundryup/install b/foundryup/install index 1a8bc8c6e..22870f939 100755 --- a/foundryup/install +++ b/foundryup/install @@ -54,7 +54,7 @@ if [[ ":$PATH:" != *":${FOUNDRY_BIN_DIR}:"* ]]; then fi # Warn MacOS users that they may need to manually install libusb via Homebrew: -if [[ "$OSTYPE" =~ ^darwin ]] && [[ ! -f /usr/local/opt/libusb/lib/libusb-1.0.0.dylib && ! -f /opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib ]]; then +if [[ "$OSTYPE" =~ ^darwin ]] && [[ ! -f /usr/local/opt/libusb/lib/libusb-1.0.0.dylib ]] && [[ ! -f /opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib ]]; then echo && echo "warning: libusb not found. You may need to install it manually on MacOS via Homebrew (brew install libusb)." fi From 2e9f53632a787323318e4575d7a0325ef3e7cc84 Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Fri, 29 Nov 2024 01:21:24 -0500 Subject: [PATCH 029/107] feat: add timeouts to fuzz testing (#9394) * feat: add timeouts to fuzz testing Adds --fuzz-timeout-secs to fuzz tests which will cause a property test to timeout after a certain number of seconds. Also adds --fuzz-allow-timeouts so that timeouts are optionally not considered to be failures. * simplify timeout implementation * use u32 for timeout * switch back to failing for timeouts * clippy * Nits: - move logic to interrupt invariant test in depth loop - add and reuse start_timer fn and TEST_TIMEOUT constant - add fuzz and invariant tests - fix failing test * Fix fmt * Changes after review: introduce FuzzTestTimer --------- Co-authored-by: grandizzy --- crates/config/src/fuzz.rs | 3 ++ crates/config/src/invariant.rs | 4 ++ crates/evm/core/src/constants.rs | 3 ++ crates/evm/evm/src/executors/fuzz/mod.rs | 34 +++++++++---- crates/evm/evm/src/executors/invariant/mod.rs | 15 +++++- crates/evm/evm/src/executors/mod.rs | 22 +++++++- crates/forge/bin/cmd/test/mod.rs | 7 +++ crates/forge/tests/it/fuzz.rs | 35 +++++++++++++ crates/forge/tests/it/invariant.rs | 50 +++++++++++++++++++ crates/forge/tests/it/test_helpers.rs | 2 + 10 files changed, 162 insertions(+), 13 deletions(-) diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index ae3d3c796..26e1c080c 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -28,6 +28,8 @@ pub struct FuzzConfig { pub failure_persist_file: Option, /// show `console.log` in fuzz test, defaults to `false` pub show_logs: bool, + /// Optional timeout (in seconds) for each property test + pub timeout: Option, } impl Default for FuzzConfig { @@ -41,6 +43,7 @@ impl Default for FuzzConfig { failure_persist_dir: None, failure_persist_file: None, show_logs: false, + timeout: None, } } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 97f189b36..334cf3b87 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -30,6 +30,8 @@ pub struct InvariantConfig { pub failure_persist_dir: Option, /// Whether to collect and display fuzzed selectors metrics. pub show_metrics: bool, + /// Optional timeout (in seconds) for each invariant test. + pub timeout: Option, } impl Default for InvariantConfig { @@ -45,6 +47,7 @@ impl Default for InvariantConfig { gas_report_samples: 256, failure_persist_dir: None, show_metrics: false, + timeout: None, } } } @@ -63,6 +66,7 @@ impl InvariantConfig { gas_report_samples: 256, failure_persist_dir: Some(cache_dir), show_metrics: false, + timeout: None, } } diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index 70c7441d2..cebbdcb87 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -37,6 +37,9 @@ pub const MAGIC_ASSUME: &[u8] = b"FOUNDRY::ASSUME"; /// Magic return value returned by the `skip` cheatcode. Optionally appended with a reason. pub const MAGIC_SKIP: &[u8] = b"FOUNDRY::SKIP"; +/// Test timeout return value. +pub const TEST_TIMEOUT: &str = "FOUNDRY::TEST_TIMEOUT"; + /// The address that deploys the default CREATE2 deployer contract. pub const DEFAULT_CREATE2_DEPLOYER_DEPLOYER: Address = address!("3fAB184622Dc19b6109349B94811493BF2a45362"); diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 2bbe80a63..0d79f8fa4 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -1,4 +1,4 @@ -use crate::executors::{Executor, RawCallResult}; +use crate::executors::{Executor, FuzzTestTimer, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; use alloy_primitives::{map::HashMap, Address, Bytes, Log, U256}; @@ -6,7 +6,7 @@ use eyre::Result; use foundry_common::evm::Breakpoints; use foundry_config::FuzzConfig; use foundry_evm_core::{ - constants::MAGIC_ASSUME, + constants::{MAGIC_ASSUME, TEST_TIMEOUT}, decode::{RevertDecoder, SkipReason}, }; use foundry_evm_coverage::HitMaps; @@ -98,7 +98,15 @@ impl FuzzedExecutor { let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; let show_logs = self.config.show_logs; + // Start timer for this fuzz test. + let timer = FuzzTestTimer::new(self.config.timeout); + let run_result = self.runner.clone().run(&strategy, |calldata| { + // Check if the timeout has been reached. + if timer.is_timed_out() { + return Err(TestCaseError::fail(TEST_TIMEOUT)); + } + let fuzz_res = self.single_fuzz(address, should_fail, calldata)?; // If running with progress then increment current run. @@ -193,17 +201,21 @@ impl FuzzedExecutor { } Err(TestError::Fail(reason, _)) => { let reason = reason.to_string(); - result.reason = (!reason.is_empty()).then_some(reason); - - let args = if let Some(data) = calldata.get(4..) { - func.abi_decode_input(data, false).unwrap_or_default() + if reason == TEST_TIMEOUT { + // If the reason is a timeout, we consider the fuzz test successful. + result.success = true; } else { - vec![] - }; + result.reason = (!reason.is_empty()).then_some(reason); + let args = if let Some(data) = calldata.get(4..) { + func.abi_decode_input(data, false).unwrap_or_default() + } else { + vec![] + }; - result.counterexample = Some(CounterExample::Single( - BaseCounterExample::from_fuzz_call(calldata, args, call.traces), - )); + result.counterexample = Some(CounterExample::Single( + BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + )); + } } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index f98dd2111..d5fdb5668 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -10,6 +10,7 @@ use foundry_config::InvariantConfig; use foundry_evm_core::{ constants::{ CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME, + TEST_TIMEOUT, }, precompiles::PRECOMPILES, }; @@ -49,7 +50,7 @@ pub use result::InvariantFuzzTestResult; use serde::{Deserialize, Serialize}; mod shrink; -use crate::executors::EvmError; +use crate::executors::{EvmError, FuzzTestTimer}; pub use shrink::check_sequence; sol! { @@ -332,6 +333,9 @@ impl<'a> InvariantExecutor<'a> { let (invariant_test, invariant_strategy) = self.prepare_test(&invariant_contract, fuzz_fixtures)?; + // Start timer for this invariant test. + let timer = FuzzTestTimer::new(self.config.timeout); + let _ = self.runner.run(&invariant_strategy, |first_input| { // Create current invariant run data. let mut current_run = InvariantTestRun::new( @@ -347,6 +351,15 @@ impl<'a> InvariantExecutor<'a> { } while current_run.depth < self.config.depth { + // Check if the timeout has been reached. + if timer.is_timed_out() { + // Since we never record a revert here the test is still considered + // successful even though it timed out. We *want* + // this behavior for now, so that's ok, but + // future developers should be aware of this. + return Err(TestCaseError::fail(TEST_TIMEOUT)); + } + let tx = current_run.inputs.last().ok_or_else(|| { TestCaseError::fail("No input generated to call fuzzed target.") })?; diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index b5b31b812..2ccfad9e2 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -35,7 +35,10 @@ use revm::{ ResultAndState, SignedAuthorization, SpecId, TxEnv, TxKind, }, }; -use std::borrow::Cow; +use std::{ + borrow::Cow, + time::{Duration, Instant}, +}; mod builder; pub use builder::ExecutorBuilder; @@ -952,3 +955,20 @@ fn convert_executed_result( chisel_state, }) } + +/// Timer for a fuzz test. +pub struct FuzzTestTimer { + /// Inner fuzz test timer - (test start time, test duration). + inner: Option<(Instant, Duration)>, +} + +impl FuzzTestTimer { + pub fn new(timeout: Option) -> Self { + Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) } + } + + /// Whether the current fuzz test timed out and should be stopped. + pub fn is_timed_out(&self) -> bool { + self.inner.is_some_and(|(start, duration)| start.elapsed() > duration) + } +} diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 982a8f0a3..18ab08d6d 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -145,6 +145,10 @@ pub struct TestArgs { #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, + /// Timeout for each fuzz run in seconds. + #[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")] + pub fuzz_timeout: Option, + /// File to rerun fuzz failures from. #[arg(long)] pub fuzz_input_file: Option, @@ -864,6 +868,9 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_timeout) = self.fuzz_timeout { + fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into()); + } if let Some(fuzz_input_file) = self.fuzz_input_file.clone() { fuzz_dict.insert("failure_persist_file".to_string(), fuzz_input_file.into()); } diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index eaa627b96..8b49d4acc 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -240,3 +240,38 @@ contract InlineMaxRejectsTest is Test { ... "#]]); }); + +// Tests that test timeout config is properly applied. +// If test doesn't timeout after one second, then test will fail with `rejected too many inputs`. +forgetest_init!(test_fuzz_timeout, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract FuzzTimeoutTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 10000 + /// forge-config: default.fuzz.timeout = 1 + function test_fuzz_bound(uint256 a) public pure { + vm.assume(a == 0); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:FuzzTimeoutTest +[PASS] test_fuzz_bound(uint256) (runs: [..], [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 2f4da4054..76afd5b36 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -949,3 +949,53 @@ Ran 2 tests for test/SelectorMetricsTest.t.sol:CounterTest ... "#]]); }); + +// Tests that invariant exists with success after configured timeout. +forgetest_init!(should_apply_configured_timeout, |prj, cmd| { + // Add initial test that breaks invariant. + prj.add_test( + "TimeoutTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract TimeoutHandler is Test { + uint256 public count; + + function increment() public { + count++; + } +} + +contract TimeoutTest is Test { + TimeoutHandler handler; + + function setUp() public { + handler = new TimeoutHandler(); + } + + /// forge-config: default.invariant.runs = 10000 + /// forge-config: default.invariant.depth = 20000 + /// forge-config: default.invariant.timeout = 1 + function invariant_counter_timeout() public view { + // Invariant will fail if more than 10000 increments. + // Make sure test timeouts after one second and remaining runs are canceled. + require(handler.count() < 10000); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_counter_timeout"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/TimeoutTest.t.sol:TimeoutTest +[PASS] invariant_counter_timeout() (runs: 0, calls: 0, reverts: 0) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 298bbae29..54985b9b6 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -121,6 +121,7 @@ impl ForgeTestProfile { failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), failure_persist_file: Some("testfailure".to_string()), show_logs: false, + timeout: None, }; config.invariant = InvariantConfig { runs: 256, @@ -145,6 +146,7 @@ impl ForgeTestProfile { .into_path(), ), show_metrics: false, + timeout: None, }; config.sanitized() From 0d76df57a28236908084f21c965b20e30ed9dfdd Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:52:07 +0200 Subject: [PATCH 030/107] feat(`cast`): `decode-error` with sig, local cache and openchain api (#9428) * feat(cast): Add custom error decoding support * Review changes * Changes after review: decode with Openchain too, add test * Review changes: nit, handle incomplete selectors --- crates/cast/bin/args.rs | 10 ++++ crates/cast/bin/main.rs | 27 ++++++++- crates/cast/tests/cli/main.rs | 56 ++++++++++++++++++- crates/cli/src/utils/cmd.rs | 3 + crates/common/src/abi.rs | 7 ++- crates/common/src/selectors.rs | 12 ++-- .../evm/traces/src/identifier/signatures.rs | 21 ++++++- 7 files changed, 124 insertions(+), 12 deletions(-) diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index 7078810e4..e4cf32639 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -539,6 +539,16 @@ pub enum CastSubcommand { data: String, }, + /// Decode custom error data. + #[command(visible_aliases = &["error-decode", "--error-decode", "erd"])] + DecodeError { + /// The error signature. If none provided then tries to decode from local cache or `https://api.openchain.xyz`. + #[arg(long, visible_alias = "error-sig")] + sig: Option, + /// The error data to decode. + data: String, + }, + /// Decode ABI-encoded input or output data. /// /// Defaults to decoding output data. To decode input data pass --input. diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 21b1df36d..cacddb834 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate tracing; -use alloy_dyn_abi::{DynSolValue, EventExt}; +use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt}; use alloy_primitives::{eip191_hash_message, hex, keccak256, Address, B256}; use alloy_provider::Provider; use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; @@ -11,7 +11,7 @@ use clap_complete::generate; use eyre::Result; use foundry_cli::{handler, utils}; use foundry_common::{ - abi::get_event, + abi::{get_error, get_event}, ens::{namehash, ProviderEnsExt}, fmt::{format_tokens, format_tokens_raw, format_uint_exp}, fs, @@ -30,6 +30,7 @@ pub mod cmd; pub mod tx; use args::{Cast as CastArgs, CastSubcommand, ToBaseArgs}; +use cast::traces::identifier::SignaturesIdentifier; #[macro_use] extern crate foundry_common; @@ -216,6 +217,28 @@ async fn main_args(args: CastArgs) -> Result<()> { let decoded_event = event.decode_log_parts(None, &hex::decode(data)?, false)?; print_tokens(&decoded_event.body); } + CastSubcommand::DecodeError { sig, data } => { + let error = if let Some(err_sig) = sig { + get_error(err_sig.as_str())? + } else { + let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let selector = data.get(..8).unwrap_or_default(); + let identified_error = + SignaturesIdentifier::new(Config::foundry_cache_dir(), false)? + .write() + .await + .identify_error(&hex::decode(selector)?) + .await; + if let Some(error) = identified_error { + let _ = sh_println!("{}", error.signature()); + error + } else { + eyre::bail!("No matching error signature found for selector `{selector}`") + } + }; + let decoded_error = error.decode_error(&hex::decode(data)?)?; + print_tokens(&decoded_error.body); + } CastSubcommand::Interface(cmd) => cmd.run().await?, CastSubcommand::CreationCode(cmd) => cmd.run().await?, CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index d2c70a779..0cd43766e 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -6,7 +6,7 @@ use alloy_primitives::{b256, B256}; use alloy_rpc_types::{BlockNumberOrTag, Index}; use anvil::{EthereumHardfork, NodeConfig}; use foundry_test_utils::{ - casttest, file, forgetest_async, + casttest, file, forgetest, forgetest_async, rpc::{ next_etherscan_api_key, next_http_rpc_endpoint, next_mainnet_etherscan_api_key, next_rpc_endpoint, next_ws_rpc_endpoint, @@ -1482,6 +1482,60 @@ casttest!(event_decode, |_prj, cmd| { "#]]); }); +// tests cast can decode traces with provided signature +casttest!(error_decode_with_sig, |_prj, cmd| { + cmd.args(["decode-error", "--sig", "AnotherValueTooHigh(uint256,address)", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]).assert_success().stdout_eq(str![[r#" +101 +0x0000000000000000000000000000000000D0004F + +"#]]); + + cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" +[ + "101", + "0x0000000000000000000000000000000000D0004F" +] + +"#]]); +}); + +// tests cast can decode traces with Openchain API +casttest!(error_decode_with_openchain, |_prj, cmd| { + cmd.args(["decode-error", "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064"]).assert_success().stdout_eq(str![[r#" +ValueTooHigh(uint256,uint256) +101 +100 + +"#]]); +}); + +// tests cast can decode traces when using local sig identifiers cache +forgetest!(error_decode_with_cache, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "LocalProjectContract", + r#" +contract ContractWithCustomError { + error AnotherValueTooHigh(uint256, address); +} + "#, + ) + .unwrap(); + // Store selectors in local cache. + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); + + // Assert cast can decode custom error with local cache. + cmd.cast_fuse() + .args(["decode-error", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]) + .assert_success() + .stdout_eq(str![[r#" +AnotherValueTooHigh(uint256,address) +101 +0x0000000000000000000000000000000000D0004F + +"#]]); +}); + casttest!(format_units, |_prj, cmd| { cmd.args(["format-units", "1000000", "6"]).assert_success().stdout_eq(str![[r#" 1 diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 67aa65073..4cf24221b 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -501,6 +501,9 @@ pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf .events .insert(event.selector().to_string(), event.full_signature()); } + for error in abi.errors() { + cached_signatures.errors.insert(error.selector().to_string(), error.signature()); + } // External libraries doesn't have functions included in abi, but `methodIdentifiers`. if let Some(method_identifiers) = &artifact.method_identifiers { method_identifiers.iter().for_each(|(signature, selector)| { diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index de9b36219..fa9f24171 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -1,7 +1,7 @@ //! ABI related helper functions. use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; -use alloy_json_abi::{Event, Function, Param}; +use alloy_json_abi::{Error, Event, Function, Param}; use alloy_primitives::{hex, Address, LogData}; use eyre::{Context, ContextCompat, Result}; use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client}; @@ -85,6 +85,11 @@ pub fn get_event(sig: &str) -> Result { Event::parse(sig).wrap_err("could not parse event signature") } +/// Given an error signature string, it tries to parse it as a `Error` +pub fn get_error(sig: &str) -> Result { + Error::parse(sig).wrap_err("could not parse event signature") +} + /// Given an event without indexed parameters and a rawlog, it tries to return the event with the /// proper indexed parameters. Otherwise, it returns the original event. pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event { diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index cd4e2ffd0..cb59e1f32 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -140,7 +140,7 @@ impl OpenChainClient { .ok_or_else(|| eyre::eyre!("No signature found")) } - /// Decodes the given function or event selectors using OpenChain + /// Decodes the given function, error or event selectors using OpenChain. pub async fn decode_selectors( &self, selector_type: SelectorType, @@ -164,8 +164,8 @@ impl OpenChainClient { self.ensure_not_spurious()?; let expected_len = match selector_type { - SelectorType::Function => 10, // 0x + hex(4bytes) - SelectorType::Event => 66, // 0x + hex(32bytes) + SelectorType::Function | SelectorType::Error => 10, // 0x + hex(4bytes) + SelectorType::Event => 66, // 0x + hex(32bytes) }; if let Some(s) = selectors.iter().find(|s| s.len() != expected_len) { eyre::bail!( @@ -193,7 +193,7 @@ impl OpenChainClient { let url = format!( "{SELECTOR_LOOKUP_URL}?{ltype}={selectors_str}", ltype = match selector_type { - SelectorType::Function => "function", + SelectorType::Function | SelectorType::Error => "function", SelectorType::Event => "event", }, selectors_str = selectors.join(",") @@ -212,7 +212,7 @@ impl OpenChainClient { } let decoded = match selector_type { - SelectorType::Function => api_response.result.function, + SelectorType::Function | SelectorType::Error => api_response.result.function, SelectorType::Event => api_response.result.event, }; @@ -391,6 +391,8 @@ pub enum SelectorType { Function, /// An event selector. Event, + /// An custom error selector. + Error, } /// Decodes the given function or event selector using OpenChain. diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index 2a5ef354a..801f9da37 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -1,7 +1,7 @@ -use alloy_json_abi::{Event, Function}; +use alloy_json_abi::{Error, Event, Function}; use alloy_primitives::{hex, map::HashSet}; use foundry_common::{ - abi::{get_event, get_func}, + abi::{get_error, get_event, get_func}, fs, selectors::{OpenChainClient, SelectorType}, }; @@ -13,6 +13,7 @@ pub type SingleSignaturesIdentifier = Arc>; #[derive(Debug, Default, Serialize, Deserialize)] pub struct CachedSignatures { + pub errors: BTreeMap, pub events: BTreeMap, pub functions: BTreeMap, } @@ -39,7 +40,7 @@ impl CachedSignatures { /// `https://openchain.xyz` or a local cache. #[derive(Debug)] pub struct SignaturesIdentifier { - /// Cached selectors for functions and events. + /// Cached selectors for functions, events and custom errors. cached: CachedSignatures, /// Location where to save `CachedSignatures`. cached_path: Option, @@ -101,6 +102,7 @@ impl SignaturesIdentifier { let cache = match selector_type { SelectorType::Function => &mut self.cached.functions, SelectorType::Event => &mut self.cached.events, + SelectorType::Error => &mut self.cached.errors, }; let hex_identifiers: Vec = @@ -157,6 +159,19 @@ impl SignaturesIdentifier { pub async fn identify_event(&mut self, identifier: &[u8]) -> Option { self.identify_events(&[identifier]).await.pop().unwrap() } + + /// Identifies `Error`s from its cache or `https://api.openchain.xyz`. + pub async fn identify_errors( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Error, identifiers, get_error).await + } + + /// Identifies `Error` from its cache or `https://api.openchain.xyz`. + pub async fn identify_error(&mut self, identifier: &[u8]) -> Option { + self.identify_errors(&[identifier]).await.pop().unwrap() + } } impl Drop for SignaturesIdentifier { From 0f7268f46d2db7502cd0a75c8cfba34f06f8fd6e Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:38:40 +0200 Subject: [PATCH 031/107] feat(`cast`): `decode-event` with local and openchain API (#9431) --- crates/cast/bin/args.rs | 5 ++-- crates/cast/bin/main.rs | 25 +++++++++++++++++-- crates/cast/tests/cli/main.rs | 47 +++++++++++++++++++++++++++++------ 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index e4cf32639..fb7fb0757 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -533,8 +533,9 @@ pub enum CastSubcommand { /// Decode event data. #[command(visible_aliases = &["event-decode", "--event-decode", "ed"])] DecodeEvent { - /// The event signature. - sig: String, + /// The event signature. If none provided then tries to decode from local cache or `https://api.openchain.xyz`. + #[arg(long, visible_alias = "event-sig")] + sig: Option, /// The event data to decode. data: String, }, diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index cacddb834..fcb5a20eb 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -213,8 +213,29 @@ async fn main_args(args: CastArgs) -> Result<()> { print_tokens(&tokens); } CastSubcommand::DecodeEvent { sig, data } => { - let event = get_event(sig.as_str())?; - let decoded_event = event.decode_log_parts(None, &hex::decode(data)?, false)?; + let decoded_event = if let Some(event_sig) = sig { + get_event(event_sig.as_str())?.decode_log_parts(None, &hex::decode(data)?, false)? + } else { + let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let selector = data.get(..64).unwrap_or_default(); + let identified_event = + SignaturesIdentifier::new(Config::foundry_cache_dir(), false)? + .write() + .await + .identify_event(&hex::decode(selector)?) + .await; + if let Some(event) = identified_event { + let _ = sh_println!("{}", event.signature()); + let data = data.get(64..).unwrap_or_default(); + get_event(event.signature().as_str())?.decode_log_parts( + None, + &hex::decode(data)?, + false, + )? + } else { + eyre::bail!("No matching event signature found for selector `{selector}`") + } + }; print_tokens(&decoded_event.body); } CastSubcommand::DecodeError { sig, data } => { diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 0cd43766e..f3d04b094 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1474,15 +1474,35 @@ casttest!(string_decode, |_prj, cmd| { "#]]); }); -casttest!(event_decode, |_prj, cmd| { - cmd.args(["decode-event", "MyEvent(uint256,address)", "0x000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000d0004f"]).assert_success().stdout_eq(str![[r#" +// tests cast can decode event with provided signature +casttest!(event_decode_with_sig, |_prj, cmd| { + cmd.args(["decode-event", "--sig", "MyEvent(uint256,address)", "0x000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000d0004f"]).assert_success().stdout_eq(str![[r#" 78 0x0000000000000000000000000000000000D0004F +"#]]); + + cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" +[ + "78", + "0x0000000000000000000000000000000000D0004F" +] + "#]]); }); -// tests cast can decode traces with provided signature +// tests cast can decode event with Openchain API +casttest!(event_decode_with_openchain, |prj, cmd| { + prj.clear_cache(); + cmd.args(["decode-event", "0xe27c4c1372396a3d15a9922f74f9dfc7c72b1ad6d63868470787249c356454c1000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]).assert_success().stdout_eq(str![[r#" +BaseCurrencySet(address,uint256) +0x000000000000000000000000000000000000004e +15187004358734 [1.518e13] + +"#]]); +}); + +// tests cast can decode error with provided signature casttest!(error_decode_with_sig, |_prj, cmd| { cmd.args(["decode-error", "--sig", "AnotherValueTooHigh(uint256,address)", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]).assert_success().stdout_eq(str![[r#" 101 @@ -1499,8 +1519,9 @@ casttest!(error_decode_with_sig, |_prj, cmd| { "#]]); }); -// tests cast can decode traces with Openchain API -casttest!(error_decode_with_openchain, |_prj, cmd| { +// tests cast can decode error with Openchain API +casttest!(error_decode_with_openchain, |prj, cmd| { + prj.clear_cache(); cmd.args(["decode-error", "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064"]).assert_success().stdout_eq(str![[r#" ValueTooHigh(uint256,uint256) 101 @@ -1509,14 +1530,16 @@ ValueTooHigh(uint256,uint256) "#]]); }); -// tests cast can decode traces when using local sig identifiers cache -forgetest!(error_decode_with_cache, |prj, cmd| { +// tests cast can decode error and event when using local sig identifiers cache +forgetest!(error_event_decode_with_cache, |prj, cmd| { + prj.clear_cache(); foundry_test_utils::util::initialize(prj.root()); prj.add_source( "LocalProjectContract", r#" contract ContractWithCustomError { error AnotherValueTooHigh(uint256, address); + event MyUniqueEventWithinLocalProject(uint256 a, address b); } "#, ) @@ -1533,6 +1556,16 @@ AnotherValueTooHigh(uint256,address) 101 0x0000000000000000000000000000000000D0004F +"#]]); + // Assert cast can decode event with local cache. + cmd.cast_fuse() + .args(["decode-event", "0xbd3699995dcc867b64dbb607be2c33be38df9134bef1178df13bfb9446e73104000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]) + .assert_success() + .stdout_eq(str![[r#" +MyUniqueEventWithinLocalProject(uint256,address) +78 +0x00000000000000000000000000000DD00000004e + "#]]); }); From fbbcc8c4521bae19dfeac451d51db97c0912e512 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:15:21 +0100 Subject: [PATCH 032/107] chore: use alloy-chains' `is_arbitrum` (#9432) * use alloy-chains' is_arbitrum * clean up --- crates/anvil/src/eth/backend/mem/mod.rs | 11 +++--- crates/cli/src/utils/cmd.rs | 47 ++++++++++--------------- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 83718ad82..fa79332e7 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -3013,11 +3013,8 @@ pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec bool { - matches!( - NamedChain::try_from(chain_id), - Ok(NamedChain::Arbitrum | - NamedChain::ArbitrumTestnet | - NamedChain::ArbitrumGoerli | - NamedChain::ArbitrumNova) - ) + if let Ok(chain) = NamedChain::try_from(chain_id) { + return chain.is_arbitrum() + } + false } diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 4cf24221b..0d2febf93 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -159,27 +159,24 @@ pub fn init_progress(len: u64, label: &str) -> indicatif::ProgressBar { /// True if the network calculates gas costs differently. pub fn has_different_gas_calc(chain_id: u64) -> bool { if let Some(chain) = Chain::from(chain_id).named() { - return matches!( - chain, - NamedChain::Acala | - NamedChain::AcalaMandalaTestnet | - NamedChain::AcalaTestnet | - NamedChain::Arbitrum | - NamedChain::ArbitrumGoerli | - NamedChain::ArbitrumSepolia | - NamedChain::ArbitrumTestnet | - NamedChain::Etherlink | - NamedChain::EtherlinkTestnet | - NamedChain::Karura | - NamedChain::KaruraTestnet | - NamedChain::Mantle | - NamedChain::MantleSepolia | - NamedChain::MantleTestnet | - NamedChain::Moonbase | - NamedChain::Moonbeam | - NamedChain::MoonbeamDev | - NamedChain::Moonriver - ); + return chain.is_arbitrum() || + matches!( + chain, + NamedChain::Acala | + NamedChain::AcalaMandalaTestnet | + NamedChain::AcalaTestnet | + NamedChain::Etherlink | + NamedChain::EtherlinkTestnet | + NamedChain::Karura | + NamedChain::KaruraTestnet | + NamedChain::Mantle | + NamedChain::MantleSepolia | + NamedChain::MantleTestnet | + NamedChain::Moonbase | + NamedChain::Moonbeam | + NamedChain::MoonbeamDev | + NamedChain::Moonriver + ); } false } @@ -187,13 +184,7 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool { /// True if it supports broadcasting in batches. pub fn has_batch_support(chain_id: u64) -> bool { if let Some(chain) = Chain::from(chain_id).named() { - return !matches!( - chain, - NamedChain::Arbitrum | - NamedChain::ArbitrumTestnet | - NamedChain::ArbitrumGoerli | - NamedChain::ArbitrumSepolia - ); + return !chain.is_arbitrum(); } true } From af0fee2031ed4273c1b697775650de1efb2a2d4e Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:39:51 +0530 Subject: [PATCH 033/107] feat: rpc_headers in cast and config (#9429) * feat: specify `rpc_headers` in cast and config * test --- Cargo.lock | 18 ++++++++++++++++++ Cargo.toml | 1 + crates/cast/Cargo.toml | 1 + crates/cast/bin/cmd/run.rs | 8 +++----- crates/cli/src/opts/ethereum.rs | 7 +++++++ crates/cli/src/utils/mod.rs | 10 ++++++++++ crates/config/src/lib.rs | 10 ++++++++++ crates/forge/tests/cli/config.rs | 1 + 8 files changed, 51 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09fab74b7..b83fd212c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,23 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-node-bindings" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9805d126f24be459b958973c0569c73e1aadd27d4535eee82b2b6764aa03616" +dependencies = [ + "alloy-genesis", + "alloy-primitives", + "k256", + "rand", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tracing", + "url", +] + [[package]] name = "alloy-primitives" version = "0.8.12" @@ -1957,6 +1974,7 @@ dependencies = [ "alloy-json-abi", "alloy-json-rpc", "alloy-network", + "alloy-node-bindings", "alloy-primitives", "alloy-provider", "alloy-rlp", diff --git a/Cargo.toml b/Cargo.toml index 814c43aa5..b07d99cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -205,6 +205,7 @@ alloy-transport = { version = "0.6.4", default-features = false } alloy-transport-http = { version = "0.6.4", default-features = false } alloy-transport-ipc = { version = "0.6.4", default-features = false } alloy-transport-ws = { version = "0.6.4", default-features = false } +alloy-node-bindings = { version = "0.6.4", default-features = false } ## alloy-core alloy-dyn-abi = "0.8.11" diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 0887649e5..f6011831a 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -94,6 +94,7 @@ tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] anvil.workspace = true foundry-test-utils.workspace = true +alloy-node-bindings.workspace = true async-trait.workspace = true divan.workspace = true diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index cfad7263a..bb5c505b1 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -107,11 +107,9 @@ impl RunArgs { let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; - let provider = foundry_common::provider::ProviderBuilder::new( - &config.get_rpc_url_or_localhost_http()?, - ) - .compute_units_per_second_opt(compute_units_per_second) - .build()?; + let provider = foundry_cli::utils::get_provider_builder(&config)? + .compute_units_per_second_opt(compute_units_per_second) + .build()?; let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?; let tx = provider diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index b858d998f..4b15b8551 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -48,6 +48,10 @@ pub struct RpcOpts { /// Default value: 45 #[arg(long, env = "ETH_RPC_TIMEOUT")] pub rpc_timeout: Option, + + /// Specify custom headers for RPC requests. + #[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))] + pub rpc_headers: Option>, } impl_figment_convert_cast!(RpcOpts); @@ -95,6 +99,9 @@ impl RpcOpts { if let Some(rpc_timeout) = self.rpc_timeout { dict.insert("eth_rpc_timeout".into(), rpc_timeout.into()); } + if let Some(headers) = &self.rpc_headers { + dict.insert("eth_rpc_headers".into(), headers.clone().into()); + } dict } } diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 2d8471e62..f833924f5 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -39,6 +39,8 @@ pub const STATIC_FUZZ_SEED: [u8; 32] = [ 0x5d, 0x64, 0x0b, 0x19, 0xad, 0xf0, 0xe3, 0x57, 0xb8, 0xd4, 0xbe, 0x7d, 0x49, 0xee, 0x70, 0xe6, ]; +const DEFAULT_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); + /// Useful extensions to [`std::path::Path`]. pub trait FoundryPathExt { /// Returns true if the [`Path`] ends with `.t.sol` @@ -110,6 +112,14 @@ pub fn get_provider_builder(config: &Config) -> Result { builder = builder.timeout(Duration::from_secs(rpc_timeout)); } + if let Some(mut rpc_headers) = config.eth_rpc_headers.clone() { + if !rpc_headers.iter().any(|h| h.starts_with("User-Agent:")) { + rpc_headers.push(format!("User-Agent:{DEFAULT_USER_AGENT}")); + } + + builder = builder.headers(rpc_headers); + } + Ok(builder) } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 72be43ab1..bcdeb04a9 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -253,6 +253,15 @@ pub struct Config { pub eth_rpc_jwt: Option, /// Timeout that should be used for any rpc calls pub eth_rpc_timeout: Option, + /// Headers that should be used for any rpc calls + /// + /// # Example + /// + /// rpc_headers = ["x-custom-header:value", "x-another-header:another-value"] + /// + /// You can also the ETH_RPC_HEADERS env variable like so: + /// `ETH_RPC_HEADERS="x-custom-header:value x-another-header:another-value"` + pub eth_rpc_headers: Option>, /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table pub etherscan_api_key: Option, /// Multiple etherscan api configs and their aliases @@ -2347,6 +2356,7 @@ impl Default for Config { eth_rpc_url: None, eth_rpc_jwt: None, eth_rpc_timeout: None, + eth_rpc_headers: None, etherscan_api_key: None, verbosity: 0, remappings: vec![], diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 72aacff49..aa38a0b77 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -109,6 +109,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { eth_rpc_url: Some("localhost".to_string()), eth_rpc_jwt: None, eth_rpc_timeout: None, + eth_rpc_headers: None, etherscan_api_key: None, etherscan: Default::default(), verbosity: 4, From 4527475bc8be4044a8daa1dddecb4086403c5b76 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:10:40 +0530 Subject: [PATCH 034/107] fix: set user-agent header in runtime transport (#9434) --- crates/cli/src/utils/mod.rs | 8 +------- crates/common/src/constants.rs | 3 +++ crates/common/src/provider/runtime_transport.rs | 10 +++++++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f833924f5..9f8475f63 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -39,8 +39,6 @@ pub const STATIC_FUZZ_SEED: [u8; 32] = [ 0x5d, 0x64, 0x0b, 0x19, 0xad, 0xf0, 0xe3, 0x57, 0xb8, 0xd4, 0xbe, 0x7d, 0x49, 0xee, 0x70, 0xe6, ]; -const DEFAULT_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); - /// Useful extensions to [`std::path::Path`]. pub trait FoundryPathExt { /// Returns true if the [`Path`] ends with `.t.sol` @@ -112,11 +110,7 @@ pub fn get_provider_builder(config: &Config) -> Result { builder = builder.timeout(Duration::from_secs(rpc_timeout)); } - if let Some(mut rpc_headers) = config.eth_rpc_headers.clone() { - if !rpc_headers.iter().any(|h| h.starts_with("User-Agent:")) { - rpc_headers.push(format!("User-Agent:{DEFAULT_USER_AGENT}")); - } - + if let Some(rpc_headers) = config.eth_rpc_headers.clone() { builder = builder.headers(rpc_headers); } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 0ba0514c2..4ff3eb8d7 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -40,6 +40,9 @@ pub const OPTIMISM_SYSTEM_ADDRESS: Address = address!("deaddeaddeaddeaddeaddeadd /// Transaction identifier of System transaction types pub const SYSTEM_TRANSACTION_TYPE: u8 = 126; +/// Default user agent set as the header for requests that don't specify one. +pub const DEFAULT_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); + /// Returns whether the sender is a known L2 system sender that is the first tx in every block. /// /// Transactions from these senders usually don't have a any fee information. diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index a95969be5..563cec313 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -1,7 +1,7 @@ //! Runtime transport that connects on first request, which can take either of an HTTP, //! WebSocket, or IPC transport and supports retries based on CUPS logic. -use crate::REQUEST_TIMEOUT; +use crate::{DEFAULT_USER_AGENT, REQUEST_TIMEOUT}; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_pubsub::{PubSubConnect, PubSubFrontend}; use alloy_rpc_types::engine::{Claims, JwtSecret}; @@ -176,6 +176,14 @@ impl RuntimeTransport { ); } + if !headers.iter().any(|(k, _v)| k.as_str().starts_with("User-Agent:")) { + headers.insert( + reqwest::header::USER_AGENT, + HeaderValue::from_str(DEFAULT_USER_AGENT) + .expect("User-Agent should be valid string"), + ); + } + client_builder = client_builder.default_headers(headers); let client = From 7f41280ee071193557f73f16bae9aee9a5548ee8 Mon Sep 17 00:00:00 2001 From: Delweng Date: Sat, 30 Nov 2024 08:10:35 +0800 Subject: [PATCH 035/107] feat(script): support custom create2 deployer (#9278) * script: add --create2-deployer Signed-off-by: jsvisa * script: add create2 deployer Signed-off-by: jsvisa * evm/constants: add get_create2_deployer from env or default Signed-off-by: jsvisa * evm/core: use env's create2 Signed-off-by: jsvisa * script: fetch create2_deployer from env or default Signed-off-by: jsvisa * fmt Signed-off-by: jsvisa * docs Signed-off-by: jsvisa * evm/constants: use sync::LazyLock Signed-off-by: jsvisa * evm/inspector: add fn create2_deployer Signed-off-by: jsvisa * config: add create2_deployer Signed-off-by: jsvisa * evm/inpector: set create2 deployer Signed-off-by: jsvisa * evm-opts: add create2_deployer Signed-off-by: jsvisa * script: pass deployer2-creater from cli or config Signed-off-by: jsvisa * script: use create2 address to fill tx meta Signed-off-by: jsvisa * config: create2 address ,no Option Signed-off-by: jsvisa * script/runner: set inspector.create2_deployer with evm_opts Signed-off-by: jsvisa * clippy Signed-off-by: jsvisa * doc typo Signed-off-by: jsvisa * fix/evm-opts: default value of create2_deployer Signed-off-by: jsvisa * evm/core: no need to extract create2 deployer from env Signed-off-by: jsvisa * evm/core: implement Default for EvmOpts.create2_deployer Signed-off-by: jsvisa * evm/core: use constants::DEFAULT create2 deployer Signed-off-by: jsvisa * evm/core: output create2 deployer Signed-off-by: jsvisa unit test Signed-off-by: jsvisa * evm/evm: set create2 deployer for trace and stack Signed-off-by: jsvisa * cast/{run,call}: set create2 deployer Signed-off-by: jsvisa * forge/runner: set create2 deployer Signed-off-by: jsvisa * script: set create2 deployer for stack Signed-off-by: jsvisa * verify: set create2 deployer Signed-off-by: jsvisa * clipy Signed-off-by: jsvisa * fmt Signed-off-by: jsvisa * script: use executor's create2 deployer Signed-off-by: jsvisa * script: wrap create2_deployer inside executor Signed-off-by: jsvisa * script: add custom create2 test Signed-off-by: jsvisa * script: add nonexist create2 Signed-off-by: jsvisa * all: set EvmOpts.create2_deployer Signed-off-by: jsvisa * script: no need to pass create2_deployer in fill_metadata Signed-off-by: jsvisa * evm/executor: duplicate set create2's deployer address Signed-off-by: jsvisa * evm: check create2 codehash Signed-off-by: jsvisa * tests/script: test with notmatched create2 deployer Signed-off-by: jsvisa * clipy Signed-off-by: jsvisa * evm: skip serialize create2_deployer if none Signed-off-by: jsvisa * test: add test of deployer2 address Signed-off-by: jsvisa * Update crates/script/src/lib.rs --------- Signed-off-by: jsvisa Co-authored-by: Arsenii Kulikov --- crates/cast/bin/cmd/call.rs | 20 ++++++-- crates/cast/bin/cmd/run.rs | 15 ++++-- crates/cheatcodes/src/inspector.rs | 4 ++ crates/common/src/evm.rs | 5 ++ crates/config/src/lib.rs | 8 +++ crates/evm/core/src/constants.rs | 5 ++ crates/evm/core/src/lib.rs | 7 +++ crates/evm/core/src/opts.rs | 32 +++++++++++- crates/evm/core/src/utils.rs | 31 +++++++++--- crates/evm/evm/src/executors/mod.rs | 6 +++ crates/evm/evm/src/executors/trace.rs | 23 ++++----- crates/evm/evm/src/inspectors/stack.rs | 25 ++++++++++ crates/forge/src/multi_runner.rs | 1 + crates/forge/tests/cli/config.rs | 1 + crates/forge/tests/cli/script.rs | 66 ++++++++++++++++++++++++- crates/script/src/build.rs | 7 +-- crates/script/src/lib.rs | 11 +++-- crates/script/src/runner.rs | 10 ++-- crates/script/src/simulate.rs | 6 ++- crates/script/src/transaction.rs | 8 +-- crates/test-utils/src/script.rs | 4 ++ crates/verify/src/utils.rs | 11 +++-- testdata/default/cheats/Broadcast.t.sol | 26 ++++++++++ 23 files changed, 281 insertions(+), 51 deletions(-) diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 2d5692efe..1704247dc 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -18,7 +18,11 @@ use foundry_config::{ }, Config, }; -use foundry_evm::{executors::TracingExecutor, opts::EvmOpts}; +use foundry_evm::{ + executors::TracingExecutor, + opts::EvmOpts, + traces::{InternalTraceMode, TraceMode}, +}; use std::str::FromStr; /// CLI arguments for `cast call`. @@ -175,6 +179,7 @@ impl CallArgs { config.fork_block_number = Some(block_number); } + let create2_deployer = evm_opts.create2_deployer; let (mut env, fork, chain, alphanet) = TracingExecutor::get_fork_material(&config, evm_opts).await?; @@ -182,14 +187,21 @@ impl CallArgs { env.cfg.disable_block_gas_limit = true; env.block.gas_limit = U256::MAX; + let trace_mode = TraceMode::Call + .with_debug(debug) + .with_decode_internal(if decode_internal { + InternalTraceMode::Full + } else { + InternalTraceMode::None + }) + .with_state_changes(shell::verbosity() > 4); let mut executor = TracingExecutor::new( env, fork, evm_version, - debug, - decode_internal, - shell::verbosity() > 4, + trace_mode, alphanet, + create2_deployer, ); let value = tx.value.unwrap_or_default(); diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index bb5c505b1..62a41ca6c 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -23,6 +23,7 @@ use foundry_config::{ use foundry_evm::{ executors::{EvmError, TracingExecutor}, opts::EvmOpts, + traces::{InternalTraceMode, TraceMode}, utils::configure_tx_env, }; @@ -136,6 +137,7 @@ impl RunArgs { // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); + let create2_deployer = evm_opts.create2_deployer; let (mut env, fork, chain, alphanet) = TracingExecutor::get_fork_material(&config, evm_opts).await?; @@ -161,14 +163,21 @@ impl RunArgs { } } + let trace_mode = TraceMode::Call + .with_debug(self.debug) + .with_decode_internal(if self.decode_internal { + InternalTraceMode::Full + } else { + InternalTraceMode::None + }) + .with_state_changes(shell::verbosity() > 4); let mut executor = TracingExecutor::new( env.clone(), fork, evm_version, - self.debug, - self.decode_internal, - shell::verbosity() > 4, + trace_mode, alphanet, + create2_deployer, ); let mut env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 6f3acb58c..447a9e747 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1587,6 +1587,10 @@ impl InspectorExt for Cheatcodes { false } } + + fn create2_deployer(&self) -> Address { + self.config.evm_opts.create2_deployer + } } impl Cheatcodes { diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index c4dfccae1..dbcaf02af 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -103,6 +103,11 @@ pub struct EvmArgs { #[serde(skip)] pub always_use_create_2_factory: bool, + /// The CREATE2 deployer address to use, this will override the one in the config. + #[arg(long, value_name = "ADDRESS")] + #[serde(skip_serializing_if = "Option::is_none")] + pub create2_deployer: Option
, + /// Sets the number of assumed available compute units per second for this provider /// /// default value: 330 diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index bcdeb04a9..b88f134d2 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -454,6 +454,9 @@ pub struct Config { /// CREATE2 salt to use for the library deployment in scripts. pub create2_library_salt: B256, + /// The CREATE2 deployer address to use. + pub create2_deployer: Address, + /// Configuration for Vyper compiler pub vyper: VyperConfig, @@ -567,6 +570,10 @@ impl Config { /// Default salt for create2 library deployments pub const DEFAULT_CREATE2_LIBRARY_SALT: FixedBytes<32> = FixedBytes::<32>::ZERO; + /// Default create2 deployer + pub const DEFAULT_CREATE2_DEPLOYER: Address = + address!("4e59b44847b379578588920ca78fbf26c0b4956c"); + /// Docker image with eof-enabled solc binary pub const EOF_SOLC_IMAGE: &'static str = "ghcr.io/paradigmxyz/forge-eof@sha256:46f868ce5264e1190881a3a335d41d7f42d6f26ed20b0c823609c715e38d603f"; @@ -2390,6 +2397,7 @@ impl Default for Config { labels: Default::default(), unchecked_cheatcode_artifacts: false, create2_library_salt: Self::DEFAULT_CREATE2_LIBRARY_SALT, + create2_deployer: Self::DEFAULT_CREATE2_DEPLOYER, skip: vec![], dependencies: Default::default(), soldeer: Default::default(), diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index cebbdcb87..2e4fdb526 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -49,6 +49,11 @@ pub const DEFAULT_CREATE2_DEPLOYER: Address = address!("4e59b44847b379578588920c pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); /// The runtime code of the default CREATE2 deployer. pub const DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE: &[u8] = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); +/// The hash of the default CREATE2 deployer code. +/// +/// This is calculated as `keccak256([`DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE`])`. +pub const DEFAULT_CREATE2_DEPLOYER_CODEHASH: B256 = + b256!("2fa86add0aed31f33a762c9d88e807c475bd51d0f52bd0955754b2608f7e4989"); #[cfg(test)] mod tests { diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 1a2ac4c4a..0bdc34cc9 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -5,6 +5,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use crate::constants::DEFAULT_CREATE2_DEPLOYER; +use alloy_primitives::Address; use auto_impl::auto_impl; use backend::DatabaseExt; use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, EvmContext, Inspector}; @@ -54,6 +56,11 @@ pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> { fn is_alphanet(&self) -> bool { false } + + /// Returns the CREATE2 deployer address. + fn create2_deployer(&self) -> Address { + DEFAULT_CREATE2_DEPLOYER + } } impl InspectorExt for NoOpInspector {} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 6f4448ae4..c5817e483 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -1,5 +1,5 @@ use super::fork::environment; -use crate::fork::CreateFork; +use crate::{constants::DEFAULT_CREATE2_DEPLOYER, fork::CreateFork}; use alloy_primitives::{Address, B256, U256}; use alloy_provider::{network::AnyRpcBlock, Provider}; use eyre::WrapErr; @@ -9,7 +9,7 @@ use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct EvmOpts { /// The EVM environment configuration. #[serde(flatten)] @@ -66,6 +66,34 @@ pub struct EvmOpts { /// whether to enable Alphanet features. pub alphanet: bool, + + /// The CREATE2 deployer's address. + pub create2_deployer: Address, +} + +impl Default for EvmOpts { + fn default() -> Self { + Self { + env: Env::default(), + fork_url: None, + fork_block_number: None, + fork_retries: None, + fork_retry_backoff: None, + compute_units_per_second: None, + no_rpc_rate_limit: false, + no_storage_caching: false, + initial_balance: U256::default(), + sender: Address::default(), + ffi: false, + always_use_create_2_factory: false, + verbosity: 0, + memory_limit: 0, + isolate: false, + disable_block_gas_limit: false, + alphanet: false, + create2_deployer: DEFAULT_CREATE2_DEPLOYER, + } + } } impl EvmOpts { diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index be9660c72..2257709e5 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,6 +1,6 @@ pub use crate::ic::*; use crate::{ - backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER, precompiles::ALPHANET_P256, + backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, precompiles::ALPHANET_P256, InspectorExt, }; use alloy_consensus::BlockHeader; @@ -149,12 +149,16 @@ pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { spent - (refunded).min(spent / refund_quotient) } -fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInputs { +fn get_create2_factory_call_inputs( + salt: U256, + inputs: CreateInputs, + deployer: Address, +) -> CallInputs { let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); CallInputs { caller: inputs.caller, - bytecode_address: DEFAULT_CREATE2_DEPLOYER, - target_address: DEFAULT_CREATE2_DEPLOYER, + bytecode_address: deployer, + target_address: deployer, scheme: CallScheme::Call, value: CallValue::Transfer(inputs.value), input: calldata.into(), @@ -165,7 +169,7 @@ fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInpu } } -/// Used for routing certain CREATE2 invocations through [DEFAULT_CREATE2_DEPLOYER]. +/// Used for routing certain CREATE2 invocations through CREATE2_DEPLOYER. /// /// Overrides create hook with CALL frame if [InspectorExt::should_use_create2_factory] returns /// true. Keeps track of overridden frames and handles outcome in the overridden insert_call_outcome @@ -190,8 +194,10 @@ pub fn create2_handler_register( let gas_limit = inputs.gas_limit; + // Get CREATE2 deployer. + let create2_deployer = ctx.external.create2_deployer(); // Generate call inputs for CREATE2 factory. - let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs); + let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs, create2_deployer); // Call inspector to change input or return outcome. let outcome = ctx.external.call(&mut ctx.evm, &mut call_inputs); @@ -202,12 +208,21 @@ pub fn create2_handler_register( .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); // Sanity check that CREATE2 deployer exists. - let code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.info.code_hash; + let code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash; if code_hash == KECCAK_EMPTY { return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, - output: "missing CREATE2 deployer".into(), + output: format!("missing CREATE2 deployer: {create2_deployer}").into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + }))) + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), gas: Gas::new(gas_limit), }, memory_offset: 0..0, diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 2ccfad9e2..8146cec82 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -24,6 +24,7 @@ use foundry_evm_core::{ }, decode::{RevertDecoder, SkipReason}, utils::StateChangeset, + InspectorExt, }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{SparsedTraceArena, TraceMode}; @@ -240,6 +241,11 @@ impl Executor { self } + #[inline] + pub fn create2_deployer(&self) -> Address { + self.inspector().create2_deployer() + } + /// Deploys a contract and commits the new state to the underlying database. /// /// Executes a CREATE transaction with the contract `code` and persistent database state diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index ceea6e672..214e7c28a 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -1,8 +1,9 @@ use crate::executors::{Executor, ExecutorBuilder}; +use alloy_primitives::Address; use foundry_compilers::artifacts::EvmVersion; use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; -use foundry_evm_traces::{InternalTraceMode, TraceMode}; +use foundry_evm_traces::TraceMode; use revm::primitives::{Env, SpecId}; use std::ops::{Deref, DerefMut}; @@ -16,25 +17,21 @@ impl TracingExecutor { env: revm::primitives::Env, fork: Option, version: Option, - debug: bool, - decode_internal: bool, - with_state_changes: bool, + trace_mode: TraceMode, alphanet: bool, + create2_deployer: Address, ) -> Self { let db = Backend::spawn(fork); - let trace_mode = TraceMode::Call - .with_debug(debug) - .with_decode_internal(if decode_internal { - InternalTraceMode::Full - } else { - InternalTraceMode::None - }) - .with_state_changes(with_state_changes); Self { // configures a bare version of the evm executor: no cheatcode inspector is enabled, // tracing will be enabled only for the targeted transaction executor: ExecutorBuilder::new() - .inspectors(|stack| stack.trace_mode(trace_mode).alphanet(alphanet)) + .inspectors(|stack| { + stack + .trace_mode(trace_mode) + .alphanet(alphanet) + .create2_deployer(create2_deployer) + }) .spec(evm_spec_id(&version.unwrap_or_default(), alphanet)) .build(env, db), } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 403e906f6..accbca4fd 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -59,6 +59,8 @@ pub struct InspectorStackBuilder { pub alphanet: bool, /// The wallets to set in the cheatcodes context. pub wallets: Option, + /// The CREATE2 deployer address. + pub create2_deployer: Address, } impl InspectorStackBuilder { @@ -156,6 +158,12 @@ impl InspectorStackBuilder { self } + #[inline] + pub fn create2_deployer(mut self, create2_deployer: Address) -> Self { + self.create2_deployer = create2_deployer; + self + } + /// Builds the stack of inspectors to use when transacting/committing on the EVM. pub fn build(self) -> InspectorStack { let Self { @@ -171,6 +179,7 @@ impl InspectorStackBuilder { enable_isolation, alphanet, wallets, + create2_deployer, } = self; let mut stack = InspectorStack::new(); @@ -197,6 +206,7 @@ impl InspectorStackBuilder { stack.enable_isolation(enable_isolation); stack.alphanet(alphanet); + stack.set_create2_deployer(create2_deployer); // environment, must come after all of the inspectors if let Some(block) = block { @@ -282,6 +292,7 @@ pub struct InspectorStackInner { pub tracer: Option, pub enable_isolation: bool, pub alphanet: bool, + pub create2_deployer: Address, /// Flag marking if we are in the inner EVM context. pub in_inner_context: bool, @@ -398,6 +409,12 @@ impl InspectorStack { self.alphanet = yes; } + /// Set the CREATE2 deployer address. + #[inline] + pub fn set_create2_deployer(&mut self, deployer: Address) { + self.create2_deployer = deployer; + } + /// Set whether to enable the log collector. #[inline] pub fn collect_logs(&mut self, yes: bool) { @@ -1022,6 +1039,10 @@ impl InspectorExt for InspectorStackRefMut<'_> { fn is_alphanet(&self) -> bool { self.inner.alphanet } + + fn create2_deployer(&self) -> Address { + self.inner.create2_deployer + } } impl Inspector<&mut dyn DatabaseExt> for InspectorStack { @@ -1124,6 +1145,10 @@ impl InspectorExt for InspectorStack { fn is_alphanet(&self) -> bool { self.alphanet } + + fn create2_deployer(&self) -> Address { + self.create2_deployer + } } impl<'a> Deref for InspectorStackRefMut<'a> { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index dfb498c06..572c3d4fe 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -260,6 +260,7 @@ impl MultiContractRunner { .coverage(self.coverage) .enable_isolation(self.isolation) .alphanet(self.alphanet) + .create2_deployer(self.evm_opts.create2_deployer) }) .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index aa38a0b77..9a7e61c6a 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -146,6 +146,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { isolate: true, unchecked_cheatcode_artifacts: false, create2_library_salt: Config::DEFAULT_CREATE2_LIBRARY_SALT, + create2_deployer: Config::DEFAULT_CREATE2_DEPLOYER, vyper: Default::default(), skip: vec![], dependencies: Default::default(), diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index d7776eee2..e52bd2fef 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -855,6 +855,70 @@ forgetest_async!(can_deploy_with_create2, |prj, cmd| { .run(ScriptOutcome::ScriptFailed); }); +forgetest_async!(can_deploy_with_custom_create2, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + let create2 = Address::from_str("0x0000000000000000000000000000000000b4956c").unwrap(); + + // Prepare CREATE2 Deployer + api.anvil_set_code( + create2, + Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE), + ) + .await + .unwrap(); + + tester + .add_deployer(0) + .load_private_keys(&[0]) + .await + .add_create2_deployer(create2) + .add_sig("BroadcastTestNoLinking", "deployCreate2(address)") + .arg(&create2.to_string()) + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 2)]) + .await; +}); + +forgetest_async!(can_deploy_with_custom_create2_notmatched_bytecode, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + let create2 = Address::from_str("0x0000000000000000000000000000000000b4956c").unwrap(); + + // Prepare CREATE2 Deployer + api.anvil_set_code( + create2, + Bytes::from_static(&hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cef")), + ) + .await + .unwrap(); + + tester + .add_deployer(0) + .load_private_keys(&[0]) + .await + .add_create2_deployer(create2) + .add_sig("BroadcastTestNoLinking", "deployCreate2()") + .simulate(ScriptOutcome::ScriptFailed) + .broadcast(ScriptOutcome::ScriptFailed); +}); + +forgetest_async!(canot_deploy_with_nonexist_create2, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + let create2 = Address::from_str("0x0000000000000000000000000000000000b4956c").unwrap(); + + tester + .add_deployer(0) + .load_private_keys(&[0]) + .await + .add_create2_deployer(create2) + .add_sig("BroadcastTestNoLinking", "deployCreate2()") + .simulate(ScriptOutcome::ScriptFailed) + .broadcast(ScriptOutcome::ScriptFailed); +}); + forgetest_async!(can_deploy_and_simulate_25_txes_concurrently, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); @@ -2002,7 +2066,7 @@ contract SimpleScript is Script { ]); cmd.assert_failure().stderr_eq(str![[r#" -Error: script failed: missing CREATE2 deployer +Error: script failed: missing CREATE2 deployer: 0x4e59b44847b379578588920cA78FbF26c0B4956C "#]]); }); diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index ef4274084..052e78e10 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -17,7 +17,7 @@ use foundry_compilers::{ utils::source_files_iter, ArtifactId, ProjectCompileOutput, }; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::debug::ContractSources}; +use foundry_evm::traces::debug::ContractSources; use foundry_linking::Linker; use std::{path::PathBuf, str::FromStr, sync::Arc}; @@ -40,9 +40,10 @@ impl BuildData { /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to /// default linking with sender nonce and address. pub async fn link(self, script_config: &ScriptConfig) -> Result { + let create2_deployer = script_config.evm_opts.create2_deployer; let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url { let provider = try_get_http_provider(fork_url)?; - let deployer_code = provider.get_code_at(DEFAULT_CREATE2_DEPLOYER).await?; + let deployer_code = provider.get_code_at(create2_deployer).await?; !deployer_code.is_empty() } else { @@ -57,7 +58,7 @@ impl BuildData { self.get_linker() .link_with_create2( known_libraries.clone(), - DEFAULT_CREATE2_DEPLOYER, + create2_deployer, script_config.config.create2_library_salt, &self.target, ) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 6ccc81841..ccf5eab2a 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -46,7 +46,6 @@ use foundry_config::{ }; use foundry_evm::{ backend::Backend, - constants::DEFAULT_CREATE2_DEPLOYER, executors::ExecutorBuilder, inspectors::{ cheatcodes::{BroadcastableTransactions, Wallets}, @@ -239,7 +238,9 @@ impl ScriptArgs { pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); - let compiled = self.preprocess().await?.compile()?; + let state = self.preprocess().await?; + let create2_deployer = state.script_config.evm_opts.create2_deployer; + let compiled = state.compile()?; // Move from `CompiledState` to `BundledState` either by resuming or executing and // simulating script. @@ -294,6 +295,7 @@ impl ScriptArgs { pre_simulation.args.check_contract_sizes( &pre_simulation.execution_result, &pre_simulation.build_data.known_contracts, + create2_deployer, )?; pre_simulation.fill_metadata().await?.bundle().await? @@ -384,6 +386,7 @@ impl ScriptArgs { &self, result: &ScriptResult, known_contracts: &ContractsByArtifact, + create2_deployer: Address, ) -> Result<()> { // (name, &init, &deployed)[] let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; @@ -428,7 +431,7 @@ impl ScriptArgs { // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. if let Some(TxKind::Call(to)) = to { - if to == DEFAULT_CREATE2_DEPLOYER { + if to == create2_deployer { // Size of the salt prefix. offset = 32; } else { @@ -553,6 +556,7 @@ impl ScriptConfig { // dapptools compatibility 1 }; + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() }) } @@ -612,6 +616,7 @@ impl ScriptConfig { stack .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call }) .alphanet(self.evm_opts.alphanet) + .create2_deployer(self.evm_opts.create2_deployer) }) .spec(self.config.evm_spec_id()) .gas_limit(self.evm_opts.gas_limit()) diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index fa8ff19d9..2d58e381f 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -7,7 +7,7 @@ use eyre::Result; use foundry_cheatcodes::BroadcastableTransaction; use foundry_config::Config; use foundry_evm::{ - constants::{CALLER, DEFAULT_CREATE2_DEPLOYER}, + constants::CALLER, executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, opts::EvmOpts, revm::interpreter::{return_ok, InstructionResult}, @@ -83,9 +83,9 @@ impl ScriptRunner { }) }), ScriptPredeployLibraries::Create2(libraries, salt) => { + let create2_deployer = self.executor.create2_deployer(); for library in libraries { - let address = - DEFAULT_CREATE2_DEPLOYER.create2_from_code(salt, library.as_ref()); + let address = create2_deployer.create2_from_code(salt, library.as_ref()); // Skip if already deployed if !self.executor.is_empty_code(address)? { continue; @@ -95,7 +95,7 @@ impl ScriptRunner { .executor .transact_raw( self.evm_opts.sender, - DEFAULT_CREATE2_DEPLOYER, + create2_deployer, calldata.clone().into(), U256::from(0), ) @@ -111,7 +111,7 @@ impl ScriptRunner { from: Some(self.evm_opts.sender), input: calldata.into(), nonce: Some(sender_nonce + library_transactions.len() as u64), - to: Some(TxKind::Call(DEFAULT_CREATE2_DEPLOYER)), + to: Some(TxKind::Call(create2_deployer)), ..Default::default() } .into(), diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index e833073ac..0d7990591 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -64,7 +64,11 @@ impl PreSimulationState { let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); if let Some(TxKind::Call(_)) = to { - builder.set_call(&address_to_abi, &self.execution_artifacts.decoder)?; + builder.set_call( + &address_to_abi, + &self.execution_artifacts.decoder, + self.script_config.evm_opts.create2_deployer, + )?; } else { builder.set_create(false, sender.create(nonce), &address_to_abi)?; } diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index ca6a62269..2cf480b9b 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -4,7 +4,7 @@ use alloy_primitives::{hex, Address, TxKind, B256}; use eyre::Result; use forge_script_sequence::TransactionWithMetadata; use foundry_common::{fmt::format_token_raw, ContractData, TransactionMaybeSigned, SELECTOR_LEN}; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder}; +use foundry_evm::traces::CallTraceDecoder; use itertools::Itertools; use revm_inspectors::tracing::types::CallKind; use std::collections::BTreeMap; @@ -29,16 +29,16 @@ impl ScriptTransactionBuilder { &mut self, local_contracts: &BTreeMap, decoder: &CallTraceDecoder, + create2_deployer: Address, ) -> Result<()> { if let Some(TxKind::Call(to)) = self.transaction.transaction.to() { - if to == DEFAULT_CREATE2_DEPLOYER { + if to == create2_deployer { if let Some(input) = self.transaction.transaction.input() { let (salt, init_code) = input.split_at(32); self.set_create( true, - DEFAULT_CREATE2_DEPLOYER - .create2_from_code(B256::from_slice(salt), init_code), + create2_deployer.create2_from_code(B256::from_slice(salt), init_code), local_contracts, )?; } diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index f15e91d5a..b82126d2d 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -171,6 +171,10 @@ impl ScriptTester { self.args(&["--tc", contract_name, "--sig", sig]) } + pub fn add_create2_deployer(&mut self, create2_deployer: Address) -> &mut Self { + self.args(&["--create2-deployer", create2_deployer.to_string().as_str()]) + } + /// Adds the `--unlocked` flag pub fn unlocked(&mut self) -> &mut Self { self.arg("--unlocked") diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 56ec035ab..824e78443 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -12,7 +12,10 @@ use foundry_block_explorers::{ use foundry_common::{abi::encode_args, compile::ProjectCompiler, provider::RetryProvider, shell}; use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; use foundry_config::Config; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, opts::EvmOpts}; +use foundry_evm::{ + constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, opts::EvmOpts, + traces::TraceMode, +}; use reqwest::Url; use revm_primitives::{ db::Database, @@ -325,6 +328,7 @@ pub async fn get_tracing_executor( fork_config.fork_block_number = Some(fork_blk_num); fork_config.evm_version = evm_version; + let create2_deployer = evm_opts.create2_deployer; let (env, fork, _chain, is_alphanet) = TracingExecutor::get_fork_material(fork_config, evm_opts).await?; @@ -332,10 +336,9 @@ pub async fn get_tracing_executor( env.clone(), fork, Some(fork_config.evm_version), - false, - false, - false, + TraceMode::Call, is_alphanet, + create2_deployer, ); Ok((env, executor)) diff --git a/testdata/default/cheats/Broadcast.t.sol b/testdata/default/cheats/Broadcast.t.sol index bca8cc2ee..97b9d5275 100644 --- a/testdata/default/cheats/Broadcast.t.sol +++ b/testdata/default/cheats/Broadcast.t.sol @@ -219,6 +219,32 @@ contract BroadcastTestNoLinking is DSTest { vm.stopBroadcast(); } + function deployCreate2(address deployer) public { + vm.startBroadcast(); + bytes32 salt = bytes32(uint256(1338)); + NoLink test_c2 = new NoLink{salt: salt}(); + assert(test_c2.view_me() == 1337); + + address expectedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + deployer, + salt, + keccak256(abi.encodePacked(type(NoLink).creationCode, abi.encode())) + ) + ) + ) + ) + ); + require(address(test_c2) == expectedAddress, "Create2 address mismatch"); + + NoLink test2 = new NoLink(); + vm.stopBroadcast(); + } + function errorStaticCall() public { vm.broadcast(); NoLink test11 = new NoLink(); From 7a23a5cf851b991bfd2fde32d4f088319bbc1183 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 30 Nov 2024 15:47:18 +0100 Subject: [PATCH 036/107] fix(coverage): clean ups, use normalized source code for locations (#9438) * feat(coverage): add function line end to LCOV, clean ups * fix(coverage): store normalized source code * fix(coverage): add a Line item for functions too * test: update snapshots * clean --- crates/evm/coverage/src/analysis.rs | 56 ++++++------ crates/evm/coverage/src/anchors.rs | 8 +- crates/evm/coverage/src/lib.rs | 134 ++++++++++++++++------------ crates/forge/bin/cmd/coverage.rs | 21 +++-- crates/forge/src/coverage.rs | 56 ++++++------ crates/forge/tests/cli/coverage.rs | 133 +++++++++++++-------------- 6 files changed, 219 insertions(+), 189 deletions(-) diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index c18ba823b..69bd73075 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -1,7 +1,10 @@ use super::{CoverageItem, CoverageItemKind, SourceLocation}; use alloy_primitives::map::HashMap; use foundry_common::TestFunctionExt; -use foundry_compilers::artifacts::ast::{self, Ast, Node, NodeType}; +use foundry_compilers::artifacts::{ + ast::{self, Ast, Node, NodeType}, + Source, +}; use rayon::prelude::*; use std::sync::Arc; @@ -19,7 +22,7 @@ pub struct ContractVisitor<'a> { /// The current branch ID branch_id: usize, /// Stores the last line we put in the items collection to ensure we don't push duplicate lines - last_line: usize, + last_line: u32, /// Coverage items pub items: Vec, @@ -47,23 +50,20 @@ impl<'a> ContractVisitor<'a> { } fn visit_function_definition(&mut self, node: &Node) -> eyre::Result<()> { - let name: String = - node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; + let Some(body) = &node.body else { return Ok(()) }; let kind: String = node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; - match &node.body { - Some(body) => { - // Do not add coverage item for constructors without statements. - if kind == "constructor" && !has_statements(body) { - return Ok(()) - } - self.push_item_kind(CoverageItemKind::Function { name }, &node.src); - self.visit_block(body) - } - _ => Ok(()), + let name: String = + node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; + + // Do not add coverage item for constructors without statements. + if kind == "constructor" && !has_statements(body) { + return Ok(()) } + self.push_item_kind(CoverageItemKind::Function { name }, &node.src); + self.visit_block(body) } fn visit_modifier_or_yul_fn_definition(&mut self, node: &Node) -> eyre::Result<()> { @@ -454,30 +454,34 @@ impl<'a> ContractVisitor<'a> { /// collection (plus additional coverage line if item is a statement). fn push_item_kind(&mut self, kind: CoverageItemKind, src: &ast::LowFidelitySourceLocation) { let item = CoverageItem { kind, loc: self.source_location_for(src), hits: 0 }; - // Push a line item if we haven't already - if matches!(item.kind, CoverageItemKind::Statement | CoverageItemKind::Branch { .. }) && - self.last_line < item.loc.line - { + + // Push a line item if we haven't already. + debug_assert!(!matches!(item.kind, CoverageItemKind::Line)); + if self.last_line < item.loc.lines.start { self.items.push(CoverageItem { kind: CoverageItemKind::Line, loc: item.loc.clone(), hits: 0, }); - self.last_line = item.loc.line; + self.last_line = item.loc.lines.start; } self.items.push(item); } fn source_location_for(&self, loc: &ast::LowFidelitySourceLocation) -> SourceLocation { - let loc_start = - self.source.char_indices().map(|(i, _)| i).nth(loc.start).unwrap_or_default(); + let bytes_start = loc.start as u32; + let bytes_end = (loc.start + loc.length.unwrap_or(0)) as u32; + let bytes = bytes_start..bytes_end; + + let start_line = self.source[..bytes.start as usize].lines().count() as u32; + let n_lines = self.source[bytes.start as usize..bytes.end as usize].lines().count() as u32; + let lines = start_line..start_line + n_lines; SourceLocation { source_id: self.source_id, contract_name: self.contract_name.clone(), - start: loc.start as u32, - length: loc.length.map(|x| x as u32), - line: self.source[..loc_start].lines().count(), + bytes, + lines, } } } @@ -556,7 +560,7 @@ impl<'a> SourceAnalyzer<'a> { .attribute("name") .ok_or_else(|| eyre::eyre!("Contract has no name"))?; - let mut visitor = ContractVisitor::new(source_id, source, &name); + let mut visitor = ContractVisitor::new(source_id, &source.content, &name); visitor.visit_contract(node)?; let mut items = visitor.items; @@ -590,7 +594,7 @@ pub struct SourceFiles<'a> { #[derive(Debug)] pub struct SourceFile<'a> { /// The source code. - pub source: String, + pub source: Source, /// The AST of the source code. pub ast: &'a Ast, } diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index 6643524d6..ee723d95c 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -177,14 +177,14 @@ fn is_in_source_range(element: &SourceElement, location: &SourceLocation) -> boo } // Needed because some source ranges in the source map mark the entire contract... - let is_within_start = element.offset() >= location.start; + let is_within_start = element.offset() >= location.bytes.start; if !is_within_start { return false; } - let start_of_ranges = location.start.max(element.offset()); - let end_of_ranges = (location.start + location.length.unwrap_or_default()) - .min(element.offset() + element.length()); + let start_of_ranges = location.bytes.start.max(element.offset()); + let end_of_ranges = + (location.bytes.start + location.len()).min(element.offset() + element.length()); let within_ranges = start_of_ranges <= end_of_ranges; if !within_ranges { return false; diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index ad4ab53e3..8d5575631 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -18,7 +18,7 @@ use semver::Version; use std::{ collections::BTreeMap, fmt::Display, - ops::{AddAssign, Deref, DerefMut}, + ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, sync::Arc, }; @@ -82,40 +82,29 @@ impl CoverageReport { self.anchors.extend(anchors); } - /// Get coverage summaries by source file path. - pub fn summary_by_file(&self) -> impl Iterator { - let mut summaries = BTreeMap::new(); - - for (version, items) in self.items.iter() { - for item in items { - let Some(path) = - self.source_paths.get(&(version.clone(), item.loc.source_id)).cloned() - else { - continue; - }; - *summaries.entry(path).or_default() += item; - } - } - - summaries.into_iter() + /// Returns an iterator over coverage summaries by source file path. + pub fn summary_by_file(&self) -> impl Iterator { + self.by_file(|summary: &mut CoverageSummary, item| summary.add_item(item)) } - /// Get coverage items by source file path. - pub fn items_by_source(&self) -> impl Iterator)> { - let mut items_by_source: BTreeMap<_, Vec<_>> = BTreeMap::new(); + /// Returns an iterator over coverage items by source file path. + pub fn items_by_file(&self) -> impl Iterator)> { + self.by_file(|list: &mut Vec<_>, item| list.push(item)) + } - for (version, items) in self.items.iter() { + fn by_file<'a, T: Default>( + &'a self, + mut f: impl FnMut(&mut T, &'a CoverageItem), + ) -> impl Iterator { + let mut by_file: BTreeMap<&Path, T> = BTreeMap::new(); + for (version, items) in &self.items { for item in items { - let Some(path) = - self.source_paths.get(&(version.clone(), item.loc.source_id)).cloned() - else { - continue; - }; - items_by_source.entry(path).or_default().push(item.clone()); + let key = (version.clone(), item.loc.source_id); + let Some(path) = self.source_paths.get(&key) else { continue }; + f(by_file.entry(path).or_default(), item); } } - - items_by_source.into_iter() + by_file.into_iter() } /// Processes data from a [`HitMap`] and sets hit counts for coverage items in this coverage @@ -345,30 +334,34 @@ impl Display for CoverageItem { } } +/// A source location. #[derive(Clone, Debug)] pub struct SourceLocation { /// The source ID. pub source_id: usize, /// The contract this source range is in. pub contract_name: Arc, - /// Start byte in the source code. - pub start: u32, - /// Number of bytes in the source code. - pub length: Option, - /// The line in the source code. - pub line: usize, + /// Byte range. + pub bytes: Range, + /// Line range. Indices are 1-based. + pub lines: Range, } impl Display for SourceLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "source ID {}, line {}, chars {}-{}", - self.source_id, - self.line, - self.start, - self.length.map_or(self.start, |length| self.start + length) - ) + write!(f, "source ID {}, lines {:?}, bytes {:?}", self.source_id, self.lines, self.bytes) + } +} + +impl SourceLocation { + /// Returns the length of the byte range. + pub fn len(&self) -> u32 { + self.bytes.len() as u32 + } + + /// Returns true if the byte range is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 } } @@ -393,21 +386,43 @@ pub struct CoverageSummary { pub function_hits: usize, } -impl AddAssign<&Self> for CoverageSummary { - fn add_assign(&mut self, other: &Self) { - self.line_count += other.line_count; - self.line_hits += other.line_hits; - self.statement_count += other.statement_count; - self.statement_hits += other.statement_hits; - self.branch_count += other.branch_count; - self.branch_hits += other.branch_hits; - self.function_count += other.function_count; - self.function_hits += other.function_hits; +impl CoverageSummary { + /// Creates a new, empty coverage summary. + pub fn new() -> Self { + Self::default() + } + + /// Creates a coverage summary from a collection of coverage items. + pub fn from_items<'a>(items: impl IntoIterator) -> Self { + let mut summary = Self::default(); + summary.add_items(items); + summary } -} -impl AddAssign<&CoverageItem> for CoverageSummary { - fn add_assign(&mut self, item: &CoverageItem) { + /// Adds another coverage summary to this one. + pub fn merge(&mut self, other: &Self) { + let Self { + line_count, + line_hits, + statement_count, + statement_hits, + branch_count, + branch_hits, + function_count, + function_hits, + } = self; + *line_count += other.line_count; + *line_hits += other.line_hits; + *statement_count += other.statement_count; + *statement_hits += other.statement_hits; + *branch_count += other.branch_count; + *branch_hits += other.branch_hits; + *function_count += other.function_count; + *function_hits += other.function_hits; + } + + /// Adds a coverage item to this summary. + pub fn add_item(&mut self, item: &CoverageItem) { match item.kind { CoverageItemKind::Line => { self.line_count += 1; @@ -435,4 +450,11 @@ impl AddAssign<&CoverageItem> for CoverageSummary { } } } + + /// Adds multiple coverage items to this summary. + pub fn add_items<'a>(&mut self, items: impl IntoIterator) { + for item in items { + self.add_item(item); + } + } } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 4ca111a56..10d67d825 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -16,13 +16,16 @@ use forge::{ use foundry_cli::utils::{LoadConfig, STATIC_FUZZ_SEED}; use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{ - artifacts::{sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage}, + artifacts::{ + sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage, Source, + }, Artifact, ArtifactId, Project, ProjectCompileOutput, }; use foundry_config::{Config, SolcReq}; use rayon::prelude::*; use semver::Version; use std::{ + io, path::{Path, PathBuf}, sync::Arc, }; @@ -153,7 +156,7 @@ impl CoverageArgs { let source = SourceFile { ast, - source: fs::read_to_string(&file) + source: Source::read(&file) .wrap_err("Could not read source code for analysis")?, }; versioned_sources @@ -290,19 +293,15 @@ impl CoverageArgs { match report_kind { CoverageReportKind::Summary => SummaryReporter::default().report(&report), CoverageReportKind::Lcov => { - if let Some(report_file) = self.report_file { - return LcovReporter::new(&mut fs::create_file(root.join(report_file))?) - .report(&report) - } else { - return LcovReporter::new(&mut fs::create_file(root.join("lcov.info"))?) - .report(&report) - } + let path = + root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref())); + let mut file = io::BufWriter::new(fs::create_file(path)?); + LcovReporter::new(&mut file).report(&report) } CoverageReportKind::Bytecode => { let destdir = root.join("bytecode-coverage"); fs::create_dir_all(&destdir)?; - BytecodeReporter::new(root.clone(), destdir).report(&report)?; - Ok(()) + BytecodeReporter::new(root.clone(), destdir).report(&report) } CoverageReportKind::Debug => DebugReporter.report(&report), }?; diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index de8d0a8aa..e1236fa2a 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -50,7 +50,7 @@ impl SummaryReporter { impl CoverageReporter for SummaryReporter { fn report(mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, summary) in report.summary_by_file() { - self.total += &summary; + self.total.merge(&summary); self.add_row(path.display(), summary); } @@ -77,66 +77,68 @@ fn format_cell(hits: usize, total: usize) -> Cell { cell } +/// Writes the coverage report in [LCOV]'s [tracefile format]. +/// +/// [LCOV]: https://github.com/linux-test-project/lcov +/// [tracefile format]: https://man.archlinux.org/man/geninfo.1.en#TRACEFILE_FORMAT pub struct LcovReporter<'a> { - /// Destination buffer - destination: &'a mut (dyn Write + 'a), + out: &'a mut (dyn Write + 'a), } impl<'a> LcovReporter<'a> { - pub fn new(destination: &'a mut (dyn Write + 'a)) -> Self { - Self { destination } + /// Create a new LCOV reporter. + pub fn new(out: &'a mut (dyn Write + 'a)) -> Self { + Self { out } } } impl CoverageReporter for LcovReporter<'_> { fn report(self, report: &CoverageReport) -> eyre::Result<()> { - for (file, items) in report.items_by_source() { - let summary = items.iter().fold(CoverageSummary::default(), |mut summary, item| { - summary += item; - summary - }); + for (path, items) in report.items_by_file() { + let summary = CoverageSummary::from_items(items.iter().copied()); - writeln!(self.destination, "TN:")?; - writeln!(self.destination, "SF:{}", file.display())?; + writeln!(self.out, "TN:")?; + writeln!(self.out, "SF:{}", path.display())?; for item in items { - let line = item.loc.line; + let line = item.loc.lines.start; + let line_end = item.loc.lines.end - 1; let hits = item.hits; match item.kind { - CoverageItemKind::Function { name } => { + CoverageItemKind::Function { ref name } => { let name = format!("{}.{name}", item.loc.contract_name); - writeln!(self.destination, "FN:{line},{name}")?; - writeln!(self.destination, "FNDA:{hits},{name}")?; + writeln!(self.out, "FN:{line},{line_end},{name}")?; + writeln!(self.out, "FNDA:{hits},{name}")?; } CoverageItemKind::Line => { - writeln!(self.destination, "DA:{line},{hits}")?; + writeln!(self.out, "DA:{line},{hits}")?; } CoverageItemKind::Branch { branch_id, path_id, .. } => { writeln!( - self.destination, + self.out, "BRDA:{line},{branch_id},{path_id},{}", if hits == 0 { "-".to_string() } else { hits.to_string() } )?; } // Statements are not in the LCOV format. // We don't add them in order to avoid doubling line hits. - _ => {} + CoverageItemKind::Statement { .. } => {} } } // Function summary - writeln!(self.destination, "FNF:{}", summary.function_count)?; - writeln!(self.destination, "FNH:{}", summary.function_hits)?; + writeln!(self.out, "FNF:{}", summary.function_count)?; + writeln!(self.out, "FNH:{}", summary.function_hits)?; // Line summary - writeln!(self.destination, "LF:{}", summary.line_count)?; - writeln!(self.destination, "LH:{}", summary.line_hits)?; + writeln!(self.out, "LF:{}", summary.line_count)?; + writeln!(self.out, "LH:{}", summary.line_hits)?; // Branch summary - writeln!(self.destination, "BRF:{}", summary.branch_count)?; - writeln!(self.destination, "BRH:{}", summary.branch_hits)?; + writeln!(self.out, "BRF:{}", summary.branch_count)?; + writeln!(self.out, "BRH:{}", summary.branch_hits)?; - writeln!(self.destination, "end_of_record")?; + writeln!(self.out, "end_of_record")?; } sh_println!("Wrote LCOV report.")?; @@ -150,7 +152,7 @@ pub struct DebugReporter; impl CoverageReporter for DebugReporter { fn report(self, report: &CoverageReport) -> eyre::Result<()> { - for (path, items) in report.items_by_source() { + for (path, items) in report.items_by_file() { sh_println!("Uncovered for {}:", path.display())?; items.iter().for_each(|item| { if item.hits == 0 { diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 65900c592..060a60371 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -62,8 +62,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | -| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | "#]]); }); @@ -161,8 +161,8 @@ contract BContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/BContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | -| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| src/BContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | "#]]); }); @@ -218,8 +218,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| -| src/AContract.sol | 50.00% (1/2) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 50.00% (1/2) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | +| src/AContract.sol | 66.67% (2/3) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 66.67% (2/3) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | "#]]); @@ -232,8 +232,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|--------------|---------------| -| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 100.00% (2/2) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 100.00% (3/3) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | "#]]); @@ -243,8 +243,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | -| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | +| Total | 100.00% (3/3) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | "#]], ); @@ -299,8 +299,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|--------------|---------------| -| src/AContract.sol | 100.00% (1/1) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 100.00% (1/1) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | "#]]); @@ -313,8 +313,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|--------------|---------------| -| src/AContract.sol | 100.00% (1/1) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 100.00% (1/1) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | "#]]); @@ -324,8 +324,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (1/1) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | -| Total | 100.00% (1/1) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +| Total | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | "#]], ); @@ -376,18 +376,21 @@ contract AContractTest is DSTest { // We want to make sure DA:8,1 is added only once so line hit is not doubled. assert_data_eq!( std::fs::read_to_string(lcov_info).unwrap(), - str![[r#"TN: + str![[r#" +TN: SF:src/AContract.sol -FN:7,AContract.foo +DA:7,1 +FN:7,9,AContract.foo FNDA:1,AContract.foo DA:8,1 FNF:1 FNH:1 -LF:1 -LH:1 +LF:2 +LH:2 BRF:0 BRH:0 -end[..] +end_of_record + "#]] ); }); @@ -617,8 +620,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|----------------|----------------|----------------|---------------| -| src/Foo.sol | 88.89% (24/27) | 90.00% (27/30) | 87.50% (14/16) | 100.00% (9/9) | -| Total | 88.89% (24/27) | 90.00% (27/30) | 87.50% (14/16) | 100.00% (9/9) | +| src/Foo.sol | 91.67% (33/36) | 90.00% (27/30) | 87.50% (14/16) | 100.00% (9/9) | +| Total | 91.67% (33/36) | 90.00% (27/30) | 87.50% (14/16) | 100.00% (9/9) | "#]]); @@ -631,8 +634,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|----------------|----------------|----------------|---------------| -| src/Foo.sol | 96.30% (26/27) | 96.67% (29/30) | 93.75% (15/16) | 100.00% (9/9) | -| Total | 96.30% (26/27) | 96.67% (29/30) | 93.75% (15/16) | 100.00% (9/9) | +| src/Foo.sol | 97.22% (35/36) | 96.67% (29/30) | 93.75% (15/16) | 100.00% (9/9) | +| Total | 97.22% (35/36) | 96.67% (29/30) | 93.75% (15/16) | 100.00% (9/9) | "#]]); @@ -642,8 +645,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|-----------------|---------------| -| src/Foo.sol | 100.00% (27/27) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | -| Total | 100.00% (27/27) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | +| src/Foo.sol | 100.00% (36/36) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | +| Total | 100.00% (36/36) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | "#]], ); @@ -713,10 +716,10 @@ contract AContractTest is DSTest { // constructor calls are not included). cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (9/9) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | -| Total | 100.00% (9/9) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|-----------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (14/14) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +| Total | 100.00% (14/14) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | "#]]); }); @@ -815,8 +818,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|----------------|----------------|--------------|---------------| -| src/Foo.sol | 66.67% (10/15) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | -| Total | 66.67% (10/15) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | +| src/Foo.sol | 75.00% (15/20) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | +| Total | 75.00% (15/20) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | "#]], ); @@ -827,8 +830,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|---------------|---------------| -| src/Foo.sol | 100.00% (15/15) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | -| Total | 100.00% (15/15) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | +| src/Foo.sol | 100.00% (20/20) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | +| Total | 100.00% (20/20) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | "#]], ); @@ -932,8 +935,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|---------------|---------------| -| src/Foo.sol | 100.00% (23/23) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | -| Total | 100.00% (23/23) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +| src/Foo.sol | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +| Total | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | "#]], ); @@ -1022,10 +1025,10 @@ contract FooTest is DSTest { cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( str![[r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------|---------------|---------------|---------------|---------------| -| src/Foo.sol | 100.00% (8/8) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | -| Total | 100.00% (8/8) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|-----------------|---------------|---------------|---------------| +| src/Foo.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +| Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | "#]], ); @@ -1082,8 +1085,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| -| src/AContract.sol | 50.00% (2/4) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | -| Total | 50.00% (2/4) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | "#]]); @@ -1096,8 +1099,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| -| src/AContract.sol | 50.00% (2/4) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | -| Total | 50.00% (2/4) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +| Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | "#]]); @@ -1107,8 +1110,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (4/4) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | -| Total | 100.00% (4/4) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +| Total | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | "#]], ); @@ -1171,8 +1174,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| -| src/AContract.sol | 75.00% (3/4) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 75.00% (3/4) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +| src/AContract.sol | 80.00% (4/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 80.00% (4/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | "#]]); @@ -1185,8 +1188,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| -| src/AContract.sol | 50.00% (2/4) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 50.00% (2/4) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +| src/AContract.sol | 60.00% (3/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +| Total | 60.00% (3/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | "#]]); @@ -1196,8 +1199,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (4/4) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | -| Total | 100.00% (4/4) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | +| Total | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | "#]], ); @@ -1264,10 +1267,10 @@ contract AContractTest is DSTest { cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (9/9) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | -| Total | 100.00% (9/9) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|-----------------|---------------|---------------|---------------| +| src/AContract.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | +| Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | "#]]); }); @@ -1316,8 +1319,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | -| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | "#]]); }); @@ -1359,8 +1362,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | -| Total | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (1/1) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | +| Total | 100.00% (1/1) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | "#]]); }); @@ -1408,8 +1411,8 @@ contract AContractTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | -| Total | 100.00% (2/2) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | "#]]); }); @@ -1442,8 +1445,8 @@ contract AContract { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|-------------|--------------|---------------|-------------| -| src/AContract.sol | 0.00% (0/4) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | -| Total | 0.00% (0/4) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | +| src/AContract.sol | 0.00% (0/5) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | +| Total | 0.00% (0/5) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | "#]]); }); From ac81a53d1d5823919ffbadd3c65f081927aa11f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 01:16:06 +0000 Subject: [PATCH 037/107] chore(deps): weekly `cargo update` (#9440) Locking 52 packages to latest compatible versions Updating alloy-dyn-abi v0.8.12 -> v0.8.14 Updating alloy-eip7702 v0.4.1 -> v0.4.2 Updating alloy-json-abi v0.8.12 -> v0.8.14 Updating alloy-primitives v0.8.12 -> v0.8.14 Updating alloy-sol-macro v0.8.12 -> v0.8.14 Updating alloy-sol-macro-expander v0.8.12 -> v0.8.14 Updating alloy-sol-macro-input v0.8.12 -> v0.8.14 Updating alloy-sol-type-parser v0.8.12 -> v0.8.14 Updating alloy-sol-types v0.8.12 -> v0.8.14 Updating bon v3.0.2 -> v3.1.1 Updating bon-macros v3.0.2 -> v3.1.1 Updating bytes v1.8.0 -> v1.9.0 Updating cargo-platform v0.1.8 -> v0.1.9 Updating cc v1.2.1 -> v1.2.2 Updating const-hex v1.13.2 -> v1.14.0 Updating divan v0.1.15 -> v0.1.16 Updating divan-macros v0.1.15 -> v0.1.16 Updating errno v0.3.9 -> v0.3.10 Updating foundry-fork-db v0.7.1 -> v0.7.2 (available: v0.8.0) Updating gix-config-value v0.14.9 -> v0.14.10 Updating gix-date v0.9.1 -> v0.9.2 Updating gix-path v0.10.12 -> v0.10.13 Updating gix-sec v0.10.9 -> v0.10.10 Updating gix-validate v0.9.1 -> v0.9.2 Updating hashbrown v0.15.1 -> v0.15.2 Updating http-range-header v0.4.1 -> v0.4.2 Updating itoa v1.0.13 -> v1.0.14 Updating jiff v0.1.14 -> v0.1.15 Updating js-sys v0.3.72 -> v0.3.74 Updating libc v0.2.164 -> v0.2.167 Updating mdbook v0.4.42 -> v0.4.43 Updating miette v7.2.0 -> v7.4.0 Updating miette-derive v7.2.0 -> v7.4.0 Updating mio v1.0.2 -> v1.0.3 Updating rustc-hash v2.0.0 -> v2.1.0 Updating rustls v0.23.18 -> v0.23.19 Updating socket2 v0.5.7 -> v0.5.8 Updating syn v2.0.89 -> v2.0.90 Updating syn-solidity v0.8.12 -> v0.8.14 Updating terminal_size v0.4.0 -> v0.4.1 Updating tracing v0.1.40 -> v0.1.41 Updating tracing-attributes v0.1.27 -> v0.1.28 Updating tracing-core v0.1.32 -> v0.1.33 Updating tracing-error v0.2.0 -> v0.2.1 Updating tracing-subscriber v0.3.18 -> v0.3.19 Updating wasm-bindgen v0.2.95 -> v0.2.97 Updating wasm-bindgen-backend v0.2.95 -> v0.2.97 Updating wasm-bindgen-futures v0.4.45 -> v0.4.47 Updating wasm-bindgen-macro v0.2.95 -> v0.2.97 Updating wasm-bindgen-macro-support v0.2.95 -> v0.2.97 Updating wasm-bindgen-shared v0.2.95 -> v0.2.97 Updating web-sys v0.3.72 -> v0.3.74 note: pass `--verbose` to see 43 unchanged dependencies behind latest Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> --- Cargo.lock | 383 +++++++++++++++++++++++++++-------------------------- 1 file changed, 192 insertions(+), 191 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b83fd212c..a0dafa83f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2364c782a245cf8725ea6dbfca5f530162702b5d685992ea03ce64529136cc" +checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6cee6a35793f3db8a5ffe60e86c695f321d081a567211245f503e8c498fce8" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -202,9 +202,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84c506bf264110fa7e90d9924f742f40ef53c6572ea56a0b0bd714a567ed389" +checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -281,9 +281,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" +checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" dependencies = [ "alloy-rlp", "arbitrary", @@ -294,7 +294,7 @@ dependencies = [ "derive_more", "foldhash", "getrandom", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "hex-literal", "indexmap 2.6.0", "itoa", @@ -391,7 +391,7 @@ checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -632,23 +632,23 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343289b4a7461ed8bab8618504c995c049c082b70c7332efd7b32125633dc05" +checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4222d70bec485ceccc5d8fd4f2909edd65b5d5e43d4aca0b5dcee65d519ae98f" +checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -658,16 +658,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e17f2677369571b976e51ea1430eb41c3690d344fef567b840bfc0b01b6f83a" +checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" dependencies = [ "alloy-json-abi", "const-hex", @@ -676,15 +676,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64d80ae58ffaafdff9d5d84f58d03775f66c84433916dc9a64ed16af5755da" +checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" dependencies = [ "serde", "winnow", @@ -692,9 +692,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6520d427d4a8eb7aa803d852d7a52ceb0c519e784c292f64bb339e636918cf27" +checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -769,7 +769,7 @@ dependencies = [ "alloy-transport", "futures", "http 1.1.0", - "rustls 0.23.18", + "rustls 0.23.19", "serde_json", "tokio", "tokio-tungstenite", @@ -1196,7 +1196,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1218,7 +1218,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1229,7 +1229,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1282,7 +1282,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1796,9 +1796,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.0.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a636f83af97c6946f3f5cf5c268ec02375bf5efd371110292dfd57961f57a509" +checksum = "1e47d5c63335658326076cf7c81795af665c534ea552da69526d6cef51b12ed9" dependencies = [ "bon-macros", "rustversion", @@ -1806,9 +1806,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.0.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7eaf1bfaa5b8d512abfd36d0c432591fef139d3de9ee54f1f839ea109d70d33" +checksum = "b162272b6d55562ea30cc937d74ef4d07399e507bfd6eb3860f6a845c7264eef" dependencies = [ "darling", "ident_case", @@ -1816,7 +1816,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1872,9 +1872,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -1936,9 +1936,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -2033,9 +2033,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "shlex", ] @@ -2199,7 +2199,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2417,9 +2417,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487981fa1af147182687064d0a2c336586d337a606595ced9ffb0c685c250c73" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -2529,7 +2529,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "rustix", "signal-hook", @@ -2614,7 +2614,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2625,7 +2625,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2698,7 +2698,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2719,7 +2719,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2729,7 +2729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2750,7 +2750,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "unicode-xid", ] @@ -2864,14 +2864,14 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "divan" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e05d17bd4ff1c1e7998ed4623d2efd91f72f1e24141ac33aac9377974270e1f" +checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" dependencies = [ "cfg-if", "clap", @@ -2883,13 +2883,13 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b4464d46ce68bfc7cb76389248c7c254def7baca8bece0693b02b83842c4c88" +checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3016,7 +3016,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -3050,12 +3050,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3151,7 +3151,7 @@ dependencies = [ "regex", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.90", "toml 0.8.19", "walkdir", ] @@ -3179,7 +3179,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.89", + "syn 2.0.90", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -3572,7 +3572,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4114,9 +4114,9 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fea8068a6b7929229f72059296da09bf3c1d15569fdb4a351d2983450587c12" +checksum = "0f040169c6573e9989d1a26c3dcbe645ef8e4edabbf64af98958552da1073e4e" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -4153,7 +4153,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4312,7 +4312,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4459,27 +4459,27 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.9" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3de3fdca9c75fa4b83a76583d265fa49b1de6b088ebcd210749c24ceeb74660" +checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" dependencies = [ "bitflags 2.6.0", "bstr", "gix-path", "libc", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] name = "gix-date" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d10d543ac13c97292a15e8e8b7889cd006faf739777437ed95362504b8fe81a0" +checksum = "691142b1a34d18e8ed6e6114bc1a2736516c5ad60ef3aa9bd1b694886e3ca92d" dependencies = [ "bstr", "itoa", "jiff", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -4562,15 +4562,15 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.12" +version = "0.10.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c04e5a94fdb56b1e91eb7df2658ad16832428b8eeda24ff1a0f0288de2bce554" +checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" dependencies = [ "bstr", "gix-trace", "home", "once_cell", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -4596,9 +4596,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.9" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2007538eda296445c07949cf04f4a767307d887184d6b3e83e2d636533ddc6e" +checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" dependencies = [ "bitflags 2.6.0", "gix-path", @@ -4637,12 +4637,12 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e187b263461bc36cea17650141567753bc6207d036cedd1de6e81a52f277ff68" +checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" dependencies = [ "bstr", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -4762,9 +4762,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -4846,7 +4846,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4907,9 +4907,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" @@ -5010,7 +5010,7 @@ dependencies = [ "http 1.1.0", "hyper 1.5.1", "hyper-util", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -5205,7 +5205,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5305,7 +5305,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5338,7 +5338,7 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -5424,7 +5424,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5494,15 +5494,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d9d414fc817d3e3d62b2598616733f76c4cc74fbac96069674739b881295c8" +checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" dependencies = [ "jiff-tzdb-platform", "windows-sys 0.59.0", @@ -5525,10 +5525,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -5662,9 +5663,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libdbus-sys" @@ -5758,7 +5759,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -5820,9 +5821,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7624879735513024d323e7267a0b3a7176aceb0db537939beb4ee31d9e8945e3" +checksum = "fe1f98b8d66e537d2f0ba06e7dec4f44001deec539a2d18bfc102d6a86189148" dependencies = [ "ammonia", "anyhow", @@ -5883,9 +5884,9 @@ dependencies = [ [[package]] name = "miette" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "cfg-if", "miette-derive", @@ -5895,13 +5896,13 @@ dependencies = [ [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5958,11 +5959,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi", @@ -5992,7 +5992,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6266,7 +6266,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6406,7 +6406,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6487,7 +6487,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6570,7 +6570,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6629,7 +6629,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6713,7 +6713,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6771,7 +6771,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6872,7 +6872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6950,7 +6950,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6970,7 +6970,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "version_check", "yansi", ] @@ -7034,7 +7034,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7057,7 +7057,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7166,7 +7166,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.18", + "rustls 0.23.19", "socket2", "thiserror 2.0.3", "tokio", @@ -7184,7 +7184,7 @@ dependencies = [ "rand", "ring", "rustc-hash", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "slab", "thiserror 2.0.3", @@ -7418,7 +7418,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "rustls-pki-types", @@ -7659,9 +7659,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" dependencies = [ "rand", ] @@ -7717,9 +7717,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", @@ -7897,7 +7897,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -7939,7 +7939,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8125,7 +8125,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8136,7 +8136,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8180,7 +8180,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8226,7 +8226,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8315,7 +8315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 1.0.2", + "mio 1.0.3", "signal-hook", ] @@ -8430,9 +8430,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -8531,7 +8531,7 @@ checksum = "f0cc54b74e214647c1bbfc098d080cc5deac77f8dcb99aca91747276b01a15ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8695,7 +8695,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8763,9 +8763,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -8774,14 +8774,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fe0a3e1476bdaa0775b9aec5b869ed9520c2b2fedfe9c6df3618f8ea6290b" +checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8807,7 +8807,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8853,9 +8853,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", "windows-sys 0.59.0", @@ -8917,7 +8917,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -8928,7 +8928,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -9046,7 +9046,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -9063,7 +9063,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -9092,7 +9092,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] @@ -9129,7 +9129,7 @@ checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -9320,9 +9320,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -9332,20 +9332,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -9353,9 +9353,9 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", @@ -9374,9 +9374,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -9455,7 +9455,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "sha1", "thiserror 1.0.69", @@ -9734,9 +9734,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -9745,36 +9745,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9782,22 +9783,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasm-streams" @@ -9891,9 +9892,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -10029,7 +10030,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10040,7 +10041,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10051,7 +10052,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10062,7 +10063,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10342,7 +10343,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -10364,7 +10365,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10384,7 +10385,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "synstructure", ] @@ -10405,7 +10406,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -10427,7 +10428,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] From 168b239486c834d9d1fafdd98950e377c044b4db Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 07:56:51 +0200 Subject: [PATCH 038/107] fix(coverage): special functions have no name (#9441) * fix(coverage): special functions have no name * test: don't to_string * test: rm --summary which is not --report=summary * test: add regression test for #9437 * fmt * docs --- Cargo.lock | 1 + crates/evm/coverage/src/analysis.rs | 15 +- crates/forge/bin/cmd/coverage.rs | 5 +- crates/forge/src/coverage.rs | 5 +- crates/forge/tests/cli/coverage.rs | 275 +++++++++++++++++----------- crates/test-utils/Cargo.toml | 1 + crates/test-utils/src/util.rs | 24 ++- 7 files changed, 202 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0dafa83f..3f8fa5bb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4173,6 +4173,7 @@ dependencies = [ "regex", "serde_json", "snapbox", + "tempfile", "tokio", "tracing", "tracing-subscriber", diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 69bd73075..07b916035 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -52,16 +52,20 @@ impl<'a> ContractVisitor<'a> { fn visit_function_definition(&mut self, node: &Node) -> eyre::Result<()> { let Some(body) = &node.body else { return Ok(()) }; - let kind: String = - node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; - let name: String = node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; + let kind: String = + node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; // Do not add coverage item for constructors without statements. if kind == "constructor" && !has_statements(body) { return Ok(()) } + + // `fallback`, `receive`, and `constructor` functions have an empty `name`. + // Use the `kind` itself as the name. + let name = if name.is_empty() { kind } else { name }; + self.push_item_kind(CoverageItemKind::Function { name }, &node.src); self.visit_block(body) } @@ -498,10 +502,7 @@ fn has_statements(node: &Node) -> bool { NodeType::TryStatement | NodeType::VariableDeclarationStatement | NodeType::WhileStatement => true, - _ => { - let statements: Vec = node.attribute("statements").unwrap_or_default(); - !statements.is_empty() - } + _ => node.attribute::>("statements").is_some_and(|s| !s.is_empty()), } } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 10d67d825..3a7d43436 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -310,9 +310,10 @@ impl CoverageArgs { } } -// TODO: HTML -#[derive(Clone, Debug, ValueEnum)] +/// Coverage reports to generate. +#[derive(Clone, Debug, Default, ValueEnum)] pub enum CoverageReportKind { + #[default] Summary, Lcov, Debug, diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index e1236fa2a..14fc5e7be 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -102,12 +102,13 @@ impl CoverageReporter for LcovReporter<'_> { for item in items { let line = item.loc.lines.start; - let line_end = item.loc.lines.end - 1; + // `lines` is half-open, so we need to subtract 1 to get the last included line. + let end_line = item.loc.lines.end - 1; let hits = item.hits; match item.kind { CoverageItemKind::Function { ref name } => { let name = format!("{}.{name}", item.loc.contract_name); - writeln!(self.out, "FN:{line},{line_end},{name}")?; + writeln!(self.out, "FN:{line},{end_line},{name}")?; writeln!(self.out, "FNDA:{hits},{name}")?; } CoverageItemKind::Line => { diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 060a60371..9b0569da6 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -1,18 +1,94 @@ -use foundry_test_utils::{assert_data_eq, str}; +use foundry_common::fs; +use foundry_test_utils::{ + snapbox::{Data, IntoData}, + TestCommand, TestProject, +}; +use std::path::Path; + +fn basic_coverage_base(prj: TestProject, mut cmd: TestCommand) { + cmd.args(["coverage", "--report=lcov", "--report=summary"]).assert_success().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Analysing contracts... +Running tests... + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) +Wrote LCOV report. +| File | % Lines | % Statements | % Branches | % Funcs | +|----------------------|---------------|---------------|---------------|---------------| +| script/Counter.s.sol | 0.00% (0/5) | 0.00% (0/3) | 100.00% (0/0) | 0.00% (0/2) | +| src/Counter.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +| Total | 44.44% (4/9) | 40.00% (2/5) | 100.00% (0/0) | 50.00% (2/4) | + +"# + ]]); + + let lcov = prj.root().join("lcov.info"); + assert!(lcov.exists(), "lcov.info was not created"); + assert_data_eq!( + Data::read_from(&lcov, None), + str![[r#" +TN: +SF:script/Counter.s.sol +DA:10,0 +FN:10,10,CounterScript.setUp +FNDA:0,CounterScript.setUp +DA:12,0 +FN:12,18,CounterScript.run +FNDA:0,CounterScript.run +DA:13,0 +DA:15,0 +DA:17,0 +FNF:2 +FNH:0 +LF:5 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Counter.sol +DA:7,258 +FN:7,9,Counter.setNumber +FNDA:258,Counter.setNumber +DA:8,258 +DA:11,1 +FN:11,13,Counter.increment +FNDA:1,Counter.increment +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record -forgetest!(basic_coverage, |_prj, cmd| { - cmd.args(["coverage"]); - cmd.assert_success(); +"#]] + ); +} + +forgetest_init!(basic_coverage, |prj, cmd| { + basic_coverage_base(prj, cmd); }); -forgetest!(report_file_coverage, |prj, cmd| { - cmd.arg("coverage").args([ - "--report".to_string(), - "lcov".to_string(), - "--report-file".to_string(), - prj.root().join("lcov.info").to_str().unwrap().to_string(), - ]); - cmd.assert_success(); +forgetest_init!(basic_coverage_crlf, |prj, cmd| { + // Manually replace `\n` with `\r\n` in the source file. + let make_crlf = |path: &Path| { + fs::write(path, fs::read_to_string(path).unwrap().replace('\n', "\r\n")).unwrap() + }; + make_crlf(&prj.paths().sources.join("Counter.sol")); + make_crlf(&prj.paths().scripts.join("Counter.s.sol")); + + // Should have identical stdout and lcov output. + basic_coverage_base(prj, cmd); }); forgetest!(test_setup_coverage, |prj, cmd| { @@ -58,7 +134,7 @@ contract AContractTest is DSTest { .unwrap(); // Assert 100% coverage (init function coverage called in setUp is accounted). - cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| @@ -151,20 +227,16 @@ contract BContractTest is DSTest { .unwrap(); // Assert AContract is not included in report. - cmd.arg("coverage") - .args([ - "--no-match-coverage".to_string(), - "AContract".to_string(), // Filter out `AContract` - ]) - .assert_success() - .stdout_eq(str![[r#" + cmd.arg("coverage").arg("--no-match-coverage=AContract").assert_success().stdout_eq(str![[ + r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| | src/BContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | | Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | -"#]]); +"# + ]]); }); forgetest!(test_assert_coverage, |prj, cmd| { @@ -211,43 +283,38 @@ contract AContractTest is DSTest { .unwrap(); // Assert 50% branch coverage for assert failure. - cmd.arg("coverage") - .args(["--mt".to_string(), "testAssertRevertBranch".to_string()]) - .assert_success() - .stdout_eq(str![[r#" + cmd.arg("coverage").args(["--mt", "testAssertRevertBranch"]).assert_success().stdout_eq(str![ + [r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| | src/AContract.sol | 66.67% (2/3) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | | Total | 66.67% (2/3) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | -"#]]); +"#] + ]); // Assert 50% branch coverage for proper assert. - cmd.forge_fuse() - .arg("coverage") - .args(["--mt".to_string(), "testAssertBranch".to_string()]) - .assert_success() - .stdout_eq(str![[r#" + cmd.forge_fuse().arg("coverage").args(["--mt", "testAssertBranch"]).assert_success().stdout_eq( + str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|--------------|---------------| | src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | | Total | 100.00% (3/3) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | -"#]]); +"#]], + ); // Assert 100% coverage (assert properly covered). - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| | src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | | Total | 100.00% (3/3) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | -"#]], - ); +"#]]); }); forgetest!(test_require_coverage, |prj, cmd| { @@ -292,10 +359,7 @@ contract AContractTest is DSTest { .unwrap(); // Assert 50% branch coverage if only revert tested. - cmd.arg("coverage") - .args(["--mt".to_string(), "testRequireRevert".to_string()]) - .assert_success() - .stdout_eq(str![[r#" + cmd.arg("coverage").args(["--mt", "testRequireRevert"]).assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|--------------|---------------| @@ -307,7 +371,7 @@ contract AContractTest is DSTest { // Assert 50% branch coverage if only happy path tested. cmd.forge_fuse() .arg("coverage") - .args(["--mt".to_string(), "testRequireNoRevert".to_string()]) + .args(["--mt", "testRequireNoRevert"]) .assert_success() .stdout_eq(str![[r#" ... @@ -319,16 +383,14 @@ contract AContractTest is DSTest { "#]]); // Assert 100% branch coverage. - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| | src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | | Total | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | -"#]], - ); +"#]]); }); forgetest!(test_line_hit_not_doubled, |prj, cmd| { @@ -363,19 +425,9 @@ contract AContractTest is DSTest { ) .unwrap(); - let lcov_info = prj.root().join("lcov.info"); - cmd.arg("coverage").args([ - "--report".to_string(), - "lcov".to_string(), - "--report-file".to_string(), - lcov_info.to_str().unwrap().to_string(), - ]); - cmd.assert_success(); - assert!(lcov_info.exists()); - // We want to make sure DA:8,1 is added only once so line hit is not doubled. - assert_data_eq!( - std::fs::read_to_string(lcov_info).unwrap(), + assert_lcov( + cmd.arg("coverage"), str![[r#" TN: SF:src/AContract.sol @@ -391,7 +443,7 @@ BRF:0 BRH:0 end_of_record -"#]] +"#]], ); }); @@ -611,10 +663,7 @@ contract FooTest is DSTest { // Assert no coverage for single path branch. 2 branches (parent and child) not covered. cmd.arg("coverage") - .args([ - "--nmt".to_string(), - "test_single_path_child_branch|test_single_path_parent_branch".to_string(), - ]) + .args(["--nmt", "test_single_path_child_branch|test_single_path_parent_branch"]) .assert_success() .stdout_eq(str![[r#" ... @@ -628,7 +677,7 @@ contract FooTest is DSTest { // Assert no coverage for single path child branch. 1 branch (child) not covered. cmd.forge_fuse() .arg("coverage") - .args(["--nmt".to_string(), "test_single_path_child_branch".to_string()]) + .args(["--nmt", "test_single_path_child_branch"]) .assert_success() .stdout_eq(str![[r#" ... @@ -640,16 +689,14 @@ contract FooTest is DSTest { "#]]); // Assert 100% coverage. - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|-----------------|---------------| | src/Foo.sol | 100.00% (36/36) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | | Total | 100.00% (36/36) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | -"#]], - ); +"#]]); }); forgetest!(test_function_call_coverage, |prj, cmd| { @@ -712,9 +759,8 @@ contract AContractTest is DSTest { ) .unwrap(); - // Assert 100% coverage and only 9 lines reported (comments, type conversions and struct - // constructor calls are not included). - cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" + // Assert 100% coverage. + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|-----------------|---------------|---------------|---------------| @@ -813,28 +859,24 @@ contract FooTest is DSTest { .unwrap(); // Assert coverage not 100% for happy paths only. - cmd.arg("coverage").args(["--mt".to_string(), "happy".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.arg("coverage").args(["--mt", "happy"]).assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|----------------|----------------|--------------|---------------| | src/Foo.sol | 75.00% (15/20) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | | Total | 75.00% (15/20) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | -"#]], - ); +"#]]); // Assert 100% branch coverage (including clauses without body). - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|---------------|---------------| | src/Foo.sol | 100.00% (20/20) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | | Total | 100.00% (20/20) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | -"#]], - ); +"#]]); }); forgetest!(test_yul_coverage, |prj, cmd| { @@ -930,16 +972,14 @@ contract FooTest is DSTest { ) .unwrap(); - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|---------------|---------------| | src/Foo.sol | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | | Total | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | -"#]], - ); +"#]]); }); forgetest!(test_misc_coverage, |prj, cmd| { @@ -1022,16 +1062,14 @@ contract FooTest is DSTest { ) .unwrap(); - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|---------------|---------------|---------------| | src/Foo.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | | Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | -"#]], - ); +"#]]); }); // https://github.com/foundry-rs/foundry/issues/8605 @@ -1078,10 +1116,7 @@ contract AContractTest is DSTest { .unwrap(); // Assert 50% coverage for true branches. - cmd.arg("coverage") - .args(["--mt".to_string(), "testTrueCoverage".to_string()]) - .assert_success() - .stdout_eq(str![[r#" + cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| @@ -1093,7 +1128,7 @@ contract AContractTest is DSTest { // Assert 50% coverage for false branches. cmd.forge_fuse() .arg("coverage") - .args(["--mt".to_string(), "testFalseCoverage".to_string()]) + .args(["--mt", "testFalseCoverage"]) .assert_success() .stdout_eq(str![[r#" ... @@ -1105,16 +1140,14 @@ contract AContractTest is DSTest { "#]]); // Assert 100% coverage (true/false branches properly covered). - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| | src/AContract.sol | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | | Total | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | -"#]], - ); +"#]]); }); // https://github.com/foundry-rs/foundry/issues/8604 @@ -1167,10 +1200,7 @@ contract AContractTest is DSTest { .unwrap(); // Assert 50% coverage for true branches. - cmd.arg("coverage") - .args(["--mt".to_string(), "testTrueCoverage".to_string()]) - .assert_success() - .stdout_eq(str![[r#" + cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|--------------|--------------|--------------|---------------| @@ -1182,7 +1212,7 @@ contract AContractTest is DSTest { // Assert 50% coverage for false branches. cmd.forge_fuse() .arg("coverage") - .args(["--mt".to_string(), "testFalseCoverage".to_string()]) + .args(["--mt", "testFalseCoverage"]) .assert_success() .stdout_eq(str![[r#" ... @@ -1194,16 +1224,14 @@ contract AContractTest is DSTest { "#]]); // Assert 100% coverage (true/false branches properly covered). - cmd.forge_fuse().arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq( - str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| | src/AContract.sol | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | | Total | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | -"#]], - ); +"#]]); }); forgetest!(test_identical_bytecodes, |prj, cmd| { @@ -1265,7 +1293,7 @@ contract AContractTest is DSTest { ) .unwrap(); - cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|-----------------|---------------|---------------|---------------| @@ -1315,7 +1343,7 @@ contract AContractTest is DSTest { ) .unwrap(); - cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| @@ -1358,7 +1386,7 @@ contract AContractTest is DSTest { .unwrap(); // Assert there's only one function (`increment`) reported. - cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| @@ -1406,8 +1434,32 @@ contract AContractTest is DSTest { ) .unwrap(); - // Assert both constructor and receive functions coverage reported. - cmd.arg("coverage").args(["--summary".to_string()]).assert_success().stdout_eq(str![[r#" + // Assert both constructor and receive functions coverage reported and appear in LCOV. + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/AContract.sol +DA:7,1 +FN:7,9,AContract.constructor +FNDA:1,AContract.constructor +DA:8,1 +DA:11,1 +FN:11,13,AContract.receive +FNDA:1,AContract.receive +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); + + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| @@ -1450,3 +1502,8 @@ contract AContract { "#]]); }); + +#[track_caller] +fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) { + cmd.args(["--report=lcov", "--report-file"]).assert_file(data); +} diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 11b7800e6..efe6d288f 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -31,6 +31,7 @@ tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } rand.workspace = true snapbox = { version = "0.6", features = ["json", "regex"] } +tempfile.workspace = true [dev-dependencies] tokio.workspace = true diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index f887a40ce..a86304923 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -11,7 +11,7 @@ use foundry_compilers::{ use foundry_config::Config; use parking_lot::Mutex; use regex::Regex; -use snapbox::{assert_data_eq, cmd::OutputAssert, str, IntoData}; +use snapbox::{assert_data_eq, cmd::OutputAssert, Data, IntoData}; use std::{ env, ffi::OsStr, @@ -902,10 +902,10 @@ impl TestCommand { assert_data_eq!(actual, expected); } - /// Runs the command and asserts that it **failed** nothing was printed to stdout. + /// Runs the command and asserts that it **succeeded** nothing was printed to stdout. #[track_caller] pub fn assert_empty_stdout(&mut self) { - self.assert_success().stdout_eq(str![[r#""#]]); + self.assert_success().stdout_eq(Data::new()); } /// Runs the command and asserts that it failed. @@ -923,7 +923,23 @@ impl TestCommand { /// Runs the command and asserts that it **failed** nothing was printed to stderr. #[track_caller] pub fn assert_empty_stderr(&mut self) { - self.assert_failure().stderr_eq(str![[r#""#]]); + self.assert_failure().stderr_eq(Data::new()); + } + + /// Runs the command with a temporary file argument and asserts that the contents of the file + /// match the given data. + #[track_caller] + pub fn assert_file(&mut self, data: impl IntoData) { + self.assert_file_with(|this, path| _ = this.arg(path).assert_success(), data); + } + + /// Creates a temporary file, passes it to `f`, then asserts that the contents of the file match + /// the given data. + #[track_caller] + pub fn assert_file_with(&mut self, f: impl FnOnce(&mut Self, &Path), data: impl IntoData) { + let file = tempfile::NamedTempFile::new().expect("couldn't create temporary file"); + f(self, file.path()); + assert_data_eq!(Data::read_from(file.path(), None), data); } /// Does not apply [`snapbox`] redactions to the command output. From d4e91c80266defb486c7b3626f44600f0cc1e0fc Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 07:57:05 +0200 Subject: [PATCH 039/107] feat(cast): allow some more stdin inputs (#9442) --- crates/cast/bin/args.rs | 14 +++++++------- crates/cast/bin/main.rs | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index fb7fb0757..47bf9a884 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -422,7 +422,7 @@ pub enum CastSubcommand { #[command(visible_alias = "ca")] ComputeAddress { /// The deployer address. - address: Option, + address: Option
, /// The nonce of the deployer address. #[arg(long)] @@ -432,11 +432,11 @@ pub enum CastSubcommand { rpc: RpcOpts, }, - /// Disassembles hex encoded bytecode into individual / human readable opcodes + /// Disassembles a hex-encoded bytecode into a human-readable representation. #[command(visible_alias = "da")] Disassemble { - /// The hex encoded bytecode. - bytecode: String, + /// The hex-encoded bytecode. + bytecode: Option, }, /// Build and sign a transaction. @@ -754,7 +754,7 @@ pub enum CastSubcommand { #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - /// Disassemble bytecodes into individual opcodes. + /// Disassemble bytecodes. #[arg(long, short)] disassemble: bool, @@ -1030,8 +1030,8 @@ pub enum CastSubcommand { /// Extracts function selectors and arguments from bytecode #[command(visible_alias = "sel")] Selectors { - /// The hex encoded bytecode. - bytecode: String, + /// The hex-encoded bytecode. + bytecode: Option, /// Resolve the function signatures for the extracted selectors using https://openchain.xyz #[arg(long, short)] diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index fcb5a20eb..01bab16ac 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -380,14 +380,16 @@ async fn main_args(args: CastArgs) -> Result<()> { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; - let address: Address = stdin::unwrap_line(address)?.parse()?; + let address = stdin::unwrap_line(address)?; let computed = Cast::new(provider).compute_address(address, nonce).await?; sh_println!("Computed Address: {}", computed.to_checksum(None))? } CastSubcommand::Disassemble { bytecode } => { + let bytecode = stdin::unwrap_line(bytecode)?; sh_println!("{}", SimpleCast::disassemble(&hex::decode(bytecode)?)?)? } CastSubcommand::Selectors { bytecode, resolve } => { + let bytecode = stdin::unwrap_line(bytecode)?; let functions = SimpleCast::extract_functions(&bytecode)?; let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0); let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0); From 8b8d1cd9da30b7d5baeb0971809ebc518752c2b1 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:17:51 +0200 Subject: [PATCH 040/107] chore(config): remove RootPath (#9448) --- crates/cheatcodes/src/config.rs | 8 ++-- crates/cli/src/opts/build/core.rs | 2 +- crates/cli/src/utils/mod.rs | 2 +- crates/config/src/lib.rs | 80 ++++++++++--------------------- crates/forge/bin/cmd/bind_json.rs | 2 +- crates/forge/bin/cmd/build.rs | 2 +- crates/forge/bin/cmd/doc/mod.rs | 2 +- crates/forge/bin/cmd/fmt.rs | 6 +-- crates/forge/bin/cmd/test/mod.rs | 2 +- crates/forge/bin/cmd/update.rs | 2 +- crates/forge/bin/cmd/watch.rs | 2 +- crates/forge/tests/cli/config.rs | 2 +- crates/forge/tests/it/repros.rs | 2 +- crates/script/src/simulate.rs | 2 +- 14 files changed, 41 insertions(+), 75 deletions(-) diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index fa1ec6039..2279e2435 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -68,7 +68,7 @@ impl CheatsConfig { running_contract: Option, running_version: Option, ) -> Self { - let mut allowed_paths = vec![config.root.0.clone()]; + let mut allowed_paths = vec![config.root.clone()]; allowed_paths.extend(config.libs.clone()); allowed_paths.extend(config.allow_paths.clone()); @@ -88,8 +88,8 @@ impl CheatsConfig { rpc_endpoints, paths: config.project_paths(), fs_permissions: config.fs_permissions.clone().joined(config.root.as_ref()), - root: config.root.0.clone(), - broadcast: config.root.0.clone().join(&config.broadcast), + root: config.root.clone(), + broadcast: config.root.clone().join(&config.broadcast), allowed_paths, evm_opts, labels: config.labels.clone(), @@ -239,7 +239,7 @@ mod tests { fn config(root: &str, fs_permissions: FsPermissions) -> CheatsConfig { CheatsConfig::new( - &Config { root: PathBuf::from(root).into(), fs_permissions, ..Default::default() }, + &Config { root: root.into(), fs_permissions, ..Default::default() }, Default::default(), None, None, diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index d25018ae8..ff52cf0d6 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -204,7 +204,7 @@ impl<'a> From<&'a CoreBuildArgs> for Config { // if `--config-path` is set we need to adjust the config's root path to the actual root // path for the project, otherwise it will the parent dir of the `--config-path` if args.project_paths.config_path.is_some() { - config.root = args.project_paths.project_root().into(); + config.root = args.project_paths.project_root(); } config } diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 9f8475f63..6a2afe657 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -287,7 +287,7 @@ impl<'a> Git<'a> { #[inline] pub fn from_config(config: &'a Config) -> Self { - Self::new(config.root.0.as_path()) + Self::new(config.root.as_path()) } pub fn root_of(relative_to: &Path) -> Result { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b88f134d2..f37c38345 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -168,6 +168,14 @@ pub struct Config { /// See `profile`. #[serde(skip)] pub profiles: Vec, + + /// The root path where the config detection started from, [`Config::with_root`]. + // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] + // representation, but will be deserialized from the `Figment` so that forge commands can + // override it. + #[serde(default = "root_default", skip_serializing)] + pub root: PathBuf, + /// path of the source contracts dir, like `src` or `contracts` pub src: PathBuf, /// path of the test dir @@ -466,13 +474,6 @@ pub struct Config { /// Soldeer custom configs pub soldeer: Option, - /// The root path where the config detection started from, [`Config::with_root`]. - // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] - // representation, but will be deserialized from the `Figment` so that forge commands can - // override it. - #[serde(default, skip_serializing)] - pub root: RootPath, - /// Whether failed assertions should revert. /// /// Note that this only applies to native (cheatcode) assertions, invoked on Vm contract. @@ -679,7 +680,7 @@ impl Config { return Figment::from(self); } - let root = self.root.0.as_path(); + let root = self.root.as_path(); let profile = Self::selected_profile(); let mut figment = Figment::default().merge(DappHardhatDirProvider(root)); @@ -760,7 +761,7 @@ impl Config { /// This joins all relative paths with the current root and attempts to make them canonic #[must_use] pub fn canonic(self) -> Self { - let root = self.root.0.clone(); + let root = self.root.clone(); self.canonic_at(root) } @@ -1006,7 +1007,7 @@ impl Config { .set_no_artifacts(no_artifacts); if !self.skip.is_empty() { - let filter = SkipBuildFilters::new(self.skip.clone(), self.root.0.clone()); + let filter = SkipBuildFilters::new(self.skip.clone(), self.root.clone()); builder = builder.sparse_output(filter); } @@ -1057,7 +1058,7 @@ impl Config { fn ensure_solc(&self) -> Result, SolcError> { if self.eof { let (tx, rx) = mpsc::channel(); - let root = self.root.0.clone(); + let root = self.root.clone(); std::thread::spawn(move || { tx.send( Solc::new_with_args( @@ -1167,7 +1168,7 @@ impl Config { .artifacts(&self.out) .libs(self.libs.iter()) .remappings(self.get_all_remappings()) - .allowed_path(&self.root.0) + .allowed_path(&self.root) .allowed_paths(&self.libs) .allowed_paths(&self.allow_paths) .include_paths(&self.include_paths); @@ -1176,7 +1177,7 @@ impl Config { builder = builder.build_infos(build_info_path); } - builder.build_with_root(&self.root.0) + builder.build_with_root(&self.root) } /// Returns configuration for a compiler to use when setting up a [Project]. @@ -1428,7 +1429,7 @@ impl Config { /// Returns the remapping for the project's _test_ directory, but only if it exists pub fn get_test_dir_remapping(&self) -> Option { - if self.root.0.join(&self.test).exists() { + if self.root.join(&self.test).exists() { get_dir_remapping(&self.test) } else { None @@ -1437,7 +1438,7 @@ impl Config { /// Returns the remapping for the project's _script_ directory, but only if it exists pub fn get_script_dir_remapping(&self) -> Option { - if self.root.0.join(&self.script).exists() { + if self.root.join(&self.script).exists() { get_dir_remapping(&self.script) } else { None @@ -1615,7 +1616,7 @@ impl Config { let paths = ProjectPathsConfig::builder().build_with_root::<()>(root); let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); Self { - root: paths.root.into(), + root: paths.root, src: paths.sources.file_name().unwrap().into(), out: artifacts.clone(), libs: paths.libraries.into_iter().map(|lib| lib.file_name().unwrap().into()).collect(), @@ -1707,7 +1708,7 @@ impl Config { pub fn update_libs(&self) -> eyre::Result<()> { self.update(|doc| { let profile = self.profile.as_str().as_str(); - let root = &self.root.0; + let root = &self.root; let libs: toml_edit::Value = self .libs .iter() @@ -1764,7 +1765,7 @@ impl Config { /// Returns the path to the `foundry.toml` of this `Config`. pub fn get_config_path(&self) -> PathBuf { - self.root.0.join(Self::FILE_NAME) + self.root.join(Self::FILE_NAME) } /// Returns the selected profile. @@ -2211,43 +2212,6 @@ pub(crate) mod from_opt_glob { } } -/// A helper wrapper around the root path used during Config detection -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct RootPath(pub PathBuf); - -impl Default for RootPath { - fn default() -> Self { - ".".into() - } -} - -impl> From

for RootPath { - fn from(p: P) -> Self { - Self(p.into()) - } -} - -impl AsRef for RootPath { - fn as_ref(&self) -> &Path { - &self.0 - } -} - -impl std::ops::Deref for RootPath { - type Target = PathBuf; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for RootPath { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - /// Parses a config profile /// /// All `Profile` date is ignored by serde, however the `Config::to_string_pretty` includes it and @@ -2299,7 +2263,7 @@ impl Default for Config { profiles: vec![Self::DEFAULT_PROFILE], fs_permissions: FsPermissions::new([PathPermission::read("out")]), isolate: cfg!(feature = "isolate-by-default"), - root: Default::default(), + root: root_default(), src: "src".into(), test: "test".into(), script: "script".into(), @@ -3068,6 +3032,10 @@ fn canonic(path: impl Into) -> PathBuf { foundry_compilers::utils::canonicalize(&path).unwrap_or(path) } +fn root_default() -> PathBuf { + ".".into() +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/cmd/bind_json.rs b/crates/forge/bin/cmd/bind_json.rs index de7a5a7a7..d8a361134 100644 --- a/crates/forge/bin/cmd/bind_json.rs +++ b/crates/forge/bin/cmd/bind_json.rs @@ -64,7 +64,7 @@ impl BindJsonArgs { let config = self.try_load_config_emit_warnings()?; let project = config.create_project(false, true)?; - let target_path = config.root.0.join(self.out.as_ref().unwrap_or(&config.bind_json.out)); + let target_path = config.root.join(self.out.as_ref().unwrap_or(&config.bind_json.out)); let sources = project.paths.read_input_files()?; let graph = Graph::::resolve_sources(&project.paths, sources)?; diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 3efe68db7..7dea6b006 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -137,7 +137,7 @@ impl BuildArgs { // directories as well as the `foundry.toml` configuration file. self.watch.watchexec_config(|| { let config = Config::from(self); - let foundry_toml: PathBuf = config.root.0.join(Config::FILE_NAME); + let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME); [config.src, config.test, config.script, foundry_toml] }) } diff --git a/crates/forge/bin/cmd/doc/mod.rs b/crates/forge/bin/cmd/doc/mod.rs index ad61facf5..2fa996a04 100644 --- a/crates/forge/bin/cmd/doc/mod.rs +++ b/crates/forge/bin/cmd/doc/mod.rs @@ -68,7 +68,7 @@ pub struct DocArgs { impl DocArgs { pub async fn run(self) -> Result<()> { let config = self.config()?; - let root = &config.root.0; + let root = &config.root; let project = config.project()?; let compiler = ProjectCompiler::new().quiet(true); let _output = compiler.compile(&project)?; diff --git a/crates/forge/bin/cmd/fmt.rs b/crates/forge/bin/cmd/fmt.rs index 49548e1b6..137e139e6 100644 --- a/crates/forge/bin/cmd/fmt.rs +++ b/crates/forge/bin/cmd/fmt.rs @@ -48,7 +48,7 @@ impl FmtArgs { let config = self.try_load_config_emit_warnings()?; // Expand ignore globs and canonicalize from the get go - let ignored = expand_globs(&config.root.0, config.fmt.ignore.iter())? + let ignored = expand_globs(&config.root, config.fmt.ignore.iter())? .iter() .flat_map(foundry_common::fs::canonicalize_path) .collect::>(); @@ -96,9 +96,7 @@ impl FmtArgs { let format = |source: String, path: Option<&Path>| -> Result<_> { let name = match path { - Some(path) => { - path.strip_prefix(&config.root.0).unwrap_or(path).display().to_string() - } + Some(path) => path.strip_prefix(&config.root).unwrap_or(path).display().to_string(), None => "stdin".to_string(), }; diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 18ab08d6d..e5a058c0d 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -559,7 +559,7 @@ impl TestArgs { if self.decode_internal.is_some() { let sources = - ContractSources::from_project_output(output, &config.root.0, Some(&libraries))?; + ContractSources::from_project_output(output, &config.root, Some(&libraries))?; builder = builder.with_debug_identifier(DebugTraceIdentifier::new(sources)); } let mut decoder = builder.build(); diff --git a/crates/forge/bin/cmd/update.rs b/crates/forge/bin/cmd/update.rs index 5ddc5460a..c61b03d7a 100644 --- a/crates/forge/bin/cmd/update.rs +++ b/crates/forge/bin/cmd/update.rs @@ -52,7 +52,7 @@ impl UpdateArgs { /// Returns `(root, paths)` where `root` is the root of the Git repository and `paths` are the /// relative paths of the dependencies. pub fn dependencies_paths(deps: &[Dependency], config: &Config) -> Result<(PathBuf, Vec)> { - let git_root = Git::root_of(&config.root.0)?; + let git_root = Git::root_of(&config.root)?; let libs = config.install_lib_dir(); let mut paths = Vec::with_capacity(deps.len()); diff --git a/crates/forge/bin/cmd/watch.rs b/crates/forge/bin/cmd/watch.rs index 1f679d2c7..926aecdba 100644 --- a/crates/forge/bin/cmd/watch.rs +++ b/crates/forge/bin/cmd/watch.rs @@ -268,7 +268,7 @@ pub async fn watch_test(args: TestArgs) -> Result<()> { args.watch.run_all; let last_test_files = Mutex::new(HashSet::::default()); - let project_root = config.root.0.to_string_lossy().into_owned(); + let project_root = config.root.to_string_lossy().into_owned(); let config = args.watch.watchexec_config_with_override( || [&config.test, &config.src], move |events, command| { diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 9a7e61c6a..02ff2fce8 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -31,7 +31,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { profile: Config::DEFAULT_PROFILE, // `profiles` is not serialized. profiles: vec![], - root: Default::default(), + root: ".".into(), src: "test-src".into(), test: "test-test".into(), script: "test-script".into(), diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 69c3a0fb3..0e4adbbc2 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -299,7 +299,7 @@ test_repro!(6538); // https://github.com/foundry-rs/foundry/issues/6554 test_repro!(6554; |config| { - let path = config.runner.config.root.0.join("out/default/Issue6554.t.sol"); + let path = config.runner.config.root.join("out/default/Issue6554.t.sol"); let mut prj_config = Config::clone(&config.runner.config); prj_config.fs_permissions.add(PathPermission::read_write(path)); diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 0d7990591..cf6aeb349 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -427,7 +427,7 @@ impl FilledTransactionsState { )?) }; - let commit = get_commit_hash(&self.script_config.config.root.0); + let commit = get_commit_hash(&self.script_config.config.root); let libraries = self .build_data From ddb19d08a7f5eea76c8a05f232aef726228b0af8 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:27:08 +0200 Subject: [PATCH 041/107] chore(config): move providers into module (#9449) --- crates/cli/src/opts/build/core.rs | 3 +- crates/config/src/lib.rs | 520 +--------------------- crates/config/src/providers/ext.rs | 562 ++++++++++++++++++++++++ crates/config/src/providers/mod.rs | 156 +------ crates/config/src/providers/warnings.rs | 109 +++++ 5 files changed, 682 insertions(+), 668 deletions(-) create mode 100644 crates/config/src/providers/ext.rs create mode 100644 crates/config/src/providers/warnings.rs diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index ff52cf0d6..52a925ebb 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -16,8 +16,7 @@ use foundry_config::{ Figment, Metadata, Profile, Provider, }, filter::SkipBuildFilter, - providers::remappings::Remappings, - Config, + Config, Remappings, }; use serde::Serialize; use std::path::PathBuf; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f37c38345..e1c3fa5ee 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -38,7 +38,6 @@ use foundry_compilers::{ ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, RestrictionsWithVersion, VyperLanguage, }; -use inflector::Inflector; use regex::Regex; use revm_primitives::{map::AddressHashMap, FixedBytes, SpecId}; use semver::Version; @@ -99,7 +98,8 @@ pub use alloy_chains::{Chain, NamedChain}; pub use figment; pub mod providers; -use providers::{remappings::RemappingsProvider, FallbackProfileProvider, WarningsProvider}; +pub use providers::Remappings; +use providers::*; mod fuzz; pub use fuzz::{FuzzConfig, FuzzDictionaryConfig}; @@ -524,7 +524,7 @@ pub struct Config { pub _non_exhaustive: (), } -/// Mapping of fallback standalone sections. See [`FallbackProfileProvider`] +/// Mapping of fallback standalone sections. See [`FallbackProfileProvider`]. pub const STANDALONE_FALLBACK_SECTIONS: &[(&str, &str)] = &[("invariant", "fuzz")]; /// Deprecated keys and their replacements. @@ -2050,7 +2050,7 @@ impl Config { let provider = toml_provider.strict_select(profiles); // apply any key fixes - let provider = BackwardsCompatTomlProvider(ForcedSnakeCaseData(provider)); + let provider = &BackwardsCompatTomlProvider(ForcedSnakeCaseData(provider)); // merge the default profile as a base if profile != Self::DEFAULT_PROFILE { @@ -2450,518 +2450,6 @@ impl> From for SolcReq { } } -/// A convenience provider to retrieve a toml file. -/// This will return an error if the env var is set but the file does not exist -struct TomlFileProvider { - pub env_var: Option<&'static str>, - pub default: PathBuf, - pub cache: Option, Error>>, -} - -impl TomlFileProvider { - fn new(env_var: Option<&'static str>, default: impl Into) -> Self { - Self { env_var, default: default.into(), cache: None } - } - - fn env_val(&self) -> Option { - self.env_var.and_then(Env::var) - } - - fn file(&self) -> PathBuf { - self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone()) - } - - fn is_missing(&self) -> bool { - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { - return true; - } - } - false - } - - pub fn cached(mut self) -> Self { - self.cache = Some(self.read()); - self - } - - fn read(&self) -> Result, Error> { - use serde::de::Error as _; - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { - return Err(Error::custom(format!( - "Config file `{}` set in env var `{}` does not exist", - file, - self.env_var.unwrap() - ))); - } - Toml::file(file) - } else { - Toml::file(&self.default) - } - .nested() - .data() - } -} - -impl Provider for TomlFileProvider { - fn metadata(&self) -> Metadata { - if self.is_missing() { - Metadata::named("TOML file provider") - } else { - Toml::file(self.file()).nested().metadata() - } - } - - fn data(&self) -> Result, Error> { - if let Some(cache) = self.cache.as_ref() { - cache.clone() - } else { - self.read() - } - } -} - -/// A Provider that ensures all keys are snake case if they're not standalone sections, See -/// `Config::STANDALONE_SECTIONS` -struct ForcedSnakeCaseData

(P); - -impl Provider for ForcedSnakeCaseData

{ - fn metadata(&self) -> Metadata { - self.0.metadata() - } - - fn data(&self) -> Result, Error> { - let mut map = Map::new(); - for (profile, dict) in self.0.data()? { - if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { - // don't force snake case for keys in standalone sections - map.insert(profile, dict); - continue; - } - map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect()); - } - Ok(map) - } -} - -/// A Provider that handles breaking changes in toml files -struct BackwardsCompatTomlProvider

(P); - -impl Provider for BackwardsCompatTomlProvider

{ - fn metadata(&self) -> Metadata { - self.0.metadata() - } - - fn data(&self) -> Result, Error> { - let mut map = Map::new(); - let solc_env = std::env::var("FOUNDRY_SOLC_VERSION") - .or_else(|_| std::env::var("DAPP_SOLC_VERSION")) - .map(Value::from) - .ok(); - for (profile, mut dict) in self.0.data()? { - if let Some(v) = solc_env.clone() { - // ENV var takes precedence over config file - dict.insert("solc".to_string(), v); - } else if let Some(v) = dict.remove("solc_version") { - // only insert older variant if not already included - if !dict.contains_key("solc") { - dict.insert("solc".to_string(), v); - } - } - - if let Some(v) = dict.remove("odyssey") { - dict.insert("alphanet".to_string(), v); - } - map.insert(profile, dict); - } - Ok(map) - } -} - -/// A provider that sets the `src` and `output` path depending on their existence. -struct DappHardhatDirProvider<'a>(&'a Path); - -impl Provider for DappHardhatDirProvider<'_> { - fn metadata(&self) -> Metadata { - Metadata::named("Dapp Hardhat dir compat") - } - - fn data(&self) -> Result, Error> { - let mut dict = Dict::new(); - dict.insert( - "src".to_string(), - ProjectPathsConfig::find_source_dir(self.0) - .file_name() - .unwrap() - .to_string_lossy() - .to_string() - .into(), - ); - dict.insert( - "out".to_string(), - ProjectPathsConfig::find_artifacts_dir(self.0) - .file_name() - .unwrap() - .to_string_lossy() - .to_string() - .into(), - ); - - // detect libs folders: - // if `lib` _and_ `node_modules` exists: include both - // if only `node_modules` exists: include `node_modules` - // include `lib` otherwise - let mut libs = vec![]; - let node_modules = self.0.join("node_modules"); - let lib = self.0.join("lib"); - if node_modules.exists() { - if lib.exists() { - libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); - } - libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string()); - } else { - libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); - } - - dict.insert("libs".to_string(), libs.into()); - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_ -struct DappEnvCompatProvider; - -impl Provider for DappEnvCompatProvider { - fn metadata(&self) -> Metadata { - Metadata::named("Dapp env compat") - } - - fn data(&self) -> Result, Error> { - use serde::de::Error as _; - use std::env; - - let mut dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_NUMBER") { - dict.insert( - "block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_TEST_ADDRESS") { - dict.insert("sender".to_string(), val.into()); - } - if let Ok(val) = env::var("DAPP_FORK_BLOCK") { - dict.insert( - "fork_block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") { - dict.insert( - "fork_block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") { - dict.insert( - "block_timestamp".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") { - dict.insert( - "optimizer_runs".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") { - // Activate Solidity optimizer (0 or 1) - let val = val.parse::().map_err(figment::Error::custom)?; - if val > 1 { - return Err( - format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into() - ); - } - dict.insert("optimizer".to_string(), (val == 1).into()); - } - - // libraries in env vars either as `[..]` or single string separated by comma - if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) { - dict.insert("libraries".to_string(), utils::to_array_value(&val)?); - } - - let mut fuzz_dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") { - fuzz_dict.insert( - "runs".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - dict.insert("fuzz".to_string(), fuzz_dict.into()); - - let mut invariant_dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_DEPTH") { - invariant_dict.insert( - "depth".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - dict.insert("invariant".to_string(), invariant_dict.into()); - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// Renames a profile from `from` to `to`. -/// -/// For example given: -/// -/// ```toml -/// [from] -/// key = "value" -/// ``` -/// -/// RenameProfileProvider will output -/// -/// ```toml -/// [to] -/// key = "value" -/// ``` -struct RenameProfileProvider

{ - provider: P, - from: Profile, - to: Profile, -} - -impl

RenameProfileProvider

{ - pub fn new(provider: P, from: impl Into, to: impl Into) -> Self { - Self { provider, from: from.into(), to: to.into() } - } -} - -impl Provider for RenameProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - let mut data = self.provider.data()?; - if let Some(data) = data.remove(&self.from) { - return Ok(Map::from([(self.to.clone(), data)])); - } - Ok(Default::default()) - } - fn profile(&self) -> Option { - Some(self.to.clone()) - } -} - -/// Unwraps a profile reducing the key depth -/// -/// For example given: -/// -/// ```toml -/// [wrapping_key.profile] -/// key = "value" -/// ``` -/// -/// UnwrapProfileProvider will output: -/// -/// ```toml -/// [profile] -/// key = "value" -/// ``` -struct UnwrapProfileProvider

{ - provider: P, - wrapping_key: Profile, - profile: Profile, -} - -impl

UnwrapProfileProvider

{ - pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { - Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } - } -} - -impl Provider for UnwrapProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - self.provider.data().and_then(|mut data| { - if let Some(profiles) = data.remove(&self.wrapping_key) { - for (profile_str, profile_val) in profiles { - let profile = Profile::new(&profile_str); - if profile != self.profile { - continue; - } - match profile_val { - Value::Dict(_, dict) => return Ok(profile.collect(dict)), - bad_val => { - let mut err = Error::from(figment::error::Kind::InvalidType( - bad_val.to_actual(), - "dict".into(), - )); - err.metadata = Some(self.provider.metadata()); - err.profile = Some(self.profile.clone()); - return Err(err); - } - } - } - } - Ok(Default::default()) - }) - } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Wraps a profile in another profile -/// -/// For example given: -/// -/// ```toml -/// [profile] -/// key = "value" -/// ``` -/// -/// WrapProfileProvider will output: -/// -/// ```toml -/// [wrapping_key.profile] -/// key = "value" -/// ``` -struct WrapProfileProvider

{ - provider: P, - wrapping_key: Profile, - profile: Profile, -} - -impl

WrapProfileProvider

{ - pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { - Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } - } -} - -impl Provider for WrapProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - if let Some(inner) = self.provider.data()?.remove(&self.profile) { - let value = Value::from(inner); - let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect(); - Ok(self.wrapping_key.collect(dict)) - } else { - Ok(Default::default()) - } - } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Extracts the profile from the `profile` key and using the original key as backup, merging -/// values where necessary -/// -/// For example given: -/// -/// ```toml -/// [profile.cool] -/// key = "value" -/// -/// [cool] -/// key2 = "value2" -/// ``` -/// -/// OptionalStrictProfileProvider will output: -/// -/// ```toml -/// [cool] -/// key = "value" -/// key2 = "value2" -/// ``` -/// -/// And emit a deprecation warning -struct OptionalStrictProfileProvider

{ - provider: P, - profiles: Vec, -} - -impl

OptionalStrictProfileProvider

{ - pub const PROFILE_PROFILE: Profile = Profile::const_new("profile"); - - pub fn new(provider: P, profiles: impl IntoIterator>) -> Self { - Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() } - } -} - -impl Provider for OptionalStrictProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - let mut figment = Figment::from(&self.provider); - for profile in &self.profiles { - figment = figment.merge(UnwrapProfileProvider::new( - &self.provider, - Self::PROFILE_PROFILE, - profile.clone(), - )); - } - figment.data().map_err(|err| { - // figment does tag metadata and tries to map metadata to an error, since we use a new - // figment in this provider this new figment does not know about the metadata of the - // provider and can't map the metadata to the error. Therefore we return the root error - // if this error originated in the provider's data. - if let Err(root_err) = self.provider.data() { - return root_err; - } - err - }) - } - fn profile(&self) -> Option { - self.profiles.last().cloned() - } -} - -trait ProviderExt: Provider { - fn rename( - &self, - from: impl Into, - to: impl Into, - ) -> RenameProfileProvider<&Self> { - RenameProfileProvider::new(self, from, to) - } - - fn wrap( - &self, - wrapping_key: impl Into, - profile: impl Into, - ) -> WrapProfileProvider<&Self> { - WrapProfileProvider::new(self, wrapping_key, profile) - } - - fn strict_select( - &self, - profiles: impl IntoIterator>, - ) -> OptionalStrictProfileProvider<&Self> { - OptionalStrictProfileProvider::new(self, profiles) - } - - fn fallback( - &self, - profile: impl Into, - fallback: impl Into, - ) -> FallbackProfileProvider<&Self> { - FallbackProfileProvider::new(self, profile, fallback) - } -} -impl ProviderExt for P {} - /// A subset of the foundry `Config` /// used to initialize a `foundry.toml` file /// diff --git a/crates/config/src/providers/ext.rs b/crates/config/src/providers/ext.rs new file mode 100644 index 000000000..040f2127c --- /dev/null +++ b/crates/config/src/providers/ext.rs @@ -0,0 +1,562 @@ +use crate::{utils, Config}; +use figment::{ + providers::{Env, Format, Toml}, + value::{Dict, Map, Value}, + Error, Figment, Metadata, Profile, Provider, +}; +use foundry_compilers::ProjectPathsConfig; +use inflector::Inflector; +use std::path::{Path, PathBuf}; + +pub(crate) trait ProviderExt: Provider + Sized { + fn rename( + self, + from: impl Into, + to: impl Into, + ) -> RenameProfileProvider { + RenameProfileProvider::new(self, from, to) + } + + fn wrap( + self, + wrapping_key: impl Into, + profile: impl Into, + ) -> WrapProfileProvider { + WrapProfileProvider::new(self, wrapping_key, profile) + } + + fn strict_select( + self, + profiles: impl IntoIterator>, + ) -> OptionalStrictProfileProvider { + OptionalStrictProfileProvider::new(self, profiles) + } + + fn fallback( + self, + profile: impl Into, + fallback: impl Into, + ) -> FallbackProfileProvider { + FallbackProfileProvider::new(self, profile, fallback) + } +} + +impl ProviderExt for P {} + +/// A convenience provider to retrieve a toml file. +/// This will return an error if the env var is set but the file does not exist +pub(crate) struct TomlFileProvider { + pub env_var: Option<&'static str>, + pub default: PathBuf, + pub cache: Option, Error>>, +} + +impl TomlFileProvider { + pub(crate) fn new(env_var: Option<&'static str>, default: impl Into) -> Self { + Self { env_var, default: default.into(), cache: None } + } + + fn env_val(&self) -> Option { + self.env_var.and_then(Env::var) + } + + fn file(&self) -> PathBuf { + self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone()) + } + + fn is_missing(&self) -> bool { + if let Some(file) = self.env_val() { + let path = Path::new(&file); + if !path.exists() { + return true; + } + } + false + } + + pub(crate) fn cached(mut self) -> Self { + self.cache = Some(self.read()); + self + } + + fn read(&self) -> Result, Error> { + use serde::de::Error as _; + if let Some(file) = self.env_val() { + let path = Path::new(&file); + if !path.exists() { + return Err(Error::custom(format!( + "Config file `{}` set in env var `{}` does not exist", + file, + self.env_var.unwrap() + ))); + } + Toml::file(file) + } else { + Toml::file(&self.default) + } + .nested() + .data() + } +} + +impl Provider for TomlFileProvider { + fn metadata(&self) -> Metadata { + if self.is_missing() { + Metadata::named("TOML file provider") + } else { + Toml::file(self.file()).nested().metadata() + } + } + + fn data(&self) -> Result, Error> { + if let Some(cache) = self.cache.as_ref() { + cache.clone() + } else { + self.read() + } + } +} + +/// A Provider that ensures all keys are snake case if they're not standalone sections, See +/// `Config::STANDALONE_SECTIONS` +pub(crate) struct ForcedSnakeCaseData

(pub(crate) P); + +impl Provider for ForcedSnakeCaseData

{ + fn metadata(&self) -> Metadata { + self.0.metadata() + } + + fn data(&self) -> Result, Error> { + let mut map = Map::new(); + for (profile, dict) in self.0.data()? { + if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { + // don't force snake case for keys in standalone sections + map.insert(profile, dict); + continue; + } + map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect()); + } + Ok(map) + } +} + +/// A Provider that handles breaking changes in toml files +pub(crate) struct BackwardsCompatTomlProvider

(pub(crate) P); + +impl Provider for BackwardsCompatTomlProvider

{ + fn metadata(&self) -> Metadata { + self.0.metadata() + } + + fn data(&self) -> Result, Error> { + let mut map = Map::new(); + let solc_env = std::env::var("FOUNDRY_SOLC_VERSION") + .or_else(|_| std::env::var("DAPP_SOLC_VERSION")) + .map(Value::from) + .ok(); + for (profile, mut dict) in self.0.data()? { + if let Some(v) = solc_env.clone() { + // ENV var takes precedence over config file + dict.insert("solc".to_string(), v); + } else if let Some(v) = dict.remove("solc_version") { + // only insert older variant if not already included + if !dict.contains_key("solc") { + dict.insert("solc".to_string(), v); + } + } + + if let Some(v) = dict.remove("odyssey") { + dict.insert("alphanet".to_string(), v); + } + map.insert(profile, dict); + } + Ok(map) + } +} + +/// A provider that sets the `src` and `output` path depending on their existence. +pub(crate) struct DappHardhatDirProvider<'a>(pub(crate) &'a Path); + +impl Provider for DappHardhatDirProvider<'_> { + fn metadata(&self) -> Metadata { + Metadata::named("Dapp Hardhat dir compat") + } + + fn data(&self) -> Result, Error> { + let mut dict = Dict::new(); + dict.insert( + "src".to_string(), + ProjectPathsConfig::find_source_dir(self.0) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() + .into(), + ); + dict.insert( + "out".to_string(), + ProjectPathsConfig::find_artifacts_dir(self.0) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() + .into(), + ); + + // detect libs folders: + // if `lib` _and_ `node_modules` exists: include both + // if only `node_modules` exists: include `node_modules` + // include `lib` otherwise + let mut libs = vec![]; + let node_modules = self.0.join("node_modules"); + let lib = self.0.join("lib"); + if node_modules.exists() { + if lib.exists() { + libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); + } + libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string()); + } else { + libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); + } + + dict.insert("libs".to_string(), libs.into()); + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_ +pub(crate) struct DappEnvCompatProvider; + +impl Provider for DappEnvCompatProvider { + fn metadata(&self) -> Metadata { + Metadata::named("Dapp env compat") + } + + fn data(&self) -> Result, Error> { + use serde::de::Error as _; + use std::env; + + let mut dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_NUMBER") { + dict.insert( + "block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_TEST_ADDRESS") { + dict.insert("sender".to_string(), val.into()); + } + if let Ok(val) = env::var("DAPP_FORK_BLOCK") { + dict.insert( + "fork_block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") { + dict.insert( + "fork_block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") { + dict.insert( + "block_timestamp".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") { + dict.insert( + "optimizer_runs".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") { + // Activate Solidity optimizer (0 or 1) + let val = val.parse::().map_err(figment::Error::custom)?; + if val > 1 { + return Err( + format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into() + ); + } + dict.insert("optimizer".to_string(), (val == 1).into()); + } + + // libraries in env vars either as `[..]` or single string separated by comma + if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) { + dict.insert("libraries".to_string(), utils::to_array_value(&val)?); + } + + let mut fuzz_dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") { + fuzz_dict.insert( + "runs".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + dict.insert("fuzz".to_string(), fuzz_dict.into()); + + let mut invariant_dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_DEPTH") { + invariant_dict.insert( + "depth".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + dict.insert("invariant".to_string(), invariant_dict.into()); + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// Renames a profile from `from` to `to`. +/// +/// For example given: +/// +/// ```toml +/// [from] +/// key = "value" +/// ``` +/// +/// RenameProfileProvider will output +/// +/// ```toml +/// [to] +/// key = "value" +/// ``` +pub(crate) struct RenameProfileProvider

{ + provider: P, + from: Profile, + to: Profile, +} + +impl

RenameProfileProvider

{ + pub(crate) fn new(provider: P, from: impl Into, to: impl Into) -> Self { + Self { provider, from: from.into(), to: to.into() } + } +} + +impl Provider for RenameProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + let mut data = self.provider.data()?; + if let Some(data) = data.remove(&self.from) { + return Ok(Map::from([(self.to.clone(), data)])); + } + Ok(Default::default()) + } + fn profile(&self) -> Option { + Some(self.to.clone()) + } +} + +/// Unwraps a profile reducing the key depth +/// +/// For example given: +/// +/// ```toml +/// [wrapping_key.profile] +/// key = "value" +/// ``` +/// +/// UnwrapProfileProvider will output: +/// +/// ```toml +/// [profile] +/// key = "value" +/// ``` +struct UnwrapProfileProvider

{ + provider: P, + wrapping_key: Profile, + profile: Profile, +} + +impl

UnwrapProfileProvider

{ + pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { + Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } + } +} + +impl Provider for UnwrapProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + self.provider.data().and_then(|mut data| { + if let Some(profiles) = data.remove(&self.wrapping_key) { + for (profile_str, profile_val) in profiles { + let profile = Profile::new(&profile_str); + if profile != self.profile { + continue; + } + match profile_val { + Value::Dict(_, dict) => return Ok(profile.collect(dict)), + bad_val => { + let mut err = Error::from(figment::error::Kind::InvalidType( + bad_val.to_actual(), + "dict".into(), + )); + err.metadata = Some(self.provider.metadata()); + err.profile = Some(self.profile.clone()); + return Err(err); + } + } + } + } + Ok(Default::default()) + }) + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Wraps a profile in another profile +/// +/// For example given: +/// +/// ```toml +/// [profile] +/// key = "value" +/// ``` +/// +/// WrapProfileProvider will output: +/// +/// ```toml +/// [wrapping_key.profile] +/// key = "value" +/// ``` +pub(crate) struct WrapProfileProvider

{ + provider: P, + wrapping_key: Profile, + profile: Profile, +} + +impl

WrapProfileProvider

{ + pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { + Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } + } +} + +impl Provider for WrapProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + if let Some(inner) = self.provider.data()?.remove(&self.profile) { + let value = Value::from(inner); + let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect(); + Ok(self.wrapping_key.collect(dict)) + } else { + Ok(Default::default()) + } + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Extracts the profile from the `profile` key and using the original key as backup, merging +/// values where necessary +/// +/// For example given: +/// +/// ```toml +/// [profile.cool] +/// key = "value" +/// +/// [cool] +/// key2 = "value2" +/// ``` +/// +/// OptionalStrictProfileProvider will output: +/// +/// ```toml +/// [cool] +/// key = "value" +/// key2 = "value2" +/// ``` +/// +/// And emit a deprecation warning +pub(crate) struct OptionalStrictProfileProvider

{ + provider: P, + profiles: Vec, +} + +impl

OptionalStrictProfileProvider

{ + pub const PROFILE_PROFILE: Profile = Profile::const_new("profile"); + + pub fn new(provider: P, profiles: impl IntoIterator>) -> Self { + Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() } + } +} + +impl Provider for OptionalStrictProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + let mut figment = Figment::from(&self.provider); + for profile in &self.profiles { + figment = figment.merge(UnwrapProfileProvider::new( + &self.provider, + Self::PROFILE_PROFILE, + profile.clone(), + )); + } + figment.data().map_err(|err| { + // figment does tag metadata and tries to map metadata to an error, since we use a new + // figment in this provider this new figment does not know about the metadata of the + // provider and can't map the metadata to the error. Therefore we return the root error + // if this error originated in the provider's data. + if let Err(root_err) = self.provider.data() { + return root_err; + } + err + }) + } + fn profile(&self) -> Option { + self.profiles.last().cloned() + } +} + +/// Extracts the profile from the `profile` key and sets unset values according to the fallback +/// provider +pub struct FallbackProfileProvider

{ + provider: P, + profile: Profile, + fallback: Profile, +} + +impl

FallbackProfileProvider

{ + /// Creates a new fallback profile provider. + pub fn new(provider: P, profile: impl Into, fallback: impl Into) -> Self { + Self { provider, profile: profile.into(), fallback: fallback.into() } + } +} + +impl Provider for FallbackProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + + fn data(&self) -> Result, Error> { + let data = self.provider.data()?; + if let Some(fallback) = data.get(&self.fallback) { + let mut inner = data.get(&self.profile).cloned().unwrap_or_default(); + for (k, v) in fallback.iter() { + if !inner.contains_key(k) { + inner.insert(k.to_owned(), v.clone()); + } + } + Ok(self.profile.collect(inner)) + } else { + Ok(data) + } + } + + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} diff --git a/crates/config/src/providers/mod.rs b/crates/config/src/providers/mod.rs index 9bd8a014f..9fec7d290 100644 --- a/crates/config/src/providers/mod.rs +++ b/crates/config/src/providers/mod.rs @@ -1,154 +1,10 @@ //! Config providers. -use crate::{Config, Warning, DEPRECATIONS}; -use figment::{ - value::{Dict, Map, Value}, - Error, Figment, Metadata, Profile, Provider, -}; -use std::collections::BTreeMap; +mod ext; +pub use ext::*; -/// Remappings provider -pub mod remappings; +mod remappings; +pub use remappings::*; -/// Generate warnings for unknown sections and deprecated keys -pub struct WarningsProvider

{ - provider: P, - profile: Profile, - old_warnings: Result, Error>, -} - -impl WarningsProvider

{ - const WARNINGS_KEY: &'static str = "__warnings"; - - /// Creates a new warnings provider. - pub fn new( - provider: P, - profile: impl Into, - old_warnings: Result, Error>, - ) -> Self { - Self { provider, profile: profile.into(), old_warnings } - } - - /// Creates a new figment warnings provider. - pub fn for_figment(provider: P, figment: &Figment) -> Self { - let old_warnings = { - let warnings_res = figment.extract_inner(Self::WARNINGS_KEY); - if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) { - Ok(vec![]) - } else { - warnings_res - } - }; - Self::new(provider, figment.profile().clone(), old_warnings) - } - - /// Collects all warnings. - pub fn collect_warnings(&self) -> Result, Error> { - let data = self.provider.data().unwrap_or_default(); - - let mut out = self.old_warnings.clone()?; - - // Add warning for unknown sections. - out.extend( - data.keys() - .filter(|k| { - **k != Config::PROFILE_SECTION && - !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) - }) - .map(|unknown_section| { - let source = self.provider.metadata().source.map(|s| s.to_string()); - Warning::UnknownSection { unknown_section: unknown_section.clone(), source } - }), - ); - - // Add warning for deprecated keys. - let deprecated_key_warning = |key| { - DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { - if key == *deprecated_key { - Some(Warning::DeprecatedKey { - old: deprecated_key.to_string(), - new: new_value.to_string(), - }) - } else { - None - } - }) - }; - let profiles = data - .iter() - .filter(|(profile, _)| **profile == Config::PROFILE_SECTION) - .map(|(_, dict)| dict); - out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning)); - out.extend( - profiles - .filter_map(|dict| dict.get(self.profile.as_str().as_str())) - .filter_map(Value::as_dict) - .flat_map(BTreeMap::keys) - .filter_map(deprecated_key_warning), - ); - - Ok(out) - } -} - -impl Provider for WarningsProvider

{ - fn metadata(&self) -> Metadata { - if let Some(source) = self.provider.metadata().source { - Metadata::from("Warnings", source) - } else { - Metadata::named("Warnings") - } - } - - fn data(&self) -> Result, Error> { - let warnings = self.collect_warnings()?; - Ok(Map::from([( - self.profile.clone(), - Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]), - )])) - } - - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Extracts the profile from the `profile` key and sets unset values according to the fallback -/// provider -pub struct FallbackProfileProvider

{ - provider: P, - profile: Profile, - fallback: Profile, -} - -impl

FallbackProfileProvider

{ - /// Creates a new fallback profile provider. - pub fn new(provider: P, profile: impl Into, fallback: impl Into) -> Self { - Self { provider, profile: profile.into(), fallback: fallback.into() } - } -} - -impl Provider for FallbackProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - - fn data(&self) -> Result, Error> { - let data = self.provider.data()?; - if let Some(fallback) = data.get(&self.fallback) { - let mut inner = data.get(&self.profile).cloned().unwrap_or_default(); - for (k, v) in fallback.iter() { - if !inner.contains_key(k) { - inner.insert(k.to_owned(), v.clone()); - } - } - Ok(self.profile.collect(inner)) - } else { - Ok(data) - } - } - - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} +mod warnings; +pub use warnings::*; diff --git a/crates/config/src/providers/warnings.rs b/crates/config/src/providers/warnings.rs new file mode 100644 index 000000000..944225be1 --- /dev/null +++ b/crates/config/src/providers/warnings.rs @@ -0,0 +1,109 @@ +use crate::{Config, Warning, DEPRECATIONS}; +use figment::{ + value::{Dict, Map, Value}, + Error, Figment, Metadata, Profile, Provider, +}; +use std::collections::BTreeMap; + +/// Generate warnings for unknown sections and deprecated keys +pub struct WarningsProvider

{ + provider: P, + profile: Profile, + old_warnings: Result, Error>, +} + +impl WarningsProvider

{ + const WARNINGS_KEY: &'static str = "__warnings"; + + /// Creates a new warnings provider. + pub fn new( + provider: P, + profile: impl Into, + old_warnings: Result, Error>, + ) -> Self { + Self { provider, profile: profile.into(), old_warnings } + } + + /// Creates a new figment warnings provider. + pub fn for_figment(provider: P, figment: &Figment) -> Self { + let old_warnings = { + let warnings_res = figment.extract_inner(Self::WARNINGS_KEY); + if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) { + Ok(vec![]) + } else { + warnings_res + } + }; + Self::new(provider, figment.profile().clone(), old_warnings) + } + + /// Collects all warnings. + pub fn collect_warnings(&self) -> Result, Error> { + let data = self.provider.data().unwrap_or_default(); + + let mut out = self.old_warnings.clone()?; + + // Add warning for unknown sections. + out.extend( + data.keys() + .filter(|k| { + **k != Config::PROFILE_SECTION && + !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) + }) + .map(|unknown_section| { + let source = self.provider.metadata().source.map(|s| s.to_string()); + Warning::UnknownSection { unknown_section: unknown_section.clone(), source } + }), + ); + + // Add warning for deprecated keys. + let deprecated_key_warning = |key| { + DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { + if key == *deprecated_key { + Some(Warning::DeprecatedKey { + old: deprecated_key.to_string(), + new: new_value.to_string(), + }) + } else { + None + } + }) + }; + let profiles = data + .iter() + .filter(|(profile, _)| **profile == Config::PROFILE_SECTION) + .map(|(_, dict)| dict); + out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning)); + out.extend( + profiles + .filter_map(|dict| dict.get(self.profile.as_str().as_str())) + .filter_map(Value::as_dict) + .flat_map(BTreeMap::keys) + .filter_map(deprecated_key_warning), + ); + + Ok(out) + } +} + +impl Provider for WarningsProvider

{ + fn metadata(&self) -> Metadata { + if let Some(source) = self.provider.metadata().source { + Metadata::from("Warnings", source) + } else { + Metadata::named("Warnings") + } + } + + fn data(&self) -> Result, Error> { + let warnings = self.collect_warnings()?; + Ok(Map::from([( + self.profile.clone(), + Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]), + )])) + } + + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} From c4d81b9f022ee2e5344f88276b662543e62460cd Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:31:02 +0530 Subject: [PATCH 042/107] chore(deps): alloy 0.7 (#9447) * bump deps * fix: receipts * bump core * fix --- Cargo.lock | 191 ++++++++++++------- Cargo.toml | 66 +++---- crates/anvil/core/src/eth/block.rs | 5 + crates/anvil/core/src/eth/transaction/mod.rs | 28 +-- crates/anvil/src/eth/backend/mem/storage.rs | 32 ++-- crates/cast/tests/cli/main.rs | 1 + crates/cheatcodes/Cargo.toml | 1 + crates/cheatcodes/src/fs.rs | 2 +- crates/common/Cargo.toml | 1 + crates/common/fmt/src/ui.rs | 16 +- crates/common/src/transactions.rs | 3 +- crates/forge/bin/cmd/create.rs | 4 +- crates/script-sequence/Cargo.toml | 1 + crates/script-sequence/src/reader.rs | 2 +- crates/script-sequence/src/sequence.rs | 2 +- crates/script/src/receipts.rs | 2 +- 16 files changed, 212 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f8fa5bb5..ea8b8da74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,14 +86,15 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae09ffd7c29062431dd86061deefe4e3c6f07fa0d674930095f8dcedb0baf02c" +checksum = "3a1ff8439834ab71a4b0ecd1a8ff80b3921c87615f158940c3364f399c732786" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", + "alloy-trie 0.7.4", "auto_impl", "c-kzg", "derive_more", @@ -101,11 +102,25 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-consensus-any" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519a86faaa6729464365a90c04eba68539b6d3a30f426edb4b3dafd78920d42f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-contract" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66430a72d5bf5edead101c8c2f0a24bada5ec9f3cf9909b3e08b6d6899b4803e" +checksum = "cca2b353d8b7f160dc930dfa174557acefece6deab5ecd7e6230d38858579eea" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -173,9 +188,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6aa3961694b30ba53d41006131a2fca3bdab22e4c344e46db2c639e7c2dfdd" +checksum = "8dedb328c2114284f767e075589ca9de8d5e9c8a91333402f4804a584ed71a38" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -191,9 +206,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f7877ded3921d18a0a9556d55bedf84535567198c9edab2aa23106da91855" +checksum = "4841e8dd4e0f53d76b501fd4c6bc21d95d688bc8ebf0ea359fc6c7ab65b48742" dependencies = [ "alloy-primitives", "alloy-serde", @@ -214,9 +229,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3694b7e480728c0b3e228384f223937f14c10caef5a4c766021190fc8f283d35" +checksum = "254f770918f96dc4ec88a15e6e2e243358e1719d66b40ef814428e7697079d25" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -228,15 +243,17 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea94b8ceb5c75d7df0a93ba0acc53b55a22b47b532b600a800a87ef04eb5b0b4" +checksum = "931dd176c6e33355f3dc0170ec69cf5b951f4d73870b276e2c837ab35f9c5136" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", + "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", @@ -251,9 +268,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9f3e281005943944d15ee8491534a1c7b3cbf7a7de26f8c433b842b93eb5f9" +checksum = "fa6ec0f23be233e851e31c5e4badfedfa9c7bc177bc37f4e03616072cd40a806" dependencies = [ "alloy-consensus", "alloy-eips", @@ -264,9 +281,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9805d126f24be459b958973c0569c73e1aadd27d4535eee82b2b6764aa03616" +checksum = "e3bce85f0f67b2248c2eb42941bb75079ac53648569a668e8bfd7de5a831ec64" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -313,9 +330,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c1f9eede27bf4c13c099e8e64d54efd7ce80ef6ea47478aa75d5d74e2dba3b" +checksum = "5545e2cbf2f8f24c68bb887ba0294fa12a2f816b9e72c4f226cd137b77d0e294" dependencies = [ "alloy-chains", "alloy-consensus", @@ -326,6 +343,7 @@ dependencies = [ "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-debug", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", @@ -355,9 +373,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f1f34232f77341076541c405482e4ae12f0ee7153d8f9969fc1691201b2247" +checksum = "b633f7731a3df2f4f334001bf80436565113816c5aa5c136c1ded563051e049b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -396,9 +414,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374dbe0dc3abdc2c964f36b3d3edf9cdb3db29d16bda34aa123f03d810bec1dd" +checksum = "aed9e40c2a73265ebf70f1e48303ee55920282e1ea5971e832873fb2d32cea74" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -422,9 +440,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c74832aa474b670309c20fffc2a869fa141edab7c79ff7963fad0a08de60bae1" +checksum = "42dea20fa715a6f39ec7adc735cfd9567342870737270ac67795d55896527772" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -438,9 +456,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca97963132f78ddfc60e43a017348e6d52eea983925c23652f5b330e8e02291" +checksum = "2750f4f694b27461915b9794df60177198bf733da38dde71aadfbe2946a3c0be" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -448,11 +466,33 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-rpc-types-any" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d7620e22d6ed7c58451dd303d0501ade5a8bec9dc8daef0fbc48ceffabbae1" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d2d4a265fb1198272cc43d8d418c0423cdfc1aebcd283be9105464874a1dda" +dependencies = [ + "alloy-primitives", + "serde", +] + [[package]] name = "alloy-rpc-types-engine" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56294dce86af23ad6ee8df46cf8b0d292eb5d1ff67dc88a0886051e32b1faf" +checksum = "9fb843daa6feb011475f0db8c499fff5ac62e1e6012fc01d97477ddb3217a83f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -468,11 +508,12 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a477281940d82d29315846c7216db45b15e90bcd52309da9f54bcf7ad94a11" +checksum = "df34b88df4deeac9ecfc80ad7cbb26a33e57437b9db8be5b952792feef6134bc" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", "alloy-primitives", @@ -487,9 +528,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd8b4877ef520c138af702097477cdd19504a8e1e4675ba37e92ba40f2d3c6f" +checksum = "db32f30a55ea4fa9d893127a84eef52fc54d23acb34c1a5a39bfe9bd95fbc149" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -501,9 +542,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4ab49acf90a71f7fb894dc5fd485f1f07a1e348966c714c4d1e0b7478850a8" +checksum = "af1588d8d799095a9bd55d9045b76add042ab725c37316a77da933683754aa4b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -513,9 +554,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dfa4a7ccf15b2492bb68088692481fd6b2604ccbee1d0d6c44c21427ae4df83" +checksum = "43a89fd4cc3f96b3c5c0dd1cebeb63323e4659bbdc837117fa3fd5ac168df7d9" dependencies = [ "alloy-primitives", "serde", @@ -524,9 +565,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e10aec39d60dc27edcac447302c7803d2371946fb737245320a05b78eb2fafd" +checksum = "532010243a96d1f8593c2246ec3971bc52303884fa1e43ca0a776798ba178910" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -540,9 +581,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0109e5b18079aec2a022e4bc9db1d74bcc046f8b66274ffa8b0e4322b44b2b44" +checksum = "fd0bdb5079a35d7559714d9f9690b2ebb462921b9ceea63488bd2bef5744c15a" dependencies = [ "alloy-consensus", "alloy-network", @@ -558,9 +599,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558651eb0d76bcf2224de694481e421112fa2cbc6fe6a413cc76fd67e14cf0d7" +checksum = "794e996552efa65a76b20c088f8a968da514f90f7e44cecc32fc544c8a66fd29" dependencies = [ "alloy-consensus", "alloy-network", @@ -576,9 +617,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29781b6a064b6235de4ec3cc0810f59fe227b8d31258f23a077570fc9525d7a6" +checksum = "416fbc9f19bed61f722181b8f10bd4d89648c254d49f594e1617215f0a30ba46" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -596,9 +637,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8396f6dff60700bc1d215ee03d86ff56de268af96e2bf833a14d0bafcab9882" +checksum = "e8080c0ab2dc729b0cbb183843d08e78d2a1629140c9fc16234d2272abb483bd" dependencies = [ "alloy-consensus", "alloy-network", @@ -615,9 +656,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21267541177607141a5db6fd1abed5a46553b7a6d9363cf3d047721634705905" +checksum = "1862a5a0e883998e65b735c71560a7b9eaca57cab2165eeb80f0b9a01fff3348" dependencies = [ "alloy-consensus", "alloy-network", @@ -705,9 +746,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99acddb34000d104961897dbb0240298e8b775a7efffb9fda2a1a3efedd65b3" +checksum = "b6f295f4b745fb9e4e663d70bc57aed991288912c7aaaf25767def921050ee43" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -725,9 +766,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc013132e34eeadaa0add7e74164c1503988bfba8bae885b32e0918ba85a8a6" +checksum = "39139015a5ec127d9c895b49b484608e27fe4538544f84cdf5eae0bd36339bc6" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -740,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063edc0660e81260653cc6a95777c29d54c2543a668aa5da2359fb450d25a1ba" +checksum = "d9b4f865b13bb8648e93f812b19b74838b9165212a2beb95fc386188c443a5e3" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -761,9 +802,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd170e600801116d5efe64f74a4fc073dbbb35c807013a7d0a388742aeebba0" +checksum = "6af91e3521b8b3eac26809b1c6f9b86e3ed455dfab812f036836aabdf709b921" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -792,6 +833,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-trie" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b2e366c0debf0af77766c23694a3f863b02633050e71e096e257ffbd395e50" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "smallvec", + "tracing", +] + [[package]] name = "ammonia" version = "4.0.0" @@ -905,7 +961,7 @@ dependencies = [ "alloy-transport", "alloy-transport-ipc", "alloy-transport-ws", - "alloy-trie", + "alloy-trie 0.6.0", "anvil-core", "anvil-rpc", "anvil-server", @@ -961,7 +1017,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-serde", - "alloy-trie", + "alloy-trie 0.6.0", "bytes", "foundry-common", "foundry-evm", @@ -3546,6 +3602,7 @@ dependencies = [ name = "forge-script-sequence" version = "0.2.0" dependencies = [ + "alloy-network", "alloy-primitives", "alloy-rpc-types", "eyre", @@ -3644,6 +3701,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-genesis", "alloy-json-abi", + "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rlp", @@ -3743,6 +3801,7 @@ dependencies = [ "alloy-eips", "alloy-json-abi", "alloy-json-rpc", + "alloy-network", "alloy-primitives", "alloy-provider", "alloy-pubsub", @@ -4114,9 +4173,9 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f040169c6573e9989d1a26c3dcbe645ef8e4edabbf64af98958552da1073e4e" +checksum = "0c3d9ee7669c2a184b83c05393abfa5c9f24ef99b9abefa627fe45660adee0ba" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6315,9 +6374,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "op-alloy-consensus" -version = "0.6.8" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce158d886815d419222daa67fcdf949a34f7950653a4498ebeb4963331f70ed" +checksum = "75353c94e7515fac7d3c280bae56bff3375784a05cb44b317260606292ff6ba9" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6331,9 +6390,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.6.8" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060ebeaea8c772e396215f69bb86d231ec8b7f36aca0dd6ce367ceaa9a8c33e6" +checksum = "680a86b63fe4c45fbd5dbf1ac6779409565211c4b234d20af94cf1f79d11f23a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7459,9 +7518,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747291a18ad6726a08dd73f8b6a6b3a844db582ecae2063ccf0a04880c44f482" +checksum = "41bbeb6004cc4ed48d27756f0479011df91a6f5642a3abab9309eda5ce67c4ad" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", diff --git a/Cargo.toml b/Cargo.toml index b07d99cc9..a4d8f2724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -170,7 +170,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.9.0", default-features = false } foundry-compilers = { version = "0.12.3", default-features = false } -foundry-fork-db = "0.7.0" +foundry-fork-db = "0.8.0" solang-parser = "=0.3.3" solar-ast = { version = "=0.1.0", default-features = false } solar-parse = { version = "=0.1.0", default-features = false } @@ -178,55 +178,55 @@ solar-parse = { version = "=0.1.0", default-features = false } ## revm revm = { version = "18.0.0", default-features = false } revm-primitives = { version = "14.0.0", default-features = false } -revm-inspectors = { version = "0.11.0", features = ["serde"] } +revm-inspectors = { version = "0.12.0", features = ["serde"] } ## ethers ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { version = "0.6.4", default-features = false } -alloy-contract = { version = "0.6.4", default-features = false } -alloy-eips = { version = "0.6.4", default-features = false } -alloy-genesis = { version = "0.6.4", default-features = false } -alloy-json-rpc = { version = "0.6.4", default-features = false } -alloy-network = { version = "0.6.4", default-features = false } -alloy-provider = { version = "0.6.4", default-features = false } -alloy-pubsub = { version = "0.6.4", default-features = false } -alloy-rpc-client = { version = "0.6.4", default-features = false } -alloy-rpc-types = { version = "0.6.4", default-features = true } -alloy-serde = { version = "0.6.4", default-features = false } -alloy-signer = { version = "0.6.4", default-features = false } -alloy-signer-aws = { version = "0.6.4", default-features = false } -alloy-signer-gcp = { version = "0.6.4", default-features = false } -alloy-signer-ledger = { version = "0.6.4", default-features = false } -alloy-signer-local = { version = "0.6.4", default-features = false } -alloy-signer-trezor = { version = "0.6.4", default-features = false } -alloy-transport = { version = "0.6.4", default-features = false } -alloy-transport-http = { version = "0.6.4", default-features = false } -alloy-transport-ipc = { version = "0.6.4", default-features = false } -alloy-transport-ws = { version = "0.6.4", default-features = false } -alloy-node-bindings = { version = "0.6.4", default-features = false } +alloy-consensus = { version = "0.7.0", default-features = false } +alloy-contract = { version = "0.7.0", default-features = false } +alloy-eips = { version = "0.7.0", default-features = false } +alloy-genesis = { version = "0.7.0", default-features = false } +alloy-json-rpc = { version = "0.7.0", default-features = false } +alloy-network = { version = "0.7.0", default-features = false } +alloy-provider = { version = "0.7.0", default-features = false } +alloy-pubsub = { version = "0.7.0", default-features = false } +alloy-rpc-client = { version = "0.7.0", default-features = false } +alloy-rpc-types = { version = "0.7.0", default-features = true } +alloy-serde = { version = "0.7.0", default-features = false } +alloy-signer = { version = "0.7.0", default-features = false } +alloy-signer-aws = { version = "0.7.0", default-features = false } +alloy-signer-gcp = { version = "0.7.0", default-features = false } +alloy-signer-ledger = { version = "0.7.0", default-features = false } +alloy-signer-local = { version = "0.7.0", default-features = false } +alloy-signer-trezor = { version = "0.7.0", default-features = false } +alloy-transport = { version = "0.7.0", default-features = false } +alloy-transport-http = { version = "0.7.0", default-features = false } +alloy-transport-ipc = { version = "0.7.0", default-features = false } +alloy-transport-ws = { version = "0.7.0", default-features = false } +alloy-node-bindings = { version = "0.7.0", default-features = false } ## alloy-core -alloy-dyn-abi = "0.8.11" -alloy-json-abi = "0.8.11" -alloy-primitives = { version = "0.8.11", features = [ +alloy-dyn-abi = "0.8.14" +alloy-json-abi = "0.8.14" +alloy-primitives = { version = "0.8.14", features = [ "getrandom", "rand", "map-foldhash", ] } -alloy-sol-macro-expander = "0.8.11" -alloy-sol-macro-input = "0.8.11" -alloy-sol-types = "0.8.11" -syn-solidity = "0.8.11" +alloy-sol-macro-expander = "0.8.14" +alloy-sol-macro-input = "0.8.14" +alloy-sol-types = "0.8.14" +syn-solidity = "0.8.14" alloy-chains = "0.1" alloy-rlp = "0.3" alloy-trie = "0.6.0" ## op-alloy -op-alloy-rpc-types = "0.6.5" -op-alloy-consensus = "0.6.5" +op-alloy-rpc-types = "0.7.1" +op-alloy-consensus = "0.7.1" ## cli anstream = "0.6" diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index c9f9048b8..50a9a66b3 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -65,6 +65,7 @@ impl Block { nonce: partial_header.nonce, base_fee_per_gas: partial_header.base_fee, requests_hash: partial_header.requests_hash, + target_blobs_per_block: None, }, transactions, ommers: vec![], @@ -157,6 +158,7 @@ mod tests { parent_beacon_block_root: Default::default(), base_fee_per_gas: None, requests_hash: None, + target_blobs_per_block: None, }; let encoded = alloy_rlp::encode(&header); @@ -198,6 +200,7 @@ mod tests { nonce: B64::ZERO, base_fee_per_gas: None, requests_hash: None, + target_blobs_per_block: None, }; header.encode(&mut data); @@ -231,6 +234,7 @@ mod tests { parent_beacon_block_root: None, base_fee_per_gas: None, requests_hash: None, + target_blobs_per_block: None, }; let header = Header::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); @@ -263,6 +267,7 @@ mod tests { excess_blob_gas: None, parent_beacon_block_root: None, requests_hash: None, + target_blobs_per_block: None, }; assert_eq!(header.hash_slow(), expected_hash); } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 8de659799..4067aa668 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -6,18 +6,18 @@ use alloy_consensus::{ eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, TxEip7702, }, - AnyReceiptEnvelope, Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, - TxEip2930, TxEnvelope, TxLegacy, TxReceipt, + Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930, + TxEnvelope, TxLegacy, TxReceipt, }; use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; -use alloy_network::{AnyRpcTransaction, AnyTxEnvelope}; +use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope}; use alloy_primitives::{ Address, Bloom, Bytes, Log, PrimitiveSignature, TxHash, TxKind, B256, U256, U64, }; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use alloy_rpc_types::{ - request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, AnyTransactionReceipt, - ConversionError, Transaction as RpcTransaction, TransactionReceipt, + request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, ConversionError, + Transaction as RpcTransaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; use bytes::BufMut; @@ -1109,7 +1109,7 @@ pub struct TransactionInfo { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -pub struct DepositReceipt { +pub struct DepositReceipt> { #[serde(flatten)] pub inner: ReceiptWithBloom, #[serde(default, with = "alloy_serde::quantity::opt")] @@ -1136,7 +1136,7 @@ impl DepositReceipt { /// Encodes the receipt data. fn encode_fields(&self, out: &mut dyn BufMut) { self.receipt_rlp_header().encode(out); - self.inner.receipt.status.encode(out); + self.inner.status().encode(out); self.inner.receipt.cumulative_gas_used.encode(out); self.inner.logs_bloom.encode(out); self.inner.receipt.logs.encode(out); @@ -1161,7 +1161,7 @@ impl DepositReceipt { let status = Decodable::decode(b)?; let cumulative_gas_used = Decodable::decode(b)?; let logs_bloom = Decodable::decode(b)?; - let logs = Decodable::decode(b)?; + let logs: Vec = Decodable::decode(b)?; let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; let deposit_nonce_version = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; @@ -1207,7 +1207,7 @@ impl alloy_rlp::Decodable for DepositReceipt { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type")] -pub enum TypedReceipt { +pub enum TypedReceipt> { #[serde(rename = "0x0", alias = "0x00")] Legacy(ReceiptWithBloom), #[serde(rename = "0x1", alias = "0x01")] @@ -1248,8 +1248,8 @@ impl From> for ReceiptWithBloom { } } -impl From> for OtsReceipt { - fn from(value: TypedReceipt) -> Self { +impl From>> for OtsReceipt { + fn from(value: TypedReceipt>) -> Self { let r#type = match value { TypedReceipt::Legacy(_) => 0x00, TypedReceipt::EIP2930(_) => 0x01, @@ -1258,7 +1258,7 @@ impl From> for OtsReceipt { TypedReceipt::EIP7702(_) => 0x04, TypedReceipt::Deposit(_) => 0x7E, } as u8; - let receipt = ReceiptWithBloom::::from(value); + let receipt = ReceiptWithBloom::>::from(value); let status = receipt.status(); let cumulative_gas_used = receipt.cumulative_gas_used() as u64; let logs = receipt.logs().to_vec(); @@ -1282,7 +1282,7 @@ impl TypedReceipt { } } -impl From> for TypedReceipt { +impl From> for TypedReceipt> { fn from(value: ReceiptEnvelope) -> Self { match value { ReceiptEnvelope::Legacy(r) => Self::Legacy(r), @@ -1439,7 +1439,7 @@ impl Decodable2718 for TypedReceipt { } } -pub type ReceiptResponse = TransactionReceipt>; +pub type ReceiptResponse = TransactionReceipt>>; pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option { let WithOtherFields { diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 056b88627..5635a7acc 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -555,15 +555,12 @@ impl MinedTransaction { } GethDebugBuiltInTracerType::CallTracer => { return match tracer_config.into_call_config() { - Ok(call_config) => Ok(GethTraceBuilder::new( - self.info.traces.clone(), - TracingInspectorConfig::from_geth_config(&config), - ) - .geth_call_traces( - call_config, - self.receipt.cumulative_gas_used() as u64, - ) - .into()), + Ok(call_config) => Ok(GethTraceBuilder::new(self.info.traces.clone()) + .geth_call_traces( + call_config, + self.receipt.cumulative_gas_used() as u64, + ) + .into()), Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), }; } @@ -579,16 +576,13 @@ impl MinedTransaction { } // default structlog tracer - Ok(GethTraceBuilder::new( - self.info.traces.clone(), - TracingInspectorConfig::from_geth_config(&config), - ) - .geth_traces( - self.receipt.cumulative_gas_used() as u64, - self.info.out.clone().unwrap_or_default(), - opts.config, - ) - .into()) + Ok(GethTraceBuilder::new(self.info.traces.clone()) + .geth_traces( + self.receipt.cumulative_gas_used() as u64, + self.info.out.clone().unwrap_or_default(), + config, + ) + .into()) } } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index f3d04b094..cd833f7c3 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -104,6 +104,7 @@ totalDifficulty [..] blobGasUsed [..] excessBlobGas [..] requestsHash [..] +targetBlobsPerBlock [..] transactions: [ ... ] diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 97b22b16f..a06416160 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -45,6 +45,7 @@ alloy-signer-local = { workspace = true, features = [ ] } parking_lot.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } +alloy-network.workspace = true alloy-rlp.workspace = true base64.workspace = true diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index e52aaa688..b96a6d4d7 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -4,9 +4,9 @@ use super::string::parse; use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; +use alloy_network::AnyTransactionReceipt; use alloy_primitives::{hex, map::Entry, Bytes, U256}; use alloy_provider::network::ReceiptResponse; -use alloy_rpc_types::AnyTransactionReceipt; use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 585e0080c..c09e23849 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -43,6 +43,7 @@ alloy-transport-ipc.workspace = true alloy-transport-ws.workspace = true alloy-transport.workspace = true alloy-consensus = { workspace = true, features = ["k256"] } +alloy-network.workspace = true tower.workspace = true diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index a82d2cdc3..7ae6adea8 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -1,14 +1,15 @@ //! Helper trait and functions to format Ethereum types. use alloy_consensus::{ - AnyReceiptEnvelope, Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, - TxType, + Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, TxType, +}; +use alloy_network::{ + AnyHeader, AnyReceiptEnvelope, AnyRpcBlock, AnyTransactionReceipt, AnyTxEnvelope, + ReceiptResponse, }; -use alloy_network::{AnyHeader, AnyRpcBlock, AnyTxEnvelope, ReceiptResponse}; use alloy_primitives::{hex, Address, Bloom, Bytes, FixedBytes, Uint, I256, U256, U64, U8}; use alloy_rpc_types::{ - AccessListItem, AnyTransactionReceipt, Block, BlockTransactions, Header, Log, Transaction, - TransactionReceipt, + AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; use serde::Deserialize; @@ -900,6 +901,7 @@ fn pretty_block_basics(block: &Block>) excess_blob_gas, parent_beacon_block_root, requests_hash, + target_blobs_per_block, }, }, uncles: _, @@ -931,7 +933,8 @@ withdrawalsRoot {} totalDifficulty {} blobGasUsed {} excessBlobGas {} -requestsHash {}", +requestsHash {} +targetBlobsPerBlock {}", base_fee_per_gas.pretty(), difficulty.pretty(), extra_data.pretty(), @@ -959,6 +962,7 @@ requestsHash {}", blob_gas_used.pretty(), excess_blob_gas.pretty(), requests_hash.pretty(), + target_blobs_per_block.pretty(), ) } diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index a05a46eae..b725fc068 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -2,12 +2,13 @@ use alloy_consensus::{Transaction, TxEnvelope}; use alloy_eips::eip7702::SignedAuthorization; +use alloy_network::AnyTransactionReceipt; use alloy_primitives::{Address, TxKind, U256}; use alloy_provider::{ network::{AnyNetwork, ReceiptResponse, TransactionBuilder}, Provider, }; -use alloy_rpc_types::{AnyTransactionReceipt, BlockId, TransactionRequest}; +use alloy_rpc_types::{BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_transport::Transport; use eyre::Result; diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 6c2fbb0cf..2294d511e 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -2,10 +2,10 @@ use crate::cmd::install; use alloy_chains::Chain; use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::{Constructor, JsonAbi}; -use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; +use alloy_network::{AnyNetwork, AnyTransactionReceipt, EthereumWallet, TransactionBuilder}; use alloy_primitives::{hex, Address, Bytes}; use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder}; -use alloy_rpc_types::{AnyTransactionReceipt, TransactionRequest}; +use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_signer::Signer; use alloy_transport::{Transport, TransportError}; diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml index 13326e684..08fd9a695 100644 --- a/crates/script-sequence/Cargo.toml +++ b/crates/script-sequence/Cargo.toml @@ -28,3 +28,4 @@ revm-inspectors.workspace = true alloy-rpc-types.workspace = true alloy-primitives.workspace = true +alloy-network.workspace = true diff --git a/crates/script-sequence/src/reader.rs b/crates/script-sequence/src/reader.rs index c4627dec0..de2e9faf1 100644 --- a/crates/script-sequence/src/reader.rs +++ b/crates/script-sequence/src/reader.rs @@ -1,5 +1,5 @@ use crate::{ScriptSequence, TransactionWithMetadata}; -use alloy_rpc_types::AnyTransactionReceipt; +use alloy_network::AnyTransactionReceipt; use eyre::{bail, Result}; use foundry_common::fs; use revm_inspectors::tracing::types::CallKind; diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs index 235f28f2c..4b2b434e1 100644 --- a/crates/script-sequence/src/sequence.rs +++ b/crates/script-sequence/src/sequence.rs @@ -1,6 +1,6 @@ use crate::transaction::TransactionWithMetadata; +use alloy_network::AnyTransactionReceipt; use alloy_primitives::{hex, map::HashMap, TxHash}; -use alloy_rpc_types::AnyTransactionReceipt; use eyre::{ContextCompat, Result, WrapErr}; use foundry_common::{fs, shell, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_compilers::ArtifactId; diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index f073e38e6..cff893b55 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,7 +1,7 @@ use alloy_chains::Chain; +use alloy_network::AnyTransactionReceipt; use alloy_primitives::{utils::format_units, TxHash, U256}; use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; -use alloy_rpc_types::AnyTransactionReceipt; use eyre::Result; use foundry_common::{provider::RetryProvider, shell}; use std::time::Duration; From 3e6d3b8b6b96a02df1264294320a840ddc88345b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:55:46 +0200 Subject: [PATCH 043/107] feat: allow any config to be defined inline (#9430) * feat: allow any config to be defined inline * com * rm duplicate * don't update everything * bump * bump --- crates/cheatcodes/src/config.rs | 15 +- crates/cheatcodes/src/inspector.rs | 4 +- crates/chisel/src/executor.rs | 2 +- crates/config/src/inline/mod.rs | 32 +- crates/config/src/lib.rs | 2 +- crates/config/src/utils.rs | 2 +- crates/evm/core/src/opts.rs | 3 +- crates/evm/core/src/utils.rs | 5 +- crates/evm/evm/src/executors/builder.rs | 2 +- crates/evm/evm/src/executors/mod.rs | 40 +- crates/evm/evm/src/executors/trace.rs | 2 +- crates/evm/traces/src/lib.rs | 4 +- crates/forge/bin/cmd/coverage.rs | 3 +- crates/forge/bin/cmd/test/mod.rs | 7 +- crates/forge/src/lib.rs | 117 ------ crates/forge/src/multi_runner.rs | 262 +++++++----- crates/forge/src/result.rs | 42 +- crates/forge/src/runner.rs | 515 +++++++++++++++--------- crates/forge/tests/cli/inline_config.rs | 78 +++- crates/forge/tests/it/config.rs | 4 +- crates/forge/tests/it/spec.rs | 5 +- crates/forge/tests/it/test_helpers.rs | 9 +- crates/script/src/lib.rs | 2 +- 23 files changed, 679 insertions(+), 478 deletions(-) diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 2279e2435..e0463dfc4 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -69,8 +69,8 @@ impl CheatsConfig { running_version: Option, ) -> Self { let mut allowed_paths = vec![config.root.clone()]; - allowed_paths.extend(config.libs.clone()); - allowed_paths.extend(config.allow_paths.clone()); + allowed_paths.extend(config.libs.iter().cloned()); + allowed_paths.extend(config.allow_paths.iter().cloned()); let rpc_endpoints = config.rpc_endpoints.clone().resolved(); trace!(?rpc_endpoints, "using resolved rpc endpoints"); @@ -101,6 +101,17 @@ impl CheatsConfig { } } + /// Returns a new `CheatsConfig` configured with the given `Config` and `EvmOpts`. + pub fn clone_with(&self, config: &Config, evm_opts: EvmOpts) -> Self { + Self::new( + config, + evm_opts, + self.available_artifacts.clone(), + self.running_contract.clone(), + self.running_version.clone(), + ) + } + /// Attempts to canonicalize (see [std::fs::canonicalize]) the path. /// /// Canonicalization fails for non-existing paths, in which case we just normalize the path. diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 447a9e747..ac8058dd9 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -439,7 +439,7 @@ pub struct Cheatcodes { /// Scripting based transactions pub broadcastable_transactions: BroadcastableTransactions, - /// Additional, user configurable context this Inspector has access to when inspecting a call + /// Additional, user configurable context this Inspector has access to when inspecting a call. pub config: Arc, /// Test-scoped context holding data that needs to be reset every test run @@ -540,7 +540,7 @@ impl Cheatcodes { /// Returns the configured wallets if available, else creates a new instance. pub fn wallets(&mut self) -> &Wallets { - self.wallets.get_or_insert(Wallets::new(MultiWallet::default(), None)) + self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None)) } /// Sets the unlocked wallets. diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 09c00f6ad..71bf18e1a 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -341,7 +341,7 @@ impl SessionSource { ) }) .gas_limit(self.config.evm_opts.gas_limit()) - .spec(self.config.foundry_config.evm_spec_id()) + .spec_id(self.config.foundry_config.evm_spec_id()) .legacy_assertions(self.config.foundry_config.legacy_assertions) .build(env, backend); diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 30b1c820e..fa67b2426 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -4,6 +4,7 @@ use figment::{ value::{Dict, Map, Value}, Figment, Profile, Provider, }; +use foundry_compilers::ProjectCompileOutput; use itertools::Itertools; mod natspec; @@ -53,6 +54,20 @@ impl InlineConfig { Self::default() } + /// Tries to create a new instance by detecting inline configurations from the project compile + /// output. + pub fn new_parsed(output: &ProjectCompileOutput, config: &Config) -> eyre::Result { + let natspecs: Vec = NatSpec::parse(output, &config.root); + let profiles = &config.profiles; + let mut inline = Self::new(); + for natspec in &natspecs { + inline.insert(natspec)?; + // Validate after parsing as TOML. + natspec.validate_profiles(profiles)?; + } + Ok(inline) + } + /// Inserts a new [`NatSpec`] into the [`InlineConfig`]. pub fn insert(&mut self, natspec: &NatSpec) -> Result<(), InlineConfigError> { let map = if let Some(function) = &natspec.function { @@ -92,13 +107,16 @@ impl InlineConfig { Figment::from(base).merge(self.provide(contract, function)) } - /// Returns `true` if a configuration is present at the given contract and function level. - pub fn contains(&self, contract: &str, function: &str) -> bool { - // Order swapped to avoid allocation in `get_function` since order doesn't matter here. - self.get_contract(contract) - .filter(|map| !map.is_empty()) - .or_else(|| self.get_function(contract, function)) - .is_some_and(|map| !map.is_empty()) + /// Returns `true` if a configuration is present at the given contract level. + pub fn contains_contract(&self, contract: &str) -> bool { + self.get_contract(contract).is_some_and(|map| !map.is_empty()) + } + + /// Returns `true` if a configuration is present at the function level. + /// + /// Does not include contract-level configurations. + pub fn contains_function(&self, contract: &str, function: &str) -> bool { + self.get_function(contract, function).is_some_and(|map| !map.is_empty()) } fn get_contract(&self, contract: &str) -> Option<&DataMap> { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index e1c3fa5ee..f2d25461d 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1125,7 +1125,7 @@ impl Config { /// Returns the [SpecId] derived from the configured [EvmVersion] #[inline] pub fn evm_spec_id(&self) -> SpecId { - evm_spec_id(&self.evm_version, self.alphanet) + evm_spec_id(self.evm_version, self.alphanet) } /// Returns whether the compiler version should be auto-detected diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index e07d7dfbc..19f70a939 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -259,7 +259,7 @@ impl FromStr for Numeric { /// Returns the [SpecId] derived from [EvmVersion] #[inline] -pub fn evm_spec_id(evm_version: &EvmVersion, alphanet: bool) -> SpecId { +pub fn evm_spec_id(evm_version: EvmVersion, alphanet: bool) -> SpecId { if alphanet { return SpecId::OSAKA; } diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index c5817e483..aec0d78a0 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -113,9 +113,8 @@ impl EvmOpts { /// And the block that was used to configure the environment. pub async fn fork_evm_env( &self, - fork_url: impl AsRef, + fork_url: &str, ) -> eyre::Result<(revm::primitives::Env, AnyRpcBlock)> { - let fork_url = fork_url.as_ref(); let provider = ProviderBuilder::new(fork_url) .compute_units_per_second(self.get_compute_units_per_second()) .build()?; diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 2257709e5..0841d9340 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -46,10 +46,7 @@ pub fn apply_chain_and_block_specific_env_changes( return; } - NamedChain::Arbitrum | - NamedChain::ArbitrumGoerli | - NamedChain::ArbitrumNova | - NamedChain::ArbitrumTestnet => { + c if c.is_arbitrum() => { // on arbitrum `block.number` is the L1 block which is included in the // `l1BlockNumber` field if let Some(l1_block_number) = block diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index fee3c249a..c371a6550 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -52,7 +52,7 @@ impl ExecutorBuilder { /// Sets the EVM spec to use. #[inline] - pub fn spec(mut self, spec: SpecId) -> Self { + pub fn spec_id(mut self, spec: SpecId) -> Self { self.spec_id = spec; self } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 8146cec82..8560c3e10 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -86,9 +86,7 @@ pub struct Executor { env: EnvWithHandlerCfg, /// The Revm inspector stack. inspector: InspectorStack, - /// The gas limit for calls and deployments. This is different from the gas limit imposed by - /// the passed in environment, as those limits are used by the EVM for certain opcodes like - /// `gaslimit`. + /// The gas limit for calls and deployments. gas_limit: u64, /// Whether `failed()` should be called on the test contract to determine if the test failed. legacy_assertions: bool, @@ -166,6 +164,36 @@ impl Executor { self.env.spec_id() } + /// Sets the EVM spec ID. + pub fn set_spec_id(&mut self, spec_id: SpecId) { + self.env.handler_cfg.spec_id = spec_id; + } + + /// Returns the gas limit for calls and deployments. + /// + /// This is different from the gas limit imposed by the passed in environment, as those limits + /// are used by the EVM for certain opcodes like `gaslimit`. + pub fn gas_limit(&self) -> u64 { + self.gas_limit + } + + /// Sets the gas limit for calls and deployments. + pub fn set_gas_limit(&mut self, gas_limit: u64) { + self.gas_limit = gas_limit; + } + + /// Returns whether `failed()` should be called on the test contract to determine if the test + /// failed. + pub fn legacy_assertions(&self) -> bool { + self.legacy_assertions + } + + /// Sets whether `failed()` should be called on the test contract to determine if the test + /// failed. + pub fn set_legacy_assertions(&mut self, legacy_assertions: bool) { + self.legacy_assertions = legacy_assertions; + } + /// Creates the default CREATE2 Contract Deployer for local tests and scripts. pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> { trace!("deploying local create2 deployer"); @@ -235,12 +263,6 @@ impl Executor { self } - #[inline] - pub fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self { - self.gas_limit = gas_limit; - self - } - #[inline] pub fn create2_deployer(&self) -> Address { self.inspector().create2_deployer() diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 214e7c28a..d9c0d74f6 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -32,7 +32,7 @@ impl TracingExecutor { .alphanet(alphanet) .create2_deployer(create2_deployer) }) - .spec(evm_spec_id(&version.unwrap_or_default(), alphanet)) + .spec_id(evm_spec_id(version.unwrap_or_default(), alphanet)) .build(env, db), } } diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index a0fa7e1fc..f88efbb1b 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -349,8 +349,8 @@ impl TraceMode { } } - pub fn with_verbosity(self, verbosiy: u8) -> Self { - if verbosiy >= 3 { + pub fn with_verbosity(self, verbosity: u8) -> Self { + if verbosity >= 3 { std::cmp::max(self, Self::Call) } else { self diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 3a7d43436..f1c862d81 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -11,7 +11,7 @@ use forge::{ }, opts::EvmOpts, utils::IcPcMap, - MultiContractRunnerBuilder, TestOptions, + MultiContractRunnerBuilder, }; use foundry_cli::utils::{LoadConfig, STATIC_FUZZ_SEED}; use foundry_common::{compile::ProjectCompiler, fs}; @@ -233,7 +233,6 @@ impl CoverageArgs { .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_test_options(TestOptions::new(output, config.clone())?) .set_coverage(true) .build(&root, output, env, evm_opts)?; diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index e5a058c0d..dd6d04a98 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -14,7 +14,7 @@ use forge::{ identifier::SignaturesIdentifier, CallTraceDecoderBuilder, InternalTraceMode, TraceKind, }, - MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, + MultiContractRunner, MultiContractRunnerBuilder, TestFilter, }; use foundry_cli::{ opts::{CoreBuildArgs, GlobalOpts}, @@ -317,9 +317,6 @@ impl TestArgs { } } - let config = Arc::new(config); - let test_options = TestOptions::new(&output, config.clone())?; - let should_debug = self.debug.is_some(); let should_draw = self.flamegraph || self.flamechart; @@ -346,6 +343,7 @@ impl TestArgs { }; // Prepare the test builder. + let config = Arc::new(config); let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) .set_decode_internal(decode_internal) @@ -353,7 +351,6 @@ impl TestArgs { .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_test_options(test_options) .enable_isolation(evm_opts.isolate) .alphanet(evm_opts.alphanet) .build(project_root, &output, env, evm_opts)?; diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 257760c4e..ddeada0a6 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -7,17 +7,6 @@ extern crate foundry_common; #[macro_use] extern crate tracing; -use alloy_primitives::U256; -use eyre::Result; -use foundry_compilers::ProjectCompileOutput; -use foundry_config::{ - figment::Figment, Config, FuzzConfig, InlineConfig, InvariantConfig, NatSpec, -}; -use proptest::test_runner::{ - FailurePersistence, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner, -}; -use std::sync::Arc; - pub mod coverage; pub mod gas_report; @@ -34,109 +23,3 @@ pub mod result; // TODO: remove pub use foundry_common::traits::TestFilter; pub use foundry_evm::*; - -/// Test configuration. -#[derive(Clone, Debug, Default)] -pub struct TestOptions { - /// The base configuration. - pub config: Arc, - /// Per-test configuration. Merged onto `base_config`. - pub inline: InlineConfig, -} - -impl TestOptions { - /// Tries to create a new instance by detecting inline configurations from the project compile - /// output. - pub fn new(output: &ProjectCompileOutput, base_config: Arc) -> eyre::Result { - let natspecs: Vec = NatSpec::parse(output, &base_config.root); - let profiles = &base_config.profiles; - let mut inline = InlineConfig::new(); - for natspec in &natspecs { - inline.insert(natspec)?; - // Validate after parsing as TOML. - natspec.validate_profiles(profiles)?; - } - Ok(Self { config: base_config, inline }) - } - - /// Creates a new instance without parsing inline configuration. - pub fn new_unparsed(base_config: Arc) -> Self { - Self { config: base_config, inline: InlineConfig::new() } - } - - /// Returns the [`Figment`] for the configuration. - pub fn figment(&self, contract: &str, function: &str) -> Result { - Ok(self.inline.merge(contract, function, &self.config)) - } - - /// Returns a "fuzz" test runner instance. Parameters are used to select tight scoped fuzz - /// configs that apply for a contract-function pair. A fallback configuration is applied - /// if no specific setup is found for a given input. - /// - /// - `contract` is the id of the test contract, expressed as a relative path from the project - /// root. - /// - `function` is the name of the test function declared inside the test contract. - pub fn fuzz_runner(&self, contract: &str, function: &str) -> Result<(FuzzConfig, TestRunner)> { - let config: FuzzConfig = self.figment(contract, function)?.extract_inner("fuzz")?; - let failure_persist_path = config - .failure_persist_dir - .as_ref() - .unwrap() - .join(config.failure_persist_file.as_ref().unwrap()) - .into_os_string() - .into_string() - .unwrap(); - let runner = fuzzer_with_cases( - config.seed, - config.runs, - config.max_test_rejects, - Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), - ); - Ok((config, runner)) - } - - /// Returns an "invariant" test runner instance. Parameters are used to select tight scoped fuzz - /// configs that apply for a contract-function pair. A fallback configuration is applied - /// if no specific setup is found for a given input. - /// - /// - `contract` is the id of the test contract, expressed as a relative path from the project - /// root. - /// - `function` is the name of the test function declared inside the test contract. - pub fn invariant_runner( - &self, - contract: &str, - function: &str, - ) -> Result<(InvariantConfig, TestRunner)> { - let figment = self.figment(contract, function)?; - let config: InvariantConfig = figment.extract_inner("invariant")?; - let seed: Option = figment.extract_inner("fuzz.seed").ok(); - let runner = fuzzer_with_cases(seed, config.runs, config.max_assume_rejects, None); - Ok((config, runner)) - } -} - -fn fuzzer_with_cases( - seed: Option, - cases: u32, - max_global_rejects: u32, - file_failure_persistence: Option>, -) -> TestRunner { - let config = proptest::test_runner::Config { - failure_persistence: file_failure_persistence, - cases, - max_global_rejects, - // Disable proptest shrink: for fuzz tests we provide single counterexample, - // for invariant tests we shrink outside proptest. - max_shrink_iters: 0, - ..Default::default() - }; - - if let Some(seed) = seed { - trace!(target: "forge::test", %seed, "building deterministic fuzzer"); - let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); - TestRunner::new_with_rng(config, rng) - } else { - trace!(target: "forge::test", "building stochastic fuzzer"); - TestRunner::new(config) - } -} diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 572c3d4fe..0372becb2 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -2,20 +2,18 @@ use crate::{ progress::TestsProgress, result::SuiteResult, runner::LIBRARY_DEPLOYER, ContractRunner, - TestFilter, TestOptions, + TestFilter, }; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_common::{get_contract_name, shell::verbosity, ContractsByArtifact, TestFunctionExt}; -use foundry_compilers::{ - artifacts::Libraries, compilers::Compiler, Artifact, ArtifactId, ProjectCompileOutput, -}; -use foundry_config::Config; +use foundry_compilers::{artifacts::Libraries, Artifact, ArtifactId, ProjectCompileOutput}; +use foundry_config::{Config, InlineConfig}; use foundry_evm::{ backend::Backend, decode::RevertDecoder, - executors::ExecutorBuilder, + executors::{Executor, ExecutorBuilder}, fork::CreateFork, inspectors::CheatsConfig, opts::EvmOpts, @@ -48,38 +46,34 @@ pub struct MultiContractRunner { /// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which /// needs to be deployed & linked against pub contracts: DeployableContracts, - /// The EVM instance used in the test runner - pub evm_opts: EvmOpts, - /// The configured evm - pub env: revm::primitives::Env, - /// The EVM spec - pub evm_spec: SpecId, - /// Revert decoder. Contains all known errors and their selectors. - pub revert_decoder: RevertDecoder, - /// The address which will be used as the `from` field in all EVM calls - pub sender: Option

, - /// The fork to use at launch - pub fork: Option, - /// Project config. - pub config: Arc, - /// Whether to collect coverage info - pub coverage: bool, - /// Whether to collect debug info - pub debug: bool, - /// Whether to enable steps tracking in the tracer. - pub decode_internal: InternalTraceMode, - /// Settings related to fuzz and/or invariant tests - pub test_options: TestOptions, - /// Whether to enable call isolation - pub isolation: bool, - /// Whether to enable Alphanet features. - pub alphanet: bool, /// Known contracts linked with computed library addresses. pub known_contracts: ContractsByArtifact, + /// Revert decoder. Contains all known errors and their selectors. + pub revert_decoder: RevertDecoder, /// Libraries to deploy. pub libs_to_deploy: Vec, /// Library addresses used to link contracts. pub libraries: Libraries, + + /// The fork to use at launch + pub fork: Option, + + /// The base configuration for the test runner. + pub tcfg: TestRunnerConfig, +} + +impl std::ops::Deref for MultiContractRunner { + type Target = TestRunnerConfig; + + fn deref(&self) -> &Self::Target { + &self.tcfg + } +} + +impl std::ops::DerefMut for MultiContractRunner { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tcfg + } } impl MultiContractRunner { @@ -196,7 +190,7 @@ impl MultiContractRunner { let result = self.run_test_suite( id, contract, - db.clone(), + &db, filter, &tokio_handle, Some(&tests_progress), @@ -219,8 +213,7 @@ impl MultiContractRunner { } else { contracts.par_iter().for_each(|&(id, contract)| { let _guard = tokio_handle.enter(); - let result = - self.run_test_suite(id, contract, db.clone(), filter, &tokio_handle, None); + let result = self.run_test_suite(id, contract, &db, filter, &tokio_handle, None); let _ = tx.send((id.identifier(), result)); }) } @@ -230,7 +223,7 @@ impl MultiContractRunner { &self, artifact_id: &ArtifactId, contract: &TestContract, - db: Backend, + db: &Backend, filter: &dyn TestFilter, tokio_handle: &tokio::runtime::Handle, progress: Option<&TestsProgress>, @@ -238,35 +231,6 @@ impl MultiContractRunner { let identifier = artifact_id.identifier(); let mut span_name = identifier.as_str(); - let cheats_config = CheatsConfig::new( - &self.config, - self.evm_opts.clone(), - Some(self.known_contracts.clone()), - Some(artifact_id.name.clone()), - Some(artifact_id.version.clone()), - ); - - let trace_mode = TraceMode::default() - .with_debug(self.debug) - .with_decode_internal(self.decode_internal) - .with_verbosity(self.evm_opts.verbosity) - .with_state_changes(verbosity() > 4); - - let executor = ExecutorBuilder::new() - .inspectors(|stack| { - stack - .cheatcodes(Arc::new(cheats_config)) - .trace_mode(trace_mode) - .coverage(self.coverage) - .enable_isolation(self.isolation) - .alphanet(self.alphanet) - .create2_deployer(self.evm_opts.create2_deployer) - }) - .spec(self.evm_spec) - .gas_limit(self.evm_opts.gas_limit()) - .legacy_assertions(self.config.legacy_assertions) - .build(self.env.clone(), db); - if !enabled!(tracing::Level::TRACE) { span_name = get_contract_name(&identifier); } @@ -276,20 +240,16 @@ impl MultiContractRunner { debug!("start executing all tests in contract"); - let runner = ContractRunner { - name: &identifier, + let runner = ContractRunner::new( + &identifier, contract, - libs_to_deploy: &self.libs_to_deploy, - executor, - revert_decoder: &self.revert_decoder, - initial_balance: self.evm_opts.initial_balance, - sender: self.sender.unwrap_or_default(), - debug: self.debug, + self.tcfg.executor(self.known_contracts.clone(), artifact_id, db.clone()), progress, tokio_handle, span, - }; - let r = runner.run_tests(filter, &self.test_options, self.known_contracts.clone()); + self, + ); + let r = runner.run_tests(filter); debug!(duration=?r.duration, "executed all tests in contract"); @@ -297,6 +257,116 @@ impl MultiContractRunner { } } +/// Configuration for the test runner. +/// +/// This is modified after instantiation through inline config. +#[derive(Clone)] +pub struct TestRunnerConfig { + /// Project config. + pub config: Arc, + /// Inline configuration. + pub inline_config: Arc, + + /// EVM configuration. + pub evm_opts: EvmOpts, + /// EVM environment. + pub env: revm::primitives::Env, + /// EVM version. + pub spec_id: SpecId, + /// The address which will be used to deploy the initial contracts and send all transactions. + pub sender: Address, + + /// Whether to collect coverage info + pub coverage: bool, + /// Whether to collect debug info + pub debug: bool, + /// Whether to enable steps tracking in the tracer. + pub decode_internal: InternalTraceMode, + /// Whether to enable call isolation. + pub isolation: bool, + /// Whether to enable Alphanet features. + pub alphanet: bool, +} + +impl TestRunnerConfig { + /// Reconfigures all fields using the given `config`. + pub fn reconfigure_with(&mut self, config: Arc) { + debug_assert!(!Arc::ptr_eq(&self.config, &config)); + + // TODO: self.evm_opts + // TODO: self.env + self.spec_id = config.evm_spec_id(); + self.sender = config.sender; + // self.coverage = N/A; + // self.debug = N/A; + // self.decode_internal = N/A; + // self.isolation = N/A; + self.alphanet = config.alphanet; + + self.config = config; + } + + /// Configures the given executor with this configuration. + pub fn configure_executor(&self, executor: &mut Executor) { + // TODO: See above + + let inspector = executor.inspector_mut(); + // inspector.set_env(&self.env); + if let Some(cheatcodes) = inspector.cheatcodes.as_mut() { + cheatcodes.config = + Arc::new(cheatcodes.config.clone_with(&self.config, self.evm_opts.clone())); + } + inspector.tracing(self.trace_mode()); + inspector.collect_coverage(self.coverage); + inspector.enable_isolation(self.isolation); + inspector.alphanet(self.alphanet); + // inspector.set_create2_deployer(self.evm_opts.create2_deployer); + + // executor.env_mut().clone_from(&self.env); + executor.set_spec_id(self.spec_id); + // executor.set_gas_limit(self.evm_opts.gas_limit()); + executor.set_legacy_assertions(self.config.legacy_assertions); + } + + /// Creates a new executor with this configuration. + pub fn executor( + &self, + known_contracts: ContractsByArtifact, + artifact_id: &ArtifactId, + db: Backend, + ) -> Executor { + let cheats_config = Arc::new(CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(known_contracts), + Some(artifact_id.name.clone()), + Some(artifact_id.version.clone()), + )); + ExecutorBuilder::new() + .inspectors(|stack| { + stack + .cheatcodes(cheats_config) + .trace_mode(self.trace_mode()) + .coverage(self.coverage) + .enable_isolation(self.isolation) + .alphanet(self.alphanet) + .create2_deployer(self.evm_opts.create2_deployer) + }) + .spec_id(self.spec_id) + .gas_limit(self.evm_opts.gas_limit()) + .legacy_assertions(self.config.legacy_assertions) + .build(self.env.clone(), db) + } + + fn trace_mode(&self) -> TraceMode { + TraceMode::default() + .with_debug(self.debug) + .with_decode_internal(self.decode_internal) + .with_verbosity(self.evm_opts.verbosity) + .with_state_changes(verbosity() > 4) + } +} + /// Builder used for instantiating the multi-contract runner #[derive(Clone, Debug)] #[must_use = "builders do nothing unless you call `build` on them"] @@ -322,8 +392,6 @@ pub struct MultiContractRunnerBuilder { pub isolation: bool, /// Whether to enable Alphanet features. pub alphanet: bool, - /// Settings related to fuzz and/or invariant tests - pub test_options: Option, } impl MultiContractRunnerBuilder { @@ -337,7 +405,6 @@ impl MultiContractRunnerBuilder { coverage: Default::default(), debug: Default::default(), isolation: Default::default(), - test_options: Default::default(), decode_internal: Default::default(), alphanet: Default::default(), } @@ -363,11 +430,6 @@ impl MultiContractRunnerBuilder { self } - pub fn with_test_options(mut self, test_options: TestOptions) -> Self { - self.test_options = Some(test_options); - self - } - pub fn set_coverage(mut self, enable: bool) -> Self { self.coverage = enable; self @@ -395,10 +457,10 @@ impl MultiContractRunnerBuilder { /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm - pub fn build( + pub fn build( self, root: &Path, - output: &ProjectCompileOutput, + output: &ProjectCompileOutput, env: revm::primitives::Env, evm_opts: EvmOpts, ) -> Result { @@ -449,22 +511,28 @@ impl MultiContractRunnerBuilder { Ok(MultiContractRunner { contracts: deployable_contracts, - evm_opts, - env, - evm_spec: self.evm_spec.unwrap_or(SpecId::CANCUN), - sender: self.sender, revert_decoder, - fork: self.fork, - config: self.config, - coverage: self.coverage, - debug: self.debug, - decode_internal: self.decode_internal, - test_options: self.test_options.unwrap_or_default(), - isolation: self.isolation, - alphanet: self.alphanet, known_contracts, libs_to_deploy, libraries, + + fork: self.fork, + + tcfg: TestRunnerConfig { + evm_opts, + env, + spec_id: self.evm_spec.unwrap_or_else(|| self.config.evm_spec_id()), + sender: self.sender.unwrap_or(self.config.sender), + + coverage: self.coverage, + debug: self.debug, + decode_internal: self.decode_internal, + inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), + isolation: self.isolation, + alphanet: self.alphanet, + + config: self.config, + }, }) } } diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 7f02db577..28b52d74c 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -467,12 +467,12 @@ impl fmt::Display for TestResult { impl TestResult { /// Creates a new test result starting from test setup results. - pub fn new(setup: TestSetup) -> Self { + pub fn new(setup: &TestSetup) -> Self { Self { - labeled_addresses: setup.labels, - logs: setup.logs, - traces: setup.traces, - coverage: setup.coverage, + labeled_addresses: setup.labels.clone(), + logs: setup.logs.clone(), + traces: setup.traces.clone(), + coverage: setup.coverage.clone(), ..Default::default() } } @@ -496,27 +496,25 @@ impl TestResult { } /// Returns the skipped result for single test (used in skipped fuzz test too). - pub fn single_skip(mut self, reason: SkipReason) -> Self { + pub fn single_skip(&mut self, reason: SkipReason) { self.status = TestStatus::Skipped; self.reason = reason.0; - self } /// Returns the failed result with reason for single test. - pub fn single_fail(mut self, reason: Option) -> Self { + pub fn single_fail(&mut self, reason: Option) { self.status = TestStatus::Failure; self.reason = reason; - self } /// Returns the result for single test. Merges execution results (logs, labeled addresses, /// traces and coverages) in initial setup results. pub fn single_result( - mut self, + &mut self, success: bool, reason: Option, raw_call_result: RawCallResult, - ) -> Self { + ) { self.kind = TestKind::Unit { gas: raw_call_result.gas_used.wrapping_sub(raw_call_result.stipend) }; @@ -539,13 +537,11 @@ impl TestResult { self.gas_snapshots = cheatcodes.gas_snapshots; self.deprecated_cheatcodes = cheatcodes.deprecated; } - - self } /// Returns the result for a fuzzed test. Merges fuzz execution results (logs, labeled /// addresses, traces and coverages) in initial setup results. - pub fn fuzz_result(mut self, result: FuzzTestResult) -> Self { + pub fn fuzz_result(&mut self, result: FuzzTestResult) { self.kind = TestKind::Fuzz { median_gas: result.median_gas(false), mean_gas: result.mean_gas(false), @@ -572,26 +568,23 @@ impl TestResult { self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect(); self.breakpoints = result.breakpoints.unwrap_or_default(); self.deprecated_cheatcodes = result.deprecated_cheatcodes; - - self } /// Returns the skipped result for invariant test. - pub fn invariant_skip(mut self, reason: SkipReason) -> Self { + pub fn invariant_skip(&mut self, reason: SkipReason) { self.kind = TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; self.status = TestStatus::Skipped; self.reason = reason.0; - self } /// Returns the fail result for replayed invariant test. pub fn invariant_replay_fail( - mut self, + &mut self, replayed_entirely: bool, invariant_name: &String, call_sequence: Vec, - ) -> Self { + ) { self.kind = TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; self.status = TestStatus::Failure; @@ -601,22 +594,20 @@ impl TestResult { Some(format!("{invariant_name} persisted failure revert")) }; self.counterexample = Some(CounterExample::Sequence(call_sequence)); - self } /// Returns the fail result for invariant test setup. - pub fn invariant_setup_fail(mut self, e: Report) -> Self { + pub fn invariant_setup_fail(&mut self, e: Report) { self.kind = TestKind::Invariant { runs: 0, calls: 0, reverts: 0, metrics: HashMap::default() }; self.status = TestStatus::Failure; self.reason = Some(format!("failed to set up invariant testing environment: {e}")); - self } /// Returns the invariant test result. #[allow(clippy::too_many_arguments)] pub fn invariant_result( - mut self, + &mut self, gas_report_traces: Vec>, success: bool, reason: Option, @@ -624,7 +615,7 @@ impl TestResult { cases: Vec, reverts: usize, metrics: Map, - ) -> Self { + ) { self.kind = TestKind::Invariant { runs: cases.len(), calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), @@ -638,7 +629,6 @@ impl TestResult { self.reason = reason; self.counterexample = counterexample; self.gas_report_traces = gas_report_traces; - self } /// Returns `true` if this is the result of a fuzz test diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index fc2b89cb0..0948df6d1 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -2,20 +2,17 @@ use crate::{ fuzz::{invariant::BasicTxDetails, BaseCounterExample}, - multi_runner::{is_matching_test, TestContract}, + multi_runner::{is_matching_test, TestContract, TestRunnerConfig}, progress::{start_fuzz_progress, TestsProgress}, result::{SuiteResult, TestResult, TestSetup}, - TestFilter, TestOptions, + MultiContractRunner, TestFilter, }; use alloy_dyn_abi::DynSolValue; use alloy_json_abi::Function; -use alloy_primitives::{address, map::HashMap, Address, Bytes, U256}; +use alloy_primitives::{address, map::HashMap, Address, U256}; use eyre::Result; -use foundry_common::{ - contracts::{ContractsByAddress, ContractsByArtifact}, - TestFunctionExt, TestFunctionKind, -}; -use foundry_config::{FuzzConfig, InvariantConfig}; +use foundry_common::{contracts::ContractsByAddress, TestFunctionExt, TestFunctionKind}; +use foundry_config::Config; use foundry_evm::{ constants::CALLER, decode::RevertDecoder, @@ -33,9 +30,12 @@ use foundry_evm::{ }, traces::{load_contracts, TraceKind, TraceMode}, }; -use proptest::test_runner::TestRunner; +use proptest::test_runner::{ + FailurePersistence, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner, +}; use rayon::prelude::*; -use std::{borrow::Cow, cmp::min, collections::BTreeMap, time::Instant}; +use std::{borrow::Cow, cmp::min, collections::BTreeMap, sync::Arc, time::Instant}; +use tracing::Span; /// 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 @@ -45,33 +45,56 @@ use std::{borrow::Cow, cmp::min, collections::BTreeMap, time::Instant}; pub const LIBRARY_DEPLOYER: Address = address!("1F95D37F27EA0dEA9C252FC09D5A6eaA97647353"); /// A type that executes all tests of a contract -#[derive(Clone, Debug)] pub struct ContractRunner<'a> { /// The name of the contract. - pub name: &'a str, + name: &'a str, /// The data of the contract. - pub contract: &'a TestContract, - /// The libraries that need to be deployed before the contract. - pub libs_to_deploy: &'a Vec, - /// The executor used by the runner. - pub executor: Executor, - /// Revert decoder. Contains all known errors. - pub revert_decoder: &'a RevertDecoder, - /// The initial balance of the test contract. - pub initial_balance: U256, - /// The address which will be used as the `from` field in all EVM calls. - pub sender: Address, - /// Whether debug traces should be generated. - pub debug: bool, + contract: &'a TestContract, + /// The EVM executor. + executor: Executor, /// Overall test run progress. - pub progress: Option<&'a TestsProgress>, + progress: Option<&'a TestsProgress>, /// The handle to the tokio runtime. - pub tokio_handle: &'a tokio::runtime::Handle, + tokio_handle: &'a tokio::runtime::Handle, /// The span of the contract. - pub span: tracing::Span, + span: tracing::Span, + /// The contract-level configuration. + tcfg: Cow<'a, TestRunnerConfig>, + /// The parent runner. + mcr: &'a MultiContractRunner, +} + +impl<'a> std::ops::Deref for ContractRunner<'a> { + type Target = Cow<'a, TestRunnerConfig>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.tcfg + } } -impl ContractRunner<'_> { +impl<'a> ContractRunner<'a> { + pub fn new( + name: &'a str, + contract: &'a TestContract, + executor: Executor, + progress: Option<&'a TestsProgress>, + tokio_handle: &'a tokio::runtime::Handle, + span: Span, + mcr: &'a MultiContractRunner, + ) -> Self { + Self { + name, + contract, + executor, + progress, + tokio_handle, + span, + tcfg: Cow::Borrowed(&mcr.tcfg), + mcr, + } + } + /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, call_setup: bool) -> TestSetup { @@ -84,6 +107,8 @@ impl ContractRunner<'_> { fn _setup(&mut self, call_setup: bool) -> Result { trace!(call_setup, "setting up"); + self.apply_contract_inline_config()?; + // We max out their balance so that they can deploy and make calls. self.executor.set_balance(self.sender, U256::MAX)?; self.executor.set_balance(CALLER, U256::MAX)?; @@ -95,12 +120,12 @@ impl ContractRunner<'_> { self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?; let mut result = TestSetup::default(); - for code in self.libs_to_deploy.iter() { + for code in self.mcr.libs_to_deploy.iter() { let deploy_result = self.executor.deploy( LIBRARY_DEPLOYER, code.clone(), U256::ZERO, - Some(self.revert_decoder), + Some(&self.mcr.revert_decoder), ); let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?; result.extend(raw, TraceKind::Deployment); @@ -115,14 +140,14 @@ impl ContractRunner<'_> { // Set the contracts initial balance before deployment, so it is available during // construction - self.executor.set_balance(address, self.initial_balance)?; + self.executor.set_balance(address, self.initial_balance())?; // Deploy the test contract let deploy_result = self.executor.deploy( self.sender, self.contract.bytecode.clone(), U256::ZERO, - Some(self.revert_decoder), + Some(&self.mcr.revert_decoder), ); if let Ok(dr) = &deploy_result { debug_assert_eq!(dr.address, address); @@ -135,16 +160,16 @@ impl ContractRunner<'_> { } // Reset `self.sender`s, `CALLER`s and `LIBRARY_DEPLOYER`'s balance to the initial balance. - self.executor.set_balance(self.sender, self.initial_balance)?; - self.executor.set_balance(CALLER, self.initial_balance)?; - self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance)?; + self.executor.set_balance(self.sender, self.initial_balance())?; + self.executor.set_balance(CALLER, self.initial_balance())?; + self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?; self.executor.deploy_create2_deployer()?; // Optionally call the `setUp` function if call_setup { trace!("calling setUp"); - let res = self.executor.setup(None, address, Some(self.revert_decoder)); + let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder)); let (raw, reason) = RawCallResult::from_evm_result(res)?; result.extend(raw, TraceKind::Setup); result.reason = reason; @@ -155,6 +180,31 @@ impl ContractRunner<'_> { Ok(result) } + fn initial_balance(&self) -> U256 { + self.evm_opts.initial_balance + } + + /// Configures this runner with the inline configuration for the contract. + fn apply_contract_inline_config(&mut self) -> Result<()> { + if self.inline_config.contains_contract(self.name) { + let new_config = Arc::new(self.inline_config(None)?); + self.tcfg.to_mut().reconfigure_with(new_config); + let prev_tracer = self.executor.inspector_mut().tracer.take(); + self.tcfg.configure_executor(&mut self.executor); + // Don't set tracer here. + self.executor.inspector_mut().tracer = prev_tracer; + } + Ok(()) + } + + /// Returns the configuration for a contract or function. + fn inline_config(&self, func: Option<&Function>) -> Result { + let function = func.map(|f| f.name.as_str()).unwrap_or(""); + let config = + self.mcr.inline_config.merge(self.name, function, &self.config).extract::()?; + Ok(config) + } + /// Collect fixtures from test contract. /// /// Fixtures can be defined: @@ -210,12 +260,7 @@ impl ContractRunner<'_> { } /// Runs all tests for a contract whose names match the provided regular expression - pub fn run_tests( - mut self, - filter: &dyn TestFilter, - test_options: &TestOptions, - known_contracts: ContractsByArtifact, - ) -> SuiteResult { + pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult { let start = Instant::now(); let mut warnings = Vec::new(); @@ -302,16 +347,16 @@ impl ContractRunner<'_> { .functions() .filter(|func| is_matching_test(func, filter)) .collect::>(); - let find_time = find_timer.elapsed(); debug!( "Found {} test functions out of {} in {:?}", functions.len(), self.contract.abi.functions().count(), - find_time, + find_timer.elapsed(), ); - let identified_contracts = has_invariants - .then(|| load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &known_contracts)); + let identified_contracts = has_invariants.then(|| { + load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts) + }); let test_results = functions .par_iter() .map(|&func| { @@ -335,36 +380,12 @@ impl ContractRunner<'_> { ) .entered(); - let setup = setup.clone(); - let mut res = match kind { - TestFunctionKind::UnitTest { should_fail } => { - self.run_unit_test(func, should_fail, setup) - } - TestFunctionKind::FuzzTest { should_fail } => { - match test_options.fuzz_runner(self.name, &func.name) { - Ok((fuzz_config, runner)) => { - self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config) - } - Err(err) => TestResult::fail(err.to_string()), - } - } - TestFunctionKind::InvariantTest => { - match test_options.invariant_runner(self.name, &func.name) { - Ok((invariant_config, runner)) => self.run_invariant_test( - runner, - setup, - invariant_config, - func, - call_after_invariant, - &known_contracts, - identified_contracts.as_ref().unwrap(), - ), - Err(err) => TestResult::fail(err.to_string()), - } - } - _ => unreachable!(), - }; - + let mut res = FunctionRunner::new(&self, &setup).run( + func, + kind, + call_after_invariant, + identified_contracts.as_ref(), + ); res.duration = start.elapsed(); (sig, res) @@ -374,6 +395,83 @@ impl ContractRunner<'_> { let duration = start.elapsed(); SuiteResult::new(duration, test_results, warnings) } +} + +/// Executes a single test function, returning a [`TestResult`]. +struct FunctionRunner<'a> { + /// The function-level configuration. + tcfg: Cow<'a, TestRunnerConfig>, + /// The EVM executor. + executor: Cow<'a, Executor>, + /// The parent runner. + cr: &'a ContractRunner<'a>, + /// The address of the test contract. + address: Address, + /// The test setup result. + setup: &'a TestSetup, + /// The test result. Returned after running the test. + result: TestResult, +} + +impl<'a> std::ops::Deref for FunctionRunner<'a> { + type Target = Cow<'a, TestRunnerConfig>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.tcfg + } +} + +impl<'a> FunctionRunner<'a> { + fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self { + Self { + tcfg: match &cr.tcfg { + Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg), + Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()), + }, + executor: Cow::Borrowed(&cr.executor), + cr, + address: setup.address, + setup, + result: TestResult::new(setup), + } + } + + fn revert_decoder(&self) -> &'a RevertDecoder { + &self.cr.mcr.revert_decoder + } + + /// Configures this runner with the inline configuration for the contract. + fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> { + if self.inline_config.contains_function(self.cr.name, &func.name) { + let new_config = Arc::new(self.cr.inline_config(Some(func))?); + self.tcfg.to_mut().reconfigure_with(new_config); + self.tcfg.configure_executor(self.executor.to_mut()); + } + Ok(()) + } + + fn run( + mut self, + func: &Function, + kind: TestFunctionKind, + call_after_invariant: bool, + identified_contracts: Option<&ContractsByAddress>, + ) -> TestResult { + if let Err(e) = self.apply_function_inline_config(func) { + self.result.single_fail(Some(e.to_string())); + return self.result; + } + + match kind { + TestFunctionKind::UnitTest { should_fail } => self.run_unit_test(func, should_fail), + TestFunctionKind::FuzzTest { should_fail } => self.run_fuzz_test(func, should_fail), + TestFunctionKind::InvariantTest => { + self.run_invariant_test(func, call_after_invariant, identified_contracts.unwrap()) + } + _ => unreachable!(), + } + } /// Runs a single unit test. /// @@ -383,80 +481,77 @@ impl ContractRunner<'_> { /// (therefore the unit test call will be made on modified state). /// State modifications of before test txes and unit test function call are discarded after /// test ends, similar to `eth_call`. - pub fn run_unit_test( - &self, - func: &Function, - should_fail: bool, - setup: TestSetup, - ) -> TestResult { + fn run_unit_test(mut self, func: &Function, should_fail: bool) -> TestResult { // Prepare unit test execution. - let (executor, test_result, address) = match self.prepare_test(func, setup) { - Ok(res) => res, - Err(res) => return res, - }; + if self.prepare_test(func).is_err() { + return self.result; + } // Run current unit test. - let (mut raw_call_result, reason) = match executor.call( + let (mut raw_call_result, reason) = match self.executor.call( self.sender, - address, + self.address, func, &[], U256::ZERO, - Some(self.revert_decoder), + Some(self.revert_decoder()), ) { Ok(res) => (res.raw, None), Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), - Err(EvmError::Skip(reason)) => return test_result.single_skip(reason), - Err(err) => return test_result.single_fail(Some(err.to_string())), + Err(EvmError::Skip(reason)) => { + self.result.single_skip(reason); + return self.result; + } + Err(err) => { + self.result.single_fail(Some(err.to_string())); + return self.result; + } }; - let success = executor.is_raw_call_mut_success(address, &mut raw_call_result, should_fail); - test_result.single_result(success, reason, raw_call_result) + let success = + self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, should_fail); + self.result.single_result(success, reason, raw_call_result); + self.result } - #[allow(clippy::too_many_arguments)] - pub fn run_invariant_test( - &self, - runner: TestRunner, - setup: TestSetup, - invariant_config: InvariantConfig, + fn run_invariant_test( + mut self, func: &Function, call_after_invariant: bool, - known_contracts: &ContractsByArtifact, identified_contracts: &ContractsByAddress, ) -> TestResult { - let address = setup.address; - let fuzz_fixtures = setup.fuzz_fixtures.clone(); - let mut test_result = TestResult::new(setup); - // First, run the test normally to see if it needs to be skipped. if let Err(EvmError::Skip(reason)) = self.executor.call( self.sender, - address, + self.address, func, &[], U256::ZERO, - Some(self.revert_decoder), + Some(self.revert_decoder()), ) { - return test_result.invariant_skip(reason); + self.result.invariant_skip(reason); + return self.result; }; + let runner = self.invariant_runner(); + let invariant_config = &self.config.invariant; + let mut evm = InvariantExecutor::new( - self.executor.clone(), + self.clone_executor(), runner, invariant_config.clone(), identified_contracts, - known_contracts, + &self.cr.mcr.known_contracts, ); let invariant_contract = InvariantContract { - address, + address: self.address, invariant_function: func, call_after_invariant, - abi: &self.contract.abi, + abi: &self.cr.contract.abi, }; - let failure_dir = invariant_config.clone().failure_dir(self.name); - let failure_file = failure_dir.join(invariant_contract.invariant_function.clone().name); + let failure_dir = invariant_config.clone().failure_dir(self.cr.name); + let failure_file = failure_dir.join(&invariant_contract.invariant_function.name); // Try to replay recorded failure if any. if let Ok(call_sequence) = @@ -474,7 +569,7 @@ impl ContractRunner<'_> { }) .collect::>(); if let Ok((success, replayed_entirely)) = check_sequence( - self.executor.clone(), + self.clone_executor(), &txes, (0..min(txes.len(), invariant_config.depth as usize)).collect(), invariant_contract.address, @@ -492,34 +587,40 @@ impl ContractRunner<'_> { // exit without executing new runs. let _ = replay_run( &invariant_contract, - self.executor.clone(), - known_contracts, + self.clone_executor(), + &self.cr.mcr.known_contracts, identified_contracts.clone(), - &mut test_result.logs, - &mut test_result.traces, - &mut test_result.coverage, - &mut test_result.deprecated_cheatcodes, + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.coverage, + &mut self.result.deprecated_cheatcodes, &txes, ); - return test_result.invariant_replay_fail( + self.result.invariant_replay_fail( replayed_entirely, &invariant_contract.invariant_function.name, call_sequence, - ) + ); + return self.result; } } } let progress = - start_fuzz_progress(self.progress, self.name, &func.name, invariant_config.runs); - let invariant_result = - match evm.invariant_fuzz(invariant_contract.clone(), &fuzz_fixtures, progress.as_ref()) - { - Ok(x) => x, - Err(e) => return test_result.invariant_setup_fail(e), - }; + start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, invariant_config.runs); + let invariant_result = match evm.invariant_fuzz( + invariant_contract.clone(), + &self.setup.fuzz_fixtures, + progress.as_ref(), + ) { + Ok(x) => x, + Err(e) => { + self.result.invariant_setup_fail(e); + return self.result; + } + }; // Merge coverage collected during invariant run with test setup coverage. - test_result.merge_coverages(invariant_result.coverage); + self.result.merge_coverages(invariant_result.coverage); let mut counterexample = None; let success = invariant_result.error.is_none(); @@ -535,13 +636,13 @@ impl ContractRunner<'_> { match replay_error( &case_data, &invariant_contract, - self.executor.clone(), - known_contracts, + self.clone_executor(), + &self.cr.mcr.known_contracts, identified_contracts.clone(), - &mut test_result.logs, - &mut test_result.traces, - &mut test_result.coverage, - &mut test_result.deprecated_cheatcodes, + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.coverage, + &mut self.result.deprecated_cheatcodes, progress.as_ref(), ) { Ok(call_sequence) => { @@ -571,13 +672,13 @@ impl ContractRunner<'_> { _ => { if let Err(err) = replay_run( &invariant_contract, - self.executor.clone(), - known_contracts, + self.clone_executor(), + &self.cr.mcr.known_contracts, identified_contracts.clone(), - &mut test_result.logs, - &mut test_result.traces, - &mut test_result.coverage, - &mut test_result.deprecated_cheatcodes, + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.coverage, + &mut self.result.deprecated_cheatcodes, &invariant_result.last_run_inputs, ) { error!(%err, "Failed to replay last invariant run"); @@ -585,7 +686,7 @@ impl ContractRunner<'_> { } } - test_result.invariant_result( + self.result.invariant_result( invariant_result.gas_report_traces, success, reason, @@ -593,7 +694,8 @@ impl ContractRunner<'_> { invariant_result.cases, invariant_result.reverts, invariant_result.metrics, - ) + ); + self.result } /// Runs a fuzzed test. @@ -605,35 +707,31 @@ impl ContractRunner<'_> { /// (therefore the fuzz test will use the modified state). /// State modifications of before test txes and fuzz test are discarded after test ends, /// similar to `eth_call`. - pub fn run_fuzz_test( - &self, - func: &Function, - should_fail: bool, - runner: TestRunner, - setup: TestSetup, - fuzz_config: FuzzConfig, - ) -> TestResult { - let progress = start_fuzz_progress(self.progress, self.name, &func.name, fuzz_config.runs); - + fn run_fuzz_test(mut self, func: &Function, should_fail: bool) -> TestResult { // Prepare fuzz test execution. - let fuzz_fixtures = setup.fuzz_fixtures.clone(); - let (executor, test_result, address) = match self.prepare_test(func, setup) { - Ok(res) => res, - Err(res) => return res, - }; + if self.prepare_test(func).is_err() { + return self.result; + } + + let runner = self.fuzz_runner(); + let fuzz_config = self.config.fuzz.clone(); + + let progress = + start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fuzz_config.runs); // Run fuzz test. let fuzzed_executor = - FuzzedExecutor::new(executor.into_owned(), runner, self.sender, fuzz_config); + FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config); let result = fuzzed_executor.fuzz( func, - &fuzz_fixtures, - address, + &self.setup.fuzz_fixtures, + self.address, should_fail, - self.revert_decoder, + &self.cr.mcr.revert_decoder, progress.as_ref(), ); - test_result.fuzz_result(result) + self.result.fuzz_result(result); + self.result } /// Prepares single unit test and fuzz test execution: @@ -645,20 +743,15 @@ impl ContractRunner<'_> { /// /// Unit tests within same contract (or even current test) are valid options for before test tx /// configuration. Test execution stops if any of before test txes fails. - fn prepare_test( - &self, - func: &Function, - setup: TestSetup, - ) -> Result<(Cow<'_, Executor>, TestResult, Address), TestResult> { - let address = setup.address; - let mut executor = Cow::Borrowed(&self.executor); - let mut test_result = TestResult::new(setup); + fn prepare_test(&mut self, func: &Function) -> Result<(), ()> { + let address = self.setup.address; // Apply before test configured functions (if any). - if self.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() == + if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() == 1 { - for calldata in executor + for calldata in self + .executor .call_sol_default( address, &ITest::beforeTestSetupCall { testSelector: func.selector() }, @@ -666,22 +759,84 @@ impl ContractRunner<'_> { .beforeTestCalldata { // Apply before test configured calldata. - match executor.to_mut().transact_raw(self.sender, address, calldata, U256::ZERO) { + match self.executor.to_mut().transact_raw( + self.tcfg.sender, + address, + calldata, + U256::ZERO, + ) { Ok(call_result) => { let reverted = call_result.reverted; // Merge tx result traces in unit test result. - test_result.extend(call_result); + self.result.extend(call_result); // To continue unit test execution the call should not revert. if reverted { - return Err(test_result.single_fail(None)) + self.result.single_fail(None); + return Err(()); } } - Err(_) => return Err(test_result.single_fail(None)), + Err(_) => { + self.result.single_fail(None); + return Err(()); + } } } } - Ok((executor, test_result, address)) + Ok(()) + } + + fn fuzz_runner(&self) -> TestRunner { + let config = &self.config.fuzz; + let failure_persist_path = config + .failure_persist_dir + .as_ref() + .unwrap() + .join(config.failure_persist_file.as_ref().unwrap()) + .into_os_string() + .into_string() + .unwrap(); + fuzzer_with_cases( + config.seed, + config.runs, + config.max_test_rejects, + Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), + ) + } + + fn invariant_runner(&self) -> TestRunner { + let config = &self.config.invariant; + fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None) + } + + fn clone_executor(&self) -> Executor { + self.executor.clone().into_owned() + } +} + +fn fuzzer_with_cases( + seed: Option, + cases: u32, + max_global_rejects: u32, + file_failure_persistence: Option>, +) -> TestRunner { + let config = proptest::test_runner::Config { + failure_persistence: file_failure_persistence, + cases, + max_global_rejects, + // Disable proptest shrink: for fuzz tests we provide single counterexample, + // for invariant tests we shrink outside proptest. + max_shrink_iters: 0, + ..Default::default() + }; + + if let Some(seed) = seed { + trace!(target: "forge::test", %seed, "building deterministic fuzzer"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(config, rng) + } else { + trace!(target: "forge::test", "building stochastic fuzzer"); + TestRunner::new(config) } } diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index de585a48c..31da29d21 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -147,14 +147,14 @@ forgetest!(invalid_value, |prj, cmd| { Compiler run successful! Ran 1 test for test/inline.sol:Inline -[FAIL: invalid type: found sequence, expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) +[FAIL: invalid type: found sequence, expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/inline.sol:Inline -[FAIL: invalid type: found sequence, expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) +[FAIL: invalid type: found sequence, expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) Encountered a total of 1 failing tests, 0 tests succeeded @@ -179,16 +179,86 @@ forgetest!(invalid_value_2, |prj, cmd| { Compiler run successful! Ran 1 test for test/inline.sol:Inline -[FAIL: invalid type: found string "2", expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) +[FAIL: invalid type: found string "2", expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/inline.sol:Inline -[FAIL: invalid type: found string "2", expected u32 for key "default.runs.fuzz" in inline config] test(bool) ([GAS]) +[FAIL: invalid type: found string "2", expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) Encountered a total of 1 failing tests, 0 tests succeeded "#]]); }); + +forgetest_init!(evm_version, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "inline.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract Dummy { + function getBlobBaseFee() public returns (uint256) { + return block.blobbasefee; + } + } + + contract FunctionConfig is Test { + Dummy dummy; + + function setUp() public { + dummy = new Dummy(); + } + + /// forge-config: default.evm_version = "shanghai" + function test_old() public { + vm.expectRevert(); + dummy.getBlobBaseFee(); + } + + function test_new() public { + dummy.getBlobBaseFee(); + } + } + + /// forge-config: default.evm_version = "shanghai" + contract ContractConfig is Test { + Dummy dummy; + + function setUp() public { + dummy = new Dummy(); + } + + function test_old() public { + vm.expectRevert(); + dummy.getBlobBaseFee(); + } + + /// forge-config: default.evm_version = "cancun" + function test_new() public { + dummy.getBlobBaseFee(); + } + } + "#, + ) + .unwrap(); + + cmd.arg("test").arg("--evm-version=cancun").assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/inline.sol:FunctionConfig +[PASS] test_new() ([GAS]) +[PASS] test_old() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 tests for test/inline.sol:ContractConfig +[PASS] test_new() ([GAS]) +[PASS] test_old() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs index 9cabd998a..655fae4db 100644 --- a/crates/forge/tests/it/config.rs +++ b/crates/forge/tests/it/config.rs @@ -31,8 +31,8 @@ impl TestConfig { Self { runner, should_fail: false, filter } } - pub fn evm_spec(mut self, spec: SpecId) -> Self { - self.runner.evm_spec = spec; + pub fn spec_id(mut self, spec: SpecId) -> Self { + self.runner.spec_id = spec; self } diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/it/spec.rs index aed2063a0..52e581c33 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/it/spec.rs @@ -7,8 +7,5 @@ use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_shanghai_compat() { let filter = Filter::new("", "ShanghaiCompat", ".*spec"); - TestConfig::with_filter(TEST_DATA_PARIS.runner(), filter) - .evm_spec(SpecId::SHANGHAI) - .run() - .await; + TestConfig::with_filter(TEST_DATA_PARIS.runner(), filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 54985b9b6..937f582f4 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -2,9 +2,7 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; -use forge::{ - revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder, TestOptions, -}; +use forge::{revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder}; use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, utils::RuntimeOrHandle, @@ -177,9 +175,7 @@ impl ForgeTestData { pub fn base_runner(&self) -> MultiContractRunnerBuilder { init_tracing(); let config = self.config.clone(); - let mut runner = MultiContractRunnerBuilder::new(config.clone()) - .sender(self.config.sender) - .with_test_options(TestOptions::new_unparsed(config)); + let mut runner = MultiContractRunnerBuilder::new(config).sender(self.config.sender); if self.profile.is_paris() { runner = runner.evm_spec(SpecId::MERGE); } @@ -216,7 +212,6 @@ impl ForgeTestData { builder .enable_isolation(opts.isolate) .sender(config.sender) - .with_test_options(TestOptions::new(&self.output, config.clone()).unwrap()) .build(root, &self.output, opts.local_evm_env(), opts) .unwrap() } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index ccf5eab2a..5f5543912 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -618,7 +618,7 @@ impl ScriptConfig { .alphanet(self.evm_opts.alphanet) .create2_deployer(self.evm_opts.create2_deployer) }) - .spec(self.config.evm_spec_id()) + .spec_id(self.config.evm_spec_id()) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions); From 7d0b0a0371765afa0734197dc01f9f7f4d5d4c1b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:21:22 +0200 Subject: [PATCH 044/107] chore(deps): bump foundry-compilers 0.12.4 (#9455) --- Cargo.lock | 34 ++++++++++++++++----------------- crates/cheatcodes/src/string.rs | 11 ++--------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea8b8da74..87ac75dee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3864,9 +3864,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6131af72175c55aa531c4851290d9cf67af7a82b03f20a8a9d28dba81b9fd3" +checksum = "daece74fc0b127e587ac343405283577b4fe9d0195317998c3031778b5f799cd" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3881,7 +3881,6 @@ dependencies = [ "home", "itertools 0.13.0", "md-5", - "once_cell", "path-slash", "rand", "rayon", @@ -3893,7 +3892,7 @@ dependencies = [ "svm-rs", "svm-rs-builds", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "tracing", "winnow", @@ -3902,9 +3901,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522c473e7a111b81f0d47f8a65ca1ef0642c8c8d1ca2d1e230338210d16bb02" +checksum = "c0c6b0571a37cc9860103df548330eaceb36e9438cb0264ec63e5db74031fa4f" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3912,9 +3911,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cd569a0c38d232244932e71933b54e418cddcc8f0c16fd8af96dd844b27788" +checksum = "278cc3f1f4f628793ae6c0db38b0d3fe4124ab0bc10da081b1eb365766512e6d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3927,7 +3926,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "tracing", "walkdir", @@ -3936,9 +3935,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b80563ba10981ec4a9667fda9318d02721a81f248ae73303603388580150a35" +checksum = "196b16fcfbbe69a315d64f74dd817ba6843f7749c64124b937118551414b9b90" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3951,15 +3950,14 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a840a87cb4845d208df3390a12d3724e6d1cc1268a51ac334a01f80f9b6419b" +checksum = "1eb08a74eb12b281038bbf74cbbd76cc3c85546896628d4c7c769c816ab4104b" dependencies = [ "alloy-primitives", "cfg-if", "dunce", "fs_extra", - "once_cell", "path-slash", "regex", "semver 1.0.23", @@ -3967,7 +3965,7 @@ dependencies = [ "serde_json", "svm-rs", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "walkdir", ] @@ -7114,7 +7112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.90", @@ -8567,7 +8565,7 @@ dependencies = [ "const-hex", "derive_builder", "dunce", - "itertools 0.13.0", + "itertools 0.11.0", "itoa", "lasso", "match_cfg", @@ -8603,7 +8601,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.6.0", "bumpalo", - "itertools 0.13.0", + "itertools 0.11.0", "memchr", "num-bigint", "num-rational", diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index a4c06eef6..080d9bc08 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -17,7 +17,7 @@ impl Cheatcode for toString_0Call { impl Cheatcode for toString_1Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { value } = self; - Ok(hex::encode_prefixed(value).abi_encode()) + Ok(value.to_string().abi_encode()) } } @@ -95,7 +95,6 @@ impl Cheatcode for parseBoolCall { } } -// toLowercase impl Cheatcode for toLowercaseCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; @@ -103,7 +102,6 @@ impl Cheatcode for toLowercaseCall { } } -// toUppercase impl Cheatcode for toUppercaseCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; @@ -111,7 +109,6 @@ impl Cheatcode for toUppercaseCall { } } -// trim impl Cheatcode for trimCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input } = self; @@ -119,7 +116,6 @@ impl Cheatcode for trimCall { } } -// Replace impl Cheatcode for replaceCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, from, to } = self; @@ -127,7 +123,6 @@ impl Cheatcode for replaceCall { } } -// Split impl Cheatcode for splitCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, delimiter } = self; @@ -136,7 +131,6 @@ impl Cheatcode for splitCall { } } -// indexOf impl Cheatcode for indexOfCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { input, key } = self; @@ -144,7 +138,6 @@ impl Cheatcode for indexOfCall { } } -// contains impl Cheatcode for containsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { subject, search } = self; @@ -202,7 +195,7 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option { - if !s.starts_with("0x") && s.chars().all(|c| c.is_ascii_hexdigit()) { + if !s.starts_with("0x") && hex::check_raw(s) { return Some(Err("missing hex prefix (\"0x\") for hex string")); } } From b7a065f79fa63c80ece43e05b5e521ae269b4635 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:56:00 +0200 Subject: [PATCH 045/107] perf(coverage): improve HitMap merging and internal repr (#9456) --- crates/evm/coverage/src/lib.rs | 88 +++++++++++++++++--------------- crates/forge/bin/cmd/coverage.rs | 4 +- crates/forge/src/coverage.rs | 5 +- 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 8d5575631..345261ad5 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -5,19 +5,20 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#[macro_use] -extern crate foundry_common; - #[macro_use] extern crate tracing; -use alloy_primitives::{map::HashMap, Bytes, B256}; -use eyre::{Context, Result}; +use alloy_primitives::{ + map::{B256HashMap, HashMap}, + Bytes, +}; +use eyre::Result; use foundry_compilers::artifacts::sourcemap::SourceMap; use semver::Version; use std::{ collections::BTreeMap, fmt::Display, + num::NonZeroU32, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, sync::Arc, @@ -119,22 +120,21 @@ impl CoverageReport { is_deployed_code: bool, ) -> Result<()> { // Add bytecode level hits - let e = self - .bytecode_hits + self.bytecode_hits .entry(contract_id.clone()) - .or_insert_with(|| HitMap::new(hit_map.bytecode.clone())); - e.merge(hit_map).wrap_err_with(|| format!("{contract_id:?}"))?; + .and_modify(|m| m.merge(hit_map)) + .or_insert_with(|| hit_map.clone()); // Add source level hits if let Some(anchors) = self.anchors.get(contract_id) { let anchors = if is_deployed_code { &anchors.1 } else { &anchors.0 }; for anchor in anchors { - if let Some(&hits) = hit_map.hits.get(&anchor.instruction) { + if let Some(hits) = hit_map.get(anchor.instruction) { self.items .get_mut(&contract_id.version) .and_then(|items| items.get_mut(anchor.item_id)) .expect("Anchor refers to non-existent coverage item") - .hits += hits; + .hits += hits.get(); } } } @@ -160,9 +160,10 @@ impl CoverageReport { /// A collection of [`HitMap`]s. #[derive(Clone, Debug, Default)] -pub struct HitMaps(pub HashMap); +pub struct HitMaps(pub B256HashMap); impl HitMaps { + /// Merges two `Option`. pub fn merge_opt(a: &mut Option, b: Option) { match (a, b) { (_, None) => {} @@ -171,17 +172,14 @@ impl HitMaps { } } + /// Merges two `HitMaps`. pub fn merge(&mut self, other: Self) { - for (code_hash, hit_map) in other.0 { - if let Some(HitMap { hits: extra_hits, .. }) = self.insert(code_hash, hit_map) { - for (pc, hits) in extra_hits { - self.entry(code_hash) - .and_modify(|map| *map.hits.entry(pc).or_default() += hits); - } - } + for (code_hash, other) in other.0 { + self.entry(code_hash).and_modify(|e| e.merge(&other)).or_insert(other); } } + /// Merges two `HitMaps`. pub fn merged(mut self, other: Self) -> Self { self.merge(other); self @@ -189,7 +187,7 @@ impl HitMaps { } impl Deref for HitMaps { - type Target = HashMap; + type Target = B256HashMap; fn deref(&self) -> &Self::Target { &self.0 @@ -207,40 +205,46 @@ impl DerefMut for HitMaps { /// Contains low-level data about hit counters for the instructions in the bytecode of a contract. #[derive(Clone, Debug)] pub struct HitMap { - pub bytecode: Bytes, - pub hits: BTreeMap, + bytecode: Bytes, + hits: HashMap, } impl HitMap { + /// Create a new hitmap with the given bytecode. pub fn new(bytecode: Bytes) -> Self { - Self { bytecode, hits: BTreeMap::new() } + Self { bytecode, hits: Default::default() } + } + + /// Returns the bytecode. + pub fn bytecode(&self) -> &Bytes { + &self.bytecode } - /// Increase the hit counter for the given program counter. + /// Returns the number of hits for the given program counter. + pub fn get(&self, pc: usize) -> Option { + NonZeroU32::new(self.hits.get(&Self::cvt_pc(pc)).copied().unwrap_or(0)) + } + + /// Increase the hit counter by 1 for the given program counter. pub fn hit(&mut self, pc: usize) { - *self.hits.entry(pc).or_default() += 1; + self.hits(pc, 1) + } + + /// Increase the hit counter by `hits` for the given program counter. + pub fn hits(&mut self, pc: usize, hits: u32) { + *self.hits.entry(Self::cvt_pc(pc)).or_default() += hits; } /// Merge another hitmap into this, assuming the bytecode is consistent - pub fn merge(&mut self, other: &Self) -> Result<(), eyre::Report> { - for (pc, hits) in &other.hits { - *self.hits.entry(*pc).or_default() += hits; + pub fn merge(&mut self, other: &Self) { + for (&pc, &hits) in &other.hits { + self.hits(pc as usize, hits); } - Ok(()) } - pub fn consistent_bytecode(&self, hm1: &Self, hm2: &Self) -> bool { - // Consider the bytecodes consistent if they are the same out as far as the - // recorded hits - let len1 = hm1.hits.last_key_value(); - let len2 = hm2.hits.last_key_value(); - if let (Some(len1), Some(len2)) = (len1, len2) { - let len = std::cmp::max(len1.0, len2.0); - let ok = hm1.bytecode.0[..*len] == hm2.bytecode.0[..*len]; - let _ = sh_println!("consistent_bytecode: {}, {}, {}, {}", ok, len1.0, len2.0, len); - return ok; - } - true + #[inline] + fn cvt_pc(pc: usize) -> u32 { + pc.try_into().expect("4GiB bytecode") } } @@ -311,7 +315,7 @@ pub struct CoverageItem { /// The location of the item in the source code. pub loc: SourceLocation, /// The number of times this item was hit. - pub hits: u64, + pub hits: u32, } impl Display for CoverageItem { diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index f1c862d81..d995e764d 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -250,10 +250,10 @@ impl CoverageArgs { for result in suite.test_results.values() { let Some(hit_maps) = result.coverage.as_ref() else { continue }; for map in hit_maps.0.values() { - if let Some((id, _)) = known_contracts.find_by_deployed_code(&map.bytecode) { + if let Some((id, _)) = known_contracts.find_by_deployed_code(map.bytecode()) { hits.push((id, map, true)); } else if let Some((id, _)) = - known_contracts.find_by_creation_code(&map.bytecode) + known_contracts.find_by_creation_code(map.bytecode()) { hits.push((id, map, false)); } diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index 14fc5e7be..f73b17da5 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -212,7 +212,7 @@ impl CoverageReporter for BytecodeReporter { let mut line_number_cache = LineNumberCache::new(self.root.clone()); for (contract_id, hits) in &report.bytecode_hits { - let ops = disassemble_bytes(hits.bytecode.to_vec())?; + let ops = disassemble_bytes(hits.bytecode().to_vec())?; let mut formatted = String::new(); let source_elements = @@ -220,8 +220,7 @@ impl CoverageReporter for BytecodeReporter { for (code, source_element) in std::iter::zip(ops.iter(), source_elements) { let hits = hits - .hits - .get(&(code.offset as usize)) + .get(code.offset as usize) .map(|h| format!("[{h:03}]")) .unwrap_or(" ".to_owned()); let source_id = source_element.index(); From d35fee62382b9bf66c946f3f9b6646e00a64db43 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:45:35 +0200 Subject: [PATCH 046/107] perf(coverage): cache computed bytecode hash in CoverageCollector (#9457) * perf(coverage): cache computed bytecode hash in CoverageCollector * perf: use get_mut instead of entry --- crates/evm/coverage/src/inspector.rs | 32 +++++++++++++++----------- crates/evm/evm/src/inspectors/stack.rs | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index 73d2ff148..07c24ae9e 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -2,35 +2,41 @@ use crate::{HitMap, HitMaps}; use alloy_primitives::B256; use revm::{interpreter::Interpreter, Database, EvmContext, Inspector}; +/// Inspector implementation for collecting coverage information. #[derive(Clone, Debug, Default)] pub struct CoverageCollector { - /// Maps that track instruction hit data. - pub maps: HitMaps, + maps: HitMaps, } impl Inspector for CoverageCollector { - #[inline] - fn initialize_interp(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { self.maps - .entry(get_contract_hash(interp)) - .or_insert_with(|| HitMap::new(interp.contract.bytecode.original_bytes())); + .entry(*get_contract_hash(interpreter)) + .or_insert_with(|| HitMap::new(interpreter.contract.bytecode.original_bytes())); } #[inline] - fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + fn step(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { + if let Some(map) = self.maps.get_mut(get_contract_hash(interpreter)) { + map.hit(interpreter.program_counter()); + } + } +} + +impl CoverageCollector { + /// Finish collecting coverage information and return the [`HitMaps`]. + pub fn finish(self) -> HitMaps { self.maps - .entry(get_contract_hash(interp)) - .and_modify(|map| map.hit(interp.program_counter())); } } /// Helper function for extracting contract hash used to record coverage hit map. /// If contract hash available in interpreter contract is zero (contract not yet created but going /// to be created in current tx) then it hash is calculated from contract bytecode. -fn get_contract_hash(interp: &mut Interpreter) -> B256 { - let mut hash = interp.contract.hash.expect("Contract hash is None"); - if hash == B256::ZERO { - hash = interp.contract.bytecode.hash_slow(); +fn get_contract_hash(interpreter: &mut Interpreter) -> &B256 { + let hash = interpreter.contract.hash.as_mut().expect("coverage does not support EOF"); + if *hash == B256::ZERO { + *hash = interpreter.contract.bytecode.hash_slow(); } hash } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index accbca4fd..94ec349b1 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -470,7 +470,7 @@ impl InspectorStack { .map(|cheatcodes| cheatcodes.labels.clone()) .unwrap_or_default(), traces, - coverage: coverage.map(|coverage| coverage.maps), + coverage: coverage.map(|coverage| coverage.finish()), cheatcodes, chisel_state: chisel_state.and_then(|state| state.state), } From ee9d23723efe7893c10547371d830b24bd2aab13 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:50:13 +0200 Subject: [PATCH 047/107] fix(coverage): also ignore empty fallbacks and receives (#9459) --- crates/evm/coverage/src/analysis.rs | 7 +-- crates/forge/tests/cli/coverage.rs | 77 +++++++++++++++++++---------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 07b916035..06673293a 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -57,9 +57,10 @@ impl<'a> ContractVisitor<'a> { let kind: String = node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; - // Do not add coverage item for constructors without statements. - if kind == "constructor" && !has_statements(body) { - return Ok(()) + // TODO: We currently can only detect empty bodies in normal functions, not any of the other + // kinds: https://github.com/foundry-rs/foundry/issues/9458 + if kind != "function" && !has_statements(body) { + return Ok(()); } // `fallback`, `receive`, and `constructor` functions have an empty `name`. diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 9b0569da6..6ffc6c2b3 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -5,7 +5,7 @@ use foundry_test_utils::{ }; use std::path::Path; -fn basic_coverage_base(prj: TestProject, mut cmd: TestCommand) { +fn basic_base(prj: TestProject, mut cmd: TestCommand) { cmd.args(["coverage", "--report=lcov", "--report=summary"]).assert_success().stdout_eq(str![[ r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -75,11 +75,11 @@ end_of_record ); } -forgetest_init!(basic_coverage, |prj, cmd| { - basic_coverage_base(prj, cmd); +forgetest_init!(basic, |prj, cmd| { + basic_base(prj, cmd); }); -forgetest_init!(basic_coverage_crlf, |prj, cmd| { +forgetest_init!(basic_crlf, |prj, cmd| { // Manually replace `\n` with `\r\n` in the source file. let make_crlf = |path: &Path| { fs::write(path, fs::read_to_string(path).unwrap().replace('\n', "\r\n")).unwrap() @@ -88,10 +88,10 @@ forgetest_init!(basic_coverage_crlf, |prj, cmd| { make_crlf(&prj.paths().scripts.join("Counter.s.sol")); // Should have identical stdout and lcov output. - basic_coverage_base(prj, cmd); + basic_base(prj, cmd); }); -forgetest!(test_setup_coverage, |prj, cmd| { +forgetest!(setup, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -144,7 +144,7 @@ contract AContractTest is DSTest { "#]]); }); -forgetest!(test_no_match_coverage, |prj, cmd| { +forgetest!(no_match, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -239,7 +239,7 @@ contract BContractTest is DSTest { ]]); }); -forgetest!(test_assert_coverage, |prj, cmd| { +forgetest!(assert, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -317,7 +317,7 @@ contract AContractTest is DSTest { "#]]); }); -forgetest!(test_require_coverage, |prj, cmd| { +forgetest!(require, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -393,7 +393,7 @@ contract AContractTest is DSTest { "#]]); }); -forgetest!(test_line_hit_not_doubled, |prj, cmd| { +forgetest!(line_hit_not_doubled, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -447,7 +447,7 @@ end_of_record ); }); -forgetest!(test_branch_coverage, |prj, cmd| { +forgetest!(branch, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "Foo.sol", @@ -699,7 +699,7 @@ contract FooTest is DSTest { "#]]); }); -forgetest!(test_function_call_coverage, |prj, cmd| { +forgetest!(function_call, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -770,7 +770,7 @@ contract AContractTest is DSTest { "#]]); }); -forgetest!(test_try_catch_coverage, |prj, cmd| { +forgetest!(try_catch, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "Foo.sol", @@ -879,7 +879,7 @@ contract FooTest is DSTest { "#]]); }); -forgetest!(test_yul_coverage, |prj, cmd| { +forgetest!(yul, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "Foo.sol", @@ -982,7 +982,7 @@ contract FooTest is DSTest { "#]]); }); -forgetest!(test_misc_coverage, |prj, cmd| { +forgetest!(misc, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "Foo.sol", @@ -1073,7 +1073,7 @@ contract FooTest is DSTest { }); // https://github.com/foundry-rs/foundry/issues/8605 -forgetest!(test_single_statement_coverage, |prj, cmd| { +forgetest!(single_statement, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -1151,7 +1151,7 @@ contract AContractTest is DSTest { }); // https://github.com/foundry-rs/foundry/issues/8604 -forgetest!(test_branch_with_calldata_reads, |prj, cmd| { +forgetest!(branch_with_calldata_reads, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -1234,7 +1234,7 @@ contract AContractTest is DSTest { "#]]); }); -forgetest!(test_identical_bytecodes, |prj, cmd| { +forgetest!(identical_bytecodes, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -1303,7 +1303,7 @@ contract AContractTest is DSTest { "#]]); }); -forgetest!(test_constructors_coverage, |prj, cmd| { +forgetest!(constructors, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -1353,9 +1353,11 @@ contract AContractTest is DSTest { "#]]); }); -// -// Test that constructor with no statements is not counted in functions coverage. -forgetest!(test_ignore_empty_constructors_coverage, |prj, cmd| { +// https://github.com/foundry-rs/foundry/issues/9270, https://github.com/foundry-rs/foundry/issues/9444 +// Test that special functions with no statements are not counted. +// TODO: We should support this, but for now just ignore them. +// See TODO in `visit_function_definition`: https://github.com/foundry-rs/foundry/issues/9458 +forgetest!(empty_functions, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -1363,6 +1365,8 @@ forgetest!(test_ignore_empty_constructors_coverage, |prj, cmd| { contract AContract { constructor() {} + receive() external payable {} + function increment() public {} } "#, @@ -1379,14 +1383,35 @@ contract AContractTest is DSTest { function test_constructors() public { AContract a = new AContract(); a.increment(); + (bool success,) = address(a).call{value: 1}(""); + require(success); } } "#, ) .unwrap(); + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/AContract.sol +DA:9,1 +FN:9,9,AContract.increment +FNDA:1,AContract.increment +FNF:1 +FNH:1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); + // Assert there's only one function (`increment`) reported. - cmd.arg("coverage").assert_success().stdout_eq(str![[r#" + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| @@ -1397,7 +1422,7 @@ contract AContractTest is DSTest { }); // Test coverage for `receive` functions. -forgetest!(test_receive_coverage, |prj, cmd| { +forgetest!(receive, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", @@ -1469,9 +1494,9 @@ end_of_record "#]]); }); -// +// https://github.com/foundry-rs/foundry/issues/9322 // Test coverage with `--ir-minimum` for solidity < 0.8.5. -forgetest!(test_ir_minimum_coverage, |prj, cmd| { +forgetest!(ir_minimum_early, |prj, cmd| { prj.insert_ds_test(); prj.add_source( "AContract.sol", From e5dbb7a320c2b871c4a4a1006ad3c15a08fcf17b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:16:59 +0200 Subject: [PATCH 048/107] fix/feat(coverage): add --lcov-version (#9462) feat(coverage): add --lcov-version --- crates/forge/bin/cmd/coverage.rs | 44 ++++++++++++- crates/forge/src/coverage.rs | 22 +++++-- crates/forge/tests/cli/coverage.rs | 102 ++++++++++++++++++++++++++--- 3 files changed, 154 insertions(+), 14 deletions(-) diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index d995e764d..9178c27be 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -23,7 +23,7 @@ use foundry_compilers::{ }; use foundry_config::{Config, SolcReq}; use rayon::prelude::*; -use semver::Version; +use semver::{Version, VersionReq}; use std::{ io, path::{Path, PathBuf}, @@ -42,6 +42,18 @@ pub struct CoverageArgs { #[arg(long, value_enum, default_value = "summary")] report: Vec, + /// The version of the LCOV "tracefile" format to use. + /// + /// Format: `MAJOR[.MINOR]`. + /// + /// Main differences: + /// - `1.x`: The original v1 format. + /// - `2.0`: Adds support for "line end" numbers for functions. LCOV 2.1 and onwards may emit + /// an error if this option is not provided. + /// - `2.2`: Changes the format of functions. + #[arg(long, default_value = "1.16", value_parser = parse_lcov_version)] + lcov_version: Version, + /// Enable viaIR with minimum optimization /// /// This can fix most of the "stack too deep" errors while resulting a @@ -295,7 +307,7 @@ impl CoverageArgs { let path = root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref())); let mut file = io::BufWriter::new(fs::create_file(path)?); - LcovReporter::new(&mut file).report(&report) + LcovReporter::new(&mut file, self.lcov_version.clone()).report(&report) } CoverageReportKind::Bytecode => { let destdir = root.join("bytecode-coverage"); @@ -404,3 +416,31 @@ impl BytecodeData { ) } } + +fn parse_lcov_version(s: &str) -> Result { + let vr = VersionReq::parse(&format!("={s}")).map_err(|e| e.to_string())?; + let [c] = &vr.comparators[..] else { + return Err("invalid version".to_string()); + }; + if c.op != semver::Op::Exact { + return Err("invalid version".to_string()); + } + if !c.pre.is_empty() { + return Err("pre-releases are not supported".to_string()); + } + Ok(Version::new(c.major, c.minor.unwrap_or(0), c.patch.unwrap_or(0))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lcov_version() { + assert_eq!(parse_lcov_version("0").unwrap(), Version::new(0, 0, 0)); + assert_eq!(parse_lcov_version("1").unwrap(), Version::new(1, 0, 0)); + assert_eq!(parse_lcov_version("1.0").unwrap(), Version::new(1, 0, 0)); + assert_eq!(parse_lcov_version("1.1").unwrap(), Version::new(1, 1, 0)); + assert_eq!(parse_lcov_version("1.11").unwrap(), Version::new(1, 11, 0)); + } +} diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index f73b17da5..813480b40 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -4,6 +4,7 @@ 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; +use semver::Version; use std::{ collections::hash_map, io::Write, @@ -83,17 +84,19 @@ fn format_cell(hits: usize, total: usize) -> Cell { /// [tracefile format]: https://man.archlinux.org/man/geninfo.1.en#TRACEFILE_FORMAT pub struct LcovReporter<'a> { out: &'a mut (dyn Write + 'a), + version: Version, } impl<'a> LcovReporter<'a> { /// Create a new LCOV reporter. - pub fn new(out: &'a mut (dyn Write + 'a)) -> Self { - Self { out } + pub fn new(out: &'a mut (dyn Write + 'a), version: Version) -> Self { + Self { out, version } } } impl CoverageReporter for LcovReporter<'_> { fn report(self, report: &CoverageReport) -> eyre::Result<()> { + let mut fn_index = 0usize; for (path, items) in report.items_by_file() { let summary = CoverageSummary::from_items(items.iter().copied()); @@ -108,8 +111,19 @@ impl CoverageReporter for LcovReporter<'_> { match item.kind { CoverageItemKind::Function { ref name } => { let name = format!("{}.{name}", item.loc.contract_name); - writeln!(self.out, "FN:{line},{end_line},{name}")?; - writeln!(self.out, "FNDA:{hits},{name}")?; + if self.version >= Version::new(2, 2, 0) { + // v2.2 changed the FN format. + writeln!(self.out, "FNL:{fn_index},{line},{end_line}")?; + writeln!(self.out, "FNA:{fn_index},{hits},{name}")?; + fn_index += 1; + } else if self.version >= Version::new(2, 0, 0) { + // v2.0 added end_line to FN. + writeln!(self.out, "FN:{line},{end_line},{name}")?; + writeln!(self.out, "FNDA:{hits},{name}")?; + } else { + writeln!(self.out, "FN:{line},{name}")?; + writeln!(self.out, "FNDA:{hits},{name}")?; + } } CoverageItemKind::Line => { writeln!(self.out, "DA:{line},{hits}")?; diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 6ffc6c2b3..8c6cbc19c 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -32,8 +32,52 @@ Wrote LCOV report. let lcov = prj.root().join("lcov.info"); assert!(lcov.exists(), "lcov.info was not created"); - assert_data_eq!( - Data::read_from(&lcov, None), + let default_lcov = str![[r#" +TN: +SF:script/Counter.s.sol +DA:10,0 +FN:10,CounterScript.setUp +FNDA:0,CounterScript.setUp +DA:12,0 +FN:12,CounterScript.run +FNDA:0,CounterScript.run +DA:13,0 +DA:15,0 +DA:17,0 +FNF:2 +FNH:0 +LF:5 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Counter.sol +DA:7,258 +FN:7,Counter.setNumber +FNDA:258,Counter.setNumber +DA:8,258 +DA:11,1 +FN:11,Counter.increment +FNDA:1,Counter.increment +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]]; + assert_data_eq!(Data::read_from(&lcov, None), default_lcov.clone()); + assert_lcov( + cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=1"]), + default_lcov, + ); + + assert_lcov( + cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=2"]), str![[r#" TN: SF:script/Counter.s.sol @@ -71,7 +115,49 @@ BRF:0 BRH:0 end_of_record -"#]] +"#]], + ); + + assert_lcov( + cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=2.2"]), + str![[r#" +TN: +SF:script/Counter.s.sol +DA:10,0 +FNL:0,10,10 +FNA:0,0,CounterScript.setUp +DA:12,0 +FNL:1,12,18 +FNA:1,0,CounterScript.run +DA:13,0 +DA:15,0 +DA:17,0 +FNF:2 +FNH:0 +LF:5 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Counter.sol +DA:7,258 +FNL:2,7,9 +FNA:2,258,Counter.setNumber +DA:8,258 +DA:11,1 +FNL:3,11,13 +FNA:3,1,Counter.increment +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]], ); } @@ -432,7 +518,7 @@ contract AContractTest is DSTest { TN: SF:src/AContract.sol DA:7,1 -FN:7,9,AContract.foo +FN:7,AContract.foo FNDA:1,AContract.foo DA:8,1 FNF:1 @@ -1397,7 +1483,7 @@ contract AContractTest is DSTest { TN: SF:src/AContract.sol DA:9,1 -FN:9,9,AContract.increment +FN:9,AContract.increment FNDA:1,AContract.increment FNF:1 FNH:1 @@ -1466,11 +1552,11 @@ contract AContractTest is DSTest { TN: SF:src/AContract.sol DA:7,1 -FN:7,9,AContract.constructor +FN:7,AContract.constructor FNDA:1,AContract.constructor DA:8,1 DA:11,1 -FN:11,13,AContract.receive +FN:11,AContract.receive FNDA:1,AContract.receive DA:12,1 FNF:2 @@ -1530,5 +1616,5 @@ contract AContract { #[track_caller] fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) { - cmd.args(["--report=lcov", "--report-file"]).assert_file(data); + cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data()); } From 7f8154c2ededd7521be50bb6498f14794c91f6ae Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:07:47 +0200 Subject: [PATCH 049/107] chore: set --lcov-version default to 1 (#9463) --- crates/forge/bin/cmd/coverage.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 9178c27be..753d4ec76 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -48,10 +48,9 @@ pub struct CoverageArgs { /// /// Main differences: /// - `1.x`: The original v1 format. - /// - `2.0`: Adds support for "line end" numbers for functions. LCOV 2.1 and onwards may emit - /// an error if this option is not provided. + /// - `2.0`: Adds support for "line end" numbers for functions. /// - `2.2`: Changes the format of functions. - #[arg(long, default_value = "1.16", value_parser = parse_lcov_version)] + #[arg(long, default_value = "1", value_parser = parse_lcov_version)] lcov_version: Version, /// Enable viaIR with minimum optimization From 9af381f91e7ad10d1bd34255a3af5fad34b9573b Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:17:57 +0530 Subject: [PATCH 050/107] fix(`anvil`): impl `maybe_as_full_db` for `ForkedDatabase` (#9465) --- crates/anvil/src/eth/backend/mem/fork_db.rs | 12 +++++-- crates/anvil/tests/it/fork.rs | 39 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index a4528a8f0..be5c3bcd7 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -5,7 +5,7 @@ use crate::{ }, revm::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::{ @@ -14,7 +14,7 @@ use foundry_evm::{ fork::database::ForkDbStateSnapshot, revm::{primitives::BlockEnv, Database}, }; -use revm::DatabaseRef; +use revm::{db::DbAccount, DatabaseRef}; pub use foundry_evm::fork::database::ForkedDatabase; @@ -92,6 +92,10 @@ impl MaybeFullDatabase for ForkedDatabase { self } + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.database().accounts) + } + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { let db = self.inner().db(); let accounts = std::mem::take(&mut *db.accounts.write()); @@ -127,6 +131,10 @@ impl MaybeFullDatabase for ForkDbStateSnapshot { self } + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.local.accounts) + } + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { std::mem::take(&mut self.state_snapshot) } diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 3d470894b..1b664d99e 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1473,3 +1473,42 @@ async fn test_reset_dev_account_nonce() { assert!(receipt.status()); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_get_account() { + let (_api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + let accounts = handle.dev_accounts().collect::>(); + + let alice = accounts[0]; + let bob = accounts[1]; + + let init_block = provider.get_block_number().await.unwrap(); + let alice_bal = provider.get_balance(alice).await.unwrap(); + let alice_nonce = provider.get_transaction_count(alice).await.unwrap(); + let alice_acc_init = provider.get_account(alice).await.unwrap(); + + assert_eq!(alice_acc_init.balance, alice_bal); + assert_eq!(alice_acc_init.nonce, alice_nonce); + + let tx = TransactionRequest::default().from(alice).to(bob).value(U256::from(142)); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + assert_eq!(init_block + 1, receipt.block_number.unwrap()); + + let alice_acc = provider.get_account(alice).await.unwrap(); + + assert_eq!( + alice_acc.balance, + alice_bal - (U256::from(142) + U256::from(receipt.gas_used * receipt.effective_gas_price)), + ); + assert_eq!(alice_acc.nonce, alice_nonce + 1); + + let alice_acc_prev_block = provider.get_account(alice).number(init_block).await.unwrap(); + + assert_eq!(alice_acc_init, alice_acc_prev_block); +} From 9ee60053de47ce18ca76ff7f2da41ab026df17f9 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:57:08 +0200 Subject: [PATCH 051/107] fix(coverage): assert should not be branch (#9467) --- crates/evm/coverage/src/analysis.rs | 3 +- crates/forge/tests/cli/coverage.rs | 58 ++++++++++++----------------- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 06673293a..f8cc746c5 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -372,8 +372,9 @@ impl<'a> ContractVisitor<'a> { let expr: Option = node.attribute("expression"); if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { // Might be a require call, add branch coverage. + // Asserts should not be considered branches: . let name: Option = expr.and_then(|expr| expr.attribute("name")); - if let Some("require" | "assert") = name.as_deref() { + if let Some("require") = name.as_deref() { let branch_id = self.branch_id; self.branch_id += 1; self.push_item_kind( diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 8c6cbc19c..141a9677d 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -368,39 +368,29 @@ contract AContractTest is DSTest { ) .unwrap(); - // Assert 50% branch coverage for assert failure. + // Assert 50% statement coverage for assert failure (assert not considered a branch). cmd.arg("coverage").args(["--mt", "testAssertRevertBranch"]).assert_success().stdout_eq(str![ [r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|--------------|--------------|--------------|---------------| -| src/AContract.sol | 66.67% (2/3) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 66.67% (2/3) | 50.00% (1/2) | 50.00% (1/2) | 100.00% (1/1) | +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------------|--------------|--------------|---------------|---------------| +| src/AContract.sol | 66.67% (2/3) | 50.00% (1/2) | 100.00% (0/0) | 100.00% (1/1) | +| Total | 66.67% (2/3) | 50.00% (1/2) | 100.00% (0/0) | 100.00% (1/1) | "#] ]); - // Assert 50% branch coverage for proper assert. + // Assert 100% statement coverage for proper assert (assert not considered a branch). cmd.forge_fuse().arg("coverage").args(["--mt", "testAssertBranch"]).assert_success().stdout_eq( str![[r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|--------------|---------------| -| src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | -| Total | 100.00% (3/3) | 100.00% (2/2) | 50.00% (1/2) | 100.00% (1/1) | - -"#]], - ); - - // Assert 100% coverage (assert properly covered). - cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" -... | File | % Lines | % Statements | % Branches | % Funcs | |-------------------|---------------|---------------|---------------|---------------| -| src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | -| Total | 100.00% (3/3) | 100.00% (2/2) | 100.00% (2/2) | 100.00% (1/1) | +| src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | +| Total | 100.00% (3/3) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | -"#]]); +"#]], + ); }); forgetest!(require, |prj, cmd| { @@ -753,10 +743,10 @@ contract FooTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------|----------------|----------------|----------------|---------------| -| src/Foo.sol | 91.67% (33/36) | 90.00% (27/30) | 87.50% (14/16) | 100.00% (9/9) | -| Total | 91.67% (33/36) | 90.00% (27/30) | 87.50% (14/16) | 100.00% (9/9) | +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|----------------|----------------|---------------|---------------| +| src/Foo.sol | 91.67% (33/36) | 90.00% (27/30) | 80.00% (8/10) | 100.00% (9/9) | +| Total | 91.67% (33/36) | 90.00% (27/30) | 80.00% (8/10) | 100.00% (9/9) | "#]]); @@ -767,10 +757,10 @@ contract FooTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... -| File | % Lines | % Statements | % Branches | % Funcs | -|-------------|----------------|----------------|----------------|---------------| -| src/Foo.sol | 97.22% (35/36) | 96.67% (29/30) | 93.75% (15/16) | 100.00% (9/9) | -| Total | 97.22% (35/36) | 96.67% (29/30) | 93.75% (15/16) | 100.00% (9/9) | +| File | % Lines | % Statements | % Branches | % Funcs | +|-------------|----------------|----------------|---------------|---------------| +| src/Foo.sol | 97.22% (35/36) | 96.67% (29/30) | 90.00% (9/10) | 100.00% (9/9) | +| Total | 97.22% (35/36) | 96.67% (29/30) | 90.00% (9/10) | 100.00% (9/9) | "#]]); @@ -779,8 +769,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|-----------------|---------------| -| src/Foo.sol | 100.00% (36/36) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | -| Total | 100.00% (36/36) | 100.00% (30/30) | 100.00% (16/16) | 100.00% (9/9) | +| src/Foo.sol | 100.00% (36/36) | 100.00% (30/30) | 100.00% (10/10) | 100.00% (9/9) | +| Total | 100.00% (36/36) | 100.00% (30/30) | 100.00% (10/10) | 100.00% (9/9) | "#]]); }); @@ -949,8 +939,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|----------------|----------------|--------------|---------------| -| src/Foo.sol | 75.00% (15/20) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | -| Total | 75.00% (15/20) | 66.67% (14/21) | 83.33% (5/6) | 100.00% (5/5) | +| src/Foo.sol | 75.00% (15/20) | 66.67% (14/21) | 75.00% (3/4) | 100.00% (5/5) | +| Total | 75.00% (15/20) | 66.67% (14/21) | 75.00% (3/4) | 100.00% (5/5) | "#]]); @@ -959,8 +949,8 @@ contract FooTest is DSTest { ... | File | % Lines | % Statements | % Branches | % Funcs | |-------------|-----------------|-----------------|---------------|---------------| -| src/Foo.sol | 100.00% (20/20) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | -| Total | 100.00% (20/20) | 100.00% (21/21) | 100.00% (6/6) | 100.00% (5/5) | +| src/Foo.sol | 100.00% (20/20) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | +| Total | 100.00% (20/20) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | "#]]); }); From ade4b35eedbab9ebe9511c7a70cd371a4b7ed2bb Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:34:19 +0530 Subject: [PATCH 052/107] fix(`forge`): run `dep.has_branch` in correct dir (#9453) fix(`forge`): run git cmd in correct dir --- crates/cli/src/utils/mod.rs | 10 ++++++++-- crates/forge/bin/cmd/install.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 6a2afe657..6bf8c5b5d 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -444,8 +444,8 @@ impl<'a> Git<'a> { self.cmd().args(["status", "--porcelain"]).exec().map(|out| out.stdout.is_empty()) } - pub fn has_branch(self, branch: impl AsRef) -> Result { - self.cmd() + pub fn has_branch(self, branch: impl AsRef, at: &Path) -> Result { + self.cmd_at(at) .args(["branch", "--list", "--no-color"]) .arg(branch) .get_stdout_lossy() @@ -567,6 +567,12 @@ ignore them in the `.gitignore` file, or run this command again with the `--no-c cmd } + pub fn cmd_at(self, path: &Path) -> Command { + let mut cmd = Self::cmd_no_root(); + cmd.current_dir(path); + cmd + } + pub fn cmd_no_root() -> Command { let mut cmd = Command::new("git"); cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); diff --git a/crates/forge/bin/cmd/install.rs b/crates/forge/bin/cmd/install.rs index 2024b3d23..3543caeea 100644 --- a/crates/forge/bin/cmd/install.rs +++ b/crates/forge/bin/cmd/install.rs @@ -164,7 +164,7 @@ impl DependencyInstallOpts { // Pin branch to submodule if branch is used if let Some(branch) = &installed_tag { // First, check if this tag has a branch - if git.has_branch(branch)? { + if git.has_branch(branch, &path)? { // always work with relative paths when directly modifying submodules git.cmd() .args(["submodule", "set-branch", "-b", branch]) From 8ef1302dab263780dee15529c0d6c478a5aa85c8 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:25:57 +0200 Subject: [PATCH 053/107] chore: fix test isolate (#9468) --- crates/forge/tests/cli/ext_integration.rs | 1 + crates/forge/tests/cli/script.rs | 10 +++++++--- crates/forge/tests/cli/test_cmd.rs | 13 +++++++------ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index e9437f04c..2e5e383e5 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -100,6 +100,7 @@ fn lil_web3() { #[test] #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +#[cfg(not(feature = "isolate-by-default"))] fn snekmate() { ExtTester::new("pcaversaccio", "snekmate", "df226f4a45e86c8f8c3ff1f9fa3443d260002050") .install_command(&["pnpm", "install", "--prefer-offline"]) diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index e52bd2fef..6f5193c53 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1923,6 +1923,10 @@ contract SimpleScript is Script { // Asserts that the script runs with expected non-output using `--quiet` flag forgetest_async!(adheres_to_json_flag, |prj, cmd| { + if cfg!(feature = "isolate-by-default") { + return; + } + foundry_test_utils::util::initialize(prj.root()); prj.add_script( "Foo", @@ -2367,12 +2371,12 @@ contract SimpleScript is Script { [SOLC_VERSION] [ELAPSED] Compiler run successful! Traces: - [103771] SimpleScript::run() + [..] SimpleScript::run() ├─ [0] VM::startBroadcast() │ └─ ← [Return] - ├─ [23273] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 + ├─ [..] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 │ └─ ← [Return] 116 bytes of code - ├─ [13162] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 + ├─ [..] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 │ ├─ [145] A::getValue() [staticcall] │ │ └─ ← [Return] 100 │ └─ ← [Return] 62 bytes of code diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 819c3e940..b24ebea66 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2357,10 +2357,10 @@ Compiler run successful! Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest [PASS] test_proxy_trace() ([GAS]) Traces: - [149783] MetadataTraceTest::test_proxy_trace() - ├─ [47297] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + [..] MetadataTraceTest::test_proxy_trace() + ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 236 bytes of code - ├─ [37762] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b + ├─ [..] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ └─ ← [Return] 62 bytes of code └─ ← [Stop] @@ -2382,10 +2382,10 @@ Compiler run successful! Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest [PASS] test_proxy_trace() ([GAS]) Traces: - [128142] MetadataTraceTest::test_proxy_trace() - ├─ [36485] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + [..] MetadataTraceTest::test_proxy_trace() + ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 182 bytes of code - ├─ [26959] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b + ├─ [..] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ └─ ← [Return] 8 bytes of code └─ ← [Stop] @@ -2667,6 +2667,7 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] }); // Tests that test traces display state changes when running with verbosity. +#[cfg(not(feature = "isolate-by-default"))] forgetest_init!(should_show_state_changes, |prj, cmd| { cmd.args(["test", "--mt", "test_Increment", "-vvvvv"]).assert_success().stdout_eq(str![[r#" ... From 2f56133ce2e7d0d0d8b1488c2784dbd799d01e16 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:54:26 +0100 Subject: [PATCH 054/107] feat: bump MSRV to 1.83 (#9473) --- Cargo.toml | 2 +- clippy.toml | 2 +- crates/cheatcodes/src/evm/mock.rs | 2 +- crates/cheatcodes/src/inspector.rs | 9 ++++----- crates/evm/evm/src/executors/mod.rs | 2 +- crates/evm/traces/src/debug/sources.rs | 2 +- crates/fmt/src/comments.rs | 2 +- crates/forge/bin/cmd/coverage.rs | 2 +- crates/script/src/build.rs | 4 ++-- crates/script/src/lib.rs | 2 +- crates/verify/src/provider.rs | 2 +- 11 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a4d8f2724..af1d118b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ resolver = "2" version = "0.2.0" edition = "2021" # Remember to update clippy.toml as well -rust-version = "1.80" +rust-version = "1.83" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" diff --git a/clippy.toml b/clippy.toml index b1756dfd9..8581063b6 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.80" +msrv = "1.83" # bytes::Bytes is included by default and alloy_primitives::Bytes is a wrapper around it, # so it is safe to ignore it as well ignore-interior-mutability = ["bytes::Bytes", "alloy_primitives::Bytes"] diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 26aae298f..fcfea7a9c 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -213,7 +213,7 @@ fn mock_calls( fn make_acc_non_empty(callee: &Address, ecx: InnerEcx) -> Result { let acc = ecx.load_account(*callee)?; - let empty_bytecode = acc.info.code.as_ref().map_or(true, Bytecode::is_empty); + let empty_bytecode = acc.info.code.as_ref().is_none_or(Bytecode::is_empty); if empty_bytecode { let code = Bytecode::new_raw(Bytes::from_static(&[0u8])); ecx.journaled_state.set_code(*callee, code); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index ac8058dd9..4e2b91b9d 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -902,12 +902,11 @@ where { *calldata == call.input[..calldata.len()] && // The value matches, if provided expected - .value - .map_or(true, |value| Some(value) == call.transfer_value()) && + .value.is_none_or(|value| Some(value) == call.transfer_value()) && // The gas matches, if provided - expected.gas.map_or(true, |gas| gas == call.gas_limit) && + expected.gas.is_none_or(|gas| gas == call.gas_limit) && // The minimum gas matches, if provided - expected.min_gas.map_or(true, |min_gas| min_gas <= call.gas_limit) + expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit) { *actual_count += 1; } @@ -925,7 +924,7 @@ where { .iter_mut() .find(|(mock, _)| { call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) && - mock.value.map_or(true, |value| Some(value) == call.transfer_value()) + mock.value.is_none_or(|value| Some(value) == call.transfer_value()) }) .map(|(_, v)| v), } { diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 8560c3e10..e70df0e16 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -203,7 +203,7 @@ impl Executor { .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; // If the deployer is not currently deployed, deploy the default one. - if create2_deployer_account.code.map_or(true, |code| code.is_empty()) { + if create2_deployer_account.code.is_none_or(|code| code.is_empty()) { let creator = DEFAULT_CREATE2_DEPLOYER_DEPLOYER; // Probably 0, but just in case. diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index ff1911493..f934be61f 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -88,7 +88,7 @@ impl ArtifactData { fn new(bytecode: ContractBytecodeSome, build_id: String, file_id: u32) -> Result { let parse = |b: &Bytecode, name: &str| { // Only parse source map if it's not empty. - let source_map = if b.source_map.as_ref().map_or(true, |s| s.is_empty()) { + let source_map = if b.source_map.as_ref().is_none_or(|s| s.is_empty()) { Ok(None) } else { b.source_map().transpose().wrap_err_with(|| { diff --git a/crates/fmt/src/comments.rs b/crates/fmt/src/comments.rs index e3fb79043..eafdb9989 100644 --- a/crates/fmt/src/comments.rs +++ b/crates/fmt/src/comments.rs @@ -88,7 +88,7 @@ impl CommentWithMetadata { return Self::new( comment, CommentPosition::Prefix, - last_line.map_or(true, str::is_empty), + last_line.is_none_or(str::is_empty), indent_len, ) } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 753d4ec76..c3d0545c2 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -293,7 +293,7 @@ impl CoverageArgs { let file_pattern = filter.args().coverage_pattern_inverse.as_ref(); let file_root = &filter.paths().root; report.filter_out_ignored_sources(|path: &Path| { - file_pattern.map_or(true, |re| { + file_pattern.is_none_or(|re| { !re.is_match(&path.strip_prefix(file_root).unwrap_or(path).to_string_lossy()) }) }); diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index 052e78e10..4824cee6b 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -199,8 +199,8 @@ impl PreprocessedState { if id.name != *name { continue; } - } else if contract.abi.as_ref().map_or(true, |abi| abi.is_empty()) || - contract.bytecode.as_ref().map_or(true, |b| match &b.object { + } else if contract.abi.as_ref().is_none_or(|abi| abi.is_empty()) || + contract.bytecode.as_ref().is_none_or(|b| match &b.object { BytecodeObject::Bytecode(b) => b.is_empty(), BytecodeObject::Unlinked(_) => false, }) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 5f5543912..7aa18dcbe 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -278,7 +278,7 @@ impl ScriptArgs { .execution_result .transactions .as_ref() - .map_or(true, |txs| txs.is_empty()) + .is_none_or(|txs| txs.is_empty()) { return Ok(()); } diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index ab6c5e9f6..9c619239b 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -172,7 +172,7 @@ impl VerificationProviderType { pub fn client(&self, key: &Option) -> Result> { match self { Self::Etherscan => { - if key.as_ref().map_or(true, |key| key.is_empty()) { + if key.as_ref().is_none_or(|key| key.is_empty()) { eyre::bail!("ETHERSCAN_API_KEY must be set") } Ok(Box::::default()) From 22202a7a2b3abed5ff74a226dfed790197ac7723 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:10:44 +0100 Subject: [PATCH 055/107] perf(coverage): cache current HitMap, reserve when merging (#9469) * perf(coverage): cache current HitMap, reserve when merging * test: shuffle RPC env --- crates/evm/coverage/src/inspector.rs | 76 +++++++++++++++++++++++----- crates/evm/coverage/src/lib.rs | 31 ++++++++++-- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index 07c24ae9e..eeea3900c 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -1,25 +1,40 @@ use crate::{HitMap, HitMaps}; use alloy_primitives::B256; use revm::{interpreter::Interpreter, Database, EvmContext, Inspector}; +use std::ptr::NonNull; /// Inspector implementation for collecting coverage information. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct CoverageCollector { + current_map: NonNull, + current_hash: B256, maps: HitMaps, } +// SAFETY: `current_map` is always valid and points into an allocation managed by self. +unsafe impl Send for CoverageCollector {} +unsafe impl Sync for CoverageCollector {} + +impl Default for CoverageCollector { + fn default() -> Self { + Self { + current_map: NonNull::dangling(), + current_hash: B256::ZERO, + maps: Default::default(), + } + } +} + impl Inspector for CoverageCollector { fn initialize_interp(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { - self.maps - .entry(*get_contract_hash(interpreter)) - .or_insert_with(|| HitMap::new(interpreter.contract.bytecode.original_bytes())); + get_or_insert_contract_hash(interpreter); + self.insert_map(interpreter); } #[inline] fn step(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { - if let Some(map) = self.maps.get_mut(get_contract_hash(interpreter)) { - map.hit(interpreter.program_counter()); - } + let map = self.get_or_insert_map(interpreter); + map.hit(interpreter.program_counter()); } } @@ -28,15 +43,50 @@ impl CoverageCollector { pub fn finish(self) -> HitMaps { self.maps } + + #[inline] + fn get_or_insert_map(&mut self, interpreter: &mut Interpreter) -> &mut HitMap { + let hash = get_or_insert_contract_hash(interpreter); + if self.current_hash != *hash { + self.insert_map(interpreter); + } + unsafe { self.current_map.as_mut() } + } + + #[cold] + #[inline(never)] + fn insert_map(&mut self, interpreter: &Interpreter) { + let Some(hash) = interpreter.contract.hash else { eof_panic() }; + self.current_hash = hash; + self.current_map = self + .maps + .entry(hash) + .or_insert_with(|| HitMap::new(interpreter.contract.bytecode.original_bytes())) + .into(); + } } /// Helper function for extracting contract hash used to record coverage hit map. -/// If contract hash available in interpreter contract is zero (contract not yet created but going -/// to be created in current tx) then it hash is calculated from contract bytecode. -fn get_contract_hash(interpreter: &mut Interpreter) -> &B256 { - let hash = interpreter.contract.hash.as_mut().expect("coverage does not support EOF"); - if *hash == B256::ZERO { - *hash = interpreter.contract.bytecode.hash_slow(); +/// +/// If the contract hash is zero (contract not yet created but it's going to be created in current +/// tx) then the hash is calculated from the bytecode. +#[inline] +fn get_or_insert_contract_hash(interpreter: &mut Interpreter) -> &B256 { + let Some(hash) = interpreter.contract.hash.as_mut() else { eof_panic() }; + if hash.is_zero() { + set_contract_hash(hash, &interpreter.contract.bytecode); } hash } + +#[cold] +#[inline(never)] +fn set_contract_hash(hash: &mut B256, bytecode: &revm::primitives::Bytecode) { + *hash = bytecode.hash_slow(); +} + +#[cold] +#[inline(never)] +fn eof_panic() -> ! { + panic!("coverage does not support EOF"); +} diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 345261ad5..52ec329ad 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -174,6 +174,7 @@ impl HitMaps { /// Merges two `HitMaps`. pub fn merge(&mut self, other: Self) { + self.reserve(other.len()); for (code_hash, other) in other.0 { self.entry(code_hash).and_modify(|e| e.merge(&other)).or_insert(other); } @@ -211,37 +212,61 @@ pub struct HitMap { impl HitMap { /// Create a new hitmap with the given bytecode. + #[inline] pub fn new(bytecode: Bytes) -> Self { - Self { bytecode, hits: Default::default() } + Self { bytecode, hits: HashMap::with_capacity_and_hasher(1024, Default::default()) } } /// Returns the bytecode. + #[inline] pub fn bytecode(&self) -> &Bytes { &self.bytecode } /// Returns the number of hits for the given program counter. + #[inline] pub fn get(&self, pc: usize) -> Option { NonZeroU32::new(self.hits.get(&Self::cvt_pc(pc)).copied().unwrap_or(0)) } /// Increase the hit counter by 1 for the given program counter. + #[inline] pub fn hit(&mut self, pc: usize) { self.hits(pc, 1) } /// Increase the hit counter by `hits` for the given program counter. + #[inline] pub fn hits(&mut self, pc: usize, hits: u32) { *self.hits.entry(Self::cvt_pc(pc)).or_default() += hits; } /// Merge another hitmap into this, assuming the bytecode is consistent pub fn merge(&mut self, other: &Self) { - for (&pc, &hits) in &other.hits { - self.hits(pc as usize, hits); + self.hits.reserve(other.len()); + for (pc, hits) in other.iter() { + self.hits(pc, hits); } } + /// Returns an iterator over all the program counters and their hit counts. + #[inline] + pub fn iter(&self) -> impl Iterator + '_ { + self.hits.iter().map(|(&pc, &hits)| (pc as usize, hits)) + } + + /// Returns the number of program counters hit in the hitmap. + #[inline] + pub fn len(&self) -> usize { + self.hits.len() + } + + /// Returns `true` if the hitmap is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.hits.is_empty() + } + #[inline] fn cvt_pc(pc: usize) -> u32 { pc.try_into().expect("4GiB bytecode") From f9d86632972c5ce8144014a864119fe937881971 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:16:17 +0100 Subject: [PATCH 056/107] test: shuffle archive URLs (#9472) * test: shuffle archive URLs * fmt * chore: clippy * print --- crates/anvil/tests/it/fork.rs | 9 +- crates/forge/tests/cli/script.rs | 9 +- crates/forge/tests/cli/test_cmd.rs | 6 +- crates/forge/tests/cli/verify_bytecode.rs | 6 +- crates/forge/tests/it/fork.rs | 4 +- crates/test-utils/src/lib.rs | 1 + crates/test-utils/src/rpc.rs | 110 +++++++++++++++++----- crates/test-utils/src/util.rs | 2 +- 8 files changed, 102 insertions(+), 45 deletions(-) diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 1b664d99e..8e7736b0d 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -55,7 +55,7 @@ impl LocalFork { pub fn fork_config() -> NodeConfig { NodeConfig::test() - .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())) + .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) .with_fork_block_number(Some(BLOCK_NUMBER)) } @@ -287,7 +287,7 @@ async fn test_fork_reset_setup() { assert_eq!(local_balance, U256::ZERO); api.anvil_reset(Some(Forking { - json_rpc_url: Some(rpc::next_http_archive_rpc_endpoint()), + json_rpc_url: Some(rpc::next_http_archive_rpc_url()), block_number: Some(BLOCK_NUMBER), })) .await @@ -829,8 +829,7 @@ async fn test_fork_init_base_fee() { #[tokio::test(flavor = "multi_thread")] async fn test_reset_fork_on_new_blocks() { let (api, handle) = - spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint()))) - .await; + spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; let anvil_provider = handle.http_provider(); let endpoint = next_http_rpc_endpoint(); @@ -864,7 +863,7 @@ async fn test_fork_call() { let to: Address = "0x99d1Fa417f94dcD62BfE781a1213c092a47041Bc".parse().unwrap(); let block_number = 14746300u64; - let provider = http_provider(rpc::next_http_archive_rpc_endpoint().as_str()); + let provider = http_provider(rpc::next_http_archive_rpc_url().as_str()); let tx = TransactionRequest::default().to(to).with_input(input.clone()); let tx = WithOtherFields::new(tx); let res0 = provider.call(&tx).block(BlockId::Number(block_number.into())).await.unwrap(); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 6f5193c53..e40db2b81 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -200,8 +200,7 @@ contract DeployScript is Script { let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; - let node_config = - NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())); + let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())); let (_api, handle) = spawn(node_config).await; let dev = handle.dev_accounts().next().unwrap(); cmd.set_current_dir(prj.root()); @@ -302,8 +301,7 @@ contract DeployScript is Script { let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; - let node_config = - NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())); + let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())); let (_api, handle) = spawn(node_config).await; let private_key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); @@ -492,8 +490,7 @@ contract DeployScript is Script { let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; - let node_config = - NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())); + let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())); let (_api, handle) = spawn(node_config).await; let private_key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index b24ebea66..7e568af0c 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -473,7 +473,7 @@ contract Contract { ) .unwrap(); - let endpoint = rpc::next_http_archive_rpc_endpoint(); + let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( "Contract.t.sol", @@ -545,7 +545,7 @@ forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| { forgetest_init!(fork_traces, |prj, cmd| { prj.wipe_contracts(); - let endpoint = rpc::next_http_archive_rpc_endpoint(); + let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( "Contract.t.sol", @@ -699,7 +699,7 @@ contract TransientTest is Test { forgetest_init!(can_disable_block_gas_limit, |prj, cmd| { prj.wipe_contracts(); - let endpoint = rpc::next_http_archive_rpc_endpoint(); + let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( "Contract.t.sol", diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 398ecb52d..6e89f1e94 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -2,7 +2,7 @@ use foundry_compilers::artifacts::{BytecodeHash, EvmVersion}; use foundry_config::Config; use foundry_test_utils::{ forgetest_async, - rpc::{next_http_archive_rpc_endpoint, next_mainnet_etherscan_api_key}, + rpc::{next_http_archive_rpc_url, next_mainnet_etherscan_api_key}, util::OutputExt, TestCommand, TestProject, }; @@ -20,7 +20,7 @@ fn test_verify_bytecode( expected_matches: (&str, &str), ) { let etherscan_key = next_mainnet_etherscan_api_key(); - let rpc_url = next_http_archive_rpc_endpoint(); + let rpc_url = next_http_archive_rpc_url(); // fetch and flatten source code let source_code = cmd @@ -75,7 +75,7 @@ fn test_verify_bytecode_with_ignore( chain: &str, ) { let etherscan_key = next_mainnet_etherscan_api_key(); - let rpc_url = next_http_archive_rpc_endpoint(); + let rpc_url = next_http_archive_rpc_url(); // fetch and flatten source code let source_code = cmd diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs index 5974a12ed..d84309275 100644 --- a/crates/forge/tests/it/fork.rs +++ b/crates/forge/tests/it/fork.rs @@ -68,7 +68,7 @@ async fn test_rpc_fork() { /// Tests that we can launch in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork() { - let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_endpoint(); + let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_url(); let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; @@ -77,7 +77,7 @@ async fn test_launch_fork() { /// Smoke test that forking workings with websockets #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork_ws() { - let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_endpoint(); + let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_url(); let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 9d4316b41..e51e911f3 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![allow(clippy::disallowed_macros)] #[macro_use] extern crate foundry_common; diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 361bf56c2..ed0dfaa3c 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -101,53 +101,109 @@ fn next(list: &[T]) -> &T { &list[next_idx() % list.len()] } -/// Returns the next _mainnet_ rpc endpoint in inline +/// Returns the next _mainnet_ rpc URL in inline /// /// This will rotate all available rpc endpoints pub fn next_http_rpc_endpoint() -> String { next_rpc_endpoint(NamedChain::Mainnet) } -/// Returns the next _mainnet_ rpc endpoint in inline +/// Returns the next _mainnet_ rpc URL in inline /// /// This will rotate all available rpc endpoints pub fn next_ws_rpc_endpoint() -> String { next_ws_endpoint(NamedChain::Mainnet) } -/// Returns the next HTTP RPC endpoint. +/// Returns the next HTTP RPC URL. pub fn next_rpc_endpoint(chain: NamedChain) -> String { next_url(false, chain) } -/// Returns the next WS RPC endpoint. +/// Returns the next WS RPC URL. pub fn next_ws_endpoint(chain: NamedChain) -> String { next_url(true, chain) } -/// Returns endpoint that has access to archive state -pub fn next_http_archive_rpc_endpoint() -> String { - next_archive_endpoint(false) +/// Returns a websocket URL that has access to archive state +pub fn next_http_archive_rpc_url() -> String { + next_archive_url(false) } -/// Returns endpoint that has access to archive state -pub fn next_ws_archive_rpc_endpoint() -> String { - next_archive_endpoint(true) +/// Returns an HTTP URL that has access to archive state +pub fn next_ws_archive_rpc_url() -> String { + next_archive_url(true) } -/// Returns endpoint that has access to archive state, http or ws. -/// Use env vars (comma separated urls) or default inline keys (Alchemy for ws, Infura for http). -fn next_archive_endpoint(is_ws: bool) -> String { - let env_urls = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS }; - - let rpc_env_vars = env::var(env_urls).unwrap_or_default(); - if !rpc_env_vars.is_empty() { - let urls = rpc_env_vars.split(',').collect::>(); - next(&urls).to_string() - } else if is_ws { - format!("wss://eth-mainnet.g.alchemy.com/v2/{}", next(&ALCHEMY_KEYS)) +/// Returns a URL that has access to archive state. +/// +/// Uses either environment variables (comma separated urls) or default keys. +fn next_archive_url(is_ws: bool) -> String { + let urls = archive_urls(is_ws); + let url = if env_archive_urls(is_ws).is_empty() { + next(urls) } else { - format!("https://eth-mainnet.g.alchemy.com/v2/{}", next(&ALCHEMY_KEYS)) + urls.choose_weighted(&mut rand::thread_rng(), |url| { + if url.contains("reth") { + 2usize + } else { + 1usize + } + }) + .unwrap() + }; + eprintln!("--- next_archive_url(is_ws={is_ws}) = {url} ---"); + url.clone() +} + +fn archive_urls(is_ws: bool) -> &'static [String] { + static WS: LazyLock> = LazyLock::new(|| get(true)); + static HTTP: LazyLock> = LazyLock::new(|| get(false)); + + fn get(is_ws: bool) -> Vec { + let env_urls = env_archive_urls(is_ws); + if !env_urls.is_empty() { + let mut urls = env_urls.to_vec(); + urls.shuffle(&mut rand::thread_rng()); + return urls; + } + + let mut urls = Vec::new(); + for &key in ALCHEMY_KEYS.iter() { + if is_ws { + urls.push(format!("wss://eth-mainnet.g.alchemy.com/v2/{key}")); + } else { + urls.push(format!("https://eth-mainnet.g.alchemy.com/v2/{key}")); + } + } + urls + } + + if is_ws { + &WS + } else { + &HTTP + } +} + +fn env_archive_urls(is_ws: bool) -> &'static [String] { + static WS: LazyLock> = LazyLock::new(|| get(true)); + static HTTP: LazyLock> = LazyLock::new(|| get(false)); + + fn get(is_ws: bool) -> Vec { + let env = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS }; + let env = env::var(env).unwrap_or_default(); + let env = env.trim(); + if env.is_empty() { + return vec![]; + } + env.split(',').map(str::trim).filter(|s| !s.is_empty()).map(ToString::to_string).collect() + } + + if is_ws { + &WS + } else { + &HTTP } } @@ -162,7 +218,9 @@ pub fn next_etherscan_api_key(chain: NamedChain) -> String { Optimism => ÐERSCAN_OPTIMISM_KEYS, _ => ÐERSCAN_MAINNET_KEYS, }; - next(keys).to_string() + let key = next(keys).to_string(); + eprintln!("--- next_etherscan_api_key(chain={chain:?}) = {key} ---"); + key } fn next_url(is_ws: bool, chain: NamedChain) -> String { @@ -206,12 +264,14 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String { }; let full = if prefix.is_empty() { network.to_string() } else { format!("{prefix}-{network}") }; - match (is_ws, is_infura) { + let url = match (is_ws, is_infura) { (false, true) => format!("https://{full}.infura.io/v3/{key}"), (true, true) => format!("wss://{full}.infura.io/ws/v3/{key}"), (false, false) => format!("https://{full}.g.alchemy.com/v2/{key}"), (true, false) => format!("wss://{full}.g.alchemy.com/v2/{key}"), - } + }; + eprintln!("--- next_url(is_ws={is_ws}, chain={chain:?}) = {url} ---"); + url } #[cfg(test)] diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index a86304923..fb9eca587 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -202,7 +202,7 @@ impl ExtTester { test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); if let Some(fork_block) = self.fork_block { - test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_endpoint()); + test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_url()); test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string()); } test_cmd.env("FOUNDRY_INVARIANT_DEPTH", "15"); From 805d7cee81e78e9163b8ce3d86a0c3beb39772d4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:35:56 +0100 Subject: [PATCH 057/107] docs: CoverageCollector comments (#9474) --- crates/evm/coverage/src/inspector.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index eeea3900c..bc3a40e56 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -6,12 +6,16 @@ use std::ptr::NonNull; /// Inspector implementation for collecting coverage information. #[derive(Clone, Debug)] pub struct CoverageCollector { + // NOTE: `current_map` is always a valid reference into `maps`. + // It is accessed only through `get_or_insert_map` which guarantees that it's valid. + // Both of these fields are unsafe to access directly outside of `*insert_map`. current_map: NonNull, current_hash: B256, + maps: HitMaps, } -// SAFETY: `current_map` is always valid and points into an allocation managed by self. +// SAFETY: See comments on `current_map`. unsafe impl Send for CoverageCollector {} unsafe impl Sync for CoverageCollector {} @@ -44,12 +48,17 @@ impl CoverageCollector { self.maps } + /// Gets the hit map for the current contract, or inserts a new one if it doesn't exist. + /// + /// The map is stored in `current_map` and returned as a mutable reference. + /// See comments on `current_map` for more details. #[inline] fn get_or_insert_map(&mut self, interpreter: &mut Interpreter) -> &mut HitMap { let hash = get_or_insert_contract_hash(interpreter); if self.current_hash != *hash { self.insert_map(interpreter); } + // SAFETY: See comments on `current_map`. unsafe { self.current_map.as_mut() } } @@ -58,6 +67,7 @@ impl CoverageCollector { fn insert_map(&mut self, interpreter: &Interpreter) { let Some(hash) = interpreter.contract.hash else { eof_panic() }; self.current_hash = hash; + // Converts the mutable reference to a `NonNull` pointer. self.current_map = self .maps .entry(hash) From 3a1e76b504348e3fd90196e445fc04934f05680c Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:34:43 +0530 Subject: [PATCH 058/107] fix(`cli`): handle id and named chain_id's correctly (#9480) * fix(`cli`): handle id and named chain_id's correctly * test --- crates/cast/tests/cli/main.rs | 19 +++++++++++++++++++ crates/cli/src/opts/ethereum.rs | 7 ++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index cd833f7c3..fe9309877 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1955,3 +1955,22 @@ Transaction successfully executed. "#]]); }); + +// https://github.com/foundry-rs/foundry/issues/9476 +forgetest_async!(cast_call_custom_chain_id, |_prj, cmd| { + let chain_id = 55555u64; + let (_api, handle) = anvil::spawn(NodeConfig::test().with_chain_id(Some(chain_id))).await; + + let http_endpoint = handle.http_endpoint(); + + cmd.cast_fuse() + .args([ + "call", + "5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &http_endpoint, + "--chain", + &chain_id.to_string(), + ]) + .assert_success(); +}); diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index 4b15b8551..8d2601be1 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -1,4 +1,5 @@ use crate::opts::ChainValueParser; +use alloy_chains::ChainKind; use clap::Parser; use eyre::Result; use foundry_config::{ @@ -154,7 +155,11 @@ impl EtherscanOpts { dict.insert("etherscan_api_key".into(), key.into()); } if let Some(chain) = self.chain { - dict.insert("chain_id".into(), chain.to_string().into()); + if let ChainKind::Id(id) = chain.kind() { + dict.insert("chain_id".into(), (*id).into()); + } else { + dict.insert("chain_id".into(), chain.to_string().into()); + } } dict } From 8ac30d9c7ebeab1b50d98b56f6b5e623e7cdbf83 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:08:20 +0100 Subject: [PATCH 059/107] feat: dedup error messages (#9481) --- crates/cast/bin/cmd/send.rs | 2 +- crates/cheatcodes/src/error.rs | 16 +------------ crates/common/src/errors/mod.rs | 35 +++++++++++++++++++++++++++++ crates/evm/core/src/backend/cow.rs | 2 +- crates/evm/core/src/backend/mod.rs | 9 +------- crates/evm/core/src/opts.rs | 7 +++--- crates/evm/evm/src/executors/mod.rs | 18 +++++---------- crates/forge/tests/cli/script.rs | 27 ---------------------- crates/forge/tests/cli/test_cmd.rs | 26 ++++++++++++++++++++- 9 files changed, 74 insertions(+), 68 deletions(-) diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 77b6a2cdd..0af83da1a 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -85,7 +85,7 @@ pub enum SendTxSubcommands { impl SendTxArgs { #[allow(unknown_lints, dependency_on_unit_never_type_fallback)] - pub async fn run(self) -> Result<(), eyre::Report> { + pub async fn run(self) -> eyre::Result<()> { let Self { eth, to, diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index d459e9274..c2c220edf 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -206,7 +206,6 @@ impl Error { } impl Drop for Error { - #[inline] fn drop(&mut self) { if self.drop { drop(unsafe { Box::<[u8]>::from_raw(self.data.cast_mut()) }); @@ -224,21 +223,18 @@ impl From> for Error { } impl From for Error { - #[inline] fn from(value: String) -> Self { Self::new_string(value) } } impl From<&'static str> for Error { - #[inline] fn from(value: &'static str) -> Self { Self::new_str(value) } } impl From> for Error { - #[inline] fn from(value: Cow<'static, [u8]>) -> Self { match value { Cow::Borrowed(bytes) => Self::new_bytes(bytes), @@ -248,21 +244,18 @@ impl From> for Error { } impl From<&'static [u8]> for Error { - #[inline] fn from(value: &'static [u8]) -> Self { Self::new_bytes(value) } } impl From<&'static [u8; N]> for Error { - #[inline] fn from(value: &'static [u8; N]) -> Self { Self::new_bytes(value) } } impl From> for Error { - #[inline] fn from(value: Vec) -> Self { Self::new_vec(value) } @@ -279,7 +272,6 @@ impl From for Error { macro_rules! impl_from { ($($t:ty),* $(,)?) => {$( impl From<$t> for Error { - #[inline] fn from(value: $t) -> Self { Self::display(value) } @@ -309,20 +301,14 @@ impl_from!( ); impl> From> for Error { - #[inline] fn from(err: EVMError) -> Self { Self::display(BackendError::from(err)) } } impl From for Error { - #[inline] fn from(err: eyre::Report) -> Self { - let mut chained_cause = String::new(); - for cause in err.chain() { - chained_cause.push_str(format!(" {cause};").as_str()); - } - Self::display(chained_cause) + Self::from(foundry_common::errors::display_chain(&err)) } } diff --git a/crates/common/src/errors/mod.rs b/crates/common/src/errors/mod.rs index cfd9a307e..c8b2c6bcc 100644 --- a/crates/common/src/errors/mod.rs +++ b/crates/common/src/errors/mod.rs @@ -5,3 +5,38 @@ pub use fs::FsPathError; mod artifacts; pub use artifacts::*; + +/// Displays a chain of errors in a single line. +pub fn display_chain(error: &eyre::Report) -> String { + let mut causes = all_sources(error); + // Deduplicate the common pattern `msg1: msg2; msg2` -> `msg1: msg2`. + causes.dedup_by(|b, a| a.contains(b.as_str())); + causes.join("; ") +} + +fn all_sources(err: &eyre::Report) -> Vec { + err.chain().map(|cause| cause.to_string().trim().to_string()).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dedups_contained() { + #[derive(thiserror::Error, Debug)] + #[error("my error: {0}")] + struct A(#[from] B); + + #[derive(thiserror::Error, Debug)] + #[error("{0}")] + struct B(String); + + let ee = eyre::Report::from(A(B("hello".into()))); + assert_eq!(ee.chain().count(), 2, "{ee:?}"); + let full = all_sources(&ee).join("; "); + assert_eq!(full, "my error: hello; hello"); + let chained = display_chain(&ee); + assert_eq!(chained, "my error: hello"); + } +} diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 8623ca2f9..3ffb9fc84 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -73,7 +73,7 @@ impl<'a> CowBackend<'a> { self.spec_id = env.handler_cfg.spec_id; let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - let res = evm.transact().wrap_err("backend: failed while inspecting")?; + let res = evm.transact().wrap_err("EVM error")?; env.env = evm.context.evm.inner.env; diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 2db34ad29..cf6e7e8be 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -7,7 +7,6 @@ use crate::{ utils::{configure_tx_env, configure_tx_req_env, new_evm_with_inspector}, InspectorExt, }; -use alloy_consensus::Transaction as TransactionTrait; use alloy_genesis::GenesisAccount; use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse}; use alloy_primitives::{keccak256, uint, Address, TxKind, B256, U256}; @@ -771,7 +770,7 @@ impl Backend { self.initialize(env); let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - let res = evm.transact().wrap_err("backend: failed while inspecting")?; + let res = evm.transact().wrap_err("EVM error")?; env.env = evm.context.evm.inner.env; @@ -1937,12 +1936,6 @@ fn commit_transaction( persistent_accounts: &HashSet
, inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - // TODO: Remove after https://github.com/foundry-rs/foundry/pull/9131 - // if the tx has the blob_versioned_hashes field, we assume it's a Cancun block - if tx.blob_versioned_hashes().is_some() { - env.handler_cfg.spec_id = SpecId::CANCUN; - } - configure_tx_env(&mut env.env, tx); let now = Instant::now(); diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index aec0d78a0..fec780b0f 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -7,6 +7,7 @@ use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; use foundry_config::{Chain, Config, GasLimit}; use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Serialize}; +use std::fmt::Write; use url::Url; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -129,13 +130,13 @@ impl EvmOpts { ) .await .wrap_err_with(|| { - let mut err_msg = "Could not instantiate forked environment".to_string(); + let mut msg = "Could not instantiate forked environment".to_string(); if let Ok(url) = Url::parse(fork_url) { if let Some(provider) = url.host() { - err_msg.push_str(&format!(" with provider {provider}")); + write!(msg, " with provider {provider}").unwrap(); } } - err_msg + msg }) } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index e70df0e16..ada7cc7b0 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -708,8 +708,12 @@ pub enum EvmError { #[error("{_0}")] Skip(SkipReason), /// Any other error. - #[error(transparent)] - Eyre(eyre::Error), + #[error("{}", foundry_common::errors::display_chain(.0))] + Eyre( + #[from] + #[source] + eyre::Report, + ), } impl From for EvmError { @@ -724,16 +728,6 @@ impl From for EvmError { } } -impl From for EvmError { - fn from(err: eyre::Report) -> Self { - let mut chained_cause = String::new(); - for cause in err.chain() { - chained_cause.push_str(format!("{cause}; ").as_str()); - } - Self::Eyre(eyre::format_err!("{chained_cause}")) - } -} - /// The result of a deployment. #[derive(Debug)] pub struct DeployResult { diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index e40db2b81..9cf3e746c 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -2397,33 +2397,6 @@ Simulated On-chain Traces: "#]]); }); -// Tests that chained errors are properly displayed. -// -forgetest_init!( - #[ignore] - should_display_evm_chained_error, - |prj, cmd| { - let script = prj - .add_source( - "Foo", - r#" -import "forge-std/Script.sol"; - -contract ContractScript is Script { - function run() public { - } -} - "#, - ) - .unwrap(); - cmd.arg("script").arg(script).args(["--fork-url", "https://public-node.testnet.rsk.co"]).assert_failure().stderr_eq(str![[r#" -Error: Failed to deploy script: -backend: failed while inspecting; header validation error: `prevrandao` not set; `prevrandao` not set; - -"#]]); - } -); - forgetest_async!(should_detect_additional_contracts, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 7e568af0c..fa3d72e0d 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2659,7 +2659,7 @@ contract ForkTest is Test { cmd.args(["test", "--mt", "test_fork_err_message"]).assert_failure().stdout_eq(str![[r#" ... Ran 1 test for test/ForkTest.t.sol:ForkTest -[FAIL: vm.createSelectFork: Could not instantiate forked environment with provider eth-mainnet.g.alchemy.com;] test_fork_err_message() ([GAS]) +[FAIL: vm.createSelectFork: Could not instantiate forked environment with provider eth-mainnet.g.alchemy.com] test_fork_err_message() ([GAS]) Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] ... @@ -2700,3 +2700,27 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); + +// Tests that chained errors are properly displayed. +// +forgetest!(displays_chained_error, |prj, cmd| { + prj.add_test( + "Foo.t.sol", + r#" +contract ContractTest { + function test_anything(uint) public {} +} + "#, + ) + .unwrap(); + + cmd.arg("test").arg("--gas-limit=100").assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/Foo.t.sol:ContractTest +[FAIL: EVM error; transaction validation error: call gas cost exceeds the gas limit] setUp() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); From 2c9719ed2fdc99c3fd75ed6f62bb298e2f080b88 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:41:59 +0100 Subject: [PATCH 060/107] chore(anvil): convert panics into errors (#9471) --- crates/anvil/src/cmd.rs | 4 +- crates/anvil/src/config.rs | 137 ++++++++++++------------ crates/anvil/src/eth/backend/mem/mod.rs | 15 +-- crates/anvil/src/eth/error.rs | 6 ++ crates/anvil/src/lib.rs | 21 ++-- 5 files changed, 93 insertions(+), 90 deletions(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index eda009418..eda36bb90 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -89,8 +89,8 @@ pub struct NodeArgs { pub slots_in_an_epoch: u64, /// Writes output of `anvil` as json to user-specified file. - #[arg(long, value_name = "OUT_FILE")] - pub config_out: Option, + #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)] + pub config_out: Option, /// Disable auto and interval mining, and mine on demand instead. #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")] diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index ada482329..b867aca26 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -28,7 +28,7 @@ use alloy_signer_local::{ }; use alloy_transport::{Transport, TransportError}; use anvil_server::ServerConfig; -use eyre::Result; +use eyre::{Context, Result}; use foundry_common::{ provider::{ProviderBuilder, RetryProvider}, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, @@ -44,15 +44,17 @@ use itertools::Itertools; use parking_lot::RwLock; use rand::thread_rng; use revm::primitives::BlobExcessGasAndPrice; -use serde_json::{json, to_writer, Value}; +use serde_json::{json, Value}; use std::{ fmt::Write as FmtWrite, fs::File, + io, net::{IpAddr, Ipv4Addr}, path::{Path, PathBuf}, sync::Arc, time::Duration, }; +use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; /// Default port the rpc will open @@ -144,7 +146,7 @@ pub struct NodeConfig { /// How transactions are sorted in the mempool pub transaction_order: TransactionOrder, /// Filename to write anvil output as json - pub config_out: Option, + pub config_out: Option, /// The genesis to use to initialize the node pub genesis: Option, /// Timeout in for requests sent to remote JSON-RPC server in forking mode @@ -195,13 +197,13 @@ pub struct NodeConfig { impl NodeConfig { fn as_string(&self, fork: Option<&ClientFork>) -> String { - let mut config_string: String = String::new(); - let _ = write!(config_string, "\n{}", BANNER.green()); - let _ = write!(config_string, "\n {VERSION_MESSAGE}"); - let _ = write!(config_string, "\n {}", "https://github.com/foundry-rs/foundry".green()); + let mut s: String = String::new(); + let _ = write!(s, "\n{}", BANNER.green()); + let _ = write!(s, "\n {VERSION_MESSAGE}"); + let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green()); let _ = write!( - config_string, + s, r#" Available Accounts @@ -210,11 +212,11 @@ Available Accounts ); let balance = alloy_primitives::utils::format_ether(self.genesis_balance); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { - write!(config_string, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap(); + write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap(); } let _ = write!( - config_string, + s, r#" Private Keys @@ -224,12 +226,12 @@ Private Keys for (idx, wallet) in self.genesis_accounts.iter().enumerate() { let hex = hex::encode(wallet.credential().to_bytes()); - let _ = write!(config_string, "\n({idx}) 0x{hex}"); + let _ = write!(s, "\n({idx}) 0x{hex}"); } if let Some(ref gen) = self.account_generator { let _ = write!( - config_string, + s, r#" Wallet @@ -244,7 +246,7 @@ Derivation path: {} if let Some(fork) = fork { let _ = write!( - config_string, + s, r#" Fork @@ -261,11 +263,11 @@ Chain ID: {} ); if let Some(tx_hash) = fork.transaction_hash() { - let _ = writeln!(config_string, "Transaction hash: {tx_hash}"); + let _ = writeln!(s, "Transaction hash: {tx_hash}"); } } else { let _ = write!( - config_string, + s, r#" Chain ID @@ -279,7 +281,7 @@ Chain ID if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) { let _ = write!( - config_string, + s, r#" Gas Price ================== @@ -290,7 +292,7 @@ Gas Price ); } else { let _ = write!( - config_string, + s, r#" Base Fee ================== @@ -302,7 +304,7 @@ Base Fee } let _ = write!( - config_string, + s, r#" Gas Limit ================== @@ -326,7 +328,7 @@ Gas Limit ); let _ = write!( - config_string, + s, r#" Genesis Timestamp ================== @@ -336,7 +338,7 @@ Genesis Timestamp self.get_genesis_timestamp().green() ); - config_string + s } fn as_json(&self, fork: Option<&ClientFork>) -> Value { @@ -749,7 +751,7 @@ impl NodeConfig { /// Sets the file path to write the Anvil node's config info to. #[must_use] - pub fn set_config_out(mut self, config_out: Option) -> Self { + pub fn set_config_out(mut self, config_out: Option) -> Self { self.config_out = config_out; self } @@ -903,21 +905,18 @@ impl NodeConfig { } /// Prints the config info - pub fn print(&self, fork: Option<&ClientFork>) { - if self.config_out.is_some() { - let config_out = self.config_out.as_deref().unwrap(); - to_writer( - &File::create(config_out).expect("Unable to create anvil config description file"), - &self.as_json(fork), - ) - .expect("Failed writing json"); + pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> { + if let Some(path) = &self.config_out { + let file = io::BufWriter::new( + File::create(path).wrap_err("unable to create anvil config description file")?, + ); + let value = self.as_json(fork); + serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?; } - - if self.silent { - return; + if !self.silent { + sh_println!("{}", self.as_string(fork))?; } - - let _ = sh_println!("{}", self.as_string(fork)); + Ok(()) } /// Returns the path where the cache file should be stored @@ -983,7 +982,7 @@ impl NodeConfig { /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now - pub(crate) async fn setup(&mut self) -> mem::Backend { + pub(crate) async fn setup(&mut self) -> Result { // configure the revm environment let mut cfg = @@ -1020,11 +1019,11 @@ impl NodeConfig { self.get_blob_excess_gas_and_price(), ); - let (db, fork): (Arc>>, Option) = + let (db, fork): (Arc>>, Option) = if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { - self.setup_fork_db(eth_rpc_url, &mut env, &fees).await + self.setup_fork_db(eth_rpc_url, &mut env, &fees).await? } else { - (Arc::new(tokio::sync::RwLock::new(Box::::default())), None) + (Arc::new(TokioRwLock::new(Box::::default())), None) }; // if provided use all settings of `genesis.json` @@ -1062,9 +1061,9 @@ impl NodeConfig { self.transaction_block_keeper, self.block_time, self.cache_path.clone(), - Arc::new(tokio::sync::RwLock::new(self.clone())), + Arc::new(TokioRwLock::new(self.clone())), ) - .await; + .await?; // Writes the default create2 deployer to the backend, // if the option is not disabled and we are not forking. @@ -1072,19 +1071,19 @@ impl NodeConfig { backend .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await - .expect("Failed to create default create2 deployer"); + .wrap_err("failed to create default create2 deployer")?; } if let Some(state) = self.init_state.clone() { - backend.load_state(state).await.expect("Failed to load init state"); + backend.load_state(state).await.wrap_err("failed to load init state")?; } - backend + Ok(backend) } /// Configures everything related to forking based on the passed `eth_rpc_url`: - /// - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](tokio::sync::RwLock) - /// and [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to + /// - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](TokioRwLock) and + /// [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to /// fork from. /// - modifying some parameters of the passed `env` /// - mutating some members of `self` @@ -1093,15 +1092,11 @@ impl NodeConfig { eth_rpc_url: String, env: &mut EnvWithHandlerCfg, fees: &FeeManager, - ) -> (Arc>>, Option) { - let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await; - - let db: Arc>> = - Arc::new(tokio::sync::RwLock::new(Box::new(db))); - + ) -> Result<(Arc>>, Option)> { + let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; + let db: Arc>> = Arc::new(TokioRwLock::new(Box::new(db))); let fork = ClientFork::new(config, Arc::clone(&db)); - - (db, Some(fork)) + Ok((db, Some(fork))) } /// Configures everything related to forking based on the passed `eth_rpc_url`: @@ -1114,7 +1109,7 @@ impl NodeConfig { eth_rpc_url: String, env: &mut EnvWithHandlerCfg, fees: &FeeManager, - ) -> (ForkedDatabase, ClientForkConfig) { + ) -> Result<(ForkedDatabase, ClientForkConfig)> { // TODO make provider agnostic let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) @@ -1125,23 +1120,22 @@ impl NodeConfig { .initial_backoff(1000) .headers(self.fork_headers.clone()) .build() - .expect("Failed to establish provider to fork url"), + .wrap_err("failed to establish provider to fork url")?, ); let (fork_block_number, fork_chain_id, force_transactions) = if let Some(fork_choice) = &self.fork_choice { let (fork_block_number, force_transactions) = - derive_block_and_transactions(fork_choice, &provider).await.expect( - "Failed to derive fork block number and force transactions from fork choice", - ); + derive_block_and_transactions(fork_choice, &provider).await.wrap_err( + "failed to derive fork block number and force transactions from fork choice", + )?; let chain_id = if let Some(chain_id) = self.fork_chain_id { Some(chain_id) } else if self.hardfork.is_none() { - // auto adjust hardfork if not specified - // but only if we're forking mainnet + // Auto-adjust hardfork if not specified, but only if we're forking mainnet. let chain_id = - provider.get_chain_id().await.expect("Failed to fetch network chain ID"); + provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; if alloy_chains::NamedChain::Mainnet == chain_id { let hardfork: EthereumHardfork = fork_block_number.into(); env.handler_cfg.spec_id = hardfork.into(); @@ -1155,15 +1149,16 @@ impl NodeConfig { (fork_block_number, chain_id, force_transactions) } else { // pick the last block number but also ensure it's not pending anymore - let bn = - find_latest_fork_block(&provider).await.expect("Failed to get fork block number"); + let bn = find_latest_fork_block(&provider) + .await + .wrap_err("failed to get fork block number")?; (bn, None, None) }; let block = provider .get_block(BlockNumberOrTag::Number(fork_block_number).into(), false.into()) .await - .expect("Failed to get fork block"); + .wrap_err("failed to get fork block")?; let block = if let Some(block) = block { block @@ -1179,9 +1174,9 @@ latest block number: {latest_block}" if fork_block_number <= latest_block { message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}")); } - panic!("{}", message); + eyre::bail!("{message}"); } - panic!("Failed to get block for block number: {fork_block_number}") + eyre::bail!("failed to get block for block number: {fork_block_number}") }; let gas_limit = self.fork_gas_limit(&block); @@ -1243,7 +1238,7 @@ latest block number: {latest_block}" let chain_id = if let Some(fork_chain_id) = fork_chain_id { fork_chain_id.to() } else { - provider.get_chain_id().await.unwrap() + provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? }; // need to update the dev signers and env with the chain id @@ -1296,7 +1291,7 @@ latest block number: {latest_block}" // need to insert the forked block's hash db.insert_block_hash(U256::from(config.block_number), config.block_hash); - (db, config) + Ok((db, config)) } /// we only use the gas limit value of the block if it is non-zero and the block gas @@ -1344,7 +1339,7 @@ async fn derive_block_and_transactions( let transaction = provider .get_transaction_by_hash(transaction_hash.0.into()) .await? - .ok_or(eyre::eyre!("Failed to get fork transaction by hash"))?; + .ok_or_else(|| eyre::eyre!("failed to get fork transaction by hash"))?; let transaction_block_number = transaction.block_number.unwrap(); // Get the block pertaining to the fork transaction @@ -1354,13 +1349,13 @@ async fn derive_block_and_transactions( alloy_rpc_types::BlockTransactionsKind::Full, ) .await? - .ok_or(eyre::eyre!("Failed to get fork block by number"))?; + .ok_or_else(|| eyre::eyre!("failed to get fork block by number"))?; // Filter out transactions that are after the fork transaction let filtered_transactions = transaction_block .transactions .as_transactions() - .ok_or(eyre::eyre!("Failed to get transactions from full fork block"))? + .ok_or_else(|| eyre::eyre!("failed to get transactions from full fork block"))? .iter() .take_while_inclusive(|&transaction| transaction.tx_hash() != transaction_hash.0) .collect::>(); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index fa79332e7..75888c130 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -75,6 +75,7 @@ use anvil_core::eth::{ }; use anvil_rpc::error::RpcError; use chrono::Datelike; +use eyre::{Context, Result}; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, @@ -229,7 +230,7 @@ impl Backend { automine_block_time: Option, cache_path: Option, node_config: Arc>, - ) -> Self { + ) -> Result { // if this is a fork then adjust the blockchain storage let blockchain = if let Some(fork) = fork.read().as_ref() { trace!(target: "backend", "using forked blockchain at {}", fork.block_number()); @@ -341,8 +342,8 @@ impl Backend { } // Note: this can only fail in forking mode, in which case we can't recover - backend.apply_genesis().await.expect("Failed to create genesis"); - backend + backend.apply_genesis().await.wrap_err("failed to create genesis")?; + Ok(backend) } /// Writes the CREATE2 deployer code directly to the database at the address provided. @@ -500,7 +501,7 @@ impl Backend { // `setup_fork_db_config` node_config.base_fee.take(); - node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await + node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await? }; *self.db.write().await = Box::new(db); @@ -536,7 +537,7 @@ impl Backend { let mut env = self.env.read().clone(); let (forked_db, client_fork_config) = - node_config.setup_fork_db_config(fork_url, &mut env, &self.fees).await; + node_config.setup_fork_db_config(fork_url, &mut env, &self.fees).await?; *self.db.write().await = Box::new(forked_db); let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db)); @@ -1232,7 +1233,7 @@ impl Backend { if storage.blocks.len() > transaction_block_keeper { let to_clear = block_number .to::() - .saturating_sub(transaction_block_keeper.try_into().unwrap()); + .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX)); storage.remove_block_transactions_by_number(to_clear) } } @@ -2877,7 +2878,7 @@ pub fn transaction_build( gas_limit, }; - let ser = serde_json::to_value(&dep_tx).unwrap(); + let ser = serde_json::to_value(&dep_tx).expect("could not serialize TxDeposit"); let maybe_deposit_fields = OtherFields::try_from(ser); match maybe_deposit_fields { diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 394f33492..dda9b8bb2 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -98,6 +98,12 @@ pub enum BlockchainError { Message(String), } +impl From for BlockchainError { + fn from(err: eyre::Report) -> Self { + Self::Message(err.to_string()) + } +} + impl From for BlockchainError { fn from(err: RpcError) -> Self { Self::RpcError(err) diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 4fc0621c8..6d2e6d5e4 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -20,6 +20,7 @@ use crate::{ use alloy_primitives::{Address, U256}; use alloy_signer_local::PrivateKeySigner; use eth::backend::fork::ClientFork; +use eyre::Result; use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; @@ -27,7 +28,6 @@ use parking_lot::Mutex; use server::try_spawn_ipc; use std::{ future::Future, - io, net::SocketAddr, pin::Pin, sync::Arc, @@ -126,11 +126,11 @@ pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { /// # Ok(()) /// # } /// ``` -pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle)> { +pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); - let backend = Arc::new(config.setup().await); + let backend = Arc::new(config.setup().await?); if config.enable_auto_impersonate { backend.auto_impersonate_account(true); @@ -251,7 +251,7 @@ pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle task_manager, }; - handle.print(fork.as_ref()); + handle.print(fork.as_ref())?; Ok((api, handle)) } @@ -281,7 +281,7 @@ impl Drop for NodeHandle { fn drop(&mut self) { // Fire shutdown signal to make sure anvil instance is terminated. if let Some(signal) = self._signal.take() { - signal.fire().unwrap() + let _ = signal.fire(); } } } @@ -293,21 +293,22 @@ impl NodeHandle { } /// Prints the launch info. - pub(crate) fn print(&self, fork: Option<&ClientFork>) { - self.config.print(fork); + pub(crate) fn print(&self, fork: Option<&ClientFork>) -> Result<()> { + self.config.print(fork)?; if !self.config.silent { if let Some(ipc_path) = self.ipc_path() { - let _ = sh_println!("IPC path: {ipc_path}"); + sh_println!("IPC path: {ipc_path}")?; } - let _ = sh_println!( + sh_println!( "Listening on {}", self.addresses .iter() .map(|addr| { addr.to_string() }) .collect::>() .join(", ") - ); + )?; } + Ok(()) } /// The address of the launched server. From 25c978ae29454454cec857de3400a885efc4bd7c Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:25:29 +0200 Subject: [PATCH 061/107] fix(remappings): project autoremappings should respect config (#9466) --- crates/config/src/providers/remappings.rs | 12 +++++---- crates/forge/tests/cli/config.rs | 33 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 343fde697..1d8a7b163 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -1,7 +1,4 @@ -use crate::{ - foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, utils::get_dir_remapping, - Config, -}; +use crate::{foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, Config}; use figment::{ value::{Dict, Map}, Error, Figment, Metadata, Profile, Provider, @@ -39,7 +36,12 @@ impl Remappings { pub fn with_figment(mut self, figment: &Figment) -> Self { let mut add_project_remapping = |path: &str| { if let Ok(path) = figment.find_value(path) { - if let Some(remapping) = path.into_string().and_then(get_dir_remapping) { + if let Some(path) = path.into_string() { + let remapping = Remapping { + context: None, + name: format!("{path}/"), + path: format!("{path}/"), + }; self.project_paths.push(remapping); } } diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 02ff2fce8..6f70f845c 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -856,3 +856,36 @@ contract MyScript is BaseScript { pretty_err(&lib_toml_file, fs::write(&lib_toml_file, lib_config.to_string_pretty().unwrap())); cmd.forge_fuse().args(["build"]).assert_success(); }); + +// Tests that project remappings use config paths. +// For `src=src/contracts` config, remapping should be `src/contracts/ = src/contracts/`. +// For `src=src` config, remapping should be `src/ = src/`. +// +forgetest!(test_project_remappings, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let config = Config { + src: "src/contracts".into(), + remappings: vec![Remapping::from_str("contracts/=src/contracts/").unwrap().into()], + ..Default::default() + }; + prj.write_config(config); + + // Add Counter.sol in `src/contracts` project dir. + let src_dir = &prj.root().join("src/contracts"); + pretty_err(src_dir, fs::create_dir_all(src_dir)); + pretty_err( + src_dir.join("Counter.sol"), + fs::write(src_dir.join("Counter.sol"), "contract Counter{}"), + ); + prj.add_test( + "CounterTest.sol", + r#" +import "contracts/Counter.sol"; + +contract CounterTest { +} + "#, + ) + .unwrap(); + cmd.forge_fuse().args(["build"]).assert_success(); +}); From 3784cd8514bf2c2248a21df6c19455e8a674ef63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Rodr=C3=ADguez?= Date: Wed, 4 Dec 2024 15:16:59 -0300 Subject: [PATCH 062/107] refactor: adapt to CompilerContract trait type (#9423) * refactor: adapt to CompilerContract trait type * chore: cargo fmt * fix: specify MultiCompiler in MultiContractRunner::build * bump * fix --------- Co-authored-by: Arsenii Kulikov --- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 2 +- crates/cast/bin/cmd/storage.rs | 6 +++--- crates/common/src/compile.rs | 19 ++++++++++++++----- crates/config/src/lib.rs | 15 +++++++++------ crates/evm/traces/src/debug/sources.rs | 4 ++-- crates/forge/bin/cmd/clone.rs | 6 +++--- crates/forge/bin/cmd/coverage.rs | 3 ++- crates/forge/bin/cmd/test/mod.rs | 7 +++++-- crates/forge/src/multi_runner.rs | 8 ++++++-- crates/forge/tests/it/test_helpers.rs | 7 ++++--- crates/test-utils/src/util.rs | 5 ++++- 12 files changed, 66 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87ac75dee..e60060bfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3864,9 +3864,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daece74fc0b127e587ac343405283577b4fe9d0195317998c3031778b5f799cd" +checksum = "611e6de7379c57fc353a53e718cd95844e9bd08b2e3ca79a34b76d4a84d38e48" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3901,9 +3901,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c6b0571a37cc9860103df548330eaceb36e9438cb0264ec63e5db74031fa4f" +checksum = "868df34b353da95395e61fd83e4a56cb075f462f58d9fa1150c9cf96ccb46637" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3911,9 +3911,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278cc3f1f4f628793ae6c0db38b0d3fe4124ab0bc10da081b1eb365766512e6d" +checksum = "ac37bffdf6d62cbc4ce03393cc45814d32274807b87486a808a370efbd08b67d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3935,9 +3935,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196b16fcfbbe69a315d64f74dd817ba6843f7749c64124b937118551414b9b90" +checksum = "8c873d45485dc4b4f351f2f6c6acbb7f5ef8ec27f12e1bd0e6dc016cb9bdda2b" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eb08a74eb12b281038bbf74cbbd76cc3c85546896628d4c7c769c816ab4104b" +checksum = "2a605a29e2c0b9c54f14540ec3d03a2434fbaabdda8e6565451cdd38ae8fbd00" dependencies = [ "alloy-primitives", "cfg-if", @@ -7112,7 +7112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.90", @@ -8565,7 +8565,7 @@ dependencies = [ "const-hex", "derive_builder", "dunce", - "itertools 0.11.0", + "itertools 0.13.0", "itoa", "lasso", "match_cfg", @@ -8601,7 +8601,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.6.0", "bumpalo", - "itertools 0.11.0", + "itertools 0.13.0", "memchr", "num-bigint", "num-rational", diff --git a/Cargo.toml b/Cargo.toml index af1d118b9..0fa4b4bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -169,7 +169,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.9.0", default-features = false } -foundry-compilers = { version = "0.12.3", default-features = false } +foundry-compilers = { version = "0.12.5", default-features = false } foundry-fork-db = "0.8.0" solang-parser = "=0.3.3" solar-ast = { version = "=0.1.0", default-features = false } diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 13fa908bc..468f20b27 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -20,7 +20,7 @@ use foundry_common::{ shell, }; use foundry_compilers::{ - artifacts::{ConfigurableContractArtifact, StorageLayout}, + artifacts::{ConfigurableContractArtifact, Contract, StorageLayout}, compilers::{ solc::{Solc, SolcCompiler}, Compiler, @@ -284,7 +284,7 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) "{}", serde_json::to_string_pretty(&serde_json::to_value(StorageReport { layout, values })?)? )?; - return Ok(()) + return Ok(()); } let mut table = Table::new(); @@ -314,7 +314,7 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) Ok(()) } -fn add_storage_layout_output(project: &mut Project) { +fn add_storage_layout_output>(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; project.update_output_selection(|selection| { selection.0.values_mut().for_each(|contract_selection| { diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 507f32830..47fe226d9 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -10,7 +10,7 @@ use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color use eyre::Result; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ - artifacts::{remappings::Remapping, BytecodeObject, Source}, + artifacts::{remappings::Remapping, BytecodeObject, Contract, Source}, compilers::{ solc::{Solc, SolcCompiler}, Compiler, @@ -129,7 +129,10 @@ impl ProjectCompiler { } /// Compiles the project. - pub fn compile(mut self, project: &Project) -> Result> { + pub fn compile>( + mut self, + project: &Project, + ) -> Result> { // TODO: Avoid process::exit if !project.paths.has_input_files() && self.files.is_empty() { sh_println!("Nothing to compile")?; @@ -163,7 +166,10 @@ impl ProjectCompiler { /// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap(); /// ``` #[instrument(target = "forge::compile", skip_all)] - fn compile_with(self, f: F) -> Result> + fn compile_with, F>( + self, + f: F, + ) -> Result> where F: FnOnce() -> Result>, { @@ -202,7 +208,10 @@ impl ProjectCompiler { } /// If configured, this will print sizes or names - fn handle_output(&self, output: &ProjectCompileOutput) { + fn handle_output>( + &self, + output: &ProjectCompileOutput, + ) { let print_names = self.print_names.unwrap_or(false); let print_sizes = self.print_sizes.unwrap_or(false); @@ -465,7 +474,7 @@ pub struct ContractInfo { /// If `verify` and it's a standalone script, throw error. Only allowed for projects. /// /// **Note:** this expects the `target_path` to be absolute -pub fn compile_target( +pub fn compile_target>( target_path: &Path, project: &Project, quiet: bool, diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f2d25461d..08c1ed0ee 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -35,8 +35,8 @@ use foundry_compilers::{ error::SolcError, multi::{MultiCompilerParsedSource, MultiCompilerRestrictions}, solc::{CliSettings, SolcSettings}, - ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, RestrictionsWithVersion, - VyperLanguage, + ArtifactOutput, ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, + RestrictionsWithVersion, VyperLanguage, }; use regex::Regex; use revm_primitives::{map::AddressHashMap, FixedBytes, SpecId}; @@ -1021,7 +1021,10 @@ impl Config { } /// Cleans the project. - pub fn cleanup(&self, project: &Project) -> Result<(), SolcError> { + pub fn cleanup>( + &self, + project: &Project, + ) -> Result<(), SolcError> { project.cleanup()?; // Remove last test run failures file. @@ -1090,7 +1093,7 @@ impl Config { rx.recv().expect("sender dropped") } Err(RecvTimeoutError::Disconnected) => panic!("sender dropped"), - } + }; } if let Some(ref solc) = self.solc { let solc = match solc { @@ -1291,11 +1294,11 @@ impl Config { ) -> Option, UnresolvedEnvVarError>> { let mut endpoints = self.rpc_endpoints.clone().resolved(); if let Some(endpoint) = endpoints.remove(maybe_alias) { - return Some(endpoint.map(Cow::Owned)) + return Some(endpoint.map(Cow::Owned)); } if let Ok(Some(endpoint)) = mesc::get_endpoint_by_query(maybe_alias, Some("foundry")) { - return Some(Ok(Cow::Owned(endpoint.url))) + return Some(Ok(Cow::Owned(endpoint.url))); } None diff --git a/crates/evm/traces/src/debug/sources.rs b/crates/evm/traces/src/debug/sources.rs index f934be61f..b2e37e32d 100644 --- a/crates/evm/traces/src/debug/sources.rs +++ b/crates/evm/traces/src/debug/sources.rs @@ -3,7 +3,7 @@ use foundry_common::compact_to_contract; use foundry_compilers::{ artifacts::{ sourcemap::{SourceElement, SourceMap}, - Bytecode, ContractBytecodeSome, Libraries, Source, + Bytecode, Contract, ContractBytecodeSome, Libraries, Source, }, multi::MultiCompilerLanguage, Artifact, Compiler, ProjectCompileOutput, @@ -137,7 +137,7 @@ impl ContractSources { Ok(sources) } - pub fn insert( + pub fn insert>( &mut self, output: &ProjectCompileOutput, root: &Path, diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index 193e51183..52ec97c48 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -266,7 +266,7 @@ impl CloneArgs { let remappings_txt_content = config.remappings.iter().map(|r| r.to_string()).collect::>().join("\n"); if fs::write(&remappings_txt, remappings_txt_content).is_err() { - return false + return false; } let profile = config.profile.as_str().as_str(); @@ -612,7 +612,7 @@ impl EtherscanClient for Client { mod tests { use super::*; use alloy_primitives::hex; - use foundry_compilers::Artifact; + use foundry_compilers::CompilerContract; use foundry_test_utils::rpc::next_mainnet_etherscan_api_key; use std::collections::BTreeMap; @@ -631,7 +631,7 @@ mod tests { contracts.iter().for_each(|(name, contract)| { if name == contract_name { let compiled_creation_code = - contract.get_bytecode_object().expect("creation code not found"); + contract.bin_ref().expect("creation code not found"); assert!( hex::encode(compiled_creation_code.as_ref()) .starts_with(stripped_creation_code), diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index c3d0545c2..65b4aad03 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -19,6 +19,7 @@ use foundry_compilers::{ artifacts::{ sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage, Source, }, + compilers::multi::MultiCompiler, Artifact, ArtifactId, Project, ProjectCompileOutput, }; use foundry_config::{Config, SolcReq}; @@ -245,7 +246,7 @@ impl CoverageArgs { .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) .set_coverage(true) - .build(&root, output, env, evm_opts)?; + .build::(&root, output, env, evm_opts)?; let known_contracts = runner.known_contracts.clone(); diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index dd6d04a98..5f245d002 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -23,7 +23,10 @@ use foundry_cli::{ use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell, TestFunctionExt}; use foundry_compilers::{ artifacts::output_selection::OutputSelection, - compilers::{multi::MultiCompilerLanguage, Language}, + compilers::{ + multi::{MultiCompiler, MultiCompilerLanguage}, + Language, + }, utils::source_files_iter, ProjectCompileOutput, }; @@ -353,7 +356,7 @@ impl TestArgs { .with_fork(evm_opts.get_fork(&config, env.clone())) .enable_isolation(evm_opts.isolate) .alphanet(evm_opts.alphanet) - .build(project_root, &output, env, evm_opts)?; + .build::(project_root, &output, env, evm_opts)?; let mut maybe_override_mt = |flag, maybe_regex: Option<&Option>| { if let Some(Some(regex)) = maybe_regex { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 0372becb2..9240c6f27 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -8,7 +8,11 @@ use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_common::{get_contract_name, shell::verbosity, ContractsByArtifact, TestFunctionExt}; -use foundry_compilers::{artifacts::Libraries, Artifact, ArtifactId, ProjectCompileOutput}; +use foundry_compilers::{ + artifacts::{Contract, Libraries}, + compilers::Compiler, + Artifact, ArtifactId, ProjectCompileOutput, +}; use foundry_config::{Config, InlineConfig}; use foundry_evm::{ backend::Backend, @@ -457,7 +461,7 @@ impl MultiContractRunnerBuilder { /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm - pub fn build( + pub fn build>( self, root: &Path, output: &ProjectCompileOutput, diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 937f582f4..3c8500772 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -5,6 +5,7 @@ use alloy_primitives::U256; use forge::{revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder}; use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, + compilers::multi::MultiCompiler, utils::RuntimeOrHandle, Project, ProjectCompileOutput, SolcConfig, Vyper, }; @@ -212,7 +213,7 @@ impl ForgeTestData { builder .enable_isolation(opts.isolate) .sender(config.sender) - .build(root, &self.output, opts.local_evm_env(), opts) + .build::(root, &self.output, opts.local_evm_env(), opts) .unwrap() } @@ -221,7 +222,7 @@ impl ForgeTestData { let mut opts = config_evm_opts(&self.config); opts.verbosity = 5; self.base_runner() - .build(self.project.root(), &self.output, opts.local_evm_env(), opts) + .build::(self.project.root(), &self.output, opts.local_evm_env(), opts) .unwrap() } @@ -237,7 +238,7 @@ impl ForgeTestData { self.base_runner() .with_fork(fork) - .build(self.project.root(), &self.output, env, opts) + .build::(self.project.root(), &self.output, env, opts) .unwrap() } } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index fb9eca587..e7410ed60 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,6 +1,7 @@ use crate::init_tracing; use eyre::{Result, WrapErr}; use foundry_compilers::{ + artifacts::Contract, cache::CompilerCache, compilers::multi::MultiCompiler, error::Result as SolcResult, @@ -420,7 +421,9 @@ pub fn setup_cast_project(test: TestProject) -> (TestProject, TestCommand) { /// /// Test projects are created from a global atomic counter to avoid duplicates. #[derive(Clone, Debug)] -pub struct TestProject { +pub struct TestProject< + T: ArtifactOutput + Default = ConfigurableArtifacts, +> { /// The directory in which this test executable is running. exe_root: PathBuf, /// The project in which the test should run. From 75fc63be4fc9241a1981a55c12b6e300fd82a51b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 4 Dec 2024 22:26:23 +0100 Subject: [PATCH 063/107] chore(deps): bump foundry-compilers 0.12.6 (#9490) --- Cargo.lock | 20 ++++++++++---------- crates/cli/src/opts/global.rs | 10 +++++----- crates/forge/bin/cmd/coverage.rs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e60060bfc..51a6b0268 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3864,9 +3864,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611e6de7379c57fc353a53e718cd95844e9bd08b2e3ca79a34b76d4a84d38e48" +checksum = "186f601e89e36e2b82d3bc4a517287190846c990c1fd9f7b6f745bcbe65947e9" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3901,9 +3901,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868df34b353da95395e61fd83e4a56cb075f462f58d9fa1150c9cf96ccb46637" +checksum = "9c32fbf90c191e5037818d9d42d85642d6da2ff6528283b1efb2a88b08a02a5d" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3911,9 +3911,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac37bffdf6d62cbc4ce03393cc45814d32274807b87486a808a370efbd08b67d" +checksum = "103d2d76d62a11b03d19e26fa6fbd996d2fac487a804392f57f96f0f677c469d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3935,9 +3935,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c873d45485dc4b4f351f2f6c6acbb7f5ef8ec27f12e1bd0e6dc016cb9bdda2b" +checksum = "17cafd2720bd20428822421bfbf34cfb33806be75e1b0c6ffcd6a4b25d6ead16" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a605a29e2c0b9c54f14540ec3d03a2434fbaabdda8e6565451cdd38ae8fbd00" +checksum = "327f11092bf779c76b3fa588ebf37c2e3bb903d55ba49f8e73b69e4cb888d0cb" dependencies = [ "alloy-primitives", "cfg-if", diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index c820ca2cf..74ed15a65 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -50,11 +50,6 @@ impl GlobalOpts { Ok(()) } - /// Initialize the global thread pool. - pub fn force_init_thread_pool(&self) -> eyre::Result<()> { - init_thread_pool(self.threads.unwrap_or(0)) - } - /// Create a new shell instance. pub fn shell(&self) -> Shell { let mode = match self.quiet { @@ -69,6 +64,11 @@ impl GlobalOpts { Shell::new_with(format, mode, color, self.verbosity) } + + /// Initialize the global thread pool. + pub fn force_init_thread_pool(&self) -> eyre::Result<()> { + init_thread_pool(self.threads.unwrap_or(0)) + } } /// Initialize the global thread pool. diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 65b4aad03..4e99cb13f 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -182,7 +182,7 @@ impl CoverageArgs { // Get source maps and bytecodes let artifacts: Vec = output .artifact_ids() - .par_bridge() + .par_bridge() // This parses source maps, so we want to run it in parallel. .filter_map(|(id, artifact)| { let source_id = report.get_source_id(id.version.clone(), id.source.clone())?; ArtifactData::new(&id, source_id, artifact) From ce9fca2538eb90551b6b880b7e72fb94d4bb8259 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 5 Dec 2024 08:35:17 +0100 Subject: [PATCH 064/107] chore: rename alphanet to odyssey (#9491) * chore: rename alphanet to odyssey * fix weird change --- crates/anvil/src/cmd.rs | 8 +++--- crates/anvil/src/config.rs | 16 +++++------ crates/anvil/src/eth/backend/executor.rs | 18 ++++++------ crates/anvil/src/eth/backend/mem/mod.rs | 14 +++++----- crates/anvil/tests/it/anvil_api.rs | 6 ++-- crates/cast/bin/cmd/call.rs | 22 ++++++--------- crates/cast/bin/cmd/run.rs | 14 +++++----- crates/common/src/evm.rs | 10 +++---- crates/config/src/lib.rs | 9 +++--- crates/config/src/providers/ext.rs | 2 +- crates/config/src/utils.rs | 4 +-- crates/evm/core/src/lib.rs | 4 +-- crates/evm/core/src/opts.rs | 6 ++-- crates/evm/core/src/precompiles.rs | 12 ++++---- crates/evm/core/src/utils.rs | 16 +++++------ crates/evm/evm/src/executors/trace.rs | 11 +++----- crates/evm/evm/src/inspectors/stack.rs | 28 +++++++++---------- crates/forge/bin/cmd/test/mod.rs | 2 +- crates/forge/src/multi_runner.rs | 22 +++++++-------- crates/forge/tests/cli/config.rs | 2 +- crates/forge/tests/cli/main.rs | 2 +- .../tests/cli/{alphanet.rs => odyssey.rs} | 0 crates/script/src/lib.rs | 2 +- crates/verify/src/utils.rs | 4 +-- 24 files changed, 113 insertions(+), 121 deletions(-) rename crates/forge/tests/cli/{alphanet.rs => odyssey.rs} (100%) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index eda36bb90..7b009a592 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -275,7 +275,7 @@ impl NodeArgs { .with_transaction_block_keeper(self.transaction_block_keeper) .with_max_persisted_states(self.max_persisted_states) .with_optimism(self.evm_opts.optimism) - .with_alphanet(self.evm_opts.alphanet) + .with_odyssey(self.evm_opts.odyssey) .with_disable_default_create2_deployer(self.evm_opts.disable_default_create2_deployer) .with_slots_in_an_epoch(self.slots_in_an_epoch) .with_memory_limit(self.evm_opts.memory_limit) @@ -583,9 +583,9 @@ pub struct AnvilEvmArgs { #[arg(long)] pub memory_limit: Option, - /// Enable Alphanet features - #[arg(long, visible_alias = "odyssey")] - pub alphanet: bool, + /// Enable Odyssey features + #[arg(long, alias = "alphanet")] + pub odyssey: bool, } /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index b867aca26..9e22adeed 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -187,8 +187,8 @@ pub struct NodeConfig { pub memory_limit: Option, /// Factory used by `anvil` to extend the EVM's precompiles. pub precompile_factory: Option>, - /// Enable Alphanet features. - pub alphanet: bool, + /// Enable Odyssey features. + pub odyssey: bool, /// Do not print log messages. pub silent: bool, /// The path where states are cached. @@ -467,7 +467,7 @@ impl Default for NodeConfig { slots_in_an_epoch: 32, memory_limit: None, precompile_factory: None, - alphanet: false, + odyssey: false, silent: false, cache_path: None, } @@ -507,7 +507,7 @@ impl NodeConfig { /// Returns the hardfork to use pub fn get_hardfork(&self) -> ChainHardfork { - if self.alphanet { + if self.odyssey { return ChainHardfork::Ethereum(EthereumHardfork::PragueEOF); } if let Some(hardfork) = self.hardfork { @@ -952,10 +952,10 @@ impl NodeConfig { self } - /// Sets whether to enable Alphanet support + /// Sets whether to enable Odyssey support #[must_use] - pub fn with_alphanet(mut self, alphanet: bool) -> Self { - self.alphanet = alphanet; + pub fn with_odyssey(mut self, odyssey: bool) -> Self { + self.odyssey = odyssey; self } @@ -1055,7 +1055,7 @@ impl NodeConfig { Arc::new(RwLock::new(fork)), self.enable_steps_tracing, self.print_logs, - self.alphanet, + self.odyssey, self.prune_history, self.max_persisted_states, self.transaction_block_keeper, diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index a00afd962..f4b20868f 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -28,7 +28,7 @@ use foundry_evm::{ }, }, traces::CallTraceNode, - utils::alphanet_handler_register, + utils::odyssey_handler_register, }; use revm::{db::WrapDatabaseRef, primitives::MAX_BLOB_GAS_PER_BLOCK}; use std::sync::Arc; @@ -106,7 +106,7 @@ pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> { /// Cumulative blob gas used by all executed transactions pub blob_gas_used: u64, pub enable_steps_tracing: bool, - pub alphanet: bool, + pub odyssey: bool, pub print_logs: bool, /// Precompiles to inject to the EVM. pub precompile_factory: Option>, @@ -314,7 +314,7 @@ impl Iterator for &mut TransactionExec } let exec_result = { - let mut evm = new_evm_with_inspector(&mut *self.db, env, &mut inspector, self.alphanet); + let mut evm = new_evm_with_inspector(&mut *self.db, env, &mut inspector, self.odyssey); if let Some(factory) = &self.precompile_factory { inject_precompiles(&mut evm, factory.precompiles()); } @@ -398,20 +398,20 @@ fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { } } -/// Creates a database with given database and inspector, optionally enabling alphanet features. +/// Creates a database with given database and inspector, optionally enabling odyssey features. pub fn new_evm_with_inspector( db: DB, env: EnvWithHandlerCfg, inspector: &mut dyn revm::Inspector, - alphanet: bool, + odyssey: bool, ) -> revm::Evm<'_, &mut dyn revm::Inspector, DB> { let EnvWithHandlerCfg { env, handler_cfg } = env; let mut handler = revm::Handler::new(handler_cfg); handler.append_handler_register_plain(revm::inspector_handle_register); - if alphanet { - handler.append_handler_register_plain(alphanet_handler_register); + if odyssey { + handler.append_handler_register_plain(odyssey_handler_register); } let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); @@ -424,10 +424,10 @@ pub fn new_evm_with_inspector_ref<'a, DB>( db: DB, env: EnvWithHandlerCfg, inspector: &mut dyn revm::Inspector>, - alphanet: bool, + odyssey: bool, ) -> revm::Evm<'a, &mut dyn revm::Inspector>, WrapDatabaseRef> where DB: revm::DatabaseRef, { - new_evm_with_inspector(WrapDatabaseRef(db), env, inspector, alphanet) + new_evm_with_inspector(WrapDatabaseRef(db), env, inspector, odyssey) } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 75888c130..26787cca6 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -195,7 +195,7 @@ pub struct Backend { active_state_snapshots: Arc>>, enable_steps_tracing: bool, print_logs: bool, - alphanet: bool, + odyssey: bool, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory @@ -223,7 +223,7 @@ impl Backend { fork: Arc>>, enable_steps_tracing: bool, print_logs: bool, - alphanet: bool, + odyssey: bool, prune_state_history_config: PruneStateHistoryConfig, max_persisted_states: Option, transaction_block_keeper: Option, @@ -275,7 +275,7 @@ impl Backend { (cfg.slots_in_an_epoch, cfg.precompile_factory.clone()) }; - let (capabilities, executor_wallet) = if alphanet { + let (capabilities, executor_wallet) = if odyssey { // Insert account that sponsors the delegated txs. And deploy P256 delegation contract. let mut db = db.write().await; @@ -326,7 +326,7 @@ impl Backend { active_state_snapshots: Arc::new(Mutex::new(Default::default())), enable_steps_tracing, print_logs, - alphanet, + odyssey, prune_state_history_config, transaction_block_keeper, node_config, @@ -999,7 +999,7 @@ impl Backend { &'i mut dyn revm::Inspector>>, WrapDatabaseRef<&'db dyn DatabaseRef>, > { - let mut evm = new_evm_with_inspector_ref(db, env, inspector, self.alphanet); + let mut evm = new_evm_with_inspector_ref(db, env, inspector, self.odyssey); if let Some(factory) = &self.precompile_factory { inject_precompiles(&mut evm, factory.precompiles()); } @@ -1080,7 +1080,7 @@ impl Backend { enable_steps_tracing: self.enable_steps_tracing, print_logs: self.print_logs, precompile_factory: self.precompile_factory.clone(), - alphanet: self.alphanet, + odyssey: self.odyssey, }; // create a new pending block @@ -1162,7 +1162,7 @@ impl Backend { blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, print_logs: self.print_logs, - alphanet: self.alphanet, + odyssey: self.odyssey, precompile_factory: self.precompile_factory.clone(), }; let executed_tx = executor.execute(); diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index b75b088b0..9eb44c69b 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -808,7 +808,7 @@ async fn test_reorg() { // === wallet endpoints === // #[tokio::test(flavor = "multi_thread")] async fn can_get_wallet_capabilities() { - let (api, handle) = spawn(NodeConfig::test().with_alphanet(true)).await; + let (api, handle) = spawn(NodeConfig::test().with_odyssey(true)).await; let provider = handle.http_provider(); @@ -834,7 +834,7 @@ async fn can_get_wallet_capabilities() { #[tokio::test(flavor = "multi_thread")] async fn can_add_capability() { - let (api, _handle) = spawn(NodeConfig::test().with_alphanet(true)).await; + let (api, _handle) = spawn(NodeConfig::test().with_odyssey(true)).await; let init_capabilities = api.get_capabilities().unwrap(); @@ -864,7 +864,7 @@ async fn can_add_capability() { #[tokio::test(flavor = "multi_thread")] async fn can_set_executor() { - let (api, _handle) = spawn(NodeConfig::test().with_alphanet(true)).await; + let (api, _handle) = spawn(NodeConfig::test().with_odyssey(true)).await; let expected_addr = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 1704247dc..1383bea27 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -73,9 +73,9 @@ pub struct CallArgs { #[arg(long, short)] block: Option, - /// Enable Alphanet features. - #[arg(long, alias = "odyssey")] - pub alphanet: bool, + /// Enable Odyssey features. + #[arg(long, alias = "alphanet")] + pub odyssey: bool, #[command(subcommand)] command: Option, @@ -180,7 +180,7 @@ impl CallArgs { } let create2_deployer = evm_opts.create2_deployer; - let (mut env, fork, chain, alphanet) = + let (mut env, fork, chain, odyssey) = TracingExecutor::get_fork_material(&config, evm_opts).await?; // modify settings that usually set in eth_call @@ -195,14 +195,8 @@ impl CallArgs { InternalTraceMode::None }) .with_state_changes(shell::verbosity() > 4); - let mut executor = TracingExecutor::new( - env, - fork, - evm_version, - trace_mode, - alphanet, - create2_deployer, - ); + let mut executor = + TracingExecutor::new(env, fork, evm_version, trace_mode, odyssey, create2_deployer); let value = tx.value.unwrap_or_default(); let input = tx.inner.input.into_input().unwrap_or_default(); @@ -247,8 +241,8 @@ impl figment::Provider for CallArgs { fn data(&self) -> Result, figment::Error> { let mut map = Map::new(); - if self.alphanet { - map.insert("alphanet".into(), self.alphanet.into()); + if self.odyssey { + map.insert("odyssey".into(), self.odyssey.into()); } if let Some(evm_version) = self.evm_version { diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 62a41ca6c..7ab5ddd51 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -85,9 +85,9 @@ pub struct RunArgs { #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] pub no_rate_limit: bool, - /// Enables Alphanet features. - #[arg(long, alias = "odyssey")] - pub alphanet: bool, + /// Enables Odyssey features. + #[arg(long, alias = "alphanet")] + pub odyssey: bool, /// Use current project artifacts for trace decoding. #[arg(long, visible_alias = "la")] @@ -138,7 +138,7 @@ impl RunArgs { config.fork_block_number = Some(tx_block_number - 1); let create2_deployer = evm_opts.create2_deployer; - let (mut env, fork, chain, alphanet) = + let (mut env, fork, chain, odyssey) = TracingExecutor::get_fork_material(&config, evm_opts).await?; let mut evm_version = self.evm_version; @@ -176,7 +176,7 @@ impl RunArgs { fork, evm_version, trace_mode, - alphanet, + odyssey, create2_deployer, ); let mut env = @@ -283,8 +283,8 @@ impl figment::Provider for RunArgs { fn data(&self) -> Result, figment::Error> { let mut map = Map::new(); - if self.alphanet { - map.insert("alphanet".into(), self.alphanet.into()); + if self.odyssey { + map.insert("odyssey".into(), self.odyssey.into()); } if let Some(api_key) = &self.etherscan.key { diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index dbcaf02af..dac5e6d9a 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -140,10 +140,10 @@ pub struct EvmArgs { #[serde(skip)] pub isolate: bool, - /// Whether to enable Alphanet features. - #[arg(long, alias = "odyssey")] + /// Whether to enable Odyssey features. + #[arg(long, alias = "alphanet")] #[serde(skip)] - pub alphanet: bool, + pub odyssey: bool, } // Make this set of options a `figment::Provider` so that it can be merged into the `Config` @@ -170,8 +170,8 @@ impl Provider for EvmArgs { dict.insert("isolate".to_string(), self.isolate.into()); } - if self.alphanet { - dict.insert("alphanet".to_string(), self.alphanet.into()); + if self.odyssey { + dict.insert("odyssey".to_string(), self.odyssey.into()); } if self.always_use_create_2_factory { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 08c1ed0ee..805aaf7cd 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -490,8 +490,9 @@ pub struct Config { #[serde(default, skip_serializing_if = "Option::is_none")] pub eof_version: Option, - /// Whether to enable Alphanet features. - pub alphanet: bool, + /// Whether to enable Odyssey features. + #[serde(alias = "alphanet")] + pub odyssey: bool, /// Timeout for transactions in seconds. pub transaction_timeout: u64, @@ -1128,7 +1129,7 @@ impl Config { /// Returns the [SpecId] derived from the configured [EvmVersion] #[inline] pub fn evm_spec_id(&self) -> SpecId { - evm_spec_id(self.evm_version, self.alphanet) + evm_spec_id(self.evm_version, self.odyssey) } /// Returns whether the compiler version should be auto-detected @@ -2373,7 +2374,7 @@ impl Default for Config { warnings: vec![], extra_args: vec![], eof_version: None, - alphanet: false, + odyssey: false, transaction_timeout: 120, additional_compiler_profiles: Default::default(), compilation_restrictions: Default::default(), diff --git a/crates/config/src/providers/ext.rs b/crates/config/src/providers/ext.rs index 040f2127c..58f418469 100644 --- a/crates/config/src/providers/ext.rs +++ b/crates/config/src/providers/ext.rs @@ -166,7 +166,7 @@ impl Provider for BackwardsCompatTomlProvider

{ } if let Some(v) = dict.remove("odyssey") { - dict.insert("alphanet".to_string(), v); + dict.insert("odyssey".to_string(), v); } map.insert(profile, dict); } diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 19f70a939..43b9b7468 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -259,8 +259,8 @@ impl FromStr for Numeric { /// Returns the [SpecId] derived from [EvmVersion] #[inline] -pub fn evm_spec_id(evm_version: EvmVersion, alphanet: bool) -> SpecId { - if alphanet { +pub fn evm_spec_id(evm_version: EvmVersion, odyssey: bool) -> SpecId { + if odyssey { return SpecId::OSAKA; } match evm_version { diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 0bdc34cc9..30986d161 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -52,8 +52,8 @@ pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> { /// Simulates `console.log` invocation. fn console_log(&mut self, _input: String) {} - /// Returns `true` if the current network is Alphanet. - fn is_alphanet(&self) -> bool { + /// Returns `true` if the current network is Odyssey. + fn is_odyssey(&self) -> bool { false } diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index fec780b0f..76be5937b 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -65,8 +65,8 @@ pub struct EvmOpts { /// Whether to disable block gas limit checks. pub disable_block_gas_limit: bool, - /// whether to enable Alphanet features. - pub alphanet: bool, + /// whether to enable Odyssey features. + pub odyssey: bool, /// The CREATE2 deployer's address. pub create2_deployer: Address, @@ -91,7 +91,7 @@ impl Default for EvmOpts { memory_limit: 0, isolate: false, disable_block_gas_limit: false, - alphanet: false, + odyssey: false, create2_deployer: DEFAULT_CREATE2_DEPLOYER, } } diff --git a/crates/evm/core/src/precompiles.rs b/crates/evm/core/src/precompiles.rs index 2544258d3..ceaf6d004 100644 --- a/crates/evm/core/src/precompiles.rs +++ b/crates/evm/core/src/precompiles.rs @@ -46,13 +46,13 @@ pub const PRECOMPILES: &[Address] = &[ EC_PAIRING, BLAKE_2F, POINT_EVALUATION, - ALPHANET_P256_ADDRESS, + ODYSSEY_P256_ADDRESS, ]; -/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212) secp256r1 precompile address on Alphanet. +/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212) secp256r1 precompile address on Odyssey. /// -/// -pub const ALPHANET_P256_ADDRESS: Address = address!("0000000000000000000000000000000000000014"); +/// +pub const ODYSSEY_P256_ADDRESS: Address = address!("0000000000000000000000000000000000000014"); /// Wrapper around revm P256 precompile, matching EIP-7212 spec. /// @@ -69,5 +69,5 @@ pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult { } /// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212#specification) secp256r1 precompile. -pub const ALPHANET_P256: PrecompileWithAddress = - PrecompileWithAddress(ALPHANET_P256_ADDRESS, Precompile::Standard(p256_verify)); +pub const ODYSSEY_P256: PrecompileWithAddress = + PrecompileWithAddress(ODYSSEY_P256_ADDRESS, Precompile::Standard(p256_verify)); diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 0841d9340..5ec396a16 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,6 +1,6 @@ pub use crate::ic::*; use crate::{ - backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, precompiles::ALPHANET_P256, + backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, precompiles::ODYSSEY_P256, InspectorExt, }; use alloy_consensus::BlockHeader; @@ -279,13 +279,13 @@ pub fn create2_handler_register( }); } -/// Adds Alphanet P256 precompile to the list of loaded precompiles. -pub fn alphanet_handler_register(handler: &mut EvmHandler<'_, EXT, DB>) { +/// Adds Odyssey P256 precompile to the list of loaded precompiles. +pub fn odyssey_handler_register(handler: &mut EvmHandler<'_, EXT, DB>) { let prev = handler.pre_execution.load_precompiles.clone(); handler.pre_execution.load_precompiles = Arc::new(move || { let mut loaded_precompiles = prev(); - loaded_precompiles.extend([ALPHANET_P256]); + loaded_precompiles.extend([ODYSSEY_P256]); loaded_precompiles }); @@ -314,8 +314,8 @@ pub fn new_evm_with_inspector<'evm, 'i, 'db, I: InspectorExt + ?Sized>( let mut handler = revm::Handler::new(handler_cfg); handler.append_handler_register_plain(revm::inspector_handle_register); - if inspector.is_alphanet() { - handler.append_handler_register_plain(alphanet_handler_register); + if inspector.is_odyssey() { + handler.append_handler_register_plain(odyssey_handler_register); } handler.append_handler_register_plain(create2_handler_register); @@ -332,8 +332,8 @@ pub fn new_evm_with_existing_context<'a>( let mut handler = revm::Handler::new(handler_cfg); handler.append_handler_register_plain(revm::inspector_handle_register); - if inspector.is_alphanet() { - handler.append_handler_register_plain(alphanet_handler_register); + if inspector.is_odyssey() { + handler.append_handler_register_plain(odyssey_handler_register); } handler.append_handler_register_plain(create2_handler_register); diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index d9c0d74f6..b55517a67 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -18,7 +18,7 @@ impl TracingExecutor { fork: Option, version: Option, trace_mode: TraceMode, - alphanet: bool, + odyssey: bool, create2_deployer: Address, ) -> Self { let db = Backend::spawn(fork); @@ -27,12 +27,9 @@ impl TracingExecutor { // tracing will be enabled only for the targeted transaction executor: ExecutorBuilder::new() .inspectors(|stack| { - stack - .trace_mode(trace_mode) - .alphanet(alphanet) - .create2_deployer(create2_deployer) + stack.trace_mode(trace_mode).odyssey(odyssey).create2_deployer(create2_deployer) }) - .spec_id(evm_spec_id(version.unwrap_or_default(), alphanet)) + .spec_id(evm_spec_id(version.unwrap_or_default(), odyssey)) .build(env, db), } } @@ -54,7 +51,7 @@ impl TracingExecutor { let fork = evm_opts.get_fork(config, env.clone()); - Ok((env, fork, evm_opts.get_remote_chain_id().await, evm_opts.alphanet)) + Ok((env, fork, evm_opts.get_remote_chain_id().await, evm_opts.odyssey)) } } diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 94ec349b1..385623f5a 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -55,8 +55,8 @@ pub struct InspectorStackBuilder { /// In isolation mode all top-level calls are executed as a separate transaction in a separate /// EVM context, enabling more precise gas accounting and transaction state changes. pub enable_isolation: bool, - /// Whether to enable Alphanet features. - pub alphanet: bool, + /// Whether to enable Odyssey features. + pub odyssey: bool, /// The wallets to set in the cheatcodes context. pub wallets: Option, /// The CREATE2 deployer address. @@ -150,11 +150,11 @@ impl InspectorStackBuilder { self } - /// Set whether to enable Alphanet features. + /// Set whether to enable Odyssey features. /// For description of call isolation, see [`InspectorStack::enable_isolation`]. #[inline] - pub fn alphanet(mut self, yes: bool) -> Self { - self.alphanet = yes; + pub fn odyssey(mut self, yes: bool) -> Self { + self.odyssey = yes; self } @@ -177,7 +177,7 @@ impl InspectorStackBuilder { print, chisel_state, enable_isolation, - alphanet, + odyssey, wallets, create2_deployer, } = self; @@ -205,7 +205,7 @@ impl InspectorStackBuilder { stack.tracing(trace_mode); stack.enable_isolation(enable_isolation); - stack.alphanet(alphanet); + stack.odyssey(odyssey); stack.set_create2_deployer(create2_deployer); // environment, must come after all of the inspectors @@ -291,7 +291,7 @@ pub struct InspectorStackInner { pub printer: Option, pub tracer: Option, pub enable_isolation: bool, - pub alphanet: bool, + pub odyssey: bool, pub create2_deployer: Address, /// Flag marking if we are in the inner EVM context. @@ -405,8 +405,8 @@ impl InspectorStack { /// Set whether to enable call isolation. #[inline] - pub fn alphanet(&mut self, yes: bool) { - self.alphanet = yes; + pub fn odyssey(&mut self, yes: bool) { + self.odyssey = yes; } /// Set the CREATE2 deployer address. @@ -1036,8 +1036,8 @@ impl InspectorExt for InspectorStackRefMut<'_> { )); } - fn is_alphanet(&self) -> bool { - self.inner.alphanet + fn is_odyssey(&self) -> bool { + self.inner.odyssey } fn create2_deployer(&self) -> Address { @@ -1142,8 +1142,8 @@ impl InspectorExt for InspectorStack { self.as_mut().should_use_create2_factory(ecx, inputs) } - fn is_alphanet(&self) -> bool { - self.alphanet + fn is_odyssey(&self) -> bool { + self.odyssey } fn create2_deployer(&self) -> Address { diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 5f245d002..75200f4b4 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -355,7 +355,7 @@ impl TestArgs { .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) .enable_isolation(evm_opts.isolate) - .alphanet(evm_opts.alphanet) + .odyssey(evm_opts.odyssey) .build::(project_root, &output, env, evm_opts)?; let mut maybe_override_mt = |flag, maybe_regex: Option<&Option>| { diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 9240c6f27..4de95d7b6 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -288,8 +288,8 @@ pub struct TestRunnerConfig { pub decode_internal: InternalTraceMode, /// Whether to enable call isolation. pub isolation: bool, - /// Whether to enable Alphanet features. - pub alphanet: bool, + /// Whether to enable Odyssey features. + pub odyssey: bool, } impl TestRunnerConfig { @@ -305,7 +305,7 @@ impl TestRunnerConfig { // self.debug = N/A; // self.decode_internal = N/A; // self.isolation = N/A; - self.alphanet = config.alphanet; + self.odyssey = config.odyssey; self.config = config; } @@ -323,7 +323,7 @@ impl TestRunnerConfig { inspector.tracing(self.trace_mode()); inspector.collect_coverage(self.coverage); inspector.enable_isolation(self.isolation); - inspector.alphanet(self.alphanet); + inspector.odyssey(self.odyssey); // inspector.set_create2_deployer(self.evm_opts.create2_deployer); // executor.env_mut().clone_from(&self.env); @@ -353,7 +353,7 @@ impl TestRunnerConfig { .trace_mode(self.trace_mode()) .coverage(self.coverage) .enable_isolation(self.isolation) - .alphanet(self.alphanet) + .odyssey(self.odyssey) .create2_deployer(self.evm_opts.create2_deployer) }) .spec_id(self.spec_id) @@ -394,8 +394,8 @@ pub struct MultiContractRunnerBuilder { pub decode_internal: InternalTraceMode, /// Whether to enable call isolation pub isolation: bool, - /// Whether to enable Alphanet features. - pub alphanet: bool, + /// Whether to enable Odyssey features. + pub odyssey: bool, } impl MultiContractRunnerBuilder { @@ -410,7 +410,7 @@ impl MultiContractRunnerBuilder { debug: Default::default(), isolation: Default::default(), decode_internal: Default::default(), - alphanet: Default::default(), + odyssey: Default::default(), } } @@ -454,8 +454,8 @@ impl MultiContractRunnerBuilder { self } - pub fn alphanet(mut self, enable: bool) -> Self { - self.alphanet = enable; + pub fn odyssey(mut self, enable: bool) -> Self { + self.odyssey = enable; self } @@ -533,7 +533,7 @@ impl MultiContractRunnerBuilder { decode_internal: self.decode_internal, inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, - alphanet: self.alphanet, + odyssey: self.odyssey, config: self.config, }, diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 6f70f845c..545cebac8 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -156,7 +156,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { legacy_assertions: false, extra_args: vec![], eof_version: None, - alphanet: false, + odyssey: false, transaction_timeout: 120, additional_compiler_profiles: Default::default(), compilation_restrictions: Default::default(), diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index d59dbc6be..abcaf1803 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -4,7 +4,6 @@ extern crate foundry_test_utils; pub mod constants; pub mod utils; -mod alphanet; mod bind_json; mod build; mod cache; @@ -20,6 +19,7 @@ mod eip712; mod geiger; mod inline_config; mod multi_script; +mod odyssey; mod script; mod soldeer; mod svm; diff --git a/crates/forge/tests/cli/alphanet.rs b/crates/forge/tests/cli/odyssey.rs similarity index 100% rename from crates/forge/tests/cli/alphanet.rs rename to crates/forge/tests/cli/odyssey.rs diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 7aa18dcbe..ab59569c7 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -615,7 +615,7 @@ impl ScriptConfig { .inspectors(|stack| { stack .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call }) - .alphanet(self.evm_opts.alphanet) + .odyssey(self.evm_opts.odyssey) .create2_deployer(self.evm_opts.create2_deployer) }) .spec_id(self.config.evm_spec_id()) diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 824e78443..6165fa718 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -329,7 +329,7 @@ pub async fn get_tracing_executor( fork_config.evm_version = evm_version; let create2_deployer = evm_opts.create2_deployer; - let (env, fork, _chain, is_alphanet) = + let (env, fork, _chain, is_odyssey) = TracingExecutor::get_fork_material(fork_config, evm_opts).await?; let executor = TracingExecutor::new( @@ -337,7 +337,7 @@ pub async fn get_tracing_executor( fork, Some(fork_config.evm_version), TraceMode::Call, - is_alphanet, + is_odyssey, create2_deployer, ); From a4de7e812bca8962e7d30ab83890712adbf4a539 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:34:32 +0100 Subject: [PATCH 065/107] feat: add JSON compatibility for `forge test --summary +/ --detailed` + apply consistent table styling (#9485) * support summary reports in json * unify table style, when show_metrics is enabled and --json, do render * apply consistent formatting and ordering and spacing for tables * clean up * make tables consistent * update layouts, fix tests * clean up * change ReportKind::Markdown to ReportKind::Text as the output is not strictly Markdown compatible * json compatibility for invariant metrics is not necessary due to different branching and could be derived from JSON * remove redundant spacer * clean up, revert InvariantMetricsReporter --- crates/cast/bin/cmd/storage.rs | 18 +- crates/cast/tests/cli/main.rs | 25 +- crates/common/fmt/src/eof.rs | 6 +- crates/common/src/compile.rs | 40 +- crates/common/src/reports.rs | 4 +- crates/forge/bin/cmd/coverage.rs | 6 +- crates/forge/bin/cmd/inspect.rs | 24 +- crates/forge/bin/cmd/selectors.rs | 11 +- crates/forge/bin/cmd/test/mod.rs | 26 +- crates/forge/bin/cmd/test/summary.rs | 182 ++++++--- crates/forge/src/coverage.rs | 23 +- crates/forge/src/gas_report.rs | 52 +-- crates/forge/tests/cli/build.rs | 56 +-- crates/forge/tests/cli/cmd.rs | 502 +++++++++++++++++------- crates/forge/tests/cli/coverage.rs | 137 +++++-- crates/forge/tests/cli/inline_config.rs | 12 +- crates/forge/tests/it/invariant.rs | 38 +- 17 files changed, 796 insertions(+), 366 deletions(-) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 468f20b27..7121f1a98 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -6,7 +6,7 @@ use alloy_rpc_types::BlockId; use alloy_transport::Transport; use cast::Cast; use clap::Parser; -use comfy_table::{presets::ASCII_MARKDOWN, Table}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; use eyre::Result; use foundry_block_explorers::Client; use foundry_cli::{ @@ -288,8 +288,18 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) } let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(["Name", "Type", "Slot", "Offset", "Bytes", "Value", "Hex Value", "Contract"]); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Name"), + Cell::new("Type"), + Cell::new("Slot"), + Cell::new("Offset"), + Cell::new("Bytes"), + Cell::new("Value"), + Cell::new("Hex Value"), + Cell::new("Contract"), + ]); for (slot, storage_value) in layout.storage.into_iter().zip(values) { let storage_type = layout.types.get(&slot.storage_type); @@ -309,7 +319,7 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) ]); } - sh_println!("{table}")?; + sh_println!("\n{table}\n")?; Ok(()) } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index fe9309877..f2ee99009 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1131,10 +1131,15 @@ casttest!(storage_layout_simple, |_prj, cmd| { ]) .assert_success() .stdout_eq(str![[r#" + +╭---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╮ | Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | -|---------|---------|------|--------|-------|-------|--------------------------------------------------------------------|-----------------------------------------------| ++========================================================================================================================================================================+ | _owner | address | 0 | 0 | 20 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer | +|---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------| | _paused | bool | 0 | 20 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer | +╰---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╯ + "#]]); }); @@ -1170,21 +1175,37 @@ casttest!(storage_layout_complex, |_prj, cmd| { ]) .assert_success() .stdout_eq(str![[r#" + +╭-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╮ | Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | -|-------------------------------|--------------------------------------------------------------------|------|--------|-------|--------------------------------------------------|--------------------------------------------------------------------|---------------------------------| ++======================================================================================================================================================================================================================================================================================+ | _status | uint256 | 0 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _generalPoolsBalances | mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _nextNonce | mapping(address => uint256) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _paused | bool | 3 | 0 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _authorizer | contract IAuthorizer | 3 | 1 | 20 | 549683469959765988649777481110995959958745616871 | 0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _approvedRelayers | mapping(address => mapping(address => bool)) | 4 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _isPoolRegistered | mapping(bytes32 => bool) | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _nextPoolNonce | uint256 | 6 | 0 | 32 | 1760 | 0x00000000000000000000000000000000000000000000000000000000000006e0 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _minimalSwapInfoPoolsBalances | mapping(bytes32 => mapping(contract IERC20 => bytes32)) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _minimalSwapInfoPoolsTokens | mapping(bytes32 => struct EnumerableSet.AddressSet) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _twoTokenPoolTokens | mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens) | 9 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _poolAssetManagers | mapping(bytes32 => mapping(contract IERC20 => address)) | 10 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| | _internalTokenBalance | mapping(address => mapping(contract IERC20 => uint256)) | 11 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +╰-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╯ + "#]]); }); diff --git a/crates/common/fmt/src/eof.rs b/crates/common/fmt/src/eof.rs index 639e175b4..1ae9de70b 100644 --- a/crates/common/fmt/src/eof.rs +++ b/crates/common/fmt/src/eof.rs @@ -1,4 +1,4 @@ -use comfy_table::{ContentArrangement, Table}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, ContentArrangement, Table}; use revm_primitives::{ eof::{EofBody, EofHeader}, Eof, @@ -24,6 +24,7 @@ pub fn pretty_eof(eof: &Eof) -> Result { let mut result = String::new(); let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.add_row(vec!["type_size", &types_size.to_string()]); table.add_row(vec!["num_code_sections", &code_sizes.len().to_string()]); if !code_sizes.is_empty() { @@ -39,6 +40,7 @@ pub fn pretty_eof(eof: &Eof) -> Result { if !code_section.is_empty() { let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.set_content_arrangement(ContentArrangement::Dynamic); table.set_header(vec!["", "Inputs", "Outputs", "Max stack height", "Code"]); for (idx, (code, type_section)) in code_section.iter().zip(types_section).enumerate() { @@ -56,6 +58,7 @@ pub fn pretty_eof(eof: &Eof) -> Result { if !container_section.is_empty() { let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.set_content_arrangement(ContentArrangement::Dynamic); for (idx, container) in container_section.iter().enumerate() { table.add_row(vec![&idx.to_string(), &container.to_string()]); @@ -66,6 +69,7 @@ pub fn pretty_eof(eof: &Eof) -> Result { if !data_section.is_empty() { let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.set_content_arrangement(ContentArrangement::Dynamic); table.add_row(vec![&data_section.to_string()]); write!(result, "\n\nData section:\n{table}")?; diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 47fe226d9..5f14c0661 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -6,7 +6,7 @@ use crate::{ term::SpinnerReporter, TestFunctionExt, }; -use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, Table}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; use eyre::Result; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ @@ -341,9 +341,8 @@ impl SizeReport { impl Display for SizeReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self.report_kind { - ReportKind::Markdown => { - let table = self.format_table_output(); - writeln!(f, "{table}")?; + ReportKind::Text => { + writeln!(f, "\n{}", self.format_table_output())?; } ReportKind::JSON => { writeln!(f, "{}", self.format_json_output())?; @@ -378,13 +377,14 @@ impl SizeReport { fn format_table_output(&self) -> Table { let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header([ - Cell::new("Contract").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Runtime Size (B)").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Initcode Size (B)").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Runtime Margin (B)").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Initcode Margin (B)").add_attribute(Attribute::Bold).fg(Color::Blue), + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Contract"), + Cell::new("Runtime Size (B)"), + Cell::new("Initcode Size (B)"), + Cell::new("Runtime Margin (B)"), + Cell::new("Initcode Margin (B)"), ]); // Filters out dev contracts (Test or Script) @@ -411,19 +411,11 @@ impl SizeReport { let locale = &Locale::en; table.add_row([ - Cell::new(name).fg(Color::Blue), - Cell::new(contract.runtime_size.to_formatted_string(locale)) - .set_alignment(CellAlignment::Right) - .fg(runtime_color), - Cell::new(contract.init_size.to_formatted_string(locale)) - .set_alignment(CellAlignment::Right) - .fg(init_color), - Cell::new(runtime_margin.to_formatted_string(locale)) - .set_alignment(CellAlignment::Right) - .fg(runtime_color), - Cell::new(init_margin.to_formatted_string(locale)) - .set_alignment(CellAlignment::Right) - .fg(init_color), + Cell::new(name), + Cell::new(contract.runtime_size.to_formatted_string(locale)).fg(runtime_color), + Cell::new(contract.init_size.to_formatted_string(locale)).fg(init_color), + Cell::new(runtime_margin.to_formatted_string(locale)).fg(runtime_color), + Cell::new(init_margin.to_formatted_string(locale)).fg(init_color), ]); } diff --git a/crates/common/src/reports.rs b/crates/common/src/reports.rs index adbdc11bf..0fdf4502e 100644 --- a/crates/common/src/reports.rs +++ b/crates/common/src/reports.rs @@ -5,7 +5,7 @@ use crate::shell; #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum ReportKind { #[default] - Markdown, + Text, JSON, } @@ -14,6 +14,6 @@ pub fn report_kind() -> ReportKind { if shell::is_json() { ReportKind::JSON } else { - ReportKind::Markdown + ReportKind::Text } } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 4e99cb13f..48bc8349c 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -6,8 +6,8 @@ use forge::{ coverage::{ analysis::{SourceAnalysis, SourceAnalyzer, SourceFile, SourceFiles}, anchors::find_anchors, - BytecodeReporter, ContractId, CoverageReport, CoverageReporter, DebugReporter, ItemAnchor, - LcovReporter, SummaryReporter, + BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, + DebugReporter, ItemAnchor, LcovReporter, }, opts::EvmOpts, utils::IcPcMap, @@ -302,7 +302,7 @@ impl CoverageArgs { // Output final report for report_kind in self.report { match report_kind { - CoverageReportKind::Summary => SummaryReporter::default().report(&report), + CoverageReportKind::Summary => CoverageSummaryReporter::default().report(&report), CoverageReportKind::Lcov => { let path = root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref())); diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index 5c7c224f7..426a8b36e 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -1,10 +1,10 @@ use alloy_primitives::{hex, keccak256, Address}; use clap::Parser; -use comfy_table::{presets::ASCII_MARKDOWN, Table}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; use eyre::{Context, Result}; use forge::revm::primitives::Eof; use foundry_cli::opts::{CompilerArgs, CoreBuildArgs}; -use foundry_common::{compile::ProjectCompiler, fmt::pretty_eof}; +use foundry_common::{compile::ProjectCompiler, fmt::pretty_eof, shell}; use foundry_compilers::{ artifacts::{ output_selection::{ @@ -111,7 +111,7 @@ impl InspectArgs { print_json(&artifact.gas_estimates)?; } ContractArtifactField::StorageLayout => { - print_storage_layout(artifact.storage_layout.as_ref(), pretty)?; + print_storage_layout(artifact.storage_layout.as_ref())?; } ContractArtifactField::DevDoc => { print_json(&artifact.devdoc)?; @@ -176,18 +176,26 @@ impl InspectArgs { } } -pub fn print_storage_layout(storage_layout: Option<&StorageLayout>, pretty: bool) -> Result<()> { +pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> { let Some(storage_layout) = storage_layout else { eyre::bail!("Could not get storage layout"); }; - if !pretty { + if shell::is_json() { return print_json(&storage_layout) } let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(["Name", "Type", "Slot", "Offset", "Bytes", "Contract"]); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Name"), + Cell::new("Type"), + Cell::new("Slot"), + Cell::new("Offset"), + Cell::new("Bytes"), + Cell::new("Contract"), + ]); for slot in &storage_layout.storage { let storage_type = storage_layout.types.get(&slot.storage_type); @@ -201,7 +209,7 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>, pretty: bool ]); } - sh_println!("{table}")?; + sh_println!("\n{table}\n")?; Ok(()) } diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index f75dabaff..31992983d 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -1,6 +1,6 @@ use alloy_primitives::hex; use clap::Parser; -use comfy_table::Table; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Table}; use eyre::Result; use foundry_cli::{ opts::{CompilerArgs, CoreBuildArgs, ProjectPathsArgs}, @@ -198,6 +198,7 @@ impl SelectorsSubcommands { sh_println!("No colliding method selectors between the two contracts.")?; } else { let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.set_header([ String::from("Selector"), first_contract.name, @@ -207,7 +208,7 @@ impl SelectorsSubcommands { table.add_row([method.0, method.1, method.2]); } sh_println!("{} collisions found:", colliding_methods.len())?; - sh_println!("{table}")?; + sh_println!("\n{table}\n")?; } } Self::List { contract, project_paths } => { @@ -267,6 +268,7 @@ impl SelectorsSubcommands { sh_println!("{contract}")?; let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.set_header(["Type", "Signature", "Selector"]); @@ -288,7 +290,7 @@ impl SelectorsSubcommands { table.add_row(["Error", &sig, &hex::encode_prefixed(selector)]); } - sh_println!("{table}")?; + sh_println!("\n{table}\n")?; if artifacts.peek().is_some() { sh_println!()? @@ -320,6 +322,7 @@ impl SelectorsSubcommands { .collect::>(); let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); table.set_header(["Type", "Signature", "Selector", "Contract"]); @@ -365,7 +368,7 @@ impl SelectorsSubcommands { if table.row_count() > 0 { sh_println!("\nFound {} instance(s)...", table.row_count())?; - sh_println!("{table}")?; + sh_println!("\n{table}\n")?; } else { return Err(eyre::eyre!("\nSelector not found in the project.")); } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 75200f4b4..c55eb520a 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -53,13 +53,10 @@ use yansi::Paint; mod filter; mod summary; - -use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; -use summary::TestSummaryReporter; - -use crate::cmd::test::summary::print_invariant_metrics; pub use filter::FilterArgs; use forge::{result::TestKind, traces::render_trace_arena_inner}; +use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; +use summary::{print_invariant_metrics, TestSummaryReport}; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_args); @@ -471,7 +468,7 @@ impl TestArgs { trace!(target: "forge::test", "running all tests"); // If we need to render to a serialized format, we should not print anything else to stdout. - let silent = self.gas_report && shell::is_json(); + let silent = self.gas_report && shell::is_json() || self.summary && shell::is_json(); let num_filtered = runner.matching_test_functions(filter).count(); if num_filtered != 1 && (self.debug.is_some() || self.flamegraph || self.flamechart) { @@ -499,7 +496,7 @@ impl TestArgs { } // Run tests in a non-streaming fashion and collect results for serialization. - if !self.gas_report && shell::is_json() { + if !self.gas_report && !self.summary && shell::is_json() { let mut results = runner.test_collect(filter); results.values_mut().for_each(|suite_result| { for test_result in suite_result.test_results.values_mut() { @@ -609,9 +606,7 @@ impl TestArgs { sh_println!("{}", result.short_result(name))?; // Display invariant metrics if invariant kind. - if let TestKind::Invariant { runs: _, calls: _, reverts: _, metrics } = - &result.kind - { + if let TestKind::Invariant { metrics, .. } = &result.kind { print_invariant_metrics(metrics); } @@ -797,14 +792,13 @@ impl TestArgs { outcome.gas_report = Some(finalized); } - if !silent && !outcome.results.is_empty() { + if !self.summary && !shell::is_json() { sh_println!("{}", outcome.summary(duration))?; + } - if self.summary { - let mut summary_table = TestSummaryReporter::new(self.detailed); - sh_println!("\n\nTest Summary:")?; - summary_table.print_summary(&outcome); - } + if self.summary && !outcome.results.is_empty() { + let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); + sh_println!("{}", &summary_report)?; } // Reattach the task. diff --git a/crates/forge/bin/cmd/test/summary.rs b/crates/forge/bin/cmd/test/summary.rs index 1922ce53b..eabf7bd9e 100644 --- a/crates/forge/bin/cmd/test/summary.rs +++ b/crates/forge/bin/cmd/test/summary.rs @@ -1,71 +1,99 @@ use crate::cmd::test::TestOutcome; -use comfy_table::{ - modifiers::UTF8_ROUND_CORNERS, presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, - Row, Table, -}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Row, Table}; +use foundry_common::reports::{report_kind, ReportKind}; use foundry_evm::executors::invariant::InvariantMetrics; use itertools::Itertools; -use std::collections::HashMap; +use serde_json::json; +use std::{collections::HashMap, fmt::Display}; + +/// Represents a test summary report. +pub struct TestSummaryReport { + /// The kind of report to generate. + report_kind: ReportKind, + /// Whether the report should be detailed. + is_detailed: bool, + /// The test outcome to report. + outcome: TestOutcome, +} -/// A simple summary reporter that prints the test results in a table. -pub struct TestSummaryReporter { - /// The test summary table. - pub(crate) table: Table, - pub(crate) is_detailed: bool, +impl TestSummaryReport { + pub fn new(is_detailed: bool, outcome: TestOutcome) -> Self { + Self { report_kind: report_kind(), is_detailed, outcome } + } } -impl TestSummaryReporter { - pub(crate) fn new(is_detailed: bool) -> Self { +impl Display for TestSummaryReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self.report_kind { + ReportKind::Text => { + writeln!(f, "\n{}", &self.format_table_output(&self.is_detailed, &self.outcome))?; + } + ReportKind::JSON => { + writeln!(f, "{}", &self.format_json_output(&self.is_detailed, &self.outcome))?; + } + } + + Ok(()) + } +} + +impl TestSummaryReport { + // Helper function to format the JSON output. + fn format_json_output(&self, is_detailed: &bool, outcome: &TestOutcome) -> String { + let output = json!({ + "results": outcome.results.iter().map(|(contract, suite)| { + let (suite_path, suite_name) = contract.split_once(':').unwrap(); + let passed = suite.successes().count(); + let failed = suite.failures().count(); + let skipped = suite.skips().count(); + let mut result = json!({ + "suite": suite_name, + "passed": passed, + "failed": failed, + "skipped": skipped, + }); + + if *is_detailed { + result["file_path"] = serde_json::Value::String(suite_path.to_string()); + result["duration"] = serde_json::Value::String(format!("{:.2?}", suite.duration)); + } + + result + }).collect::>(), + }); + + serde_json::to_string_pretty(&output).unwrap() + } + + fn format_table_output(&self, is_detailed: &bool, outcome: &TestOutcome) -> Table { let mut table = Table::new(); table.apply_modifier(UTF8_ROUND_CORNERS); + let mut row = Row::from(vec![ - Cell::new("Test Suite") - .set_alignment(CellAlignment::Center) - .add_attribute(Attribute::Bold), - Cell::new("Passed") - .set_alignment(CellAlignment::Center) - .add_attribute(Attribute::Bold) - .fg(Color::Green), - Cell::new("Failed") - .set_alignment(CellAlignment::Center) - .add_attribute(Attribute::Bold) - .fg(Color::Red), - Cell::new("Skipped") - .set_alignment(CellAlignment::Center) - .add_attribute(Attribute::Bold) - .fg(Color::Yellow), + Cell::new("Test Suite"), + Cell::new("Passed").fg(Color::Green), + Cell::new("Failed").fg(Color::Red), + Cell::new("Skipped").fg(Color::Yellow), ]); - if is_detailed { - row.add_cell( - Cell::new("File Path") - .set_alignment(CellAlignment::Center) - .add_attribute(Attribute::Bold), - ); - row.add_cell( - Cell::new("Duration") - .set_alignment(CellAlignment::Center) - .add_attribute(Attribute::Bold), - ); + if *is_detailed { + row.add_cell(Cell::new("File Path").fg(Color::Cyan)); + row.add_cell(Cell::new("Duration").fg(Color::Cyan)); } table.set_header(row); - Self { table, is_detailed } - } - - pub(crate) fn print_summary(&mut self, outcome: &TestOutcome) { // Traverse the test_results vector and build the table for (contract, suite) in &outcome.results { let mut row = Row::new(); let (suite_path, suite_name) = contract.split_once(':').unwrap(); let passed = suite.successes().count(); - let mut passed_cell = Cell::new(passed).set_alignment(CellAlignment::Center); + let mut passed_cell = Cell::new(passed); let failed = suite.failures().count(); - let mut failed_cell = Cell::new(failed).set_alignment(CellAlignment::Center); + let mut failed_cell = Cell::new(failed); let skipped = suite.skips().count(); - let mut skipped_cell = Cell::new(skipped).set_alignment(CellAlignment::Center); + let mut skipped_cell = Cell::new(skipped); row.add_cell(Cell::new(suite_name)); @@ -89,43 +117,75 @@ impl TestSummaryReporter { row.add_cell(Cell::new(format!("{:.2?}", suite.duration).to_string())); } - self.table.add_row(row); + table.add_row(row); } - let _ = sh_println!("\n{}", self.table); + table } } -/// Helper to create and render invariant metrics summary table: +/// Helper function to print the invariant metrics. +/// +/// ╭-----------------------+----------------+-------+---------+----------╮ /// | Contract | Selector | Calls | Reverts | Discards | -/// |-----------------------|----------------|-------|---------|----------| -/// | AnotherCounterHandler | doWork | 7451 | 123 | 4941 | -/// | AnotherCounterHandler | doWorkThing | 7279 | 137 | 4849 | -/// | CounterHandler | doAnotherThing | 7302 | 150 | 4794 | -/// | CounterHandler | doSomething | 7382 | 160 | 4830 | +/// +=====================================================================+ +/// | AnotherCounterHandler | doWork | 7451 | 123 | 4941 | +/// |-----------------------+----------------+-------+---------+----------| +/// | AnotherCounterHandler | doWorkThing | 7279 | 137 | 4849 | +/// |-----------------------+----------------+-------+---------+----------| +/// | CounterHandler | doAnotherThing | 7302 | 150 | 4794 | +/// |-----------------------+----------------+-------+---------+----------| +/// | CounterHandler | doSomething | 7382 | 160 |4794 | +/// ╰-----------------------+----------------+-------+---------+----------╯ pub(crate) fn print_invariant_metrics(test_metrics: &HashMap) { if !test_metrics.is_empty() { let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(["Contract", "Selector", "Calls", "Reverts", "Discards"]); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Contract"), + Cell::new("Selector"), + Cell::new("Calls").fg(Color::Green), + Cell::new("Reverts").fg(Color::Red), + Cell::new("Discards").fg(Color::Yellow), + ]); for name in test_metrics.keys().sorted() { if let Some((contract, selector)) = name.split_once(':').and_then(|(_, contract)| contract.split_once('.')) { let mut row = Row::new(); - row.add_cell(Cell::new(contract).set_alignment(CellAlignment::Left)); - row.add_cell(Cell::new(selector).set_alignment(CellAlignment::Left)); + row.add_cell(Cell::new(contract)); + row.add_cell(Cell::new(selector)); + if let Some(metrics) = test_metrics.get(name) { - row.add_cell(Cell::new(metrics.calls).set_alignment(CellAlignment::Center)); - row.add_cell(Cell::new(metrics.reverts).set_alignment(CellAlignment::Center)); - row.add_cell(Cell::new(metrics.discards).set_alignment(CellAlignment::Center)); + let calls_cell = Cell::new(metrics.calls).fg(if metrics.calls > 0 { + Color::Green + } else { + Color::White + }); + + let reverts_cell = Cell::new(metrics.reverts).fg(if metrics.reverts > 0 { + Color::Red + } else { + Color::White + }); + + let discards_cell = Cell::new(metrics.discards).fg(if metrics.discards > 0 { + Color::Yellow + } else { + Color::White + }); + + row.add_cell(calls_cell); + row.add_cell(reverts_cell); + row.add_cell(discards_cell); } table.add_row(row); } } - let _ = sh_println!("{table}\n"); + let _ = sh_println!("\n{table}\n"); } } diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index 813480b40..ff3cac46e 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -1,7 +1,7 @@ //! Coverage reports. use alloy_primitives::map::HashMap; -use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Row, Table}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Attribute, Cell, Color, Row, Table}; use evm_disassembler::disassemble_bytes; use foundry_common::fs; use semver::Version; @@ -19,24 +19,31 @@ pub trait CoverageReporter { } /// A simple summary reporter that prints the coverage results in a table. -pub struct SummaryReporter { +pub struct CoverageSummaryReporter { /// The summary table. table: Table, /// The total coverage of the entire project. total: CoverageSummary, } -impl Default for SummaryReporter { +impl Default for CoverageSummaryReporter { fn default() -> Self { let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(["File", "% Lines", "% Statements", "% Branches", "% Funcs"]); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("File"), + Cell::new("% Lines"), + Cell::new("% Statements"), + Cell::new("% Branches"), + Cell::new("% Funcs"), + ]); Self { table, total: CoverageSummary::default() } } } -impl SummaryReporter { +impl CoverageSummaryReporter { fn add_row(&mut self, name: impl Into, summary: CoverageSummary) { let mut row = Row::new(); row.add_cell(name.into()) @@ -48,7 +55,7 @@ impl SummaryReporter { } } -impl CoverageReporter for SummaryReporter { +impl CoverageReporter for CoverageSummaryReporter { fn report(mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, summary) in report.summary_by_file() { self.total.merge(&summary); @@ -56,7 +63,7 @@ impl CoverageReporter for SummaryReporter { } self.add_row("Total", self.total.clone()); - sh_println!("{}", self.table)?; + sh_println!("\n{}", self.table)?; Ok(()) } } diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 7e87af077..dc8527d5e 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -5,13 +5,14 @@ use crate::{ traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; use alloy_primitives::map::HashSet; -use comfy_table::{presets::ASCII_MARKDOWN, *}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; use foundry_common::{ calc, reports::{report_kind, ReportKind}, TestFunctionExt, }; use foundry_evm::traces::CallKind; + use serde::{Deserialize, Serialize}; use serde_json::json; use std::{collections::BTreeMap, fmt::Display}; @@ -156,7 +157,7 @@ impl GasReport { impl Display for GasReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self.report_kind { - ReportKind::Markdown => { + ReportKind::Text => { for (name, contract) in &self.contracts { if contract.functions.is_empty() { trace!(name, "gas report contract without functions"); @@ -164,8 +165,7 @@ impl Display for GasReport { } let table = self.format_table_output(contract, name); - writeln!(f, "{table}")?; - writeln!(f, "\n")?; + writeln!(f, "\n{table}")?; } } ReportKind::JSON => { @@ -214,27 +214,31 @@ impl GasReport { .unwrap() } - // Helper function to format the table output fn format_table_output(&self, contract: &ContractInfo, name: &str) -> Table { let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header([Cell::new(format!("{name} contract")) - .add_attribute(Attribute::Bold) - .fg(Color::Green)]); - - table.add_row([ - Cell::new("Deployment Cost").add_attribute(Attribute::Bold).fg(Color::Cyan), - Cell::new("Deployment Size").add_attribute(Attribute::Bold).fg(Color::Cyan), + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![Cell::new(format!("{name} Contract")).fg(Color::Magenta)]); + + table.add_row(vec![ + Cell::new("Deployment Cost").fg(Color::Cyan), + Cell::new("Deployment Size").fg(Color::Cyan), ]); - table.add_row([contract.gas.to_string(), contract.size.to_string()]); - - table.add_row([ - Cell::new("Function Name").add_attribute(Attribute::Bold).fg(Color::Magenta), - Cell::new("min").add_attribute(Attribute::Bold).fg(Color::Green), - Cell::new("avg").add_attribute(Attribute::Bold).fg(Color::Yellow), - Cell::new("median").add_attribute(Attribute::Bold).fg(Color::Yellow), - Cell::new("max").add_attribute(Attribute::Bold).fg(Color::Red), - Cell::new("# calls").add_attribute(Attribute::Bold), + table.add_row(vec![ + Cell::new(contract.gas.to_string()), + Cell::new(contract.size.to_string()), + ]); + + // Add a blank row to separate deployment info from function info. + table.add_row(vec![Cell::new("")]); + + table.add_row(vec![ + Cell::new("Function Name"), + Cell::new("Min").fg(Color::Green), + Cell::new("Avg").fg(Color::Yellow), + Cell::new("Median").fg(Color::Yellow), + Cell::new("Max").fg(Color::Red), + Cell::new("# Calls").fg(Color::Cyan), ]); contract.functions.iter().for_each(|(fname, sigs)| { @@ -243,8 +247,8 @@ impl GasReport { let display_name = if sigs.len() == 1 { fname.to_string() } else { sig.replace(':', "") }; - table.add_row([ - Cell::new(display_name).add_attribute(Attribute::Bold), + table.add_row(vec![ + Cell::new(display_name), Cell::new(gas_info.min.to_string()).fg(Color::Green), Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), Cell::new(gas_info.median.to_string()).fg(Color::Yellow), diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index ca54ae0d0..76dd718c6 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -73,15 +73,19 @@ contract Dummy { forgetest!(initcode_size_exceeds_limit, |prj, cmd| { prj.add_source("LargeContract", generate_large_contract(5450).as_str()).unwrap(); - cmd.args(["build", "--sizes"]).assert_failure().stdout_eq(str![ - r#" -... + cmd.args(["build", "--sizes"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +╭--------------+------------------+-------------------+--------------------+---------------------╮ | Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | -|--------------|------------------|-------------------|--------------------|---------------------| -| HugeContract | 194 | 49,344 | 24,382 | -192 | -... -"# - ]); ++================================================================================================+ +| HugeContract | 194 | 49,344 | 24,382 | -192 | +╰--------------+------------------+-------------------+--------------------+---------------------╯ + + +"#]]); cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_failure().stdout_eq( str![[r#" @@ -100,15 +104,19 @@ forgetest!(initcode_size_exceeds_limit, |prj, cmd| { forgetest!(initcode_size_limit_can_be_ignored, |prj, cmd| { prj.add_source("LargeContract", generate_large_contract(5450).as_str()).unwrap(); - cmd.args(["build", "--sizes", "--ignore-eip-3860"]).assert_success().stdout_eq(str![ - r#" -... + cmd.args(["build", "--sizes", "--ignore-eip-3860"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +╭--------------+------------------+-------------------+--------------------+---------------------╮ | Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | -|--------------|------------------|-------------------|--------------------|---------------------| -| HugeContract | 194 | 49,344 | 24,382 | -192 | -... -"# - ]); ++================================================================================================+ +| HugeContract | 194 | 49,344 | 24,382 | -192 | +╰--------------+------------------+-------------------+--------------------+---------------------╯ + + +"#]]); cmd.forge_fuse() .args(["build", "--sizes", "--ignore-eip-3860", "--json"]) @@ -145,15 +153,17 @@ forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { ..Default::default() }); - cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![ - r#" + cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" ... + +╭----------+------------------+-------------------+--------------------+---------------------╮ | Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | -|----------|------------------|-------------------|--------------------|---------------------| -| Counter | 236 | 263 | 24,340 | 48,889 | -... -"# - ]); ++============================================================================================+ +| Counter | 236 | 263 | 24,340 | 48,889 | +╰----------+------------------+-------------------+--------------------+---------------------╯ + + +"#]]); cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_success().stdout_eq( str![[r#" diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 35f5c2314..82d819b20 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1584,29 +1584,50 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ - -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ - -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 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( @@ -1668,29 +1689,50 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ - -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ - -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 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( @@ -1752,29 +1794,50 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { 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#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ - -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ - -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 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( @@ -1843,29 +1906,50 @@ forgetest!(gas_report_all_contracts, |prj, cmd| { }); cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ - -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ - -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 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( @@ -1934,13 +2018,22 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 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( @@ -1972,13 +2065,22 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 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( @@ -2013,13 +2115,22 @@ forgetest!(gas_report_some_contracts, |prj, cmd| { cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 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( @@ -2060,21 +2171,36 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ - -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 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( @@ -2127,21 +2253,36 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { cmd.forge_fuse(); cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ - -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 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( @@ -2205,29 +2346,51 @@ forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { .assert_success() .stdout_eq(str![[r#" ... -| src/Contracts.sol:ContractOne contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101532 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | foo | 45370 | 45370 | 45370 | 45370 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ - -| src/Contracts.sol:ContractThree contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| | 101748 | 242 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| | baz | 259210 | 259210 | 259210 | 259210 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ - -| src/Contracts.sol:ContractTwo contract | | | | | | -|----------------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| | 101520 | 241 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| | bar | 64832 | 64832 | 64832 | 64832 | 1 | -... +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + "#]]) .stderr_eq(str![[r#" ... @@ -2348,16 +2511,29 @@ contract CounterTest is DSTest { cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" ... -| src/Counter.sol:Counter contract | | | | | | -|----------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Counter.sol:Counter Contract | | | | | | ++=======================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| | 99711 | 240 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------+-----------------+-------+--------+-------+---------| | a | 2259 | 2259 | 2259 | 2259 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| | b | 2304 | 2304 | 2304 | 2304 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| | setNumber(int256) | 23646 | 33602 | 33602 | 43558 | 2 | +|----------------------------------+-----------------+-------+--------+-------+---------| | setNumber(uint256) | 23601 | 33557 | 33557 | 43513 | 2 | -... +╰----------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + "#]]); cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( str![[r#" @@ -2462,39 +2638,38 @@ contract GasReportFallbackTest is Test { .assert_success() .stdout_eq(str![[r#" ... -Ran 1 test for test/DelegateProxyTest.sol:GasReportFallbackTest -[PASS] test_fallback_gas_report() ([GAS]) -Traces: - [327404] GasReportFallbackTest::test_fallback_gas_report() - ├─ [104475] → new ProxiedContract@[..] - │ └─ ← [Return] 236 bytes of code - ├─ [107054] → new DelegateProxy@[..] - │ └─ ← [Return] 135 bytes of code - ├─ [29384] DelegateProxy::fallback(100) - │ ├─ [3316] ProxiedContract::deposit(100) [delegatecall] - │ │ └─ ← [Stop] - │ └─ ← [Return] - ├─ [21159] DelegateProxy::deposit() - │ └─ ← [Stop] - └─ ← [Stop] - -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] -| test/DelegateProxyTest.sol:DelegateProxy contract | | | | | | -|---------------------------------------------------|-----------------|-------|--------|-------|---------| +╭---------------------------------------------------+-----------------+-------+--------+-------+---------╮ +| test/DelegateProxyTest.sol:DelegateProxy Contract | | | | | | ++========================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| | 107054 | 300 | | | | | -| Function Name | min | avg | median | max | # calls | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| | deposit | 21159 | 21159 | 21159 | 21159 | 1 | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| | fallback | 29384 | 29384 | 29384 | 29384 | 1 | +╰---------------------------------------------------+-----------------+-------+--------+-------+---------╯ - -| test/DelegateProxyTest.sol:ProxiedContract contract | | | | | | -|-----------------------------------------------------|-----------------|------|--------|------|---------| +╭-----------------------------------------------------+-----------------+------+--------+------+---------╮ +| test/DelegateProxyTest.sol:ProxiedContract Contract | | | | | | ++========================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|-----------------------------------------------------+-----------------+------+--------+------+---------| | 104475 | 263 | | | | | -| Function Name | min | avg | median | max | # calls | +|-----------------------------------------------------+-----------------+------+--------+------+---------| +| | | | | | | +|-----------------------------------------------------+-----------------+------+--------+------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-----------------------------------------------------+-----------------+------+--------+------+---------| | deposit | 3316 | 3316 | 3316 | 3316 | 1 | -... +╰-----------------------------------------------------+-----------------+------+--------+------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); @@ -2587,32 +2762,51 @@ contract NestedDeploy is Test { .assert_success() .stdout_eq(str![[r#" ... -Ran 1 test for test/NestedDeployTest.sol:NestedDeploy -[PASS] test_nested_create_gas_report() ([GAS]) -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] -| test/NestedDeployTest.sol:AnotherChild contract | | | | | | -|-------------------------------------------------|-----------------|-------|--------|-------|---------| +╭-------------------------------------------------+-----------------+-------+--------+-------+---------╮ +| test/NestedDeployTest.sol:AnotherChild Contract | | | | | | ++======================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| | 0 | 124 | | | | | -| Function Name | min | avg | median | max | # calls | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| | w | 21161 | 21161 | 21161 | 21161 | 1 | +╰-------------------------------------------------+-----------------+-------+--------+-------+---------╯ - -| test/NestedDeployTest.sol:Child contract | | | | | | -|------------------------------------------|-----------------|-----|--------|-----|---------| +╭------------------------------------------+-----------------+-----+--------+-----+---------╮ +| test/NestedDeployTest.sol:Child Contract | | | | | | ++===========================================================================================+ | Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+-----+--------+-----+---------| | 0 | 477 | | | | | -| Function Name | min | avg | median | max | # calls | +|------------------------------------------+-----------------+-----+--------+-----+---------| +| | | | | | | +|------------------------------------------+-----------------+-----+--------+-----+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+-----+--------+-----+---------| | child | 323 | 323 | 323 | 323 | 1 | +╰------------------------------------------+-----------------+-----+--------+-----+---------╯ - -| test/NestedDeployTest.sol:Parent contract | | | | | | -|-------------------------------------------|-----------------|-----|--------|-----|---------| +╭-------------------------------------------+-----------------+-----+--------+-----+---------╮ +| test/NestedDeployTest.sol:Parent Contract | | | | | | ++============================================================================================+ | Deployment Cost | Deployment Size | | | | | +|-------------------------------------------+-----------------+-----+--------+-----+---------| | 251997 | 739 | | | | | -| Function Name | min | avg | median | max | # calls | +|-------------------------------------------+-----------------+-----+--------+-----+---------| +| | | | | | | +|-------------------------------------------+-----------------+-----+--------+-----+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-------------------------------------------+-----------------+-----+--------+-----+---------| | child | 181 | 181 | 181 | 181 | 1 | -... +╰-------------------------------------------+-----------------+-----+--------+-----+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + "#]]); cmd.forge_fuse() @@ -2973,12 +3167,12 @@ forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { prj.clear_cache(); cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" -[COMPILING_FILES] with [SOLC_VERSION] -[SOLC_VERSION] [ELAPSED] -Compiler run successful! +... +╭----------+------------------+-------------------+--------------------+---------------------╮ | Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | -|----------|------------------|-------------------|--------------------|---------------------| -| Counter | 236 | 263 | 24,340 | 48,889 | ++============================================================================================+ +| Counter | 236 | 263 | 24,340 | 48,889 | +╰----------+------------------+-------------------+--------------------+---------------------╯ "#]]); @@ -3044,24 +3238,42 @@ forgetest_init!(gas_report_include_tests, |prj, cmd| { cmd.args(["test", "--mt", "test_Increment", "--gas-report"]).assert_success().stdout_eq(str![ [r#" ... -| src/Counter.sol:Counter contract | | | | | | -|----------------------------------|-----------------|-------|--------|-------|---------| +╭----------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Counter.sol:Counter Contract | | | | | | ++=======================================================================================+ | Deployment Cost | Deployment Size | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| | 104475 | 263 | | | | | -| Function Name | min | avg | median | max | # calls | +|----------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------+-----------------+-------+--------+-------+---------| | increment | 43401 | 43401 | 43401 | 43401 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| | number | 281 | 281 | 281 | 281 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| | setNumber | 23579 | 23579 | 23579 | 23579 | 1 | +╰----------------------------------+-----------------+-------+--------+-------+---------╯ - -| test/Counter.t.sol:CounterTest contract | | | | | | -|-----------------------------------------|-----------------|--------|--------|--------|---------| +╭-----------------------------------------+-----------------+--------+--------+--------+---------╮ +| test/Counter.t.sol:CounterTest Contract | | | | | | ++================================================================================================+ | Deployment Cost | Deployment Size | | | | | +|-----------------------------------------+-----------------+--------+--------+--------+---------| | 938190 | 4522 | | | | | -| Function Name | min | avg | median | max | # calls | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-----------------------------------------+-----------------+--------+--------+--------+---------| | setUp | 165834 | 165834 | 165834 | 165834 | 1 | +|-----------------------------------------+-----------------+--------+--------+--------+---------| | test_Increment | 52357 | 52357 | 52357 | 52357 | 1 | -... +╰-----------------------------------------+-----------------+--------+--------+--------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#] ]); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index 141a9677d..c840a8036 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -21,11 +21,16 @@ Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) Wrote LCOV report. + +╭----------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|----------------------|---------------|---------------|---------------|---------------| ++======================================================================================+ | script/Counter.s.sol | 0.00% (0/5) | 0.00% (0/3) | 100.00% (0/0) | 0.00% (0/2) | +|----------------------+---------------+---------------+---------------+---------------| | src/Counter.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|----------------------+---------------+---------------+---------------+---------------| | Total | 44.44% (4/9) | 40.00% (2/5) | 100.00% (0/0) | 50.00% (2/4) | +╰----------------------+---------------+---------------+---------------+---------------╯ "# ]]); @@ -222,10 +227,13 @@ contract AContractTest is DSTest { // Assert 100% coverage (init function coverage called in setUp is accounted). cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -316,10 +324,13 @@ contract BContractTest is DSTest { cmd.arg("coverage").arg("--no-match-coverage=AContract").assert_success().stdout_eq(str![[ r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/BContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ "# ]]); @@ -372,10 +383,13 @@ contract AContractTest is DSTest { cmd.arg("coverage").args(["--mt", "testAssertRevertBranch"]).assert_success().stdout_eq(str![ [r#" ... +╭-------------------+--------------+--------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|--------------|--------------|---------------|---------------| ++=================================================================================+ | src/AContract.sol | 66.67% (2/3) | 50.00% (1/2) | 100.00% (0/0) | 100.00% (1/1) | +|-------------------+--------------+--------------+---------------+---------------| | Total | 66.67% (2/3) | 50.00% (1/2) | 100.00% (0/0) | 100.00% (1/1) | +╰-------------------+--------------+--------------+---------------+---------------╯ "#] ]); @@ -384,10 +398,13 @@ contract AContractTest is DSTest { cmd.forge_fuse().arg("coverage").args(["--mt", "testAssertBranch"]).assert_success().stdout_eq( str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (3/3) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]], ); @@ -437,10 +454,13 @@ contract AContractTest is DSTest { // Assert 50% branch coverage if only revert tested. cmd.arg("coverage").args(["--mt", "testRequireRevert"]).assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|--------------|---------------| ++==================================================================================+ | src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+--------------+---------------| | Total | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+--------------+---------------╯ "#]]); @@ -451,20 +471,26 @@ contract AContractTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|--------------|---------------| ++==================================================================================+ | src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+--------------+---------------| | Total | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+--------------+---------------╯ "#]]); // Assert 100% branch coverage. cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -743,10 +769,13 @@ contract FooTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... +╭-------------+----------------+----------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|----------------|----------------|---------------|---------------| ++===============================================================================+ | src/Foo.sol | 91.67% (33/36) | 90.00% (27/30) | 80.00% (8/10) | 100.00% (9/9) | +|-------------+----------------+----------------+---------------+---------------| | Total | 91.67% (33/36) | 90.00% (27/30) | 80.00% (8/10) | 100.00% (9/9) | +╰-------------+----------------+----------------+---------------+---------------╯ "#]]); @@ -757,20 +786,26 @@ contract FooTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... +╭-------------+----------------+----------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|----------------|----------------|---------------|---------------| ++===============================================================================+ | src/Foo.sol | 97.22% (35/36) | 96.67% (29/30) | 90.00% (9/10) | 100.00% (9/9) | +|-------------+----------------+----------------+---------------+---------------| | Total | 97.22% (35/36) | 96.67% (29/30) | 90.00% (9/10) | 100.00% (9/9) | +╰-------------+----------------+----------------+---------------+---------------╯ "#]]); // Assert 100% coverage. cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------+-----------------+-----------------+-----------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|-----------------|-----------------|-----------------|---------------| ++===================================================================================+ | src/Foo.sol | 100.00% (36/36) | 100.00% (30/30) | 100.00% (10/10) | 100.00% (9/9) | +|-------------+-----------------+-----------------+-----------------+---------------| | Total | 100.00% (36/36) | 100.00% (30/30) | 100.00% (10/10) | 100.00% (9/9) | +╰-------------+-----------------+-----------------+-----------------+---------------╯ "#]]); }); @@ -838,10 +873,13 @@ contract AContractTest is DSTest { // Assert 100% coverage. cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+-----------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|-----------------|---------------|---------------|---------------| ++=====================================================================================+ | src/AContract.sol | 100.00% (14/14) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +|-------------------+-----------------+---------------+---------------+---------------| | Total | 100.00% (14/14) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +╰-------------------+-----------------+---------------+---------------+---------------╯ "#]]); }); @@ -937,20 +975,26 @@ contract FooTest is DSTest { // Assert coverage not 100% for happy paths only. cmd.arg("coverage").args(["--mt", "happy"]).assert_success().stdout_eq(str![[r#" ... +╭-------------+----------------+----------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|----------------|----------------|--------------|---------------| ++==============================================================================+ | src/Foo.sol | 75.00% (15/20) | 66.67% (14/21) | 75.00% (3/4) | 100.00% (5/5) | +|-------------+----------------+----------------+--------------+---------------| | Total | 75.00% (15/20) | 66.67% (14/21) | 75.00% (3/4) | 100.00% (5/5) | +╰-------------+----------------+----------------+--------------+---------------╯ "#]]); // Assert 100% branch coverage (including clauses without body). cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------+-----------------+-----------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|-----------------|-----------------|---------------|---------------| ++=================================================================================+ | src/Foo.sol | 100.00% (20/20) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | +|-------------+-----------------+-----------------+---------------+---------------| | Total | 100.00% (20/20) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | +╰-------------+-----------------+-----------------+---------------+---------------╯ "#]]); }); @@ -1050,10 +1094,13 @@ contract FooTest is DSTest { cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------+-----------------+-----------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|-----------------|-----------------|---------------|---------------| ++=================================================================================+ | src/Foo.sol | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +|-------------+-----------------+-----------------+---------------+---------------| | Total | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +╰-------------+-----------------+-----------------+---------------+---------------╯ "#]]); }); @@ -1140,10 +1187,13 @@ contract FooTest is DSTest { cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------+-----------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------|-----------------|---------------|---------------|---------------| ++===============================================================================+ | src/Foo.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +|-------------+-----------------+---------------+---------------+---------------| | Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +╰-------------+-----------------+---------------+---------------+---------------╯ "#]]); }); @@ -1194,10 +1244,13 @@ contract AContractTest is DSTest { // Assert 50% coverage for true branches. cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" ... +╭-------------------+--------------+--------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|--------------|--------------|--------------|---------------| ++================================================================================+ | src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| | Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ "#]]); @@ -1208,20 +1261,26 @@ contract AContractTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... +╭-------------------+--------------+--------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|--------------|--------------|--------------|---------------| ++================================================================================+ | src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| | Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ "#]]); // Assert 100% coverage (true/false branches properly covered). cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -1278,10 +1337,13 @@ contract AContractTest is DSTest { // Assert 50% coverage for true branches. cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" ... +╭-------------------+--------------+--------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|--------------|--------------|--------------|---------------| ++================================================================================+ | src/AContract.sol | 80.00% (4/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| | Total | 80.00% (4/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ "#]]); @@ -1292,20 +1354,26 @@ contract AContractTest is DSTest { .assert_success() .stdout_eq(str![[r#" ... +╭-------------------+--------------+--------------+--------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|--------------|--------------|--------------|---------------| ++================================================================================+ | src/AContract.sol | 60.00% (3/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| | Total | 60.00% (3/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ "#]]); // Assert 100% coverage (true/false branches properly covered). cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -1371,10 +1439,13 @@ contract AContractTest is DSTest { cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+-----------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|-----------------|---------------|---------------|---------------| ++=====================================================================================+ | src/AContract.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | +|-------------------+-----------------+---------------+---------------+---------------| | Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | +╰-------------------+-----------------+---------------+---------------+---------------╯ "#]]); }); @@ -1421,10 +1492,13 @@ contract AContractTest is DSTest { cmd.arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -1489,10 +1563,13 @@ end_of_record // Assert there's only one function (`increment`) reported. cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (1/1) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (1/1) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -1562,10 +1639,13 @@ end_of_record cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" ... +╭-------------------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|---------------|---------------|---------------|---------------| ++===================================================================================+ | src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| | Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ "#]]); }); @@ -1596,10 +1676,13 @@ contract AContract { // Assert coverage doesn't fail with `Error: Unknown key "inliner"`. cmd.arg("coverage").arg("--ir-minimum").assert_success().stdout_eq(str![[r#" ... +╭-------------------+-------------+--------------+---------------+-------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------------------|-------------|--------------|---------------|-------------| ++==============================================================================+ | src/AContract.sol | 0.00% (0/5) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | +|-------------------+-------------+--------------+---------------+-------------| | Total | 0.00% (0/5) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | +╰-------------------+-------------+--------------+---------------+-------------╯ "#]]); }); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index 31da29d21..5e0273195 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -30,16 +30,24 @@ Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) // Make sure inline config is parsed in coverage too. cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" -... +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Analysing contracts... +Running tests... + Ran 2 tests for test/inline.sol:Inline [PASS] test1(bool) (runs: 2, [AVG_GAS]) [PASS] test2(bool) (runs: 3, [AVG_GAS]) Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +╭-------+---------------+---------------+---------------+---------------╮ | File | % Lines | % Statements | % Branches | % Funcs | -|-------|---------------|---------------|---------------|---------------| ++=======================================================================+ | Total | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | +╰-------+---------------+---------------+---------------+---------------╯ "#]]); }); diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 76afd5b36..ab04ae7b5 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -929,24 +929,38 @@ contract AnotherCounterHandler is Test { cmd.args(["test", "--mt", "invariant_"]).assert_success().stdout_eq(str![[r#" ... -Ran 2 tests for test/SelectorMetricsTest.t.sol:CounterTest [PASS] invariant_counter() (runs: 10, calls: 5000, reverts: [..]) + +╭-----------------------+----------------+-------+---------+----------╮ | Contract | Selector | Calls | Reverts | Discards | -|-----------------------|----------------|-------|---------|----------| -| AnotherCounterHandler | doWork | [..] | [..] | [..] | -| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | -| CounterHandler | doAnotherThing | [..] | [..] | [..] | -| CounterHandler | doSomething | [..] | [..] | [..] | ++=====================================================================+ +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doSomething | [..] | [..] | [..] | +╰-----------------------+----------------+-------+---------+----------╯ [PASS] invariant_counter2() (runs: 10, calls: 5000, reverts: [..]) + +╭-----------------------+----------------+-------+---------+----------╮ | Contract | Selector | Calls | Reverts | Discards | -|-----------------------|----------------|-------|---------|----------| -| AnotherCounterHandler | doWork | [..] | [..] | [..] | -| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | -| CounterHandler | doAnotherThing | [..] | [..] | [..] | -| CounterHandler | doSomething | [..] | [..] | [..] | ++=====================================================================+ +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doSomething | [..] | [..] | [..] | +╰-----------------------+----------------+-------+---------+----------╯ + +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) -... "#]]); }); From c161c7c9ed5f939adca5e88ff279654ae37c4a3d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 5 Dec 2024 11:11:37 +0100 Subject: [PATCH 066/107] fix: force `prevrandao` on Moonbeam networks (#9489) * chore: force prevrandao * add test for fix * fix forge fmt --------- Co-authored-by: zerosnacks Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/evm/core/src/utils.rs | 11 +++++++-- crates/forge/tests/it/repros.rs | 3 +++ crates/forge/tests/it/test_helpers.rs | 1 + testdata/default/repros/Issue4232.t.sol | 31 +++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 testdata/default/repros/Issue4232.t.sol diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 5ec396a16..9a1dd8910 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -6,7 +6,7 @@ use crate::{ use alloy_consensus::BlockHeader; use alloy_json_abi::{Function, JsonAbi}; use alloy_network::AnyTxEnvelope; -use alloy_primitives::{Address, Selector, TxKind, U256}; +use alloy_primitives::{Address, Selector, TxKind, B256, U256}; use alloy_provider::{network::BlockResponse, Network}; use alloy_rpc_types::{Transaction, TransactionRequest}; use foundry_config::NamedChain; @@ -34,11 +34,12 @@ pub fn apply_chain_and_block_specific_env_changes( env: &mut revm::primitives::Env, block: &N::BlockResponse, ) { + use NamedChain::*; if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { let block_number = block.header().number(); match chain { - NamedChain::Mainnet => { + Mainnet => { // after merge difficulty is supplanted with prevrandao EIP-4399 if block_number >= 15_537_351u64 { env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); @@ -46,6 +47,12 @@ pub fn apply_chain_and_block_specific_env_changes( return; } + Moonbeam | Moonbase | Moonriver | MoonbeamDev => { + if env.block.prevrandao.is_none() { + // + env.block.prevrandao = Some(B256::random()); + } + } c if c.is_arbitrum() => { // on arbitrum `block.number` is the L1 block which is included in the // `l1BlockNumber` field diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 0e4adbbc2..96708bdf5 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -180,6 +180,9 @@ test_repro!(3753); // https://github.com/foundry-rs/foundry/issues/3792 test_repro!(3792); +// https://github.com/foundry-rs/foundry/issues/4232 +test_repro!(4232); + // https://github.com/foundry-rs/foundry/issues/4402 test_repro!(4402); diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 3c8500772..af957ccdc 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -346,6 +346,7 @@ pub fn rpc_endpoints() -> RpcEndpoints { ("arbitrum", RpcEndpoint::Url(next_rpc_endpoint(NamedChain::Arbitrum))), ("polygon", RpcEndpoint::Url(next_rpc_endpoint(NamedChain::Polygon))), ("avaxTestnet", RpcEndpoint::Url("https://api.avax-test.network/ext/bc/C/rpc".into())), + ("moonbeam", RpcEndpoint::Url("https://moonbeam-rpc.publicnode.com".into())), ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".into())), ]) } diff --git a/testdata/default/repros/Issue4232.t.sol b/testdata/default/repros/Issue4232.t.sol new file mode 100644 index 000000000..0ac6a77c7 --- /dev/null +++ b/testdata/default/repros/Issue4232.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/4232 +contract Issue4232Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testFork() public { + // Smoke test, worked previously as well + vm.createSelectFork("sepolia", 7215400); + vm.assertFalse(block.prevrandao == 0); + + // Would previously fail with: + // [FAIL: backend: failed while inspecting; header validation error: `prevrandao` not set; `prevrandao` not set; ] setUp() (gas: 0) + // + // Related fix: + // Moonbeam | Moonbase | Moonriver | MoonbeamDev => { + // if env.block.prevrandao.is_none() { + // // + // env.block.prevrandao = Some(B256::random()); + // } + // } + // + // Note: public RPC node used for `moonbeam` discards state quickly so we need to fork against the latest block + vm.createSelectFork("moonbeam"); + vm.assertFalse(block.prevrandao == 0); + } +} From 8c033184c8705d1a382ad190dbb552cb4ca7acd5 Mon Sep 17 00:00:00 2001 From: Maxim Andreev Date: Thu, 5 Dec 2024 13:16:38 +0300 Subject: [PATCH 067/107] chore(cast): upgrade evmole to 0.6.1, use new style API (#9493) --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- crates/cast/src/lib.rs | 25 ++++++++++++++++++++----- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51a6b0268..caaae0464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3265,9 +3265,9 @@ dependencies = [ [[package]] name = "evmole" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fcfb15a14bc209e2b3d2bd32291ec445b1e348d7d9d986aa61a09149350fd7" +checksum = "f58e21c69e0ae62877b65241d25cae9e28477818482fab8c1101d15289725a46" dependencies = [ "alloy-dyn-abi", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 0fa4b4bdb..0518bab79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -224,7 +224,7 @@ alloy-chains = "0.1" alloy-rlp = "0.3" alloy-trie = "0.6.0" -## op-alloy +## op-alloy op-alloy-rpc-types = "0.7.1" op-alloy-consensus = "0.7.1" @@ -260,7 +260,7 @@ color-eyre = "0.6" comfy-table = "7" dunce = "1" evm-disassembler = "0.5" -evmole = "0.5" +evmole = "0.6" eyre = "0.6" figment = "0.10" futures = "0.3" diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index b5b719e39..01d7da6ef 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -2103,13 +2103,28 @@ impl SimpleCast { /// ``` pub fn extract_functions(bytecode: &str) -> Result> { let code = hex::decode(strip_0x(bytecode))?; - Ok(evmole::function_selectors(&code, 0) + let info = evmole::contract_info( + evmole::ContractInfoArgs::new(&code) + .with_selectors() + .with_arguments() + .with_state_mutability(), + ); + Ok(info + .functions + .expect("functions extraction was requested") .into_iter() - .map(|s| { + .map(|f| { ( - hex::encode_prefixed(s), - evmole::function_arguments(&code, &s, 0), - evmole::function_state_mutability(&code, &s, 0).as_json_str(), + hex::encode_prefixed(f.selector), + f.arguments + .expect("arguments extraction was requested") + .into_iter() + .map(|t| t.sol_type_name().to_string()) + .collect::>() + .join(","), + f.state_mutability + .expect("state_mutability extraction was requested") + .as_json_str(), ) }) .collect()) From e264381e8728eeee4cdf5b6d103655e60493a4f3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 5 Dec 2024 21:33:37 +0100 Subject: [PATCH 068/107] chore: bump compilers 0.12.7 (#9498) --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index caaae0464..8ba03cf80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -3864,9 +3864,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f601e89e36e2b82d3bc4a517287190846c990c1fd9f7b6f745bcbe65947e9" +checksum = "7235826f00dd9196bcbdbb9c168ea38235601db95883a78819ba2303dee34bb8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3901,9 +3901,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c32fbf90c191e5037818d9d42d85642d6da2ff6528283b1efb2a88b08a02a5d" +checksum = "097bc5db7be5acf6d92938ad7daabf1932d7aa7c44326cdfc256531a53034d31" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3911,9 +3911,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103d2d76d62a11b03d19e26fa6fbd996d2fac487a804392f57f96f0f677c469d" +checksum = "b4168053c1ad217866c677a074517e8d51988e5b1bad044b95f3c513aa5b6caa" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3935,9 +3935,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cafd2720bd20428822421bfbf34cfb33806be75e1b0c6ffcd6a4b25d6ead16" +checksum = "1b7beffe7182551d01d249f022a5eab17c36c73b39ae8efd404e0fb9c98b9f80" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3950,9 +3950,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327f11092bf779c76b3fa588ebf37c2e3bb903d55ba49f8e73b69e4cb888d0cb" +checksum = "ac5247875b96dfb99da12d0cd0f6ce98954116d1cf8a9188d613b2a35cd6937b" dependencies = [ "alloy-primitives", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 0518bab79..7c4f565e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -169,7 +169,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.9.0", default-features = false } -foundry-compilers = { version = "0.12.5", default-features = false } +foundry-compilers = { version = "0.12.7", default-features = false } foundry-fork-db = "0.8.0" solang-parser = "=0.3.3" solar-ast = { version = "=0.1.0", default-features = false } From 4f9f904a4780422cb7b0bfd6d4f425fc3aaea957 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:16:33 +0100 Subject: [PATCH 069/107] chore(deps): bump breaking deps (#9502) --- Cargo.lock | 92 +++++++++++++--------------- Cargo.toml | 2 +- crates/chisel/Cargo.toml | 2 +- crates/chisel/src/solidity_helper.rs | 4 +- crates/debugger/Cargo.toml | 2 +- crates/fmt/Cargo.toml | 2 +- crates/fmt/src/helpers.rs | 10 +-- crates/forge/Cargo.toml | 8 +-- 8 files changed, 56 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ba03cf80..a175015f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" @@ -995,7 +995,7 @@ dependencies = [ "serde_repr", "similar-asserts", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "tikv-jemallocator", "tokio", "tower 0.4.13", @@ -1026,7 +1026,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1052,7 +1052,7 @@ dependencies = [ "pin-project 1.1.7", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio-util", "tower-http", "tracing", @@ -1075,9 +1075,9 @@ dependencies = [ [[package]] name = "ariadne" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44055e597c674aef7cb903b2b9f6e4cba1277ed0d2d61dae7cd52d7ffa81f8e2" +checksum = "31beedec3ce83ae6da3a79592b3d8d7afd146a5b15bb9bb940279aced60faa89" dependencies = [ "unicode-width 0.1.14", "yansi", @@ -3498,7 +3498,7 @@ dependencies = [ "strum", "svm-rs", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "tikv-jemallocator", "tokio", "toml 0.8.19", @@ -3531,7 +3531,7 @@ dependencies = [ "serde", "serde_json", "solang-parser", - "thiserror 1.0.69", + "thiserror 2.0.3", "toml 0.8.19", "tracing", ] @@ -3546,7 +3546,7 @@ dependencies = [ "itertools 0.13.0", "similar-asserts", "solang-parser", - "thiserror 1.0.69", + "thiserror 2.0.3", "toml 0.8.19", "tracing", "tracing-subscriber", @@ -3734,7 +3734,7 @@ dependencies = [ "revm-inspectors", "semver 1.0.23", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.3", "toml 0.8.19", "tracing", "vergen", @@ -3833,7 +3833,7 @@ dependencies = [ "serde_json", "similar-asserts", "terminal_size", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "tower 0.4.13", "tracing", @@ -3999,7 +3999,7 @@ dependencies = [ "similar-asserts", "solang-parser", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "toml 0.8.19", "toml_edit", "tracing", @@ -4048,7 +4048,7 @@ dependencies = [ "revm", "revm-inspectors", "serde", - "thiserror 1.0.69", + "thiserror 2.0.3", "tracing", ] @@ -4094,7 +4094,7 @@ dependencies = [ "revm-inspectors", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "tracing", "url", @@ -4137,7 +4137,7 @@ dependencies = [ "rand", "revm", "serde", - "thiserror 1.0.69", + "thiserror 2.0.3", "tracing", ] @@ -4200,7 +4200,7 @@ dependencies = [ "alloy-primitives", "foundry-compilers", "semver 1.0.23", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -4262,7 +4262,7 @@ dependencies = [ "gcloud-sdk", "rpassword", "serde", - "thiserror 1.0.69", + "thiserror 2.0.3", "tokio", "tracing", ] @@ -5421,17 +5421,16 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "inferno" -version = "0.11.21" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" +checksum = "75a5d75fee4d36809e6b021e4b96b686e763d365ffdb03af2bd00786353f84fe" dependencies = [ "ahash", - "is-terminal", "itoa", "log", "num-format", "once_cell", - "quick-xml 0.26.0", + "quick-xml 0.37.1", "rgb", "str_stack", ] @@ -7195,15 +7194,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.37.1" @@ -7332,23 +7322,23 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ "bitflags 2.6.0", "cassowary", "compact_str", "crossterm", + "indoc", "instability", "itertools 0.13.0", "lru", "paste", "strum", - "strum_macros", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] @@ -7880,9 +7870,9 @@ dependencies = [ [[package]] name = "rustyline" -version = "14.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -7892,12 +7882,12 @@ dependencies = [ "libc", "log", "memchr", - "nix 0.28.0", + "nix 0.29.0", "radix_trie", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width 0.2.0", "utf8parse", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -9887,9 +9877,9 @@ dependencies = [ [[package]] name = "watchexec" -version = "4.1.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c635816bdb583dcd1cf58935899df38b5c5ffb1b9d0cc89f8d3c7b33e2c005e3" +checksum = "81e682bb1fe9526a6c78ffcfc6bb662ab36c213764fdd173babfbaf05cc56254" dependencies = [ "async-priority-channel", "async-recursion", @@ -9897,7 +9887,7 @@ dependencies = [ "futures", "ignore-files", "miette", - "nix 0.28.0", + "nix 0.29.0", "normalize-path", "notify", "once_cell", @@ -9913,34 +9903,34 @@ dependencies = [ [[package]] name = "watchexec-events" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce015ba32ff91a7f796cea3798e7998d3645411f03fc373ef0e7c7e564291bc" +checksum = "2404ed3aa5e4a8f6139a2ee137926886c9144234c945102143ef9bf65309a751" dependencies = [ - "nix 0.28.0", + "nix 0.29.0", "notify", "watchexec-signals", ] [[package]] name = "watchexec-signals" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f7ccc54db7df8cbbe3251508321e46986ce179af4c4a03b4c70bda539d72755" +checksum = "be07d7855a3617d996ce0c7df4b6232159c526634dff668dd95491c22a9a7262" dependencies = [ "miette", - "nix 0.28.0", + "nix 0.29.0", "thiserror 1.0.69", ] [[package]] name = "watchexec-supervisor" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97efb9292bebdf72a777a0d6e400b69b32b4f3daee1ddd30214317a18ff20ab" +checksum = "6026815bdc9653d7820f6499b83ecadacd97a804dfabf2b2c55b061557f5f1f4" dependencies = [ "futures", - "nix 0.28.0", + "nix 0.29.0", "process-wrap", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 7c4f565e1..2967d8573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -239,7 +239,7 @@ quote = "1.0" syn = "2.0" async-trait = "0.1" derive_more = { version = "1.0", features = ["full"] } -thiserror = "1" +thiserror = "2" # bench divan = "0.1" diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 5f098817c..167329d45 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -53,7 +53,7 @@ eyre.workspace = true regex.workspace = true reqwest.workspace = true revm.workspace = true -rustyline = "14" +rustyline = "15" semver.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index c56f9e9eb..465b7b535 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -9,7 +9,7 @@ use crate::{ }; use rustyline::{ completion::Completer, - highlight::Highlighter, + highlight::{CmdKind, Highlighter}, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, @@ -188,7 +188,7 @@ impl Highlighter for SolidityHelper { self.highlight(line) } - fn highlight_char(&self, line: &str, pos: usize, _forced: bool) -> bool { + fn highlight_char(&self, line: &str, pos: usize, _kind: CmdKind) -> bool { pos == line.len() } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 4fb417db5..4cf86e20a 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -23,7 +23,7 @@ alloy-primitives.workspace = true crossterm = "0.28" eyre.workspace = true -ratatui = { version = "0.28", default-features = false, features = [ +ratatui = { version = "0.29", default-features = false, features = [ "crossterm", ] } revm.workspace = true diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index 0bc3e06a6..bc1f44fdc 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -17,7 +17,7 @@ foundry-config.workspace = true alloy-primitives.workspace = true -ariadne = "0.4" +ariadne = "0.5" itertools.workspace = true solang-parser.workspace = true thiserror.workspace = true diff --git a/crates/fmt/src/helpers.rs b/crates/fmt/src/helpers.rs index 7f05a9c09..1d036ba6b 100644 --- a/crates/fmt/src/helpers.rs +++ b/crates/fmt/src/helpers.rs @@ -97,20 +97,20 @@ pub fn format_diagnostics_report( path.map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_default(); let mut s = Vec::new(); for diag in diagnostics { - let (start, end) = (diag.loc.start(), diag.loc.end()); - let mut report = Report::build(ReportKind::Error, &filename, start) + let span = (filename.as_str(), diag.loc.start()..diag.loc.end()); + let mut report = Report::build(ReportKind::Error, span.clone()) .with_message(format!("{:?}", diag.ty)) .with_label( - Label::new((&filename, start..end)) + Label::new(span) .with_color(Color::Red) - .with_message(format!("{}", diag.message.as_str().fg(Color::Red))), + .with_message(diag.message.as_str().fg(Color::Red)), ); for note in &diag.notes { report = report.with_note(¬e.message); } - report.finish().write((&filename, Source::from(content)), &mut s).unwrap(); + report.finish().write((filename.as_str(), Source::from(content)), &mut s).unwrap(); } String::from_utf8(s).unwrap() } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 208ea8430..cbbfa814d 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -80,7 +80,7 @@ dialoguer = { version = "0.11", default-features = false } dunce.workspace = true futures.workspace = true indicatif = "0.17" -inferno = { version = "0.11", default-features = false } +inferno = { version = "0.12", default-features = false } itertools.workspace = true parking_lot.workspace = true regex = { workspace = true, default-features = false } @@ -96,9 +96,9 @@ thiserror.workspace = true tokio = { workspace = true, features = ["time"] } toml = { workspace = true, features = ["preserve_order"] } toml_edit = "0.22" -watchexec = "4.1" -watchexec-events = "3.0" -watchexec-signals = "3.0" +watchexec = "5.0" +watchexec-events = "4.0" +watchexec-signals = "4.0" clearscreen = "3.0" evm-disassembler.workspace = true From 43a033d39a42e7e329db482570933471829edd03 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:34:58 +0100 Subject: [PATCH 070/107] chore: improve Retry usage and warning (#9503) --- crates/common/src/retry.rs | 33 +++++++++---- crates/forge/tests/cli/verify.rs | 10 ++-- crates/verify/src/etherscan/mod.rs | 78 ++++++++++++++---------------- crates/verify/src/retry.rs | 7 +-- crates/verify/src/sourcify.rs | 16 +++--- crates/verify/src/verify.rs | 2 +- 6 files changed, 79 insertions(+), 67 deletions(-) diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index 59ba2055f..b79e095ce 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -16,23 +16,28 @@ pub enum RetryError { #[derive(Clone, Debug)] pub struct Retry { retries: u32, - delay: Option, + delay: Duration, } impl Retry { /// Creates a new `Retry` instance. - pub fn new(retries: u32, delay: Option) -> Self { + pub fn new(retries: u32, delay: Duration) -> Self { Self { retries, delay } } + /// Creates a new `Retry` instance with no delay between retries. + pub fn new_no_delay(retries: u32) -> Self { + Self::new(retries, Duration::ZERO) + } + /// Runs the given closure in a loop, retrying if it fails up to the specified number of times. pub fn run Result, T>(mut self, mut callback: F) -> Result { loop { match callback() { Err(e) if self.retries > 0 => { self.handle_err(e); - if let Some(delay) = self.delay { - std::thread::sleep(delay); + if !self.delay.is_zero() { + std::thread::sleep(self.delay); } } res => return res, @@ -51,8 +56,8 @@ impl Retry { match callback().await { Err(e) if self.retries > 0 => { self.handle_err(e); - if let Some(delay) = self.delay { - tokio::time::sleep(delay).await; + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; } } res => return res, @@ -71,8 +76,8 @@ impl Retry { match callback().await { Err(RetryError::Retry(e)) if self.retries > 0 => { self.handle_err(e); - if let Some(delay) = self.delay { - tokio::time::sleep(delay).await; + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; } } Err(RetryError::Retry(e) | RetryError::Break(e)) => return Err(e), @@ -82,7 +87,17 @@ impl Retry { } fn handle_err(&mut self, err: Error) { + debug_assert!(self.retries > 0); self.retries -= 1; - let _ = sh_warn!("{} ({} tries remaining)", err.root_cause(), self.retries); + let _ = sh_warn!( + "{msg}{delay} ({retries} tries remaining)", + msg = crate::errors::display_chain(&err), + delay = if self.delay.is_zero() { + String::new() + } else { + format!("; waiting {} seconds before trying again", self.delay.as_secs()) + }, + retries = self.retries, + ); } } diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index 154c74e30..60a794477 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -75,9 +75,8 @@ contract Verify is Unique { #[allow(clippy::disallowed_macros)] fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Result<()> { - // give etherscan some time to verify the contract - let retry = Retry::new(retries, Some(Duration::from_secs(30))); - retry.run(|| -> eyre::Result<()> { + // Give Etherscan some time to verify the contract. + Retry::new(retries, Duration::from_secs(30)).run(|| -> eyre::Result<()> { let output = cmd.execute(); let out = String::from_utf8_lossy(&output.stdout); println!("{out}"); @@ -94,9 +93,8 @@ fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Resul fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { let guid = { - // give etherscan some time to detect the transaction - let retry = Retry::new(5, Some(Duration::from_secs(60))); - retry + // Give Etherscan some time to detect the transaction. + Retry::new(5, Duration::from_secs(60)) .run(|| -> eyre::Result { let output = cmd.execute(); let out = String::from_utf8_lossy(&output.stdout); diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 78f39fe96..26832e3b4 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -15,14 +15,10 @@ use foundry_block_explorers::{ Client, }; use foundry_cli::utils::{get_provider, read_constructor_args_file, LoadConfig}; -use foundry_common::{ - abi::encode_function_args, - retry::{Retry, RetryError}, -}; +use foundry_common::{abi::encode_function_args, retry::RetryError}; use foundry_compilers::{artifacts::BytecodeObject, Artifact}; use foundry_config::{Chain, Config}; use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; -use futures::FutureExt; use regex::Regex; use semver::{BuildMetadata, Version}; use std::{fmt::Debug, sync::LazyLock}; @@ -77,8 +73,9 @@ impl VerificationProvider for EtherscanVerificationProvider { trace!(?verify_args, "submitting verification request"); - let retry: Retry = args.retry.into(); - let resp = retry + let resp = args + .retry + .into_retry() .run_async(|| async { sh_println!( "\nSubmitting verification for [{}] {}.", @@ -157,48 +154,45 @@ impl VerificationProvider for EtherscanVerificationProvider { args.etherscan.key().as_deref(), &config, )?; - let retry: Retry = args.retry.into(); - retry - .run_async_until_break(|| { - async { - let resp = etherscan - .check_contract_verification_status(args.id.clone()) - .await - .wrap_err("Failed to request verification status") - .map_err(RetryError::Retry)?; - - trace!(?resp, "Received verification response"); - - let _ = sh_println!( - "Contract verification status:\nResponse: `{}`\nDetails: `{}`", - resp.message, - resp.result - ); + args.retry + .into_retry() + .run_async_until_break(|| async { + let resp = etherscan + .check_contract_verification_status(args.id.clone()) + .await + .wrap_err("Failed to request verification status") + .map_err(RetryError::Retry)?; - if resp.result == "Pending in queue" { - return Err(RetryError::Retry(eyre!("Verification is still pending...",))) - } + trace!(?resp, "Received verification response"); - if resp.result == "Unable to verify" { - return Err(RetryError::Retry(eyre!("Unable to verify.",))) - } + let _ = sh_println!( + "Contract verification status:\nResponse: `{}`\nDetails: `{}`", + resp.message, + resp.result + ); - if resp.result == "Already Verified" { - let _ = sh_println!("Contract source code already verified"); - return Ok(()) - } + if resp.result == "Pending in queue" { + return Err(RetryError::Retry(eyre!("Verification is still pending..."))) + } - if resp.status == "0" { - return Err(RetryError::Break(eyre!("Contract failed to verify.",))) - } + if resp.result == "Unable to verify" { + return Err(RetryError::Retry(eyre!("Unable to verify."))) + } - if resp.result == "Pass - Verified" { - let _ = sh_println!("Contract successfully verified"); - } + if resp.result == "Already Verified" { + let _ = sh_println!("Contract source code already verified"); + return Ok(()) + } - Ok(()) + if resp.status == "0" { + return Err(RetryError::Break(eyre!("Contract failed to verify."))) + } + + if resp.result == "Pass - Verified" { + let _ = sh_println!("Contract successfully verified"); } - .boxed() + + Ok(()) }) .await .wrap_err("Checking verification result failed") diff --git a/crates/verify/src/retry.rs b/crates/verify/src/retry.rs index 6067d9d85..a01b1c945 100644 --- a/crates/verify/src/retry.rs +++ b/crates/verify/src/retry.rs @@ -35,9 +35,10 @@ impl Default for RetryArgs { } } -impl From for Retry { - fn from(r: RetryArgs) -> Self { - Self::new(r.retries, Some(Duration::from_secs(r.delay as u64))) +impl RetryArgs { + /// Converts the arguments into a `Retry` instance. + pub fn into_retry(self) -> Retry { + Retry::new(self.retries, Duration::from_secs(self.delay as u64)) } } diff --git a/crates/verify/src/sourcify.rs b/crates/verify/src/sourcify.rs index b215fd1af..a57335d39 100644 --- a/crates/verify/src/sourcify.rs +++ b/crates/verify/src/sourcify.rs @@ -5,7 +5,7 @@ use crate::{ use alloy_primitives::map::HashMap; use async_trait::async_trait; use eyre::Result; -use foundry_common::{fs, retry::Retry}; +use foundry_common::fs; use futures::FutureExt; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -36,8 +36,9 @@ impl VerificationProvider for SourcifyVerificationProvider { let client = reqwest::Client::new(); - let retry: Retry = args.retry.into(); - let resp = retry + let resp = args + .retry + .into_retry() .run_async(|| { async { sh_println!( @@ -56,7 +57,9 @@ impl VerificationProvider for SourcifyVerificationProvider { if !status.is_success() { let error: serde_json::Value = response.json().await?; eyre::bail!( - "Sourcify verification request for address ({}) failed with status code {status}\nDetails: {error:#}", + "Sourcify verification request for address ({}) \ + failed with status code {status}\n\ + Details: {error:#}", args.address, ); } @@ -72,8 +75,9 @@ impl VerificationProvider for SourcifyVerificationProvider { } async fn check(&self, args: VerifyCheckArgs) -> Result<()> { - let retry: Retry = args.retry.into(); - let resp = retry + let resp = args + .retry + .into_retry() .run_async(|| { async { let url = Url::from_str( diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index 5a8efe4c6..de7779ac3 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -50,7 +50,7 @@ impl Default for VerifierArgs { } } -/// CLI arguments for `forge verify`. +/// CLI arguments for `forge verify-contract`. #[derive(Clone, Debug, Parser)] pub struct VerifyArgs { /// The address of the contract to verify. From 92cd1650cedfe64b0985e224fcba7ebac38ba382 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:35:19 +0100 Subject: [PATCH 071/107] fix: restore lock version 3 (#9501) From e52076714ace23c7a68e14f0048a40be3c6c8f0b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 5 Dec 2024 23:35:47 +0100 Subject: [PATCH 072/107] chore(deps): remove async_recursion (#9500) Native async recursion was stabilized in 1.77. --- Cargo.lock | 1 - crates/script/Cargo.toml | 1 - crates/script/src/execute.rs | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a175015f1..37369e34e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3568,7 +3568,6 @@ dependencies = [ "alloy-serde", "alloy-signer", "alloy-transport", - "async-recursion", "clap", "dialoguer", "dunce", diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index e1a6428d9..4322bbb0e 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -35,7 +35,6 @@ clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } semver.workspace = true futures.workspace = true tokio.workspace = true -async-recursion = "1.1" itertools.workspace = true parking_lot.workspace = true diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 3e6cc30a1..4aad978d5 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -12,7 +12,6 @@ use alloy_primitives::{ }; use alloy_provider::Provider; use alloy_rpc_types::TransactionInput; -use async_recursion::async_recursion; use eyre::{OptionExt, Result}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; @@ -101,7 +100,6 @@ pub struct PreExecutionState { impl PreExecutionState { /// Executes the script and returns the state after execution. /// Might require executing script twice in cases when we determine sender from execution. - #[async_recursion] pub async fn execute(mut self) -> Result { let mut runner = self .script_config @@ -127,7 +125,7 @@ impl PreExecutionState { build_data: self.build_data.build_data, }; - return state.link().await?.prepare_execution().await?.execute().await; + return Box::pin(state.link().await?.prepare_execution().await?.execute()).await; } Ok(ExecutedState { From 63484d0a65c56e3378cc3f282ed962d5d499a490 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:04:05 +0530 Subject: [PATCH 073/107] feat(`cheatcodes`): count assertion for `expectRevert` (#9484) * expectRevert count overload boilerplate * introduce `count` variable * populate `ExpectedRevert` for count overloads * intro `actual_count` and make ExpectedRevert mut * increment `actual_account` on success and tests * handle non-zero count reverts separately * handle count for specific reverts * nit * more tests * fix: handle count > 1 with reverter specified * test: ExpectRevertCountWithReverter * expectRevert with reverter and count 0 * nit * reverter count with data * nit * cleanup * nit * nit * clippy * nit * cargo cheats --- crates/cheatcodes/assets/cheatcodes.json | 120 +++++++++ crates/cheatcodes/spec/src/vm.rs | 24 ++ crates/cheatcodes/src/inspector.rs | 40 ++- crates/cheatcodes/src/test/expect.rs | 273 ++++++++++++++++----- testdata/cheats/Vm.sol | 6 + testdata/default/cheats/ExpectRevert.t.sol | 214 +++++++++++++++- 6 files changed, 609 insertions(+), 68 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index d8f8d21df..54556043a 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5051,6 +5051,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "expectRevert_10", + "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data.", + "declaration": "function expectRevert(bytes4 revertData, address reverter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4,address,uint64)", + "selector": "0xb0762d73", + "selectorBytes": [ + 176, + 118, + 45, + 115 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_11", + "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data.", + "declaration": "function expectRevert(bytes calldata revertData, address reverter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes,address,uint64)", + "selector": "0xd345fb1f", + "selectorBytes": [ + 211, + 69, + 251, + 31 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "expectRevert_2", @@ -5131,6 +5171,86 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "expectRevert_6", + "description": "Expects a `count` number of reverts from the upcoming calls with any revert data or reverter.", + "declaration": "function expectRevert(uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(uint64)", + "selector": "0x4ee38244", + "selectorBytes": [ + 78, + 227, + 130, + 68 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_7", + "description": "Expects a `count` number of reverts from the upcoming calls that match the revert data.", + "declaration": "function expectRevert(bytes4 revertData, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4,uint64)", + "selector": "0xe45ca72d", + "selectorBytes": [ + 228, + 92, + 167, + 45 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_8", + "description": "Expects a `count` number of reverts from the upcoming calls that exactly match the revert data.", + "declaration": "function expectRevert(bytes calldata revertData, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes,uint64)", + "selector": "0x4994c273", + "selectorBytes": [ + 73, + 148, + 194, + 115 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_9", + "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address.", + "declaration": "function expectRevert(address reverter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(address,uint64)", + "selector": "0x1ff5f952", + "selectorBytes": [ + 31, + 245, + 249, + 82 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "expectSafeMemory", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 6b66d31dd..6954fd1e5 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1019,6 +1019,30 @@ interface Vm { #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes calldata revertData, address reverter) external; + /// Expects a `count` number of reverts from the upcoming calls with any revert data or reverter. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls that match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes4 revertData, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls that exactly match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes calldata revertData, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls from the reverter address. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(address reverter, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes4 revertData, address reverter, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes calldata revertData, address reverter, uint64 count) external; + /// Expects an error on next call that starts with the revert data. #[cheatcode(group = Testing, safety = Unsafe)] function expectPartialRevert(bytes4 revertData) external; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 4e2b91b9d..329b89d0e 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -754,16 +754,23 @@ where { if ecx.journaled_state.depth() <= expected_revert.depth && matches!(expected_revert.kind, ExpectedRevertKind::Default) { - let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match expect::handle_expect_revert( + let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + let handler_result = expect::handle_expect_revert( false, true, - &expected_revert, + &mut expected_revert, outcome.result.result, outcome.result.output.clone(), &self.config.available_artifacts, - ) { + ); + + return match handler_result { Ok((address, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } + outcome.result.result = InstructionResult::Return; outcome.result.output = retdata; outcome.address = address; @@ -1302,6 +1309,14 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { expected_revert.reverted_by.is_none() { expected_revert.reverted_by = Some(call.target_address); + } else if outcome.result.is_revert() && + expected_revert.reverter.is_some() && + expected_revert.reverted_by.is_some() && + expected_revert.count > 1 + { + // If we're expecting more than one revert, we need to reset the reverted_by address + // to latest reverter. + expected_revert.reverted_by = Some(call.target_address); } if ecx.journaled_state.depth() <= expected_revert.depth { @@ -1315,15 +1330,20 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { }; if needs_processing { - let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match expect::handle_expect_revert( + // Only `remove` the expected revert from state if `expected_revert.count` == + // `expected_revert.actual_count` + let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + + let handler_result = expect::handle_expect_revert( cheatcode_call, false, - &expected_revert, + &mut expected_revert, outcome.result.result, outcome.result.output.clone(), &self.config.available_artifacts, - ) { + ); + + return match handler_result { Err(error) => { trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); outcome.result.result = InstructionResult::Revert; @@ -1331,6 +1351,10 @@ impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { outcome } Ok((_, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } outcome.result.result = InstructionResult::Return; outcome.result.output = retdata; outcome diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 3ee58407a..a3ddef8c1 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -87,6 +87,10 @@ pub struct ExpectedRevert { pub reverter: Option

, /// Actual reverter of the call. pub reverted_by: Option
, + /// Number of times this revert is expected. + pub count: u64, + /// Actual number of times this revert has been seen. + pub actual_count: u64, } #[derive(Clone, Debug)] @@ -295,7 +299,7 @@ impl Cheatcode for expectEmitAnonymous_3Call { impl Cheatcode for expectRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None) + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, 1) } } @@ -309,6 +313,7 @@ impl Cheatcode for expectRevert_1Call { false, false, None, + 1, ) } } @@ -323,6 +328,7 @@ impl Cheatcode for expectRevert_2Call { false, false, None, + 1, ) } } @@ -337,6 +343,7 @@ impl Cheatcode for expectRevert_3Call { false, false, Some(*reverter), + 1, ) } } @@ -351,6 +358,7 @@ impl Cheatcode for expectRevert_4Call { false, false, Some(*reverter), + 1, ) } } @@ -365,6 +373,89 @@ impl Cheatcode for expectRevert_5Call { false, false, Some(*reverter), + 1, + ) + } +} + +impl Cheatcode for expectRevert_6Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { count } = self; + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, *count) + } +} + +impl Cheatcode for expectRevert_7Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, count } = self; + expect_revert( + ccx.state, + Some(revertData.as_ref()), + ccx.ecx.journaled_state.depth(), + false, + false, + None, + *count, + ) + } +} + +impl Cheatcode for expectRevert_8Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, count } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + false, + false, + None, + *count, + ) + } +} + +impl Cheatcode for expectRevert_9Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { reverter, count } = self; + expect_revert( + ccx.state, + None, + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + *count, + ) + } +} + +impl Cheatcode for expectRevert_10Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter, count } = self; + expect_revert( + ccx.state, + Some(revertData.as_ref()), + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + *count, + ) + } +} + +impl Cheatcode for expectRevert_11Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter, count } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + *count, ) } } @@ -379,6 +470,7 @@ impl Cheatcode for expectPartialRevert_0Call { false, true, None, + 1, ) } } @@ -393,13 +485,14 @@ impl Cheatcode for expectPartialRevert_1Call { false, true, Some(*reverter), + 1, ) } } impl Cheatcode for _expectCheatcodeRevert_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None) + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None, 1) } } @@ -413,6 +506,7 @@ impl Cheatcode for _expectCheatcodeRevert_1Call { true, false, None, + 1, ) } } @@ -427,6 +521,7 @@ impl Cheatcode for _expectCheatcodeRevert_2Call { true, false, None, + 1, ) } } @@ -662,6 +757,7 @@ fn expect_revert( cheatcode: bool, partial_match: bool, reverter: Option
, + count: u64, ) -> Result { ensure!( state.expected_revert.is_none(), @@ -678,6 +774,8 @@ fn expect_revert( partial_match, reverter, reverted_by: None, + count, + actual_count: 0, }); Ok(Default::default()) } @@ -685,7 +783,7 @@ fn expect_revert( pub(crate) fn handle_expect_revert( is_cheatcode: bool, is_create: bool, - expected_revert: &ExpectedRevert, + expected_revert: &mut ExpectedRevert, status: InstructionResult, retdata: Bytes, known_contracts: &Option, @@ -698,72 +796,117 @@ pub(crate) fn handle_expect_revert( } }; - ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); - - // If expected reverter address is set then check it matches the actual reverter. - if let (Some(expected_reverter), Some(actual_reverter)) = - (expected_revert.reverter, expected_revert.reverted_by) - { - if expected_reverter != actual_reverter { - return Err(fmt_err!( - "Reverter != expected reverter: {} != {}", - actual_reverter, - expected_reverter - )); + let stringify = |data: &[u8]| { + if let Ok(s) = String::abi_decode(data, true) { + return s; } - } - - let expected_reason = expected_revert.reason.as_deref(); - // If None, accept any revert. - let Some(expected_reason) = expected_reason else { - return Ok(success_return()); + if data.is_ascii() { + return std::str::from_utf8(data).unwrap().to_owned(); + } + hex::encode_prefixed(data) }; - if !expected_reason.is_empty() && retdata.is_empty() { - bail!("call reverted as expected, but without data"); - } - - let mut actual_revert: Vec = retdata.into(); + if expected_revert.count == 0 { + if expected_revert.reverter.is_none() && expected_revert.reason.is_none() { + ensure!( + matches!(status, return_ok!()), + "call reverted when it was expected not to revert" + ); + return Ok(success_return()); + } - // Compare only the first 4 bytes if partial match. - if expected_revert.partial_match && actual_revert.get(..4) == expected_reason.get(..4) { - return Ok(success_return()) - } + // Flags to track if the reason and reverter match. + let mut reason_match = expected_revert.reason.as_ref().map(|_| false); + let mut reverter_match = expected_revert.reverter.as_ref().map(|_| false); - // Try decoding as known errors. - if matches!( - actual_revert.get(..4).map(|s| s.try_into().unwrap()), - Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR) - ) { - if let Ok(decoded) = Vec::::abi_decode(&actual_revert[4..], false) { - actual_revert = decoded; + // Reverter check + if let (Some(expected_reverter), Some(actual_reverter)) = + (expected_revert.reverter, expected_revert.reverted_by) + { + if expected_reverter == actual_reverter { + reverter_match = Some(true); + } } - } - if actual_revert == expected_reason || - (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some()) - { - Ok(success_return()) + // Reason check + let expected_reason = expected_revert.reason.as_deref(); + if let Some(expected_reason) = expected_reason { + let mut actual_revert: Vec = retdata.into(); + actual_revert = decode_revert(actual_revert); + + if actual_revert == expected_reason { + reason_match = Some(true); + } + }; + + match (reason_match, reverter_match) { + (Some(true), Some(true)) => Err(fmt_err!( + "expected 0 reverts with reason: {}, from address: {}, but got one", + &stringify(expected_reason.unwrap_or_default()), + expected_revert.reverter.unwrap() + )), + (Some(true), None) => Err(fmt_err!( + "expected 0 reverts with reason: {}, but got one", + &stringify(expected_reason.unwrap_or_default()) + )), + (None, Some(true)) => Err(fmt_err!( + "expected 0 reverts from address: {}, but got one", + expected_revert.reverter.unwrap() + )), + _ => Ok(success_return()), + } } else { - let (actual, expected) = if let Some(contracts) = known_contracts { - let decoder = RevertDecoder::new().with_abis(contracts.iter().map(|(_, c)| &c.abi)); - ( - &decoder.decode(actual_revert.as_slice(), Some(status)), - &decoder.decode(expected_reason, Some(status)), - ) + ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); + + // If expected reverter address is set then check it matches the actual reverter. + if let (Some(expected_reverter), Some(actual_reverter)) = + (expected_revert.reverter, expected_revert.reverted_by) + { + if expected_reverter != actual_reverter { + return Err(fmt_err!( + "Reverter != expected reverter: {} != {}", + actual_reverter, + expected_reverter + )); + } + } + + let expected_reason = expected_revert.reason.as_deref(); + // If None, accept any revert. + let Some(expected_reason) = expected_reason else { + return Ok(success_return()); + }; + + if !expected_reason.is_empty() && retdata.is_empty() { + bail!("call reverted as expected, but without data"); + } + + let mut actual_revert: Vec = retdata.into(); + + // Compare only the first 4 bytes if partial match. + if expected_revert.partial_match && actual_revert.get(..4) == expected_reason.get(..4) { + return Ok(success_return()) + } + + // Try decoding as known errors. + actual_revert = decode_revert(actual_revert); + + if actual_revert == expected_reason || + (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some()) + { + Ok(success_return()) } else { - let stringify = |data: &[u8]| { - if let Ok(s) = String::abi_decode(data, true) { - return s; - } - if data.is_ascii() { - return std::str::from_utf8(data).unwrap().to_owned(); - } - hex::encode_prefixed(data) + let (actual, expected) = if let Some(contracts) = known_contracts { + let decoder = RevertDecoder::new().with_abis(contracts.iter().map(|(_, c)| &c.abi)); + ( + &decoder.decode(actual_revert.as_slice(), Some(status)), + &decoder.decode(expected_reason, Some(status)), + ) + } else { + (&stringify(&actual_revert), &stringify(expected_reason)) }; - (&stringify(&actual_revert), &stringify(expected_reason)) - }; - Err(fmt_err!("Error != expected error: {} != {}", actual, expected,)) + Err(fmt_err!("Error != expected error: {} != {}", actual, expected,)) + } } } @@ -774,3 +917,15 @@ fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) offsets.push(start..end); Ok(Default::default()) } + +fn decode_revert(revert: Vec) -> Vec { + if matches!( + revert.get(..4).map(|s| s.try_into().unwrap()), + Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR) + ) { + if let Ok(decoded) = Vec::::abi_decode(&revert[4..], false) { + return decoded; + } + } + revert +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index f6f66969f..b3746b6bc 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -246,10 +246,16 @@ interface Vm { function expectPartialRevert(bytes4 revertData, address reverter) external; function expectRevert() external; function expectRevert(bytes4 revertData) external; + function expectRevert(bytes4 revertData, address reverter, uint64 count) external; + function expectRevert(bytes calldata revertData, address reverter, uint64 count) external; function expectRevert(bytes calldata revertData) external; function expectRevert(address reverter) external; function expectRevert(bytes4 revertData, address reverter) external; function expectRevert(bytes calldata revertData, address reverter) external; + function expectRevert(uint64 count) external; + function expectRevert(bytes4 revertData, uint64 count) external; + function expectRevert(bytes calldata revertData, uint64 count) external; + function expectRevert(address reverter, uint64 count) external; function expectSafeMemory(uint64 min, uint64 max) external; function expectSafeMemoryCall(uint64 min, uint64 max) external; function fee(uint256 newBasefee) external; diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol index 18a90bac6..fef4ebaf5 100644 --- a/testdata/default/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -30,6 +30,10 @@ contract Reverter { revert(message); } + function callThenNoRevert(Dummy dummy) public pure { + dummy.callMe(); + } + function revertWithoutReason() public pure { revert(); } @@ -188,7 +192,7 @@ contract ExpectRevertTest is DSTest { } function testexpectCheatcodeRevert() public { - vm._expectCheatcodeRevert("JSON value at \".a\" is not an object"); + vm._expectCheatcodeRevert('JSON value at ".a" is not an object'); vm.parseJsonKeys('{"a": "b"}', ".a"); } @@ -351,3 +355,211 @@ contract ExpectRevertWithReverterTest is DSTest { aContract.callAndRevert(); } } + +contract ExpectRevertCount is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRevertCountAny() public { + uint64 count = 3; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert2"); + reverter.revertWithMessage("revert3"); + + vm.expectRevert("revert"); + reverter.revertWithMessage("revert"); + } + + function testFailRevertCountAny() public { + uint64 count = 3; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert2"); + } + + function testNoRevert() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.doNotRevert(); + } + + function testFailNoRevert() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.revertWithMessage("revert"); + } + + function testRevertCountSpecific() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert"); + } + + function testFailReverCountSpecifc() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("second-revert"); + } + + function testNoRevertSpecific() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.doNotRevert(); + } + + function testFailNoRevertSpecific() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert"); + } + + function testNoRevertSpecificButDiffRevert() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert2"); + } + + function testRevertCountWithConstructor() public { + uint64 count = 1; + vm.expectRevert("constructor revert", count); + new ConstructorReverter("constructor revert"); + } + + function testNoRevertWithConstructor() public { + uint64 count = 0; + vm.expectRevert("constructor revert", count); + new CContract(); + } + + function testRevertCountNestedSpecific() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Reverter inner = new Reverter(); + + vm.expectRevert("nested revert", count); + reverter.revertWithMessage("nested revert"); + reverter.nestedRevert(inner, "nested revert"); + + vm.expectRevert("nested revert", count); + reverter.nestedRevert(inner, "nested revert"); + reverter.nestedRevert(inner, "nested revert"); + } + + function testRevertCountCallsThenReverts() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + + vm.expectRevert("called a function and then reverted", count); + reverter.callThenRevert(dummy, "called a function and then reverted"); + reverter.callThenRevert(dummy, "called a function and then reverted"); + } + + function testFailRevertCountCallsThenReverts() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + + vm.expectRevert("called a function and then reverted", count); + reverter.callThenRevert(dummy, "called a function and then reverted"); + reverter.callThenRevert(dummy, "wrong revert"); + } + + function testNoRevertCall() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + + vm.expectRevert("called a function and then reverted", count); + reverter.callThenNoRevert(dummy); + } +} + +contract ExpectRevertCountWithReverter is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRevertCountWithReverter() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert"); + } + + function testFailRevertCountWithReverter() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.revertWithMessage("revert"); + reverter2.revertWithMessage("revert"); + } + + function testNoRevertWithReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.doNotRevert(); + } + + function testNoRevertWithWrongReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter2.revertWithMessage("revert"); // revert from wrong reverter + } + + function testFailNoRevertWithReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.revertWithMessage("revert"); + } + + function testReverterCountWithData() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert"); + } + + function testFailReverterCountWithWrongData() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("wrong revert"); + } + + function testFailWrongReverterCountWithData() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert"); + reverter2.revertWithMessage("revert"); + } + + function testNoReverterCountWithData() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.doNotRevert(); + + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert2"); + } +} From 00efa0d5965269149f374ba142fb1c3c7edd6c94 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:03:32 +0200 Subject: [PATCH 074/107] feat(cheatcodes): add `vm.getStateDiff` to get state diffs as string (#9435) * feat(cheatcodes): add vm.getStateDiff() to get state diffs as string * Nit arrow * Add json output * Better json format * Rename to original and dirty * Changes after review: split in 2 cheatcodes, rename to prev/newValues * Slots as hex strings, add balance diffs, cleanup * Record balance diffs only if changed. Add nonce diff placeholder * Backoff nonce placeholder --- Cargo.lock | 1 + crates/cheatcodes/Cargo.toml | 1 + crates/cheatcodes/assets/cheatcodes.json | 40 +++++ crates/cheatcodes/spec/src/vm.rs | 8 + crates/cheatcodes/src/evm.rs | 143 +++++++++++++++++- testdata/cheats/Vm.sol | 2 + .../cheats/RecordAccountAccesses.t.sol | 37 +++++ 7 files changed, 231 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 37369e34e..857f0a915 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3732,6 +3732,7 @@ dependencies = [ "revm", "revm-inspectors", "semver 1.0.23", + "serde", "serde_json", "thiserror 2.0.3", "toml 0.8.19", diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index a06416160..e358fdce7 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -68,3 +68,4 @@ toml = { workspace = true, features = ["preserve_order"] } tracing.workspace = true walkdir.workspace = true proptest.workspace = true +serde.workspace = true diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 54556043a..82a7de2aa 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5791,6 +5791,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getStateDiff", + "description": "Returns state diffs from current `vm.startStateDiffRecording` session.", + "declaration": "function getStateDiff() external view returns (string memory diff);", + "visibility": "external", + "mutability": "view", + "signature": "getStateDiff()", + "selector": "0x80df01cc", + "selectorBytes": [ + 128, + 223, + 1, + 204 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getStateDiffJson", + "description": "Returns state diffs from current `vm.startStateDiffRecording` session, in json format.", + "declaration": "function getStateDiffJson() external view returns (string memory diff);", + "visibility": "external", + "mutability": "view", + "signature": "getStateDiffJson()", + "selector": "0xf54fe009", + "selectorBytes": [ + 245, + 79, + 224, + 9 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getWallets", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 6954fd1e5..4bc8c9b03 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -384,6 +384,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); + /// Returns state diffs from current `vm.startStateDiffRecording` session. + #[cheatcode(group = Evm, safety = Safe)] + function getStateDiff() external view returns (string memory diff); + + /// Returns state diffs from current `vm.startStateDiffRecording` session, in json format. + #[cheatcode(group = Evm, safety = Safe)] + function getStateDiffJson() external view returns (string memory diff); + // -------- Recording Map Writes -------- /// Starts recording all map SSTOREs for later retrieval. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 723beb1cf..6c1a47185 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -18,10 +18,15 @@ use foundry_evm_core::{ use foundry_evm_traces::StackSnapshotType; use rand::Rng; use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; -use std::{collections::BTreeMap, path::Path}; +use std::{ + collections::{btree_map::Entry, BTreeMap}, + fmt::Display, + path::Path, +}; mod record_debug_step; use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace}; +use serde::Serialize; mod fork; pub(crate) mod mapping; @@ -76,6 +81,70 @@ pub struct DealRecord { pub new_balance: U256, } +/// Storage slot diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct SlotStateDiff { + /// Initial storage value. + previous_value: B256, + /// Current storage value. + new_value: B256, +} + +/// Balance diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct BalanceDiff { + /// Initial storage value. + previous_value: U256, + /// Current storage value. + new_value: U256, +} + +/// Account state diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct AccountStateDiffs { + /// Address label, if any set. + label: Option, + /// Account balance changes. + balance_diff: Option, + /// State changes, per slot. + state_diff: BTreeMap, +} + +impl Display for AccountStateDiffs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> { + // Print changed account. + if let Some(label) = &self.label { + writeln!(f, "label: {label}")?; + } + // Print balance diff if changed. + if let Some(balance_diff) = &self.balance_diff { + if balance_diff.previous_value != balance_diff.new_value { + writeln!( + f, + "- balance diff: {} → {}", + balance_diff.previous_value, balance_diff.new_value + )?; + } + } + // Print state diff if any. + if !&self.state_diff.is_empty() { + writeln!(f, "- state diff:")?; + for (slot, slot_changes) in &self.state_diff { + writeln!( + f, + "@ {slot}: {} → {}", + slot_changes.previous_value, slot_changes.new_value + )?; + } + } + + Ok(()) + } +} + impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; @@ -683,6 +752,25 @@ impl Cheatcode for stopAndReturnStateDiffCall { } } +impl Cheatcode for getStateDiffCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let mut diffs = String::new(); + let state_diffs = get_recorded_state_diffs(state); + for (address, state_diffs) in state_diffs { + diffs.push_str(&format!("{address}\n")); + diffs.push_str(&format!("{state_diffs}\n")); + } + Ok(diffs.abi_encode()) + } +} + +impl Cheatcode for getStateDiffJsonCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let state_diffs = get_recorded_state_diffs(state); + Ok(serde_json::to_string(&state_diffs)?.abi_encode()) + } +} + impl Cheatcode for broadcastRawTransactionCall { fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let tx = TxEnvelope::decode(&mut self.data.as_ref()) @@ -1044,3 +1132,56 @@ fn genesis_account(account: &Account) -> GenesisAccount { private_key: None, } } + +/// Helper function to returns state diffs recorded for each changed account. +fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap { + let mut state_diffs: BTreeMap = BTreeMap::default(); + if let Some(records) = &state.recorded_account_diffs_stack { + records + .iter() + .flatten() + .filter(|account_access| { + !account_access.storageAccesses.is_empty() || + account_access.oldBalance != account_access.newBalance + }) + .for_each(|account_access| { + let account_diff = + state_diffs.entry(account_access.account).or_insert(AccountStateDiffs { + label: state.labels.get(&account_access.account).cloned(), + ..Default::default() + }); + + // Record account balance diffs. + if account_access.oldBalance != account_access.newBalance { + // Update balance diff. Do not overwrite the initial balance if already set. + if let Some(diff) = &mut account_diff.balance_diff { + diff.new_value = account_access.newBalance; + } else { + account_diff.balance_diff = Some(BalanceDiff { + previous_value: account_access.oldBalance, + new_value: account_access.newBalance, + }); + } + } + + // Record account state diffs. + for storage_access in &account_access.storageAccesses { + if storage_access.isWrite && !storage_access.reverted { + // Update state diff. Do not overwrite the initial value if already set. + match account_diff.state_diff.entry(storage_access.slot) { + Entry::Vacant(slot_state_diff) => { + slot_state_diff.insert(SlotStateDiff { + previous_value: storage_access.previousValue, + new_value: storage_access.newValue, + }); + } + Entry::Occupied(mut slot_state_diff) => { + slot_state_diff.get_mut().new_value = storage_access.newValue; + } + } + } + } + }); + } + state_diffs +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index b3746b6bc..bdbb68e37 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -283,6 +283,8 @@ interface Vm { function getNonce(address account) external view returns (uint64 nonce); function getNonce(Wallet calldata wallet) external returns (uint64 nonce); function getRecordedLogs() external returns (Log[] memory logs); + function getStateDiff() external view returns (string memory diff); + function getStateDiffJson() external view returns (string memory diff); function getWallets() external returns (address[] memory wallets); function indexOf(string calldata input, string calldata key) external pure returns (uint256); function isContext(ForgeContext context) external view returns (bool result); diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index 98b5843b2..8de7bcdc5 100644 --- a/testdata/default/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.18; import "ds-test/test.sol"; import "cheats/Vm.sol"; +import "../logs/console.sol"; /// @notice Helper contract with a construction that makes a call to itself then /// optionally reverts if zero-length data is passed @@ -261,6 +262,16 @@ contract RecordAccountAccessesTest is DSTest { two.write(bytes32(uint256(5678)), bytes32(uint256(123469))); two.write(bytes32(uint256(5678)), bytes32(uint256(1234))); + string memory diffs = cheats.getStateDiff(); + assertEq( + "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n", + diffs + ); + string memory diffsJson = cheats.getStateDiffJson(); + assertEq( + "{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}", + diffsJson + ); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 4, "incorrect length"); @@ -332,6 +343,15 @@ contract RecordAccountAccessesTest is DSTest { // contract calls to self in constructor SelfCaller caller = new SelfCaller{value: 2 ether}("hello2 world2"); + assertEq( + "0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n0x1d1499e622D69689cdf9004d05Ec547d650Ff211\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n\n", + cheats.getStateDiff() + ); + assertEq( + "{\"0x000000000000000000000000000000000000162e\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0xde0b6b3a7640000\"},\"stateDiff\":{}},\"0x1d1499e622d69689cdf9004d05ec547d650ff211\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x1bc16d674ec80000\"},\"stateDiff\":{}}}", + cheats.getStateDiffJson() + ); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 6); assertEq( @@ -451,6 +471,14 @@ contract RecordAccountAccessesTest is DSTest { uint256 initBalance = address(this).balance; cheats.startStateDiffRecording(); try this.revertingCall{value: 1 ether}(address(1234), "") {} catch {} + assertEq( + "0x00000000000000000000000000000000000004d2\n- balance diff: 0 \xE2\x86\x92 100000000000000000\n\n", + cheats.getStateDiff() + ); + assertEq( + "{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"stateDiff\":{}}}", + cheats.getStateDiffJson() + ); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 2); assertEq( @@ -768,6 +796,15 @@ contract RecordAccountAccessesTest is DSTest { function testNestedStorage() public { cheats.startStateDiffRecording(); nestedStorer.run(); + cheats.label(address(nestedStorer), "NestedStorer"); + assertEq( + "0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n", + cheats.getStateDiff() + ); + assertEq( + "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"balanceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}", + cheats.getStateDiffJson() + ); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); From 2e56b8f63beeffab36d8c6f8b7563b9e92601f71 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:35:56 +0200 Subject: [PATCH 075/107] fix(verify): cached artifacts by version (#9520) * fix(verify): cached artifacts by version * Comments --- crates/verify/src/verify.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index de7779ac3..c2ac28770 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -20,6 +20,7 @@ use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, C use itertools::Itertools; use reqwest::Url; use revm_primitives::HashSet; +use semver::BuildMetadata; use std::path::PathBuf; use crate::provider::VerificationContext; @@ -275,7 +276,7 @@ impl VerifyArgs { let cache = project.read_cache_file().ok(); - let version = if let Some(ref version) = self.compiler_version { + let mut version = if let Some(ref version) = self.compiler_version { version.trim_start_matches('v').parse()? } else if let Some(ref solc) = config.solc { match solc { @@ -321,7 +322,21 @@ impl VerifyArgs { let profiles = entry .artifacts .get(&contract.name) - .and_then(|artifacts| artifacts.get(&version)) + .and_then(|artifacts| { + let mut cached_artifacts = artifacts.get(&version); + // If we try to verify with specific build version and no cached artifacts + // found, then check if we have artifacts cached for same version but + // without any build metadata. + // This could happen when artifacts are built / cached + // with a version like `0.8.20` but verify is using a compiler-version arg + // as `0.8.20+commit.a1b79de6`. + // See . + if cached_artifacts.is_none() && version.build != BuildMetadata::EMPTY { + version.build = BuildMetadata::EMPTY; + cached_artifacts = artifacts.get(&version); + } + cached_artifacts + }) .map(|artifacts| artifacts.keys().collect::>()) .unwrap_or_default(); From aa69ed1e46dd61fbf9d73399396a4db4dd527431 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:06:46 +0100 Subject: [PATCH 076/107] ci: move deny to test (#9524) --- .github/workflows/deny.yml | 26 -------------------------- .github/workflows/test.yml | 8 ++++++++ 2 files changed, 8 insertions(+), 26 deletions(-) delete mode 100644 .github/workflows/deny.yml diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml deleted file mode 100644 index e8e4d5b84..000000000 --- a/.github/workflows/deny.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: deny - -on: - push: - branches: [master] - paths: [Cargo.lock, deny.toml] - pull_request: - branches: [master] - paths: [Cargo.lock, deny.toml] - -env: - CARGO_TERM_COLOR: always - -jobs: - cargo-deny: - name: cargo deny check - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check all - # Clear out arguments to not pass `--all-features` to `cargo deny`. - # many crates have an `openssl` feature which enables banned dependencies - arguments: "" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5a2eabb0..3ca0daa17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -110,6 +110,13 @@ jobs: cache-on-failure: true - run: cargo hack check + deny: + uses: ithacaxyz/ci/.github/workflows/deny.yml@main + with: + # Clear out arguments to not pass `--all-features` to `cargo deny`. + # Many crates have an `openssl` feature which enables banned dependencies. + deny-flags: "" + ci-success: runs-on: ubuntu-latest if: always() @@ -122,6 +129,7 @@ jobs: - rustfmt - forge-fmt - crate-checks + - deny timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed From fd9ee169e911c97f0c127a79ce8501e42b8ea4fc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 10 Dec 2024 13:18:07 +0100 Subject: [PATCH 077/107] chore: bump alloy (#9496) * chore: bump alloy * chore: bump alloy 0.8 * use prim sig --- Cargo.lock | 482 +++++++++---------- Cargo.toml | 55 +-- crates/anvil/core/src/eth/transaction/mod.rs | 4 +- crates/cast/bin/cmd/wallet/mod.rs | 2 +- crates/common/fmt/src/ui.rs | 2 +- 5 files changed, 273 insertions(+), 272 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 857f0a915..a69b2ddf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" @@ -86,15 +86,15 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1ff8439834ab71a4b0ecd1a8ff80b3921c87615f158940c3364f399c732786" +checksum = "8ba14856660f31807ebb26ce8f667e814c72694e1077e97ef102e326ad580f3f" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", - "alloy-trie 0.7.4", + "alloy-trie", "auto_impl", "c-kzg", "derive_more", @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519a86faaa6729464365a90c04eba68539b6d3a30f426edb4b3dafd78920d42f" +checksum = "28666307e76441e7af37a2b90cde7391c28112121bea59f4e0d804df8b20057e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -118,9 +118,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca2b353d8b7f160dc930dfa174557acefece6deab5ecd7e6230d38858579eea" +checksum = "f3510769905590b8991a8e63a5e0ab4aa72cf07a13ab5fbe23f12f4454d161da" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -134,14 +134,14 @@ dependencies = [ "alloy-transport", "futures", "futures-util", - "thiserror 1.0.69", + "thiserror 2.0.4", ] [[package]] name = "alloy-dyn-abi" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dedb328c2114284f767e075589ca9de8d5e9c8a91333402f4804a584ed71a38" +checksum = "47e922d558006ba371681d484d12aa73fe673d84884f83747730af7433c0e86d" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -206,20 +206,21 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4841e8dd4e0f53d76b501fd4c6bc21d95d688bc8ebf0ea359fc6c7ab65b48742" +checksum = "5dca170827a7ca156b43588faebf9e9d27c27d0fb07cab82cfd830345e2b24f5" dependencies = [ "alloy-primitives", "alloy-serde", + "alloy-trie", "serde", ] [[package]] name = "alloy-json-abi" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -229,23 +230,23 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "254f770918f96dc4ec88a15e6e2e243358e1719d66b40ef814428e7697079d25" +checksum = "9335278f50b0273e0a187680ee742bb6b154a948adf036f448575bacc5ccb315" dependencies = [ "alloy-primitives", "alloy-sol-types", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", "tracing", ] [[package]] name = "alloy-network" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931dd176c6e33355f3dc0170ec69cf5b951f4d73870b276e2c837ab35f9c5136" +checksum = "ad4e6ad4230df8c4a254c20f8d6a84ab9df151bfca13f463177dbc96571cc1f8" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -263,14 +264,14 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", ] [[package]] name = "alloy-network-primitives" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6ec0f23be233e851e31c5e4badfedfa9c7bc177bc37f4e03616072cd40a806" +checksum = "c4df88a2f8020801e0fefce79471d3946d39ca3311802dbbd0ecfdeee5e972e3" dependencies = [ "alloy-consensus", "alloy-eips", @@ -281,9 +282,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bce85f0f67b2248c2eb42941bb75079ac53648569a668e8bfd7de5a831ec64" +checksum = "2db5cefbc736b2b26a960dcf82279c70a03695dd11a0032a6dc27601eeb29182" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -291,16 +292,16 @@ dependencies = [ "rand", "serde_json", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.4", "tracing", "url", ] [[package]] name = "alloy-primitives" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "arbitrary", @@ -313,7 +314,7 @@ dependencies = [ "getrandom", "hashbrown 0.15.2", "hex-literal", - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "k256", "keccak-asm", @@ -330,9 +331,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5545e2cbf2f8f24c68bb887ba0294fa12a2f816b9e72c4f226cd137b77d0e294" +checksum = "5115c74c037714e1b02a86f742289113afa5d494b5ea58308ba8aa378e739101" dependencies = [ "alloy-chains", "alloy-consensus", @@ -364,7 +365,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", "tokio", "tracing", "url", @@ -373,9 +374,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b633f7731a3df2f4f334001bf80436565113816c5aa5c136c1ded563051e049b" +checksum = "b073afa409698d1b9a30522565815f3bf7010e5b47b997cf399209e6110df097" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -414,9 +415,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed9e40c2a73265ebf70f1e48303ee55920282e1ea5971e832873fb2d32cea74" +checksum = "5c6a0bd0ce5660ac48e4f3bb0c7c5c3a94db287a0be94971599d83928476cbcd" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -440,9 +441,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dea20fa715a6f39ec7adc735cfd9567342870737270ac67795d55896527772" +checksum = "374ac12e35bb90ebccd86e7c943ddba9590149a6e35cc4d9cd860d6635fd1018" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -456,9 +457,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2750f4f694b27461915b9794df60177198bf733da38dde71aadfbe2946a3c0be" +checksum = "f0b85a5f5f5d99047544f4ec31330ee15121dcb8ef5af3e791a5207e6b92b05b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -468,11 +469,10 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d7620e22d6ed7c58451dd303d0501ade5a8bec9dc8daef0fbc48ceffabbae1" +checksum = "ea98f81bcd759dbfa3601565f9d7a02220d8ef1d294ec955948b90aaafbfd857" dependencies = [ - "alloy-consensus", "alloy-consensus-any", "alloy-rpc-types-eth", "alloy-serde", @@ -480,9 +480,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d2d4a265fb1198272cc43d8d418c0423cdfc1aebcd283be9105464874a1dda" +checksum = "4fd14f68a482e67dfba52d404dfff1d3b0d9fc3b4775bd0923f3175d7661c3bd" dependencies = [ "alloy-primitives", "serde", @@ -490,9 +490,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb843daa6feb011475f0db8c499fff5ac62e1e6012fc01d97477ddb3217a83f" +checksum = "9ca5898f753ff0d15a0dc955c169523d8fee57e05bb5a38a398b3451b0b988be" dependencies = [ "alloy-consensus", "alloy-eips", @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df34b88df4deeac9ecfc80ad7cbb26a33e57437b9db8be5b952792feef6134bc" +checksum = "0e518b0a7771e00728f18be0708f828b18a1cfc542a7153bef630966a26388e0" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -528,23 +528,23 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db32f30a55ea4fa9d893127a84eef52fc54d23acb34c1a5a39bfe9bd95fbc149" +checksum = "cdff93fa38be6982f8613a060e18fa0a37ce440d69ed3b7f37c6c69036ce1c53" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", ] [[package]] name = "alloy-rpc-types-txpool" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1588d8d799095a9bd55d9045b76add042ab725c37316a77da933683754aa4b" +checksum = "2d9dc647985db41fd164e807577134da1179b9f5ba0959f8698d6587eaa568f5" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -554,9 +554,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a89fd4cc3f96b3c5c0dd1cebeb63323e4659bbdc837117fa3fd5ac168df7d9" +checksum = "ed3dc8d4a08ffc90c1381d39a4afa2227668259a42c97ab6eecf51cbd82a8761" dependencies = [ "alloy-primitives", "serde", @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532010243a96d1f8593c2246ec3971bc52303884fa1e43ca0a776798ba178910" +checksum = "16188684100f6e0f2a2b949968fe3007749c5be431549064a1bce4e7b3a196a9" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -576,14 +576,14 @@ dependencies = [ "auto_impl", "elliptic-curve", "k256", - "thiserror 1.0.69", + "thiserror 2.0.4", ] [[package]] name = "alloy-signer-aws" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0bdb5079a35d7559714d9f9690b2ebb462921b9ceea63488bd2bef5744c15a" +checksum = "fe06d524ac84fefce1184f2d1273704e62faade7ff1f29c17ac9d493d3ffbdbf" dependencies = [ "alloy-consensus", "alloy-network", @@ -593,15 +593,15 @@ dependencies = [ "aws-sdk-kms", "k256", "spki", - "thiserror 1.0.69", + "thiserror 2.0.4", "tracing", ] [[package]] name = "alloy-signer-gcp" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794e996552efa65a76b20c088f8a968da514f90f7e44cecc32fc544c8a66fd29" +checksum = "492cedcb4819a588aaef8d59edd5d65291f485d25f64b2aa0806dd86feeafd18" dependencies = [ "alloy-consensus", "alloy-network", @@ -611,15 +611,15 @@ dependencies = [ "gcloud-sdk", "k256", "spki", - "thiserror 1.0.69", + "thiserror 2.0.4", "tracing", ] [[package]] name = "alloy-signer-ledger" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416fbc9f19bed61f722181b8f10bd4d89648c254d49f594e1617215f0a30ba46" +checksum = "426409a02587b98e118d2fd32dda3f423805e264a32f9e247a65164163bc0e9b" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -631,15 +631,15 @@ dependencies = [ "coins-ledger", "futures-util", "semver 1.0.23", - "thiserror 1.0.69", + "thiserror 2.0.4", "tracing", ] [[package]] name = "alloy-signer-local" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8080c0ab2dc729b0cbb183843d08e78d2a1629140c9fc16234d2272abb483bd" +checksum = "e2184dab8c9493ab3e1c9f6bd3bdb563ed322b79023d81531935e84a4fdf7cf1" dependencies = [ "alloy-consensus", "alloy-network", @@ -651,14 +651,14 @@ dependencies = [ "eth-keystore", "k256", "rand", - "thiserror 1.0.69", + "thiserror 2.0.4", ] [[package]] name = "alloy-signer-trezor" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1862a5a0e883998e65b735c71560a7b9eaca57cab2165eeb80f0b9a01fff3348" +checksum = "290ead62e020b751761de95f60056340faba341b20493ae929013d1357b9ba5b" dependencies = [ "alloy-consensus", "alloy-network", @@ -666,16 +666,16 @@ dependencies = [ "alloy-signer", "async-trait", "semver 1.0.23", - "thiserror 1.0.69", + "thiserror 2.0.4", "tracing", "trezor-client", ] [[package]] name = "alloy-sol-macro" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -687,15 +687,15 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.6.0", + "indexmap 2.7.0", "proc-macro-error2", "proc-macro2", "quote", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" dependencies = [ "alloy-json-abi", "const-hex", @@ -723,9 +723,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" +checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" dependencies = [ "serde", "winnow", @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f295f4b745fb9e4e663d70bc57aed991288912c7aaaf25767def921050ee43" +checksum = "628be5b9b75e4f4c4f2a71d985bbaca4f23de356dc83f1625454c505f5eef4df" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -756,7 +756,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", "tokio", "tower 0.5.1", "tracing", @@ -766,9 +766,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39139015a5ec127d9c895b49b484608e27fe4538544f84cdf5eae0bd36339bc6" +checksum = "4e24412cf72f79c95cd9b1d9482e3a31f9d94c24b43c4b3b710cc8d4341eaab0" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -781,9 +781,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b4f865b13bb8648e93f812b19b74838b9165212a2beb95fc386188c443a5e3" +checksum = "0577a1f67ce70ece3f2b27cf1011da7222ef0a5701f7dcb558e5356278eeb531" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -802,14 +802,14 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af91e3521b8b3eac26809b1c6f9b86e3ed455dfab812f036836aabdf709b921" +checksum = "1ca46272d17f9647fdb56080ed26c72b3ea5078416831130f5ed46f3b4be0ed6" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http 1.1.0", + "http 1.2.0", "rustls 0.23.19", "serde_json", "tokio", @@ -820,30 +820,16 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9703ce68b97f8faae6f7739d1e003fc97621b856953cbcdbb2b515743f23288" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "derive_more", - "nybbles", - "serde", - "smallvec", - "tracing", -] - -[[package]] -name = "alloy-trie" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b2e366c0debf0af77766c23694a3f863b02633050e71e096e257ffbd395e50" +checksum = "3a5fd8fea044cc9a8c8a50bb6f28e31f0385d820f116c5b98f6f4e55d6e5590b" dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", "derive_more", "nybbles", + "serde", "smallvec", "tracing", ] @@ -961,7 +947,7 @@ dependencies = [ "alloy-transport", "alloy-transport-ipc", "alloy-transport-ws", - "alloy-trie 0.6.0", + "alloy-trie", "anvil-core", "anvil-rpc", "anvil-server", @@ -995,7 +981,7 @@ dependencies = [ "serde_repr", "similar-asserts", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.4", "tikv-jemallocator", "tokio", "tower 0.4.13", @@ -1017,7 +1003,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "alloy-serde", - "alloy-trie 0.6.0", + "alloy-trie", "bytes", "foundry-common", "foundry-evm", @@ -1026,7 +1012,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -1052,7 +1038,7 @@ dependencies = [ "pin-project 1.1.7", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio-util", "tower-http", "tracing", @@ -1060,9 +1046,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arbitrary" @@ -1212,6 +1198,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "ascii-canvas" @@ -1360,7 +1349,7 @@ dependencies = [ "aws-sdk-sts", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.60.7", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1391,9 +1380,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -1416,15 +1405,15 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd059dacda4dfd5b57f2bd453fc6555f9acb496cb77508d517da24cf5d73167" +checksum = "3c30f6fd5646b99d9b45ec3a0c22e67112c175b2383100c960d7ee39d96c8d96" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1438,15 +1427,15 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1460,15 +1449,15 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -1482,15 +1471,15 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -1505,9 +1494,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -1518,7 +1507,7 @@ dependencies = [ "hex", "hmac", "http 0.2.12", - "http 1.1.0", + "http 1.2.0", "once_cell", "percent-encoding", "sha2", @@ -1566,6 +1555,15 @@ dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-json" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" +dependencies = [ + "aws-smithy-types", +] + [[package]] name = "aws-smithy-query" version = "0.60.7" @@ -1578,9 +1576,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -1613,7 +1611,7 @@ dependencies = [ "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.1.0", + "http 1.2.0", "pin-project-lite", "tokio", "tracing", @@ -1630,7 +1628,7 @@ dependencies = [ "bytes", "bytes-utils", "http 0.2.12", - "http 1.1.0", + "http 1.2.0", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -1677,7 +1675,7 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.5.1", @@ -1712,7 +1710,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "mime", @@ -1852,9 +1850,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e47d5c63335658326076cf7c81795af665c534ea552da69526d6cef51b12ed9" +checksum = "9276fe602371cd8a7f70fe68c4db55b2d3e92c570627d6ed0427646edfa5cf47" dependencies = [ "bon-macros", "rustversion", @@ -1862,9 +1860,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b162272b6d55562ea30cc937d74ef4d07399e507bfd6eb3860f6a845c7264eef" +checksum = "94828b84b32b4f3ac3865f692fcdbc46c7d0dd87b29658a391d58a244e1ce45a" dependencies = [ "darling", "ident_case", @@ -2204,9 +2202,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" dependencies = [ "clap_builder", "clap_derive", @@ -2214,9 +2212,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" dependencies = [ "anstream", "anstyle", @@ -2925,9 +2923,9 @@ dependencies = [ [[package]] name = "divan" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" +checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4" dependencies = [ "cfg-if", "clap", @@ -2939,9 +2937,9 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ "proc-macro2", "quote", @@ -3498,7 +3496,7 @@ dependencies = [ "strum", "svm-rs", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.4", "tikv-jemallocator", "tokio", "toml 0.8.19", @@ -3531,7 +3529,7 @@ dependencies = [ "serde", "serde_json", "solang-parser", - "thiserror 2.0.3", + "thiserror 2.0.4", "toml 0.8.19", "tracing", ] @@ -3546,7 +3544,7 @@ dependencies = [ "itertools 0.13.0", "similar-asserts", "solang-parser", - "thiserror 2.0.3", + "thiserror 2.0.4", "toml 0.8.19", "tracing", "tracing-subscriber", @@ -3734,7 +3732,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.4", "toml 0.8.19", "tracing", "vergen", @@ -3833,7 +3831,7 @@ dependencies = [ "serde_json", "similar-asserts", "terminal_size", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tower 0.4.13", "tracing", @@ -3892,7 +3890,7 @@ dependencies = [ "svm-rs", "svm-rs-builds", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", "winnow", @@ -3926,7 +3924,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", "walkdir", @@ -3965,7 +3963,7 @@ dependencies = [ "serde_json", "svm-rs", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "walkdir", ] @@ -3999,7 +3997,7 @@ dependencies = [ "similar-asserts", "solang-parser", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.4", "toml 0.8.19", "toml_edit", "tracing", @@ -4048,7 +4046,7 @@ dependencies = [ "revm", "revm-inspectors", "serde", - "thiserror 2.0.3", + "thiserror 2.0.4", "tracing", ] @@ -4094,7 +4092,7 @@ dependencies = [ "revm-inspectors", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", "url", @@ -4130,14 +4128,14 @@ dependencies = [ "foundry-evm-core", "foundry-evm-coverage", "foundry-evm-traces", - "indexmap 2.6.0", + "indexmap 2.7.0", "itertools 0.13.0", "parking_lot", "proptest", "rand", "revm", "serde", - "thiserror 2.0.3", + "thiserror 2.0.4", "tracing", ] @@ -4171,9 +4169,9 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d9ee7669c2a184b83c05393abfa5c9f24ef99b9abefa627fe45660adee0ba" +checksum = "491e9f9f138086b3627a8c406730dfbb6afcdcf688e6da0eb15df52f0c8ed163" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -4187,7 +4185,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", "tokio", "tracing", "url", @@ -4200,7 +4198,7 @@ dependencies = [ "alloy-primitives", "foundry-compilers", "semver 1.0.23", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -4262,7 +4260,7 @@ dependencies = [ "gcloud-sdk", "rpassword", "serde", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", ] @@ -4439,9 +4437,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ "cfg-if", "libc", @@ -4525,7 +4523,7 @@ dependencies = [ "bstr", "gix-path", "libc", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -4537,7 +4535,7 @@ dependencies = [ "bstr", "itoa", "jiff", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -4628,7 +4626,7 @@ dependencies = [ "gix-trace", "home", "once_cell", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -4700,7 +4698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" dependencies = [ "bstr", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] @@ -4745,7 +4743,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -4763,8 +4761,8 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.1.0", - "indexmap 2.6.0", + "http 1.2.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -4920,9 +4918,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -4947,7 +4945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -4958,7 +4956,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] @@ -5031,7 +5029,7 @@ dependencies = [ "futures-channel", "futures-util", "h2 0.4.7", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "httparse", "httpdate", @@ -5065,7 +5063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.1.0", + "http 1.2.0", "hyper 1.5.1", "hyper-util", "rustls 0.23.19", @@ -5115,7 +5113,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "hyper 1.5.1", "pin-project-lite", @@ -5390,9 +5388,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "arbitrary", "equivalent", @@ -6371,9 +6369,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "op-alloy-consensus" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75353c94e7515fac7d3c280bae56bff3375784a05cb44b317260606292ff6ba9" +checksum = "f9d95d0ec6457ad4d3d7fc0ad41db490b219587ed837ada87a26b28e535db15f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6382,14 +6380,14 @@ dependencies = [ "alloy-serde", "derive_more", "serde", - "thiserror 2.0.3", + "thiserror 2.0.4", ] [[package]] name = "op-alloy-rpc-types" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680a86b63fe4c45fbd5dbf1ac6779409565211c4b234d20af94cf1f79d11f23a" +checksum = "eba1b44e2035ec04cc61762cb9b5457d0ecd29d9af631e1a1c107ef571ce2318" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6522,29 +6520,28 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 1.0.109", ] [[package]] @@ -6707,7 +6704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.7.0", ] [[package]] @@ -7039,7 +7036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38ee68ae331824036479c84060534b18254c864fa73366c58d86db3b7b811619" dependencies = [ "futures", - "indexmap 2.6.0", + "indexmap 2.7.0", "nix 0.28.0", "tokio", "tracing", @@ -7177,11 +7174,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" dependencies = [ "chrono", - "indexmap 2.6.0", + "indexmap 2.7.0", "newtype-uuid", "quick-xml 0.37.1", "strip-ansi-escapes", - "thiserror 2.0.3", + "thiserror 2.0.4", "uuid 1.11.0", ] @@ -7216,7 +7213,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.19", "socket2", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "tracing", ] @@ -7235,7 +7232,7 @@ dependencies = [ "rustls 0.23.19", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.4", "tinyvec", "tracing", "web-time", @@ -7449,7 +7446,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.5.1", @@ -7506,9 +7503,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41bbeb6004cc4ed48d27756f0479011df91a6f5642a3abab9309eda5ce67c4ad" +checksum = "8d056aaa21f36038ab35fe8ce940ee332903a0b4b992b8ca805fb60c85eb2086" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -7519,7 +7516,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.4", ] [[package]] @@ -8193,7 +8190,7 @@ version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -8444,6 +8441,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smawk" @@ -8536,7 +8536,7 @@ checksum = "8b6e4eb0b72ed7adbb808897c85de08ea99609774a58c72e3dce55c758043ca2" dependencies = [ "bumpalo", "index_vec", - "indexmap 2.6.0", + "indexmap 2.7.0", "parking_lot", "rayon", "rustc-hash", @@ -8641,7 +8641,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 2.0.3", + "thiserror 2.0.4", "tokio", "toml_edit", "uuid 1.11.0", @@ -8822,9 +8822,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" dependencies = [ "paste", "proc-macro2", @@ -8950,11 +8950,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.4", ] [[package]] @@ -8970,9 +8970,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" dependencies = [ "proc-macro2", "quote", @@ -9020,9 +9020,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -9043,9 +9043,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -9087,9 +9087,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -9187,9 +9187,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -9213,7 +9213,7 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -9235,7 +9235,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -9254,7 +9254,7 @@ dependencies = [ "base64 0.22.1", "bytes", "h2 0.4.7", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.5.1", @@ -9326,7 +9326,7 @@ dependencies = [ "bitflags 2.6.0", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -9499,7 +9499,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand", @@ -10491,9 +10491,9 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.6.0", + "indexmap 2.7.0", "memchr", - "thiserror 2.0.3", + "thiserror 2.0.4", "zopfli", ] diff --git a/Cargo.toml b/Cargo.toml index 2967d8573..68414b683 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -170,7 +170,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.9.0", default-features = false } foundry-compilers = { version = "0.12.7", default-features = false } -foundry-fork-db = "0.8.0" +foundry-fork-db = "0.9.0" solang-parser = "=0.3.3" solar-ast = { version = "=0.1.0", default-features = false } solar-parse = { version = "=0.1.0", default-features = false } @@ -178,34 +178,35 @@ solar-parse = { version = "=0.1.0", default-features = false } ## revm revm = { version = "18.0.0", default-features = false } revm-primitives = { version = "14.0.0", default-features = false } -revm-inspectors = { version = "0.12.0", features = ["serde"] } +revm-inspectors = { version = "0.13.0", features = ["serde"] } ## ethers ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { version = "0.7.0", default-features = false } -alloy-contract = { version = "0.7.0", default-features = false } -alloy-eips = { version = "0.7.0", default-features = false } -alloy-genesis = { version = "0.7.0", default-features = false } -alloy-json-rpc = { version = "0.7.0", default-features = false } -alloy-network = { version = "0.7.0", default-features = false } -alloy-provider = { version = "0.7.0", default-features = false } -alloy-pubsub = { version = "0.7.0", default-features = false } -alloy-rpc-client = { version = "0.7.0", default-features = false } -alloy-rpc-types = { version = "0.7.0", default-features = true } -alloy-serde = { version = "0.7.0", default-features = false } -alloy-signer = { version = "0.7.0", default-features = false } -alloy-signer-aws = { version = "0.7.0", default-features = false } -alloy-signer-gcp = { version = "0.7.0", default-features = false } -alloy-signer-ledger = { version = "0.7.0", default-features = false } -alloy-signer-local = { version = "0.7.0", default-features = false } -alloy-signer-trezor = { version = "0.7.0", default-features = false } -alloy-transport = { version = "0.7.0", default-features = false } -alloy-transport-http = { version = "0.7.0", default-features = false } -alloy-transport-ipc = { version = "0.7.0", default-features = false } -alloy-transport-ws = { version = "0.7.0", default-features = false } -alloy-node-bindings = { version = "0.7.0", default-features = false } +alloy-consensus = { version = "0.8.0", default-features = false } +alloy-contract = { version = "0.8.0", default-features = false } +alloy-eips = { version = "0.8.0", default-features = false } +alloy-genesis = { version = "0.8.0", default-features = false } +alloy-json-rpc = { version = "0.8.0", default-features = false } +alloy-network = { version = "0.8.0", default-features = false } +alloy-provider = { version = "0.8.0", default-features = false } +alloy-pubsub = { version = "0.8.0", default-features = false } +alloy-rpc-client = { version = "0.8.0", default-features = false } +alloy-rpc-types = { version = "0.8.0", default-features = true } +alloy-serde = { version = "0.8.0", default-features = false } +alloy-signer = { version = "0.8.0", default-features = false } +alloy-signer-aws = { version = "0.8.0", default-features = false } +alloy-signer-gcp = { version = "0.8.0", default-features = false } +alloy-signer-ledger = { version = "0.8.0", default-features = false } +alloy-signer-local = { version = "0.8.0", default-features = false } +alloy-signer-trezor = { version = "0.8.0", default-features = false } +alloy-transport = { version = "0.8.0", default-features = false } +alloy-transport-http = { version = "0.8.0", default-features = false } +alloy-transport-ipc = { version = "0.8.0", default-features = false } +alloy-transport-ws = { version = "0.8.0", default-features = false } +alloy-node-bindings = { version = "0.8.0", default-features = false } +alloy-network-primitives = { version = "0.8.0", default-features = false } ## alloy-core alloy-dyn-abi = "0.8.14" @@ -222,11 +223,11 @@ syn-solidity = "0.8.14" alloy-chains = "0.1" alloy-rlp = "0.3" -alloy-trie = "0.6.0" +alloy-trie = "0.7.0" ## op-alloy -op-alloy-rpc-types = "0.7.1" -op-alloy-consensus = "0.7.1" +op-alloy-rpc-types = "0.8.0" +op-alloy-consensus = "0.8.0" ## cli anstream = "0.6" diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 4067aa668..29b8aee88 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -6,8 +6,8 @@ use alloy_consensus::{ eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, TxEip7702, }, - Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930, - TxEnvelope, TxLegacy, TxReceipt, + Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, + TxReceipt, Typed2718, }; use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope}; diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index b6dea48e1..7960cab6e 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -1,6 +1,6 @@ use alloy_chains::Chain; use alloy_dyn_abi::TypedData; -use alloy_primitives::{hex, Address, Signature, B256}; +use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, B256}; use alloy_provider::Provider; use alloy_signer::Signer; use alloy_signer_local::{ diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 7ae6adea8..9962a8583 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -1,7 +1,7 @@ //! Helper trait and functions to format Ethereum types. use alloy_consensus::{ - Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, TxType, + Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, TxType, Typed2718, }; use alloy_network::{ AnyHeader, AnyReceiptEnvelope, AnyRpcBlock, AnyTransactionReceipt, AnyTxEnvelope, From b0906386497c03aef53f67b929ca6418aebe34ed Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:33:36 +0200 Subject: [PATCH 078/107] fix(cheatcodes): clear orderings together with trace steps on debug trace stop (#9529) fix(cheatcodes): empty ordering and step logs too --- crates/cheatcodes/src/evm.rs | 3 ++- crates/forge/tests/cli/test_cmd.rs | 41 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 6c1a47185..9f8eb27a4 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -855,11 +855,12 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { let debug_steps: Vec = steps.iter().map(|&step| convert_call_trace_to_debug_step(step)).collect(); - // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage. if !record_info.original_tracer_config.record_steps { tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| { node.trace.steps = Vec::new(); + node.logs = Vec::new(); + node.ordering = Vec::new(); }); } diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index fa3d72e0d..6431c5424 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2724,3 +2724,44 @@ Encountered a total of 1 failing tests, 0 tests succeeded "#]]); }); + +// Tests that `start/stopAndReturn` debugTraceRecording does not panic when running with +// verbosity > 3. +forgetest_init!(should_not_panic_on_debug_trace_verbose, |prj, cmd| { + prj.add_test( + "DebugTraceRecordingTest.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract DebugTraceRecordingTest is Test { + function test_start_stop_recording() public { + vm.startDebugTraceRecording(); + Counter counter = new Counter(); + counter.increment(); + vm.stopAndReturnDebugTraceRecording(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_start_stop_recording", "-vvvv"]).assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/DebugTraceRecordingTest.t.sol:DebugTraceRecordingTest +[PASS] test_start_stop_recording() ([GAS]) +Traces: + [476338] DebugTraceRecordingTest::test_start_stop_recording() + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]], + ); +}); From 0eff1ef18fa1d21ec1280ed2b8b0f6e1549250ff Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:32:21 +0200 Subject: [PATCH 079/107] feat(cheatcodes): skip test suite in setup (#9532) --- crates/forge/src/result.rs | 12 ++++++--- crates/forge/src/runner.rs | 13 +++++---- crates/forge/tests/cli/test_cmd.rs | 42 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 28b52d74c..eed3f2977 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -482,10 +482,10 @@ impl TestResult { Self { status: TestStatus::Failure, reason: Some(reason), ..Default::default() } } - /// Creates a failed test setup result. - pub fn setup_fail(setup: TestSetup) -> Self { + /// Creates a test setup result. + pub fn setup_result(setup: TestSetup) -> Self { Self { - status: TestStatus::Failure, + status: if setup.skipped { TestStatus::Skipped } else { TestStatus::Failure }, reason: setup.reason, logs: setup.logs, traces: setup.traces, @@ -755,6 +755,8 @@ pub struct TestSetup { /// The reason the setup failed, if it did. pub reason: Option, + /// Whether setup and entire test suite is skipped. + pub skipped: bool, } impl TestSetup { @@ -762,6 +764,10 @@ impl TestSetup { Self { reason: Some(reason), ..Default::default() } } + pub fn skipped(reason: String) -> Self { + Self { reason: Some(reason), skipped: true, ..Default::default() } + } + pub fn extend(&mut self, raw: RawCallResult, trace_kind: TraceKind) { self.logs.extend(raw.logs); self.labels.extend(raw.labels); diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 0948df6d1..ef769c662 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -98,10 +98,13 @@ impl<'a> ContractRunner<'a> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. pub fn setup(&mut self, call_setup: bool) -> TestSetup { - match self._setup(call_setup) { - Ok(setup) => setup, - Err(err) => TestSetup::failed(err.to_string()), - } + self._setup(call_setup).unwrap_or_else(|err| { + if err.to_string().contains("skipped") { + TestSetup::skipped(err.to_string()) + } else { + TestSetup::failed(err.to_string()) + } + }) } fn _setup(&mut self, call_setup: bool) -> Result { @@ -333,7 +336,7 @@ impl<'a> ContractRunner<'a> { // The setup failed, so we return a single test result for `setUp` return SuiteResult::new( start.elapsed(), - [("setUp()".to_string(), TestResult::setup_fail(setup))].into(), + [("setUp()".to_string(), TestResult::setup_result(setup))].into(), warnings, ) } diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 6431c5424..e8da6a490 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2026,6 +2026,48 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 6 skipped (6 total tests) "#]]); }); +forgetest_init!(skip_setup, |prj, cmd| { + prj.add_test( + "Counter.t.sol", + r#" +import "forge-std/Test.sol"; + +contract SkipCounterSetup is Test { + + function setUp() public { + vm.skip(true, "skip counter test"); + } + + function test_require1() public pure { + require(1 > 2); + } + + function test_require2() public pure { + require(1 > 2); + } + + function test_require3() public pure { + require(1 > 2); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "SkipCounterSetup"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:SkipCounterSetup +[SKIP: skipped: skip counter test] setUp() ([GAS]) +Suite result: ok. 0 passed; 0 failed; 1 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 1 skipped (1 total tests) + +"#]]); +}); + forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { prj.wipe_contracts(); prj.insert_ds_test(); From 91030daee6e622dce6dd725fd4c48bcd36a54f46 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:35:50 +0100 Subject: [PATCH 080/107] fix: mark flag incompatibility (#9530) * enforce stricter command compatibility mode for forge test * add conflicting cases for anvil * revert anvil changes, derivation_path is not exclusive to mnemonics --- crates/anvil/src/cmd.rs | 4 ++-- crates/forge/bin/cmd/test/mod.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 7b009a592..19e9193f9 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -75,7 +75,7 @@ pub struct NodeArgs { /// The EVM hardfork to use. /// - /// Choose the hardfork by name, e.g. `shanghai`, `paris`, `london`, etc... + /// Choose the hardfork by name, e.g. `cancun`, `shanghai`, `paris`, `london`, etc... /// [default: latest] #[arg(long)] pub hardfork: Option, @@ -177,7 +177,7 @@ pub struct NodeArgs { /// Max number of states to persist on disk. /// /// Note that `prune_history` will overwrite `max_persisted_states` to 0. - #[arg(long)] + #[arg(long, conflicts_with = "prune_history")] pub max_persisted_states: Option, /// Number of blocks with transactions to keep in memory. diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index c55eb520a..85f75a19b 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -79,7 +79,7 @@ pub struct TestArgs { /// /// If the matching test is a fuzz test, then it will open the debugger on the first failure /// case. If the fuzz test does not fail, it will open the debugger on the last fuzz case. - #[arg(long, value_name = "DEPRECATED_TEST_FUNCTION_REGEX")] + #[arg(long, conflicts_with_all = ["flamegraph", "flamechart", "decode_internal", "rerun"], value_name = "DEPRECATED_TEST_FUNCTION_REGEX")] debug: Option>, /// Generate a flamegraph for a single test. Implies `--decode-internal`. @@ -123,7 +123,7 @@ pub struct TestArgs { allow_failure: bool, /// Output test results as JUnit XML report. - #[arg(long, conflicts_with_all = ["quiet", "json", "gas_report"], help_heading = "Display options")] + #[arg(long, conflicts_with_all = ["quiet", "json", "gas_report", "summary", "list", "show_progress"], help_heading = "Display options")] pub junit: bool, /// Stop running tests after the first failure. @@ -135,7 +135,7 @@ pub struct TestArgs { etherscan_api_key: Option, /// List tests instead of running them. - #[arg(long, short, help_heading = "Display options")] + #[arg(long, short, conflicts_with_all = ["show_progress", "decode_internal", "summary"], help_heading = "Display options")] list: bool, /// Set seed used to generate randomness during your fuzz runs. @@ -154,7 +154,7 @@ pub struct TestArgs { pub fuzz_input_file: Option, /// Show test execution progress. - #[arg(long)] + #[arg(long, conflicts_with_all = ["quiet", "json"], help_heading = "Display options")] pub show_progress: bool, #[command(flatten)] From 09894efa7d8c0256d68cd0ec92b01ba3191bdfe8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:59:58 +0000 Subject: [PATCH 081/107] chore(deps): weekly `cargo update` (#9533) Locking 36 packages to latest compatible versions Updating alloy-chains v0.1.47 -> v0.1.48 Updating alloy-rlp v0.3.9 -> v0.3.10 Updating alloy-rlp-derive v0.3.9 -> v0.3.10 Updating annotate-snippets v0.11.4 -> v0.11.5 Updating bon v3.2.0 -> v3.3.0 Updating bon-macros v3.2.0 -> v3.3.0 Updating cc v1.2.2 -> v1.2.3 Updating chrono v0.4.38 -> v0.4.39 Updating clap v4.5.22 -> v4.5.23 Updating clap_builder v4.5.22 -> v4.5.23 Updating clap_lex v0.7.3 -> v0.7.4 Updating evmole v0.6.1 -> v0.6.2 Updating fastrand v2.2.0 -> v2.3.0 Updating js-sys v0.3.74 -> v0.3.76 Updating libc v0.2.167 -> v0.2.168 Updating pest v2.7.14 -> v2.7.15 Updating pest_derive v2.7.14 -> v2.7.15 Updating pest_generator v2.7.14 -> v2.7.15 Updating pest_meta v2.7.14 -> v2.7.15 Updating prost v0.13.3 -> v0.13.4 Updating prost-derive v0.13.3 -> v0.13.4 Updating prost-types v0.13.3 -> v0.13.4 Updating quinn-udp v0.5.7 -> v0.5.8 Updating rustix v0.38.41 -> v0.38.42 Updating thiserror v2.0.4 -> v2.0.6 Updating thiserror-impl v2.0.4 -> v2.0.6 Updating tokio-rustls v0.26.0 -> v0.26.1 Updating tokio-stream v0.1.16 -> v0.1.17 Updating tracy-client v0.17.4 -> v0.17.5 Updating wasm-bindgen v0.2.97 -> v0.2.99 Updating wasm-bindgen-backend v0.2.97 -> v0.2.99 Updating wasm-bindgen-futures v0.4.47 -> v0.4.49 Updating wasm-bindgen-macro v0.2.97 -> v0.2.99 Updating wasm-bindgen-macro-support v0.2.97 -> v0.2.99 Updating wasm-bindgen-shared v0.2.97 -> v0.2.99 Updating web-sys v0.3.74 -> v0.3.76 note: pass `--verbose` to see 10 unchanged dependencies behind latest Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --- Cargo.lock | 246 ++++++++++++++++++++++++++--------------------------- 1 file changed, 122 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a69b2ddf3..1422df4b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,9 +74,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.1.47" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c5c520273946ecf715c0010b4e3503d7eba9893cd9ce6b7fff5654c4a3c470" +checksum = "a0161082e0edd9013d23083465cc04b20e44b7a15646d36ba7b0cdb7cd6fe18f" dependencies = [ "alloy-primitives", "num_enum", @@ -134,7 +134,7 @@ dependencies = [ "alloy-transport", "futures", "futures-util", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -238,7 +238,7 @@ dependencies = [ "alloy-sol-types", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", ] @@ -264,7 +264,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -292,7 +292,7 @@ dependencies = [ "rand", "serde_json", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", "url", ] @@ -365,7 +365,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "url", @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" +checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -404,9 +404,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +checksum = "5a833d97bf8a5f0f878daf2c8451fff7de7f9de38baa5a45d936ec718d81255a" dependencies = [ "proc-macro2", "quote", @@ -537,7 +537,7 @@ dependencies = [ "alloy-serde", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -576,7 +576,7 @@ dependencies = [ "auto_impl", "elliptic-curve", "k256", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -593,7 +593,7 @@ dependencies = [ "aws-sdk-kms", "k256", "spki", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", ] @@ -611,7 +611,7 @@ dependencies = [ "gcloud-sdk", "k256", "spki", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", ] @@ -631,7 +631,7 @@ dependencies = [ "coins-ledger", "futures-util", "semver 1.0.23", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", ] @@ -651,7 +651,7 @@ dependencies = [ "eth-keystore", "k256", "rand", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -666,7 +666,7 @@ dependencies = [ "alloy-signer", "async-trait", "semver 1.0.23", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", "trezor-client", ] @@ -756,7 +756,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tower 0.5.1", "tracing", @@ -864,12 +864,12 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e35ed54e5ea7997c14ed4c70ba043478db1112e98263b3b035907aa197d991" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] @@ -981,7 +981,7 @@ dependencies = [ "serde_repr", "similar-asserts", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.6", "tikv-jemallocator", "tokio", "tower 0.4.13", @@ -1012,7 +1012,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -1038,7 +1038,7 @@ dependencies = [ "pin-project 1.1.7", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio-util", "tower-http", "tracing", @@ -1850,9 +1850,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9276fe602371cd8a7f70fe68c4db55b2d3e92c570627d6ed0427646edfa5cf47" +checksum = "f265cdb2e8501f1c952749e78babe8f1937be92c98120e5f78fc72d634682bad" dependencies = [ "bon-macros", "rustversion", @@ -1860,9 +1860,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94828b84b32b4f3ac3865f692fcdbc46c7d0dd87b29658a391d58a244e1ce45a" +checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" dependencies = [ "darling", "ident_case", @@ -2087,9 +2087,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "shlex", ] @@ -2152,9 +2152,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2202,9 +2202,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -2212,9 +2212,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -2258,9 +2258,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clearscreen" @@ -3263,9 +3263,9 @@ dependencies = [ [[package]] name = "evmole" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e21c69e0ae62877b65241d25cae9e28477818482fab8c1101d15289725a46" +checksum = "c19906a94bb5656904a6c9c0f36d492cb1da96f284d59bb56f555bd472d96e51" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -3289,9 +3289,9 @@ checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -3496,7 +3496,7 @@ dependencies = [ "strum", "svm-rs", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.6", "tikv-jemallocator", "tokio", "toml 0.8.19", @@ -3529,7 +3529,7 @@ dependencies = [ "serde", "serde_json", "solang-parser", - "thiserror 2.0.4", + "thiserror 2.0.6", "toml 0.8.19", "tracing", ] @@ -3544,7 +3544,7 @@ dependencies = [ "itertools 0.13.0", "similar-asserts", "solang-parser", - "thiserror 2.0.4", + "thiserror 2.0.6", "toml 0.8.19", "tracing", "tracing-subscriber", @@ -3732,7 +3732,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "toml 0.8.19", "tracing", "vergen", @@ -3831,7 +3831,7 @@ dependencies = [ "serde_json", "similar-asserts", "terminal_size", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tower 0.4.13", "tracing", @@ -3890,7 +3890,7 @@ dependencies = [ "svm-rs", "svm-rs-builds", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "winnow", @@ -3924,7 +3924,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "walkdir", @@ -3963,7 +3963,7 @@ dependencies = [ "serde_json", "svm-rs", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "walkdir", ] @@ -3997,7 +3997,7 @@ dependencies = [ "similar-asserts", "solang-parser", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.6", "toml 0.8.19", "toml_edit", "tracing", @@ -4046,7 +4046,7 @@ dependencies = [ "revm", "revm-inspectors", "serde", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", ] @@ -4092,7 +4092,7 @@ dependencies = [ "revm-inspectors", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "url", @@ -4135,7 +4135,7 @@ dependencies = [ "rand", "revm", "serde", - "thiserror 2.0.4", + "thiserror 2.0.6", "tracing", ] @@ -4185,7 +4185,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "url", @@ -4198,7 +4198,7 @@ dependencies = [ "alloy-primitives", "foundry-compilers", "semver 1.0.23", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -4260,7 +4260,7 @@ dependencies = [ "gcloud-sdk", "rpassword", "serde", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -4523,7 +4523,7 @@ dependencies = [ "bstr", "gix-path", "libc", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -4535,7 +4535,7 @@ dependencies = [ "bstr", "itoa", "jiff", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -4626,7 +4626,7 @@ dependencies = [ "gix-trace", "home", "once_cell", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -4698,7 +4698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" dependencies = [ "bstr", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -5070,7 +5070,7 @@ dependencies = [ "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", "webpki-roots", ] @@ -5580,9 +5580,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -5718,9 +5718,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libdbus-sys" @@ -6380,7 +6380,7 @@ dependencies = [ "alloy-serde", "derive_more", "serde", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -6654,20 +6654,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -6675,9 +6675,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -6688,9 +6688,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -7093,9 +7093,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -7103,9 +7103,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", "itertools 0.13.0", @@ -7116,9 +7116,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" dependencies = [ "prost", ] @@ -7178,7 +7178,7 @@ dependencies = [ "newtype-uuid", "quick-xml 0.37.1", "strip-ansi-escapes", - "thiserror 2.0.4", + "thiserror 2.0.6", "uuid 1.11.0", ] @@ -7213,7 +7213,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.19", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -7232,7 +7232,7 @@ dependencies = [ "rustls 0.23.19", "rustls-pki-types", "slab", - "thiserror 2.0.4", + "thiserror 2.0.6", "tinyvec", "tracing", "web-time", @@ -7240,9 +7240,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" dependencies = [ "cfg_aliases 0.2.1", "libc", @@ -7473,7 +7473,7 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tokio-socks", "tokio-util", "tower-service", @@ -7516,7 +7516,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", ] [[package]] @@ -7737,15 +7737,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8641,7 +8641,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "toml_edit", "uuid 1.11.0", @@ -8950,11 +8950,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.6", ] [[package]] @@ -8970,9 +8970,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -9136,12 +9136,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls 0.23.19", - "rustls-pki-types", "tokio", ] @@ -9159,9 +9158,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -9180,7 +9179,7 @@ dependencies = [ "rustls 0.23.19", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tungstenite", "webpki-roots", ] @@ -9267,7 +9266,7 @@ dependencies = [ "rustls-pemfile 2.2.0", "socket2", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tokio-stream", "tower 0.4.13", "tower-layer", @@ -9451,9 +9450,9 @@ dependencies = [ [[package]] name = "tracy-client" -version = "0.17.4" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746b078c6a09ebfd5594609049e07116735c304671eaab06ce749854d23435bc" +checksum = "51e295eae54124872df35720dc3a5b1e827c7deee352b342ec7f7e626d0d0ef3" dependencies = [ "loom", "once_cell", @@ -9782,9 +9781,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -9793,13 +9792,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -9808,9 +9806,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -9821,9 +9819,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9831,9 +9829,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -9844,9 +9842,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-streams" @@ -9940,9 +9938,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -10493,7 +10491,7 @@ dependencies = [ "flate2", "indexmap 2.7.0", "memchr", - "thiserror 2.0.4", + "thiserror 2.0.6", "zopfli", ] From 59f354c179f4e7f6d7292acb3d068815c79286d1 Mon Sep 17 00:00:00 2001 From: grandizzy <38490174+grandizzy@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:08:40 +0200 Subject: [PATCH 082/107] fix(fuzz): exclude exernal libraries addresses from fuzz inputs (#9527) --- crates/evm/evm/src/executors/fuzz/mod.rs | 14 ++++-- crates/evm/evm/src/executors/invariant/mod.rs | 11 +++-- crates/evm/fuzz/src/strategies/param.rs | 16 ++++++- crates/evm/fuzz/src/strategies/state.rs | 10 ++++- crates/forge/src/result.rs | 2 + crates/forge/src/runner.rs | 8 ++++ crates/forge/tests/it/core.rs | 4 +- crates/forge/tests/it/repros.rs | 3 ++ testdata/default/repros/Issue8639.t.sol | 43 +++++++++++++++++++ 9 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 testdata/default/repros/Issue8639.t.sol diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 0d79f8fa4..b99499cf3 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -77,10 +77,12 @@ impl FuzzedExecutor { /// test case. /// /// Returns a list of all the consumed gas and calldata of every fuzz case + #[allow(clippy::too_many_arguments)] pub fn fuzz( &self, func: &Function, fuzz_fixtures: &FuzzFixtures, + deployed_libs: &[Address], address: Address, should_fail: bool, rd: &RevertDecoder, @@ -88,7 +90,7 @@ impl FuzzedExecutor { ) -> FuzzTestResult { // Stores the fuzz test execution data. let execution_data = RefCell::new(FuzzTestData::default()); - let state = self.build_fuzz_state(); + let state = self.build_fuzz_state(deployed_libs); let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); let strategy = proptest::prop_oneof![ 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures), @@ -274,11 +276,15 @@ impl FuzzedExecutor { } /// Stores fuzz state for use with [fuzz_calldata_from_state] - pub fn build_fuzz_state(&self) -> EvmFuzzState { + pub fn build_fuzz_state(&self, deployed_libs: &[Address]) -> EvmFuzzState { if let Some(fork_db) = self.executor.backend().active_fork_db() { - EvmFuzzState::new(fork_db, self.config.dictionary) + EvmFuzzState::new(fork_db, self.config.dictionary, deployed_libs) } else { - EvmFuzzState::new(self.executor.backend().mem_db(), self.config.dictionary) + EvmFuzzState::new( + self.executor.backend().mem_db(), + self.config.dictionary, + deployed_libs, + ) } } } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index d5fdb5668..4582e4682 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -323,6 +323,7 @@ impl<'a> InvariantExecutor<'a> { &mut self, invariant_contract: InvariantContract<'_>, fuzz_fixtures: &FuzzFixtures, + deployed_libs: &[Address], progress: Option<&ProgressBar>, ) -> Result { // Throw an error to abort test run if the invariant function accepts input params @@ -331,7 +332,7 @@ impl<'a> InvariantExecutor<'a> { } let (invariant_test, invariant_strategy) = - self.prepare_test(&invariant_contract, fuzz_fixtures)?; + self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?; // Start timer for this invariant test. let timer = FuzzTestTimer::new(self.config.timeout); @@ -506,6 +507,7 @@ impl<'a> InvariantExecutor<'a> { &mut self, invariant_contract: &InvariantContract<'_>, fuzz_fixtures: &FuzzFixtures, + deployed_libs: &[Address], ) -> Result<(InvariantTest, impl Strategy)> { // Finds out the chosen deployed contracts and/or senders. self.select_contract_artifacts(invariant_contract.address)?; @@ -513,8 +515,11 @@ impl<'a> InvariantExecutor<'a> { self.select_contracts_and_senders(invariant_contract.address)?; // Stores fuzz state for use with [fuzz_calldata_from_state]. - let fuzz_state = - EvmFuzzState::new(self.executor.backend().mem_db(), self.config.dictionary); + let fuzz_state = EvmFuzzState::new( + self.executor.backend().mem_db(), + self.config.dictionary, + deployed_libs, + ); // Creates the invariant strategy. let strategy = invariant_strat( diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 7e5218fd8..643c70bde 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -130,7 +130,19 @@ pub fn fuzz_param_from_state( // Convert the value based on the parameter type match *param { DynSolType::Address => { - value().prop_map(move |value| DynSolValue::Address(Address::from_word(value))).boxed() + let deployed_libs = state.deployed_libs.clone(); + value() + .prop_filter_map("filter address fuzzed from state", move |value| { + let fuzzed_addr = Address::from_word(value); + // Do not use addresses of deployed libraries as fuzz input. + // See . + if !deployed_libs.contains(&fuzzed_addr) { + Some(DynSolValue::Address(fuzzed_addr)) + } else { + None + } + }) + .boxed() } DynSolType::Function => value() .prop_map(move |value| { @@ -217,7 +229,7 @@ mod tests { let f = "testArray(uint64[2] calldata values)"; let func = get_func(f).unwrap(); let db = CacheDB::new(EmptyDB::default()); - let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default()); + let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), &[]); let strategy = proptest::prop_oneof![ 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()), 40 => fuzz_calldata_from_state(func, &state), diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index 4df55772c..b1c5d8e00 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -27,10 +27,16 @@ const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024; #[derive(Clone, Debug)] pub struct EvmFuzzState { inner: Arc>, + /// Addresses of external libraries deployed in test setup, excluded from fuzz test inputs. + pub deployed_libs: Vec
, } impl EvmFuzzState { - pub fn new(db: &CacheDB, config: FuzzDictionaryConfig) -> Self { + pub fn new( + db: &CacheDB, + config: FuzzDictionaryConfig, + deployed_libs: &[Address], + ) -> Self { // Sort accounts to ensure deterministic dictionary generation from the same setUp state. let mut accs = db.accounts.iter().collect::>(); accs.sort_by_key(|(address, _)| *address); @@ -38,7 +44,7 @@ impl EvmFuzzState { // Create fuzz dictionary and insert values from db state. let mut dictionary = FuzzDictionary::new(config); dictionary.insert_db_values(accs); - Self { inner: Arc::new(RwLock::new(dictionary)) } + Self { inner: Arc::new(RwLock::new(dictionary)), deployed_libs: deployed_libs.to_vec() } } pub fn collect_values(&self, values: impl IntoIterator) { diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index eed3f2977..5b134194f 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -752,6 +752,8 @@ pub struct TestSetup { pub traces: Traces, /// Coverage info during setup. pub coverage: Option, + /// Addresses of external libraries deployed during setup. + pub deployed_libs: Vec
, /// The reason the setup failed, if it did. pub reason: Option, diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index ef769c662..7b1293a72 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -130,6 +130,12 @@ impl<'a> ContractRunner<'a> { U256::ZERO, Some(&self.mcr.revert_decoder), ); + + // Record deployed library address. + if let Ok(deployed) = &deploy_result { + result.deployed_libs.push(deployed.address); + } + let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?; result.extend(raw, TraceKind::Deployment); if reason.is_some() { @@ -614,6 +620,7 @@ impl<'a> FunctionRunner<'a> { let invariant_result = match evm.invariant_fuzz( invariant_contract.clone(), &self.setup.fuzz_fixtures, + &self.setup.deployed_libs, progress.as_ref(), ) { Ok(x) => x, @@ -728,6 +735,7 @@ impl<'a> FunctionRunner<'a> { let result = fuzzed_executor.fuzz( func, &self.setup.fuzz_fixtures, + &self.setup.deployed_libs, self.address, should_fail, &self.cr.mcr.revert_decoder, diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs index a2b4916d3..94dc945e5 100644 --- a/crates/forge/tests/it/core.rs +++ b/crates/forge/tests/it/core.rs @@ -742,8 +742,8 @@ async fn test_trace() { assert_eq!( deployment_traces.count(), - 12, - "Test {test_name} did not have exactly 12 deployment trace." + 13, + "Test {test_name} did not have exactly 13 deployment trace." ); assert!(setup_traces.count() <= 1, "Test {test_name} had more than 1 setup trace."); assert_eq!( diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 96708bdf5..2a47d3d3e 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -386,3 +386,6 @@ test_repro!(8971; |config| { prj_config.isolate = true; config.runner.config = Arc::new(prj_config); }); + +// https://github.com/foundry-rs/foundry/issues/8639 +test_repro!(8639); diff --git a/testdata/default/repros/Issue8639.t.sol b/testdata/default/repros/Issue8639.t.sol new file mode 100644 index 000000000..6f0a7b526 --- /dev/null +++ b/testdata/default/repros/Issue8639.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +library ExternalLibrary { + function doWork(uint256 a) public returns (uint256) { + return a++; + } +} + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + ExternalLibrary.doWork(1); + } + + function increment() public {} +} + +// https://github.com/foundry-rs/foundry/issues/8639 +contract Issue8639Test is DSTest { + Counter counter; + + function setUp() public { + counter = new Counter(); + } + + /// forge-config: default.fuzz.runs = 1000 + /// forge-config: default.fuzz.seed = '100' + function test_external_library_address(address test) public { + require(test != address(ExternalLibrary)); + } +} + +contract Issue8639AnotherTest is DSTest { + /// forge-config: default.fuzz.runs = 1000 + /// forge-config: default.fuzz.seed = '100' + function test_another_external_library_address(address test) public { + require(test != address(ExternalLibrary)); + } +} From 279849516fb1323bb9eb00ae8d2fbc5fa60485aa Mon Sep 17 00:00:00 2001 From: elfedy Date: Mon, 16 Dec 2024 16:23:59 -0300 Subject: [PATCH 083/107] add zksync compilers libs --- Cargo.lock | 38 +- Cargo.toml | 2 +- crates/cheatcodes/Cargo.toml | 2 +- crates/common/Cargo.toml | 2 +- crates/common/src/compile.rs | 29 +- crates/config/Cargo.toml | 3 + crates/config/src/zksync.rs | 237 +++++- crates/evm/traces/Cargo.toml | 2 +- crates/forge/Cargo.toml | 2 +- crates/script/Cargo.toml | 2 +- crates/verify/Cargo.toml | 2 +- crates/zksync/compiler/src/lib.rs | 228 ----- .../zksync/{compiler => compilers}/Cargo.toml | 17 +- .../compilers/src/artifacts/contract.rs | 128 +++ .../zksync/compilers/src/artifacts/error.rs | 270 ++++++ crates/zksync/compilers/src/artifacts/mod.rs | 184 ++++ .../src/artifacts/output_selection.rs | 71 ++ .../src/compilers/artifact_output/mod.rs | 1 + .../src/compilers/artifact_output/zk.rs | 149 ++++ .../compilers/artifact_output/zk/bytecode.rs | 63 ++ crates/zksync/compilers/src/compilers/mod.rs | 2 + .../compilers/src/compilers/zksolc/input.rs | 159 ++++ .../compilers/src/compilers/zksolc/mod.rs | 793 ++++++++++++++++++ .../src/compilers/zksolc/settings.rs | 442 ++++++++++ .../src/dual_compiled_contracts.rs} | 9 +- crates/zksync/compilers/src/lib.rs | 9 + .../{compiler => compilers}/src/libraries.rs | 0 .../compilers/test-data/in/compiler-in-1.json | 1 + .../test-data/library-remapping-in-2.json | 1 + .../test-data/library-remapping-in.json | 1 + .../test-data/yul-sample/SimpleStore.yul | 11 + .../test-data/yul-sample/SimpleStore.yul.json | 1 + 32 files changed, 2586 insertions(+), 275 deletions(-) delete mode 100644 crates/zksync/compiler/src/lib.rs rename crates/zksync/{compiler => compilers}/Cargo.toml (56%) create mode 100644 crates/zksync/compilers/src/artifacts/contract.rs create mode 100644 crates/zksync/compilers/src/artifacts/error.rs create mode 100644 crates/zksync/compilers/src/artifacts/mod.rs create mode 100644 crates/zksync/compilers/src/artifacts/output_selection.rs create mode 100644 crates/zksync/compilers/src/compilers/artifact_output/mod.rs create mode 100644 crates/zksync/compilers/src/compilers/artifact_output/zk.rs create mode 100644 crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs create mode 100644 crates/zksync/compilers/src/compilers/mod.rs create mode 100644 crates/zksync/compilers/src/compilers/zksolc/input.rs create mode 100644 crates/zksync/compilers/src/compilers/zksolc/mod.rs create mode 100644 crates/zksync/compilers/src/compilers/zksolc/settings.rs rename crates/zksync/{compiler/src/zksolc/mod.rs => compilers/src/dual_compiled_contracts.rs} (97%) create mode 100644 crates/zksync/compilers/src/lib.rs rename crates/zksync/{compiler => compilers}/src/libraries.rs (100%) create mode 100644 crates/zksync/compilers/test-data/in/compiler-in-1.json create mode 100644 crates/zksync/compilers/test-data/library-remapping-in-2.json create mode 100644 crates/zksync/compilers/test-data/library-remapping-in.json create mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul create mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json diff --git a/Cargo.lock b/Cargo.lock index fd2091c4b..f6aa7c7ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4962,7 +4962,7 @@ dependencies = [ "foundry-linking", "foundry-test-utils", "foundry-wallets", - "foundry-zksync-compiler", + "foundry-zksync-compilers", "foundry-zksync-core", "futures 0.3.31", "globset", @@ -5082,7 +5082,7 @@ dependencies = [ "foundry-evm", "foundry-linking", "foundry-wallets", - "foundry-zksync-compiler", + "foundry-zksync-compilers", "foundry-zksync-core", "futures 0.3.31", "indicatif", @@ -5153,7 +5153,7 @@ dependencies = [ "foundry-config", "foundry-evm", "foundry-test-utils", - "foundry-zksync-compiler", + "foundry-zksync-compilers", "futures 0.3.31", "itertools 0.13.0", "regex", @@ -5225,7 +5225,7 @@ dependencies = [ "foundry-evm-core", "foundry-evm-traces", "foundry-wallets", - "foundry-zksync-compiler", + "foundry-zksync-compilers", "foundry-zksync-core", "foundry-zksync-inspectors", "itertools 0.13.0", @@ -5342,7 +5342,7 @@ dependencies = [ "foundry-common-fmt", "foundry-compilers", "foundry-config", - "foundry-zksync-compiler", + "foundry-zksync-compilers", "itertools 0.13.0", "num-format", "reqwest 0.12.9", @@ -5501,6 +5501,7 @@ dependencies = [ "figment", "foundry-block-explorers", "foundry-compilers", + "foundry-zksync-compilers", "glob", "globset", "itertools 0.13.0", @@ -5679,7 +5680,7 @@ dependencies = [ "foundry-config", "foundry-evm-core", "foundry-linking", - "foundry-zksync-compiler", + "foundry-zksync-compilers", "futures 0.3.31", "itertools 0.13.0", "rayon", @@ -5796,17 +5797,26 @@ dependencies = [ ] [[package]] -name = "foundry-zksync-compiler" +name = "foundry-zksync-compilers" version = "0.0.2" dependencies = [ + "alloy-json-abi", "alloy-primitives", + "dirs 5.0.1", "eyre", + "fd-lock", "foundry-compilers", - "foundry-config", + "foundry-compilers-artifacts-solc", + "fs4 0.8.4", + "itertools 0.13.0", + "reqwest 0.12.9", "semver 1.0.23", "serde", "serde_json", + "similar-asserts", "tracing", + "walkdir", + "yansi", "zksync_types", ] @@ -5888,6 +5898,16 @@ dependencies = [ "zksync_bellman", ] +[[package]] +name = "fs4" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +dependencies = [ + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs4" version = "0.9.1" @@ -12537,7 +12557,7 @@ checksum = "4aebac1b1ef2b46e2e2bdf3c09db304800f2a77c1fa902bd5231490203042be8" dependencies = [ "const-hex", "dirs 5.0.1", - "fs4", + "fs4 0.9.1", "reqwest 0.12.9", "semver 1.0.23", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1eead3ebe..ad57e73c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,7 +171,7 @@ foundry-test-utils = { path = "crates/test-utils" } foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } foundry-zksync-core = { path = "crates/zksync/core" } -foundry-zksync-compiler = { path = "crates/zksync/compiler" } +foundry-zksync-compilers = { path = "crates/zksync/compilers" } foundry-zksync-inspectors = { path = "crates/zksync/inspectors" } # solc & compilation utilities diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 9f1882346..44711055f 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -32,7 +32,7 @@ foundry-evm-traces.workspace = true foundry-wallets.workspace = true forge-script-sequence.workspace = true foundry-zksync-core.workspace = true -foundry-zksync-compiler.workspace = true +foundry-zksync-compilers.workspace = true foundry-zksync-inspectors.workspace = true zksync_types.workspace = true diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 9652d9c03..ce688416e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } -foundry-zksync-compiler.workspace = true +foundry-zksync-compilers.workspace = true foundry-common-fmt.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index dfd0234c0..3e6f465b9 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -6,6 +6,7 @@ use crate::{ term::SpinnerReporter, TestFunctionExt, }; +use alloy_zksync::provider::ZksyncProvider; use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; use eyre::Result; use foundry_block_explorers::contract::Metadata; @@ -17,14 +18,16 @@ use foundry_compilers::{ }, report::{BasicStdoutReporter, NoReporter, Report}, solc::SolcSettings, - zksolc::{ZkSolc, ZkSolcCompiler}, - zksync::{ + Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, +}; +use foundry_zksync_compilers::{ + compilers::{ artifact_output::zk::ZkArtifactOutput, - compile::output::ProjectCompileOutput as ZkProjectCompileOutput, + zksolc::{ZkSolc, ZkSolcCompiler}, }, - Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, + libraries::{self, ZkMissingLibrary}, }; -use foundry_zksync_compiler::libraries::{self, ZkMissingLibrary}; + use num_format::{Locale, ToFormattedString}; use std::{ collections::{BTreeMap, HashSet}, @@ -315,7 +318,7 @@ impl ProjectCompiler { pub fn zksync_compile( self, project: &Project, - ) -> Result { + ) -> Result> { // TODO: Avoid process::exit if !project.paths.has_input_files() && self.files.is_empty() { sh_println!("Nothing to compile")?; @@ -338,11 +341,9 @@ impl ProjectCompiler { let files_to_compile = if !files.is_empty() { files } else { project.paths.input_files() }; let sources = Source::read_all(files_to_compile)?; - foundry_compilers::zksync::compile::project::ProjectCompiler::with_sources( - project, sources, - )? - .compile() - .map_err(Into::into) + foundry_compilers::project::ProjectCompiler::with_sources(project, sources)? + .compile() + .map_err(Into::into) }) } @@ -351,9 +352,9 @@ impl ProjectCompiler { self, root_path: impl AsRef, f: F, - ) -> Result + ) -> Result> where - F: FnOnce() -> Result, + F: FnOnce() -> Result>, { let quiet = self.quiet.unwrap_or(false); let bail = self.bail.unwrap_or(true); @@ -404,7 +405,7 @@ impl ProjectCompiler { fn zksync_handle_output( &self, root_path: impl AsRef, - output: &ZkProjectCompileOutput, + output: &ProjectCompileOutput, ) -> Result<()> { let print_names = self.print_names.unwrap_or(false); let print_sizes = self.print_sizes.unwrap_or(false); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 0abe4d2b2..b02c9904a 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -46,6 +46,9 @@ tracing.workspace = true walkdir.workspace = true yansi.workspace = true +# zksync +foundry-zksync-compilers.workspace = true + [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2" diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index 925612b4c..d3f5397c1 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -1,22 +1,38 @@ use foundry_compilers::{ + artifacts::{EvmVersion, Libraries, Severity}, + error::SolcError, + solc::{CliSettings, Solc, SolcCompiler, SolcLanguage}, + Project, ProjectBuilder, ProjectPathsConfig, +}; +use foundry_zksync_compilers::{ artifacts::{ - zksolc::output_selection::{FileOutputSelection, OutputSelection, OutputSelectionFlag}, - EvmVersion, Libraries, + contract::Contract, + output_selection::{FileOutputSelection, OutputSelection, OutputSelectionFlag}, }, - solc::CliSettings, - zksolc::{ - settings::{ - BytecodeHash, Codegen, Optimizer, OptimizerDetails, SettingsMetadata, ZkSolcError, - ZkSolcSettings, ZkSolcWarning, + compilers::{ + artifact_output::zk::{ZkArtifactOutput, ZkContractArtifact}, + zksolc::{ + get_solc_version_info, + settings::{ + BytecodeHash, Codegen, Optimizer, OptimizerDetails, SettingsMetadata, ZkSolcError, + ZkSolcSettings, ZkSolcWarning, + }, + ZkSettings, ZkSolc, ZkSolcCompiler, }, - ZkSettings, }, }; +use semver::Version; use serde::{Deserialize, Serialize}; use std::{collections::HashSet, path::PathBuf}; -use crate::SolcReq; +use crate::{Config, SkipBuildFilters, SolcReq}; + +/// Filename for zksync cache +pub const ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME: &str = "zksync-solidity-files-cache.json"; + +/// Directory for zksync artifacts +pub const ZKSYNC_ARTIFACTS_DIR: &str = "zkout"; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] /// ZkSync configuration @@ -143,3 +159,206 @@ impl ZkSyncConfig { ZkSolcSettings { settings: zk_settings, cli_settings: CliSettings::default() } } } + +// Config overrides to create zksync specific foundry-compilers data structures + +/// Returns the configured `zksolc` `Settings` that includes: +/// - all libraries +/// - the optimizer (including details, if configured) +/// - evm version +pub fn config_zksolc_settings(config: &Config) -> Result { + let libraries = match config.parsed_libraries() { + Ok(libs) => config.project_paths::().apply_lib_remappings(libs), + Err(e) => return Err(SolcError::msg(format!("Failed to parse libraries: {e}"))), + }; + + Ok(config.zksync.settings(libraries, config.evm_version, config.via_ir)) +} + +/// Create a new zkSync project +pub fn config_create_project( + config: &Config, + cached: bool, + no_artifacts: bool, +) -> Result, SolcError> { + let mut builder = ProjectBuilder::::default() + .artifacts(ZkArtifactOutput {}) + .paths(config_project_paths(config)) + .settings(config_zksolc_settings(config)?) + .ignore_error_codes(config.ignored_error_codes.iter().copied().map(Into::into)) + .ignore_paths(config.ignored_file_paths.clone()) + .set_compiler_severity_filter(if config.deny_warnings { + Severity::Warning + } else { + Severity::Error + }) + .set_offline(config.offline) + .set_cached(cached) + .set_build_info(!no_artifacts && config.build_info) + .set_no_artifacts(no_artifacts); + + if !config.skip.is_empty() { + let filter = SkipBuildFilters::new(config.skip.clone(), config.root.clone()); + builder = builder.sparse_output(filter); + } + + let zksolc = if let Some(zksolc) = + config_ensure_zksolc(config.zksync.zksolc.as_ref(), config.offline)? + { + zksolc + } else if !config.offline { + let default_version = semver::Version::new(1, 5, 7); + let mut zksolc = ZkSolc::find_installed_version(&default_version)?; + if zksolc.is_none() { + ZkSolc::blocking_install(&default_version)?; + zksolc = ZkSolc::find_installed_version(&default_version)?; + } + zksolc.unwrap_or_else(|| panic!("Could not install zksolc v{}", default_version)) + } else { + "zksolc".into() + }; + + let zksolc_compiler = ZkSolcCompiler { zksolc, solc: config_solc_compiler(config)? }; + + let project = builder.build(zksolc_compiler)?; + + if config.force { + config.cleanup(&project)?; + } + + Ok(project) +} + +/// Returns solc compiler to use along zksolc using the following rules: +/// 1. If `solc_path` in zksync config options is set, use it. +/// 2. If `solc_path` is not set, check the `solc` requirements: a. If a version is specified, use +/// zkVm solc matching that version. b. If a path is specified, use it. +/// 3. If none of the above, use autodetect which will match source files to a compiler version and +/// use zkVm solc matching that version. +fn config_solc_compiler(config: &Config) -> Result { + if let Some(path) = &config.zksync.solc_path { + if !path.is_file() { + return Err(SolcError::msg(format!("`solc` {} does not exist", path.display()))) + } + let version = get_solc_version_info(path)?.version; + let solc = + Solc::new_with_version(path, Version::new(version.major, version.minor, version.patch)); + return Ok(SolcCompiler::Specific(solc)) + } + + if let Some(ref solc) = config.solc { + let solc = match solc { + SolcReq::Version(version) => { + let solc_version_without_metadata = + format!("{}.{}.{}", version.major, version.minor, version.patch); + let maybe_solc = + ZkSolc::find_solc_installed_version(&solc_version_without_metadata)?; + let path = if let Some(solc) = maybe_solc { + solc + } else { + ZkSolc::solc_blocking_install(&solc_version_without_metadata)? + }; + Solc::new_with_version( + path, + Version::new(version.major, version.minor, version.patch), + ) + } + SolcReq::Local(path) => { + if !path.is_file() { + return Err(SolcError::msg(format!("`solc` {} does not exist", path.display()))) + } + let version = get_solc_version_info(path)?.version; + Solc::new_with_version( + path, + Version::new(version.major, version.minor, version.patch), + ) + } + }; + Ok(SolcCompiler::Specific(solc)) + } else { + Ok(SolcCompiler::AutoDetect) + } +} + +/// Returns the `ProjectPathsConfig` sub set of the config. +pub fn config_project_paths(config: &Config) -> ProjectPathsConfig { + let builder = ProjectPathsConfig::builder() + .cache(config.cache_path.join(ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME)) + .sources(&config.src) + .tests(&config.test) + .scripts(&config.script) + .artifacts(config.root.join(ZKSYNC_ARTIFACTS_DIR)) + .libs(config.libs.iter()) + .remappings(config.get_all_remappings()) + .allowed_path(&config.root) + .allowed_paths(&config.libs) + .allowed_paths(&config.allow_paths) + .include_paths(&config.include_paths); + + builder.build_with_root(&config.root) +} + +/// Ensures that the configured version is installed if explicitly set +/// +/// If `zksolc` is [`SolcReq::Version`] then this will download and install the solc version if +/// it's missing, unless the `offline` flag is enabled, in which case an error is thrown. +/// +/// If `zksolc` is [`SolcReq::Local`] then this will ensure that the path exists. +pub fn config_ensure_zksolc( + zksolc: Option<&SolcReq>, + offline: bool, +) -> Result, SolcError> { + if let Some(ref zksolc) = zksolc { + let zksolc = match zksolc { + SolcReq::Version(version) => { + let mut zksolc = ZkSolc::find_installed_version(version)?; + if zksolc.is_none() { + if offline { + return Err(SolcError::msg(format!( + "can't install missing zksolc {version} in offline mode" + ))) + } + ZkSolc::blocking_install(version)?; + zksolc = ZkSolc::find_installed_version(version)?; + } + zksolc + } + SolcReq::Local(zksolc) => { + if !zksolc.is_file() { + return Err(SolcError::msg(format!( + "`zksolc` {} does not exist", + zksolc.display() + ))) + } + Some(zksolc.clone()) + } + }; + return Ok(zksolc) + } + + Ok(None) +} + +#[cfg(test)] +mod tests { + use foundry_compilers::solc::SolcCompiler; + use semver::Version; + + use crate::Config; + + use super::*; + + #[test] + fn zksync_project_has_zksync_solc_when_solc_req_is_a_version() { + let config = + Config { solc: Some(SolcReq::Version(Version::new(0, 8, 26))), ..Default::default() }; + let project = config_create_project(&config, false, true).unwrap(); + let solc_compiler = project.compiler.solc; + if let SolcCompiler::Specific(path) = solc_compiler { + let version = get_solc_version_info(&path.solc).unwrap(); + assert!(version.zksync_version.is_some()); + } else { + panic!("Expected SolcCompiler::Specific"); + } + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index a10e9230d..8e9e2394c 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -21,7 +21,7 @@ foundry-compilers.workspace = true foundry-linking.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true -foundry-zksync-compiler.workspace = true +foundry-zksync-compilers.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 4eaa0eee8..8dd228040 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -36,7 +36,7 @@ foundry-evm-abi.workspace = true foundry-wallets.workspace = true foundry-linking.workspace = true foundry-zksync-core.workspace = true -foundry-zksync-compiler.workspace = true +foundry-zksync-compilers.workspace = true forge-script-sequence.workspace = true ethers-contract-abigen = { workspace = true, features = ["providers"] } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index 56a024337..624af4123 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -25,7 +25,7 @@ foundry-wallets.workspace = true foundry-linking.workspace = true forge-script-sequence.workspace = true foundry-zksync-core.workspace = true -foundry-zksync-compiler.workspace = true +foundry-zksync-compilers.workspace = true serde.workspace = true eyre.workspace = true diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index eb600294c..30be8d59e 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -43,7 +43,7 @@ itertools.workspace = true ciborium = "0.2" # zk -foundry-zksync-compiler.workspace = true +foundry-zksync-compilers.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros"] } diff --git a/crates/zksync/compiler/src/lib.rs b/crates/zksync/compiler/src/lib.rs deleted file mode 100644 index 69b086b04..000000000 --- a/crates/zksync/compiler/src/lib.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! # foundry-zksync -//! -//! Main Foundry ZKSync implementation. -#![warn(missing_docs, unused_crate_dependencies)] - -/// ZKSolc specific logic. -mod zksolc; - -use std::path::PathBuf; - -use foundry_config::{Config, SkipBuildFilters, SolcReq}; -use semver::Version; -pub use zksolc::*; - -pub mod libraries; - -use foundry_compilers::{ - artifacts::Severity, - error::SolcError, - solc::{Solc, SolcCompiler, SolcLanguage}, - zksolc::{get_solc_version_info, ZkSolc, ZkSolcCompiler, ZkSolcSettings}, - zksync::artifact_output::zk::ZkArtifactOutput, - Project, ProjectBuilder, ProjectPathsConfig, -}; - -/// Filename for zksync cache -pub const ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME: &str = "zksync-solidity-files-cache.json"; - -/// Directory for zksync artifacts -pub const ZKSYNC_ARTIFACTS_DIR: &str = "zkout"; - -// Config overrides to create zksync specific foundry-compilers data structures - -/// Returns the configured `zksolc` `Settings` that includes: -/// - all libraries -/// - the optimizer (including details, if configured) -/// - evm version -pub fn config_zksolc_settings(config: &Config) -> Result { - let libraries = match config.parsed_libraries() { - Ok(libs) => config.project_paths::().apply_lib_remappings(libs), - Err(e) => return Err(SolcError::msg(format!("Failed to parse libraries: {e}"))), - }; - - Ok(config.zksync.settings(libraries, config.evm_version, config.via_ir)) -} - -/// Create a new zkSync project -pub fn config_create_project( - config: &Config, - cached: bool, - no_artifacts: bool, -) -> Result, SolcError> { - let mut builder = ProjectBuilder::::default() - .artifacts(ZkArtifactOutput {}) - .paths(config_project_paths(config)) - .settings(config_zksolc_settings(config)?) - .ignore_error_codes(config.ignored_error_codes.iter().copied().map(Into::into)) - .ignore_paths(config.ignored_file_paths.clone()) - .set_compiler_severity_filter(if config.deny_warnings { - Severity::Warning - } else { - Severity::Error - }) - .set_offline(config.offline) - .set_cached(cached) - .set_build_info(!no_artifacts && config.build_info) - .set_no_artifacts(no_artifacts); - - if !config.skip.is_empty() { - let filter = SkipBuildFilters::new(config.skip.clone(), config.root.0.clone()); - builder = builder.sparse_output(filter); - } - - let zksolc = if let Some(zksolc) = - config_ensure_zksolc(config.zksync.zksolc.as_ref(), config.offline)? - { - zksolc - } else if !config.offline { - let default_version = semver::Version::new(1, 5, 7); - let mut zksolc = ZkSolc::find_installed_version(&default_version)?; - if zksolc.is_none() { - ZkSolc::blocking_install(&default_version)?; - zksolc = ZkSolc::find_installed_version(&default_version)?; - } - zksolc.unwrap_or_else(|| panic!("Could not install zksolc v{}", default_version)) - } else { - "zksolc".into() - }; - - let zksolc_compiler = ZkSolcCompiler { zksolc, solc: config_solc_compiler(config)? }; - - let project = builder.build(zksolc_compiler)?; - - if config.force { - config.cleanup(&project)?; - } - - Ok(project) -} - -/// Returns solc compiler to use along zksolc using the following rules: -/// 1. If `solc_path` in zksync config options is set, use it. -/// 2. If `solc_path` is not set, check the `solc` requirements: a. If a version is specified, use -/// zkVm solc matching that version. b. If a path is specified, use it. -/// 3. If none of the above, use autodetect which will match source files to a compiler version and -/// use zkVm solc matching that version. -fn config_solc_compiler(config: &Config) -> Result { - if let Some(path) = &config.zksync.solc_path { - if !path.is_file() { - return Err(SolcError::msg(format!("`solc` {} does not exist", path.display()))) - } - let version = get_solc_version_info(path)?.version; - let solc = - Solc::new_with_version(path, Version::new(version.major, version.minor, version.patch)); - return Ok(SolcCompiler::Specific(solc)) - } - - if let Some(ref solc) = config.solc { - let solc = match solc { - SolcReq::Version(version) => { - let solc_version_without_metadata = - format!("{}.{}.{}", version.major, version.minor, version.patch); - let maybe_solc = - ZkSolc::find_solc_installed_version(&solc_version_without_metadata)?; - let path = if let Some(solc) = maybe_solc { - solc - } else { - ZkSolc::solc_blocking_install(&solc_version_without_metadata)? - }; - Solc::new_with_version( - path, - Version::new(version.major, version.minor, version.patch), - ) - } - SolcReq::Local(path) => { - if !path.is_file() { - return Err(SolcError::msg(format!("`solc` {} does not exist", path.display()))) - } - let version = get_solc_version_info(path)?.version; - Solc::new_with_version( - path, - Version::new(version.major, version.minor, version.patch), - ) - } - }; - Ok(SolcCompiler::Specific(solc)) - } else { - Ok(SolcCompiler::AutoDetect) - } -} - -/// Returns the `ProjectPathsConfig` sub set of the config. -pub fn config_project_paths(config: &Config) -> ProjectPathsConfig { - let builder = ProjectPathsConfig::builder() - .cache(config.cache_path.join(ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME)) - .sources(&config.src) - .tests(&config.test) - .scripts(&config.script) - .artifacts(config.root.0.join(ZKSYNC_ARTIFACTS_DIR)) - .libs(config.libs.iter()) - .remappings(config.get_all_remappings()) - .allowed_path(&config.root.0) - .allowed_paths(&config.libs) - .allowed_paths(&config.allow_paths) - .include_paths(&config.include_paths); - - builder.build_with_root(&config.root.0) -} - -/// Ensures that the configured version is installed if explicitly set -/// -/// If `zksolc` is [`SolcReq::Version`] then this will download and install the solc version if -/// it's missing, unless the `offline` flag is enabled, in which case an error is thrown. -/// -/// If `zksolc` is [`SolcReq::Local`] then this will ensure that the path exists. -pub fn config_ensure_zksolc( - zksolc: Option<&SolcReq>, - offline: bool, -) -> Result, SolcError> { - if let Some(ref zksolc) = zksolc { - let zksolc = match zksolc { - SolcReq::Version(version) => { - let mut zksolc = ZkSolc::find_installed_version(version)?; - if zksolc.is_none() { - if offline { - return Err(SolcError::msg(format!( - "can't install missing zksolc {version} in offline mode" - ))) - } - ZkSolc::blocking_install(version)?; - zksolc = ZkSolc::find_installed_version(version)?; - } - zksolc - } - SolcReq::Local(zksolc) => { - if !zksolc.is_file() { - return Err(SolcError::msg(format!( - "`zksolc` {} does not exist", - zksolc.display() - ))) - } - Some(zksolc.clone()) - } - }; - return Ok(zksolc) - } - - Ok(None) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn zksync_project_has_zksync_solc_when_solc_req_is_a_version() { - let config = - Config { solc: Some(SolcReq::Version(Version::new(0, 8, 26))), ..Default::default() }; - let project = config_create_project(&config, false, true).unwrap(); - let solc_compiler = project.compiler.solc; - if let SolcCompiler::Specific(path) = solc_compiler { - let version = get_solc_version_info(&path.solc).unwrap(); - assert!(version.zksync_version.is_some()); - } else { - panic!("Expected SolcCompiler::Specific"); - } - } -} diff --git a/crates/zksync/compiler/Cargo.toml b/crates/zksync/compilers/Cargo.toml similarity index 56% rename from crates/zksync/compiler/Cargo.toml rename to crates/zksync/compilers/Cargo.toml index 7836d4ec8..6fc1c412c 100644 --- a/crates/zksync/compiler/Cargo.toml +++ b/crates/zksync/compilers/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "foundry-zksync-compiler" -description = "Foundry zksync-vm compiler implementations" +name = "foundry-zksync-compilers" +description = "ZKsync extensions for foundry compilers" version.workspace = true edition.workspace = true @@ -12,15 +12,24 @@ repository.workspace = true exclude.workspace = true [dependencies] -foundry-config.workspace = true foundry-compilers = { workspace = true, features = ["svm-solc"] } +foundry-compilers-artifacts-solc = "0.12.7" alloy-primitives.workspace = true +alloy-json-abi.workspace = true tracing.workspace = true serde_json.workspace = true serde.workspace = true semver.workspace = true +itertools.workspace = true +eyre.workspace = true +walkdir.workspace = true +reqwest.workspace = true +yansi.workspace = true +fd-lock = "4.0.2" +fs4 = "0.8.2" +dirs = "5" +similar-asserts.workspace = true # zk zksync_types.workspace = true -eyre = "0.6" diff --git a/crates/zksync/compilers/src/artifacts/contract.rs b/crates/zksync/compilers/src/artifacts/contract.rs new file mode 100644 index 000000000..04d1f4243 --- /dev/null +++ b/crates/zksync/compilers/src/artifacts/contract.rs @@ -0,0 +1,128 @@ +//! Contract related types. +use crate::artifacts::EraVM; +use alloy_json_abi::JsonAbi; +use foundry_compilers_artifacts_solc::{ + Bytecode, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow, + CompactContractRef, CompactDeployedBytecode, DevDoc, Offsets, StorageLayout, UserDoc, +}; +use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, collections::BTreeMap}; + +/// Represents a compiled solidity contract +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Contract { + pub abi: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(default)] + pub userdoc: UserDoc, + #[serde(default)] + pub devdoc: DevDoc, + /// The contract optimized IR code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ir_optimized: Option, + /// The contract storage layout. + #[serde(default, skip_serializing_if = "storage_layout_is_empty")] + pub storage_layout: StorageLayout, + /// The contract EraVM bytecode hash. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash: Option, + /// The contract factory dependencies. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub factory_dependencies: Option>, + /// EVM-related outputs + #[serde(default, skip_serializing_if = "Option::is_none")] + pub eravm: Option, + /// The contract's unlinked libraries + #[serde(default)] + pub missing_libraries: Vec, +} + +fn storage_layout_is_empty(storage_layout: &StorageLayout) -> bool { + storage_layout.storage.is_empty() && storage_layout.types.is_empty() +} + +impl Contract { + pub fn is_unlinked(&self) -> bool { + self.hash.is_none() || !self.missing_libraries.is_empty() + } + + pub fn missing_libs_to_link_references( + missing_libraries: &[String], + ) -> BTreeMap>> { + missing_libraries + .iter() + .map(|file_and_lib| { + let mut parts = file_and_lib.split(':'); + let filename = parts.next().expect("missing library contract file (:)"); + let contract = parts.next().expect("missing library contract name (:)"); + (filename.to_owned(), contract.to_owned()) + }) + .fold(BTreeMap::default(), |mut acc, (filename, contract)| { + acc.entry(filename) + .or_default() + //empty offsets since we can't patch it anyways + .insert(contract, vec![]); + acc + }) + } + + fn link_references(&self) -> BTreeMap>> { + Self::missing_libs_to_link_references(self.missing_libraries.as_slice()) + } + + pub fn bytecode(&self) -> Option { + self.eravm.as_ref().and_then(|eravm| eravm.bytecode(self.is_unlinked())).map(|object| { + let mut bytecode: Bytecode = object.into(); + bytecode.link_references = self.link_references(); + bytecode + }) + } +} + +// CompactContract variants +// TODO: for zkEvm, the distinction between bytecode and deployed_bytecode makes little sense, +// and there some fields that the ouptut doesn't provide (e.g: source_map) +// However, we implement these because we get the Artifact trait and can reuse lots of +// the crate's helpers without needing to duplicate everything. Maybe there's a way +// we can get all these without having to add the same bytecode twice on each struct. +// Ideally the Artifacts trait would not be coupled to a specific Contract type +impl<'a> From<&'a Contract> for CompactContractBytecodeCow<'a> { + fn from(artifact: &'a Contract) -> Self { + let bc = artifact.bytecode(); + let bytecode = bc.clone().map(|bc| CompactBytecode { + object: bc.object, + source_map: None, + link_references: bc.link_references, + }); + let deployed_bytecode = bytecode.clone().map(|bytecode| CompactDeployedBytecode { + bytecode: Some(bytecode), + immutable_references: Default::default(), + }); + + CompactContractBytecodeCow { + abi: artifact.abi.as_ref().map(Cow::Borrowed), + bytecode: bytecode.map(Cow::Owned), + deployed_bytecode: deployed_bytecode.map(Cow::Owned), + } + } +} + +impl From for CompactContractBytecode { + fn from(c: Contract) -> Self { + CompactContractBytecodeCow::from(&c).into() + } +} + +impl<'a> From<&'a Contract> for CompactContractRef<'a> { + fn from(c: &'a Contract) -> Self { + let (bin, bin_runtime) = if let Some(ref eravm) = c.eravm { + (eravm.bytecode.as_ref(), eravm.bytecode.as_ref()) + } else { + (None, None) + }; + + Self { abi: c.abi.as_ref(), bin, bin_runtime } + } +} diff --git a/crates/zksync/compilers/src/artifacts/error.rs b/crates/zksync/compilers/src/artifacts/error.rs new file mode 100644 index 000000000..72c5a40a9 --- /dev/null +++ b/crates/zksync/compilers/src/artifacts/error.rs @@ -0,0 +1,270 @@ +use foundry_compilers_artifacts_solc::error::{Severity, SourceLocation}; + +use foundry_compilers_artifacts_solc::serde_helpers; +use serde::{Deserialize, Serialize}; +use std::{fmt, ops::Range}; +use yansi::{Color, Style}; + +/// The `solc --standard-json` output error. +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Error { + /// The component type. + pub component: String, + /// The error code. + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub error_code: Option, + /// The formatted error message. + pub formatted_message: Option, + /// The non-formatted error message. + pub message: String, + /// The error severity. + pub severity: Severity, + /// The error location data. + pub source_location: Option, + /// The error type. + pub r#type: String, +} + +impl Error { + /// Returns `true` if the error is an error. + pub const fn is_error(&self) -> bool { + self.severity.is_error() + } + + /// Returns `true` if the error is a warning. + pub const fn is_warning(&self) -> bool { + self.severity.is_warning() + } + + /// Returns `true` if the error is an info. + pub const fn is_info(&self) -> bool { + self.severity.is_info() + } +} +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Adding short msg for zksolc results in duplicate error messages. + // Check if this is always the case or if it would be useful to + // add it sometimes. + //let mut short_msg = self.message.trim(); + let fmtd_msg = self.formatted_message.as_deref().unwrap_or(""); + + /* + if short_msg.is_empty() { + // if the message is empty, try to extract the first line from the formatted message + if let Some(first_line) = fmtd_msg.lines().next() { + // this is something like `ParserError: ` + if let Some((_, s)) = first_line.split_once(':') { + short_msg = s.trim_start(); + } else { + short_msg = first_line; + } + } + } + */ + + // Error (XXXX): Error Message + styled(f, self.severity.color().bold(), |f| self.fmt_severity(f))?; + //fmt_msg(f, short_msg)?; + + let mut lines = fmtd_msg.lines(); + + /* + // skip the first line if it contains the same message as the one we just formatted, + // unless it also contains a source location, in which case the entire error message is an + // old style error message, like: + // path/to/file:line:column: ErrorType: message + if lines.clone().next().map_or(false, |l| { + l.contains(short_msg) && l.bytes().filter(|b| *b == b':').count() < 3 + }) { + let _ = lines.next(); + } + */ + + // format the main source location + fmt_source_location(f, &mut lines)?; + + // format remaining lines as secondary locations + while let Some(line) = lines.next() { + f.write_str("\n")?; + + if let Some((note, msg)) = line.split_once(':') { + styled(f, Self::secondary_style(), |f| f.write_str(note))?; + fmt_msg(f, msg)?; + } else { + f.write_str(line)?; + } + + fmt_source_location(f, &mut lines)?; + } + + Ok(()) + } +} + +impl Error { + /// The style of the diagnostic severity. + pub fn error_style(&self) -> Style { + self.severity.color().bold() + } + + /// The style of the diagnostic message. + pub fn message_style() -> Style { + Color::White.bold() + } + + /// The style of the secondary source location. + pub fn secondary_style() -> Style { + Color::Cyan.bold() + } + + /// The style of the source location highlight. + pub fn highlight_style() -> Style { + Style::new().fg(Color::Yellow) + } + + /// The style of the diagnostics. + pub fn diag_style() -> Style { + Color::Yellow.bold() + } + + /// The style of the source location frame. + pub fn frame_style() -> Style { + Style::new().fg(Color::Blue) + } + + /// Formats the diagnostic severity: + /// + /// ```text + /// Error (XXXX) + /// ``` + fn fmt_severity(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.severity.as_str())?; + if let Some(code) = self.error_code { + write!(f, " ({code})")?; + } + Ok(()) + } +} + +/// Formats the diagnostic message. +fn fmt_msg(f: &mut fmt::Formatter<'_>, msg: &str) -> fmt::Result { + styled(f, Error::message_style(), |f| { + f.write_str(": ")?; + f.write_str(msg.trim_start()) + }) +} + +fn fmt_source_location(f: &mut fmt::Formatter<'_>, lines: &mut std::str::Lines<'_>) -> fmt::Result { + // --> source + if let Some(line) = lines.next() { + f.write_str("\n")?; + + let arrow = "-->"; + if let Some((left, loc)) = line.split_once(arrow) { + f.write_str(left)?; + styled(f, Error::frame_style(), |f| f.write_str(arrow))?; + f.write_str(loc)?; + } else { + f.write_str(line)?; + } + } + + // get the next 3 lines + let Some(line1) = lines.next() else { + return Ok(()); + }; + let Some(line2) = lines.next() else { + f.write_str("\n")?; + f.write_str(line1)?; + return Ok(()); + }; + let Some(line3) = lines.next() else { + f.write_str("\n")?; + f.write_str(line1)?; + f.write_str("\n")?; + f.write_str(line2)?; + return Ok(()); + }; + + // line 1, just a frame + fmt_framed_location(f, line1, None)?; + + // line 2, frame and code; highlight the text based on line 3's carets + let hl_start = line3.find('^'); + let highlight = hl_start.map(|start| { + let end = if line3.contains("^ (") { + // highlight the entire line because of "spans across multiple lines" diagnostic + line2.len() + } else if let Some(carets) = line3[start..].find(|c: char| c != '^') { + // highlight the text that the carets point to + start + carets + } else { + // the carets span the entire third line + line3.len() + } + // bound in case carets span longer than the code they point to + .min(line2.len()); + (start.min(end)..end, Error::highlight_style()) + }); + fmt_framed_location(f, line2, highlight)?; + + // line 3, frame and maybe highlight, this time till the end unconditionally + let highlight = hl_start.map(|i| (i..line3.len(), Error::diag_style())); + fmt_framed_location(f, line3, highlight) +} + +/// Colors a single Solidity framed source location line. Part of [`fmt_source_location`]. +fn fmt_framed_location( + f: &mut fmt::Formatter<'_>, + line: &str, + highlight: Option<(Range, Style)>, +) -> fmt::Result { + f.write_str("\n")?; + + if let Some((space_or_line_number, rest)) = line.split_once('|') { + // if the potential frame is not just whitespace or numbers, don't color it + if !space_or_line_number.chars().all(|c| c.is_whitespace() || c.is_numeric()) { + return f.write_str(line); + } + + styled(f, Error::frame_style(), |f| { + f.write_str(space_or_line_number)?; + f.write_str("|") + })?; + + if let Some((range, style)) = highlight { + let Range { start, end } = range; + // Skip highlighting if the range is not valid unicode. + if !line.is_char_boundary(start) || !line.is_char_boundary(end) { + f.write_str(rest) + } else { + let rest_start = line.len() - rest.len(); + f.write_str(&line[rest_start..start])?; + styled(f, style, |f| f.write_str(&line[range]))?; + f.write_str(&line[end..]) + } + } else { + f.write_str(rest) + } + } else { + f.write_str(line) + } +} + +/// Calls `fun` in between [`Style::fmt_prefix`] and [`Style::fmt_suffix`]. +fn styled(f: &mut fmt::Formatter<'_>, style: Style, fun: F) -> fmt::Result +where + F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result, +{ + let enabled = yansi::is_enabled(); + if enabled { + style.fmt_prefix(f)?; + } + fun(f)?; + if enabled { + style.fmt_suffix(f)?; + } + Ok(()) +} diff --git a/crates/zksync/compilers/src/artifacts/mod.rs b/crates/zksync/compilers/src/artifacts/mod.rs new file mode 100644 index 000000000..edcda9550 --- /dev/null +++ b/crates/zksync/compilers/src/artifacts/mod.rs @@ -0,0 +1,184 @@ +use foundry_compilers_artifacts_solc::{ + BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles, +}; + +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, HashSet}, + path::{Path, PathBuf}, +}; + +pub mod contract; +pub mod error; +pub mod output_selection; + +use self::{contract::Contract, error::Error}; + +/// file -> (contract name -> Contract) +pub type Contracts = FileToContractsMap; + +/// Output type `zksolc` produces +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct CompilerOutput { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub errors: Vec, + #[serde(default)] + pub sources: BTreeMap, + #[serde(default)] + pub contracts: FileToContractsMap, + /// The `solc` compiler version. + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + /// The `solc` compiler long version. + #[serde(skip_serializing_if = "Option::is_none")] + pub long_version: Option, + /// The `zksolc` compiler version. + #[serde(skip_serializing_if = "Option::is_none")] + pub zk_version: Option, + /// The ZKsync solc compiler version (if it was used). This field is + /// inserted by this crate and not an actual part of the compiler output + #[serde(skip_serializing_if = "Option::is_none")] + pub zksync_solc_version: Option, +} + +impl CompilerOutput { + /// Whether the output contains a compiler error + pub fn has_error(&self) -> bool { + self.errors.iter().any(|err| err.severity.is_error()) + } + + /// Returns the output's source files and contracts separately, wrapped in helper types that + /// provide several helper methods + pub fn split(self) -> (SourceFiles, OutputContracts) { + (SourceFiles(self.sources), OutputContracts(self.contracts)) + } + + /// Retains only those files the given iterator yields + /// + /// In other words, removes all contracts for files not included in the iterator + pub fn retain_files<'a, I>(&mut self, files: I) + where + I: IntoIterator, + { + // Note: use `to_lowercase` here because solc not necessarily emits the exact file name, + // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol` + let files: HashSet<_> = + files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect(); + self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase())); + self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase())); + } + + pub fn merge(&mut self, other: Self) { + self.errors.extend(other.errors); + self.contracts.extend(other.contracts); + self.sources.extend(other.sources); + } + + pub fn join_all(&mut self, root: impl AsRef) { + let root = root.as_ref(); + self.contracts = std::mem::take(&mut self.contracts) + .into_iter() + .map(|(path, contracts)| (root.join(path), contracts)) + .collect(); + self.sources = std::mem::take(&mut self.sources) + .into_iter() + .map(|(path, source)| (root.join(path), source)) + .collect(); + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct EraVM { + /// The contract EraVM assembly code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub assembly: Option, + /// The contract bytecode. + /// Is reset by that of EraVM before yielding the compiled project artifacts. + #[serde(default, skip_serializing_if = "Option::is_none")] + bytecode: Option, +} + +impl EraVM { + pub fn bytecode(&self, should_be_unlinked: bool) -> Option { + self.bytecode.as_ref().map(|object| match (should_be_unlinked, object) { + (true, BytecodeObject::Bytecode(bc)) => { + // convert to unlinked + let encoded = alloy_primitives::hex::encode(bc); + BytecodeObject::Unlinked(encoded) + } + (false, BytecodeObject::Unlinked(bc)) => { + // convert to linked + let bytecode = alloy_primitives::hex::decode(bc).expect("valid bytecode"); + BytecodeObject::Bytecode(bytecode.into()) + } + _ => object.to_owned(), + }) + } + + // TODO: tmp to make compiler abstraction sample work, needs some thought on + // how do transform linked/to unlinked + pub fn bytecode_ref(&self) -> Option<&BytecodeObject> { + self.bytecode.as_ref() + } +} + +/// +/// The `solc --standard-json` output contract EVM extra metadata. +#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ExtraMetadata { + /// The list of recursive functions. + #[serde(default = "Vec::new")] + pub recursive_functions: Vec, +} + +/// +/// The `solc --standard-json` output contract EVM recursive function. +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct RecursiveFunction { + /// The function name. + pub name: String, + /// The creation code function block tag. + pub creation_tag: Option, + /// The runtime code function block tag. + pub runtime_tag: Option, + /// The number of input arguments. + #[serde(rename = "totalParamSize")] + pub input_size: usize, + /// The number of output arguments. + #[serde(rename = "totalRetParamSize")] + pub output_size: usize, +} + +/// A wrapper helper type for the `Contracts` type alias +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct OutputContracts(pub Contracts); + +impl OutputContracts { + /// Returns an iterator over all contracts and their source names. + pub fn into_contracts(self) -> impl Iterator { + self.0.into_values().flatten() + } + + /// Iterate over all contracts and their names + pub fn contracts_iter(&self) -> impl Iterator { + self.0.values().flatten() + } + + /// Finds the _first_ contract with the given name + pub fn find(&self, contract: impl AsRef) -> Option> { + let contract_name = contract.as_ref(); + self.contracts_iter().find_map(|(name, contract)| { + (name == contract_name).then(|| CompactContractRef::from(contract)) + }) + } + + /// Finds the first contract with the given name and removes it from the set + pub fn remove(&mut self, contract: impl AsRef) -> Option { + let contract_name = contract.as_ref(); + self.0.values_mut().find_map(|c| c.remove(contract_name)) + } +} diff --git a/crates/zksync/compilers/src/artifacts/output_selection.rs b/crates/zksync/compilers/src/artifacts/output_selection.rs new file mode 100644 index 000000000..e05b27515 --- /dev/null +++ b/crates/zksync/compilers/src/artifacts/output_selection.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; + +use std::collections::HashSet; + +/// +/// The `solc --standard-json` output selection. +#[derive(Debug, Default, Serialize, Deserialize, Eq, PartialEq, Clone)] +pub struct OutputSelection { + /// Only the 'all' wildcard is available for robustness reasons. + #[serde(rename = "*")] + pub all: FileOutputSelection, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct FileOutputSelection { + /// The per-file output selections. + #[serde(rename = "")] + pub per_file: HashSet, + /// The per-contract output selections. + #[serde(rename = "*")] + pub per_contract: HashSet, +} + +/// +/// The `solc --standard-json` expected output selection flag. +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +pub enum OutputSelectionFlag { + /// The ABI JSON. + #[serde(rename = "abi")] + ABI, + /// The metadata. + #[serde(rename = "metadata")] + Metadata, + /// The developer documentation. + #[serde(rename = "devdoc")] + Devdoc, + /// The user documentation. + #[serde(rename = "userdoc")] + Userdoc, + /// The function signature hashes JSON. + #[serde(rename = "evm.methodIdentifiers")] + MethodIdentifiers, + /// The storage layout. + #[serde(rename = "storageLayout")] + StorageLayout, + /// The AST JSON. + #[serde(rename = "ast")] + AST, + /// The Yul IR. + #[serde(rename = "irOptimized")] + Yul, + /// The EVM legacy assembly JSON. + #[serde(rename = "evm.legacyAssembly")] + EVMLA, +} + +impl std::fmt::Display for OutputSelectionFlag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ABI => write!(f, "abi"), + Self::Metadata => write!(f, "metadata"), + Self::Devdoc => write!(f, "devdoc"), + Self::Userdoc => write!(f, "userdoc"), + Self::MethodIdentifiers => write!(f, "evm.methodIdentifiers"), + Self::StorageLayout => write!(f, "storageLayout"), + Self::AST => write!(f, "ast"), + Self::Yul => write!(f, "irOptimized"), + Self::EVMLA => write!(f, "evm.legacyAssembly"), + } + } +} diff --git a/crates/zksync/compilers/src/compilers/artifact_output/mod.rs b/crates/zksync/compilers/src/compilers/artifact_output/mod.rs new file mode 100644 index 000000000..fc4887264 --- /dev/null +++ b/crates/zksync/compilers/src/compilers/artifact_output/mod.rs @@ -0,0 +1 @@ +pub mod zk; diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs new file mode 100644 index 000000000..b28f4b0e3 --- /dev/null +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs @@ -0,0 +1,149 @@ +use crate::artifacts::contract::Contract; +use alloy_json_abi::JsonAbi; +use foundry_compilers::{ + artifacts::{DevDoc, SourceFile, StorageLayout, UserDoc}, + sources::VersionedSourceFile, + ArtifactOutput, +}; +use foundry_compilers_artifacts_solc::{ + CompactBytecode, CompactContract, CompactContractBytecode, CompactContractBytecodeCow, + CompactDeployedBytecode, +}; +use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, collections::BTreeMap, path::Path}; + +mod bytecode; +pub use bytecode::ZkArtifactBytecode; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ZkContractArtifact { + pub abi: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bytecode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub assembly: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage_layout: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub userdoc: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub devdoc: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ir_optimized: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub factory_dependencies: Option>, + /// The identifier of the source file + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, +} + +impl ZkContractArtifact { + pub fn missing_libraries(&self) -> Option<&Vec> { + self.bytecode.as_ref().map(|bc| &bc.missing_libraries) + } +} + +// CompactContract variants +// TODO: for zkEvm, the distinction between bytecode and deployed_bytecode makes little sense, +// and there some fields that the ouptut doesn't provide (e.g: source_map) +// However, we implement these because we get the Artifact trait and can reuse lots of +// the crate's helpers without needing to duplicate everything. Maybe there's a way +// we can get all these without having to add the same bytecode twice on each struct. +// Ideally the Artifacts trait would not be coupled to a specific Contract type +impl<'a> From<&'a ZkContractArtifact> for CompactContractBytecodeCow<'a> { + fn from(artifact: &'a ZkContractArtifact) -> Self { + // TODO: artifact.abi might have None, we need to get this field from solc_metadata + CompactContractBytecodeCow { + abi: artifact.abi.as_ref().map(Cow::Borrowed), + bytecode: artifact.bytecode.clone().map(|b| Cow::Owned(CompactBytecode::from(b))), + deployed_bytecode: artifact + .bytecode + .clone() + .map(|b| Cow::Owned(CompactDeployedBytecode::from(b))), + } + } +} + +impl From for CompactContractBytecode { + fn from(c: ZkContractArtifact) -> Self { + Self { + abi: c.abi.map(Into::into), + deployed_bytecode: c.bytecode.clone().map(|b| b.into()), + bytecode: c.bytecode.clone().map(|b| b.into()), + } + } +} + +impl From for CompactContract { + fn from(c: ZkContractArtifact) -> Self { + // TODO: c.abi might have None, we need to get this field from solc_metadata + Self { + bin: c.bytecode.clone().map(|b| b.object()), + bin_runtime: c.bytecode.clone().map(|b| b.object()), + abi: c.abi, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +pub struct ZkArtifactOutput(); + +impl ArtifactOutput for ZkArtifactOutput { + type Artifact = ZkContractArtifact; + type CompilerContract = Contract; + + fn contract_to_artifact( + &self, + _file: &Path, + _name: &str, + contract: Self::CompilerContract, + source_file: Option<&SourceFile>, + ) -> Self::Artifact { + let is_unlinked = contract.is_unlinked(); + let Contract { + abi, + metadata, + userdoc, + devdoc, + storage_layout, + eravm, + ir_optimized, + hash, + factory_dependencies, + missing_libraries, + } = contract; + + let (bytecode, assembly) = eravm + .map(|eravm| (eravm.bytecode(is_unlinked), eravm.assembly)) + .unwrap_or_else(|| (None, None)); + let bytecode = bytecode + .map(|object| ZkArtifactBytecode::with_object(object, is_unlinked, missing_libraries)); + + ZkContractArtifact { + abi, + hash, + factory_dependencies, + storage_layout: Some(storage_layout), + bytecode, + assembly, + metadata, + userdoc: Some(userdoc), + devdoc: Some(devdoc), + ir_optimized, + id: source_file.as_ref().map(|s| s.id), + } + } + + fn standalone_source_file_to_artifact( + &self, + _path: &Path, + _file: &VersionedSourceFile, + ) -> Option { + None + } +} diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs new file mode 100644 index 000000000..19c2045bf --- /dev/null +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs @@ -0,0 +1,63 @@ +use std::collections::BTreeMap; + +use crate::artifacts::contract::Contract; +use alloy_primitives::Bytes; +use foundry_compilers_artifacts_solc::{ + BytecodeObject, CompactBytecode, CompactDeployedBytecode, Offsets, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct ZkArtifactBytecode { + object: Bytes, + is_unlinked: bool, + + #[serde(default)] + pub missing_libraries: Vec, +} + +impl ZkArtifactBytecode { + pub fn with_object( + object: BytecodeObject, + is_unlinked: bool, + missing_libraries: Vec, + ) -> Self { + let object = match object { + BytecodeObject::Bytecode(bc) => bc, + BytecodeObject::Unlinked(s) => { + alloy_primitives::hex::decode(s).expect("valid bytecode").into() + } + }; + Self { object, is_unlinked, missing_libraries } + } + + fn link_references(&self) -> BTreeMap>> { + Contract::missing_libs_to_link_references(self.missing_libraries.as_slice()) + } + + pub fn object(&self) -> BytecodeObject { + if self.is_unlinked { + // convert to unlinked + let encoded = alloy_primitives::hex::encode(&self.object); + BytecodeObject::Unlinked(encoded) + } else { + // convert to linked + BytecodeObject::Bytecode(self.object.clone()) + } + } +} + +// NOTE: distinction between bytecode and deployed bytecode makes no sense of zkEvm, but +// we implement these conversions in order to be able to use the Artifacts trait. +impl From for CompactBytecode { + fn from(bcode: ZkArtifactBytecode) -> Self { + let link_references = bcode.link_references(); + Self { object: bcode.object(), source_map: None, link_references } + } +} + +impl From for CompactDeployedBytecode { + fn from(bcode: ZkArtifactBytecode) -> Self { + Self { bytecode: Some(bcode.into()), immutable_references: BTreeMap::default() } + } +} diff --git a/crates/zksync/compilers/src/compilers/mod.rs b/crates/zksync/compilers/src/compilers/mod.rs new file mode 100644 index 000000000..c7886bf97 --- /dev/null +++ b/crates/zksync/compilers/src/compilers/mod.rs @@ -0,0 +1,2 @@ +pub mod artifact_output; +pub mod zksolc; diff --git a/crates/zksync/compilers/src/compilers/zksolc/input.rs b/crates/zksync/compilers/src/compilers/zksolc/input.rs new file mode 100644 index 000000000..fe8de50c6 --- /dev/null +++ b/crates/zksync/compilers/src/compilers/zksolc/input.rs @@ -0,0 +1,159 @@ +use super::{ + settings::{ZkSolcError, ZkSolcSettings, ZkSolcWarning}, + ZkSettings, +}; +use foundry_compilers::{ + compilers::{solc::SolcLanguage, CompilerInput}, + solc, +}; +use foundry_compilers_artifacts_solc::{remappings::Remapping, serde_helpers, Source, Sources}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + collections::HashSet, + path::{Path, PathBuf}, +}; +use tracing::warn; + +#[derive(Debug, Clone, Serialize)] +pub struct ZkSolcVersionedInput { + #[serde(flatten)] + pub input: ZkSolcInput, + pub solc_version: Version, + pub cli_settings: solc::CliSettings, +} + +impl CompilerInput for ZkSolcVersionedInput { + type Settings = ZkSolcSettings; + type Language = SolcLanguage; + + // WARN: version is the solc version and NOT the zksolc version + // This is because we use solc's version resolution to figure + // out what solc to pair zksolc with. + fn build( + sources: Sources, + settings: Self::Settings, + language: Self::Language, + version: Version, + ) -> Self { + let ZkSolcSettings { settings, cli_settings } = settings; + let input = ZkSolcInput::new(language, sources, settings).sanitized(&version); + + Self { solc_version: version, input, cli_settings } + } + + fn language(&self) -> Self::Language { + self.input.language + } + + // TODO: This is the solc_version and not the zksolc version. We store this here because + // the input is not associated with a zksolc version and we use solc's version resolution + // features to know what solc to use to compile a file with. We should think about + // how to best honor this api so the value is not confusing. + fn version(&self) -> &Version { + &self.solc_version + } + + fn sources(&self) -> impl Iterator { + self.input.sources.iter().map(|(path, source)| (path.as_path(), source)) + } + + fn compiler_name(&self) -> Cow<'static, str> { + "zksolc and solc".into() + } + + fn strip_prefix(&mut self, base: &Path) { + self.input.strip_prefix(base); + } +} + +/// Input type `zksolc` expects. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ZkSolcInput { + pub language: SolcLanguage, + pub sources: Sources, + pub settings: ZkSettings, + // For `zksolc` versions <1.5.7, suppressed warnings / errors were specified on the same level + // as `settings`. For `zksolc` 1.5.7+, they are specified inside `settings`. Since we want to + // support both options at the time, we duplicate fields from `settings` here. + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub suppressed_warnings: HashSet, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub suppressed_errors: HashSet, +} + +/// Default `language` field is set to `"Solidity"`. +impl Default for ZkSolcInput { + fn default() -> Self { + Self { + language: SolcLanguage::Solidity, + sources: Sources::default(), + settings: ZkSettings::default(), + suppressed_warnings: HashSet::default(), + suppressed_errors: HashSet::default(), + } + } +} + +impl ZkSolcInput { + fn new(language: SolcLanguage, sources: Sources, settings: ZkSettings) -> Self { + let suppressed_warnings = settings.suppressed_warnings.clone(); + let suppressed_errors = settings.suppressed_errors.clone(); + Self { language, sources, settings, suppressed_warnings, suppressed_errors } + } + + /// Removes the `base` path from all source files + pub fn strip_prefix(&mut self, base: impl AsRef) { + let base = base.as_ref(); + self.sources = std::mem::take(&mut self.sources) + .into_iter() + .map(|(path, s)| (path.strip_prefix(base).map(Into::into).unwrap_or(path), s)) + .collect(); + + self.settings.strip_prefix(base); + } + /// The flag indicating whether the current [CompilerInput] is + /// constructed for the yul sources + pub fn is_yul(&self) -> bool { + self.language == SolcLanguage::Yul + } + /// Consumes the type and returns a [ZkSolcInput::sanitized] version + pub fn sanitized(mut self, version: &Version) -> Self { + self.settings.sanitize(version); + self + } + + pub fn with_remappings(mut self, remappings: Vec) -> Self { + if self.language == SolcLanguage::Yul { + if !remappings.is_empty() { + warn!("omitting remappings supplied for the yul sources"); + } + } else { + self.settings.remappings = remappings; + } + + self + } +} + +/// A `CompilerInput` representation used for verify +/// +/// This type is an alternative `CompilerInput` but uses non-alphabetic ordering of the `sources` +/// and instead emits the (Path -> Source) path in the same order as the pairs in the `sources` +/// `Vec`. This is used over a map, so we can determine the order in which etherscan will display +/// the verified contracts +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StandardJsonCompilerInput { + pub language: SolcLanguage, + #[serde(with = "serde_helpers::tuple_vec_map")] + pub sources: Vec<(PathBuf, Source)>, + pub settings: ZkSettings, +} + +impl StandardJsonCompilerInput { + pub fn new(sources: Vec<(PathBuf, Source)>, settings: ZkSettings) -> Self { + Self { language: SolcLanguage::Solidity, sources, settings } + } +} diff --git a/crates/zksync/compilers/src/compilers/zksolc/mod.rs b/crates/zksync/compilers/src/compilers/zksolc/mod.rs new file mode 100644 index 000000000..8518249f7 --- /dev/null +++ b/crates/zksync/compilers/src/compilers/zksolc/mod.rs @@ -0,0 +1,793 @@ +use self::input::{ZkSolcInput, ZkSolcVersionedInput}; +use crate::artifacts::{contract::Contract, error::Error, CompilerOutput as ZkCompilerOutput}; +use alloy_json_abi::JsonAbi; +use foundry_compilers::{ + error::{Result, SolcError}, + resolver::parse::SolData, + solc::SolcCompiler, + CompilationError, Compiler, CompilerContract, CompilerOutput, CompilerVersion, +}; +use foundry_compilers_artifacts_solc::{ + error::SourceLocation, BytecodeObject, Severity, SolcLanguage, +}; + +use itertools::Itertools; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::{Path, PathBuf}, + process::{Command, Output, Stdio}, + str::FromStr, +}; + +use std::{ + fs::{self, create_dir_all, set_permissions, File}, + io::Write, +}; +use tracing::{debug, instrument, trace}; + +#[cfg(target_family = "unix")] +use std::os::unix::fs::PermissionsExt; + +pub mod input; +pub mod settings; +pub use settings::{ZkSettings, ZkSolcSettings}; + +pub const ZKSOLC: &str = "zksolc"; +pub const ZKSYNC_SOLC_RELEASE: Version = Version::new(1, 0, 1); +pub const ZKSOLC_VERSION: Version = Version::new(1, 5, 7); + +#[cfg(test)] +macro_rules! take_solc_installer_lock { + ($lock:ident) => { + let lock_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".lock"); + let lock_file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(false) + .open(lock_path) + .unwrap(); + let mut lock = fd_lock::RwLock::new(lock_file); + let $lock = lock.write().unwrap(); + }; +} + +impl CompilerContract for Contract { + fn abi_ref(&self) -> Option<&JsonAbi> { + self.abi.as_ref() + } + fn bin_ref(&self) -> Option<&BytecodeObject> { + if let Some(ref eravm) = self.eravm { + eravm.bytecode_ref() + } else { + None + } + } + fn bin_runtime_ref(&self) -> Option<&BytecodeObject> { + if let Some(ref eravm) = self.eravm { + eravm.bytecode_ref() + } else { + None + } + } +} + +#[derive(Debug, Clone, Serialize)] +enum ZkSolcOS { + LinuxAMD64, + LinuxARM64, + MacAMD, + MacARM, +} + +fn get_operating_system() -> Result { + match std::env::consts::OS { + "linux" => match std::env::consts::ARCH { + "aarch64" => Ok(ZkSolcOS::LinuxARM64), + _ => Ok(ZkSolcOS::LinuxAMD64), + }, + "macos" | "darwin" => match std::env::consts::ARCH { + "aarch64" => Ok(ZkSolcOS::MacARM), + _ => Ok(ZkSolcOS::MacAMD), + }, + _ => Err(SolcError::msg(format!("Unsupported operating system {}", std::env::consts::OS))), + } +} + +impl ZkSolcOS { + fn get_zksolc_prefix(&self) -> &str { + match self { + Self::LinuxAMD64 => "zksolc-linux-amd64-musl-", + Self::LinuxARM64 => "zksolc-linux-arm64-musl-", + Self::MacAMD => "zksolc-macosx-amd64-", + Self::MacARM => "zksolc-macosx-arm64-", + } + } + + fn get_solc_prefix(&self) -> &str { + match self { + Self::LinuxAMD64 => "solc-linux-amd64-", + Self::LinuxARM64 => "solc-linux-arm64-", + Self::MacAMD => "solc-macosx-amd64-", + Self::MacARM => "solc-macosx-arm64-", + } + } +} + +#[derive(Debug, Clone)] +pub struct ZkSolcCompiler { + pub zksolc: PathBuf, + pub solc: SolcCompiler, +} + +#[cfg(feature = "project-util")] +impl Default for ZkSolcCompiler { + fn default() -> Self { + let zksolc = + ZkSolc::get_path_for_version(&ZKSOLC_VERSION).expect("Could not install zksolc"); + Self { zksolc, solc: Default::default() } + } +} + +impl Compiler for ZkSolcCompiler { + type Input = ZkSolcVersionedInput; + type CompilationError = Error; + type CompilerContract = Contract; + type ParsedSource = SolData; + type Settings = ZkSolcSettings; + type Language = SolcLanguage; + + fn compile( + &self, + input: &Self::Input, + ) -> Result> { + let zksolc = self.zksolc(input)?; + + let mut zk_output = zksolc.compile(&input.input)?; + let mut metadata = BTreeMap::new(); + if let Some(solc_version) = zk_output.version.take() { + metadata.insert("solcVersion".to_string(), solc_version.into()); + } + if let Some(solc_long_version) = zk_output.long_version.take() { + metadata.insert("solcLongVersion".to_string(), solc_long_version.into()); + } + if let Some(zk_version) = zk_output.zk_version.take() { + metadata.insert("zksolcVersion".to_string(), zk_version.into()); + } + if let Some(zksync_solc_version) = zk_output.zksync_solc_version { + metadata + .insert("zksyncSolcVersion".to_string(), zksync_solc_version.to_string().into()); + } + + Ok(CompilerOutput { + sources: zk_output.sources, + errors: zk_output.errors, + contracts: zk_output.contracts, + metadata, + }) + } + + // NOTE: This is used in the context of matching source files to compiler version so + // the solc versions are returned + fn available_versions(&self, _language: &Self::Language) -> Vec { + match &self.solc { + SolcCompiler::Specific(solc) => vec![CompilerVersion::Installed(Version::new( + solc.version.major, + solc.version.minor, + solc.version.patch, + ))], + SolcCompiler::AutoDetect => { + let mut all_versions = ZkSolc::solc_installed_versions() + .into_iter() + .map(CompilerVersion::Installed) + .collect::>(); + let mut uniques = all_versions + .iter() + .map(|v| { + let v = v.as_ref(); + (v.major, v.minor, v.patch) + }) + .collect::>(); + all_versions.extend( + ZkSolc::solc_available_versions() + .into_iter() + .filter(|v| uniques.insert((v.major, v.minor, v.patch))) + .map(CompilerVersion::Remote), + ); + all_versions.sort_unstable(); + all_versions + } + } + } +} + +impl ZkSolcCompiler { + pub fn zksolc(&self, input: &ZkSolcVersionedInput) -> Result { + let solc = match &self.solc { + SolcCompiler::Specific(solc) => Some(solc.solc.clone()), + SolcCompiler::AutoDetect => { + #[cfg(test)] + take_solc_installer_lock!(_lock); + + let solc_version_without_metadata = format!( + "{}.{}.{}", + input.solc_version.major, input.solc_version.minor, input.solc_version.patch + ); + let maybe_solc = + ZkSolc::find_solc_installed_version(&solc_version_without_metadata)?; + if let Some(solc) = maybe_solc { + Some(solc) + } else { + { + let installed_solc_path = + ZkSolc::solc_blocking_install(&solc_version_without_metadata)?; + Some(installed_solc_path) + } + } + } + }; + + let mut zksolc = ZkSolc::new(self.zksolc.clone(), solc)?; + + zksolc.base_path.clone_from(&input.cli_settings.base_path); + zksolc.allow_paths.clone_from(&input.cli_settings.allow_paths); + zksolc.include_paths.clone_from(&input.cli_settings.include_paths); + + Ok(zksolc) + } +} + +/// Version metadata. Will include `zksync_version` if compiler is zksync solc. +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct SolcVersionInfo { + /// The solc compiler version (e.g: 0.8.20) + pub version: Version, + /// The full zksync solc compiler version (e.g: 0.8.20-1.0.1) + pub zksync_version: Option, +} + +/// Given a solc path, get both the solc semver and optional zkSync version. +pub fn get_solc_version_info(path: &Path) -> Result { + let mut cmd = Command::new(path); + cmd.arg("--version").stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped()); + debug!(?cmd, "getting Solc versions"); + + let output = cmd.output().map_err(|e| SolcError::io(e, path))?; + trace!(?output); + + if !output.status.success() { + return Err(SolcError::solc_output(&output)); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let lines: Vec<&str> = stdout.lines().filter(|l| !l.trim().is_empty()).collect(); + + // Get solc version from second line + let version = lines.get(1).ok_or_else(|| SolcError::msg("Version not found in Solc output"))?; + let version = + Version::from_str(&version.trim_start_matches("Version: ").replace(".g++", ".gcc"))?; + + // Check for ZKsync version in the last line + let zksync_version = lines.last().and_then(|line| { + if line.starts_with("ZKsync") { + let version_str = line.trim_start_matches("ZKsync:").trim(); + Version::parse(version_str).ok() + } else { + None + } + }); + + Ok(SolcVersionInfo { version, zksync_version }) +} + +/// Abstraction over `zksolc` command line utility +/// +/// Supports sync and async functions. +/// +/// By default the zksolc path is configured as follows, with descending priority: +/// 1. `ZKSOLC_PATH` environment variable +/// 2. `zksolc` otherwise +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct ZkSolc { + /// Path to the `zksolc` executable + pub zksolc: PathBuf, + /// Value for --base path + pub base_path: Option, + /// Value for --allow-paths arg. + pub allow_paths: BTreeSet, + /// Value for --include-paths arg. + pub include_paths: BTreeSet, + /// Value for --solc arg + pub solc: Option, + /// Version data for solc + pub solc_version_info: SolcVersionInfo, +} + +impl ZkSolc { + /// A new instance which points to `zksolc` + pub fn new(path: PathBuf, solc: Option) -> Result { + let default_solc_path = PathBuf::from("solc"); + let solc_path = solc.as_ref().unwrap_or(&default_solc_path); + let solc_version_info = get_solc_version_info(solc_path)?; + Ok(Self { + zksolc: path, + base_path: None, + allow_paths: Default::default(), + include_paths: Default::default(), + solc, + solc_version_info, + }) + } + + pub fn get_path_for_version(version: &Version) -> Result { + let maybe_zksolc = Self::find_installed_version(version)?; + + let path = + if let Some(zksolc) = maybe_zksolc { zksolc } else { Self::blocking_install(version)? }; + + Ok(path) + } + + /// Invokes `zksolc --version` and parses the output as a SemVer [`Version`]. + pub fn get_version_for_path(path: &Path) -> Result { + let mut cmd = Command::new(path); + cmd.arg("--version").stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped()); + debug!(?cmd, "getting ZkSolc version"); + let output = cmd.output().map_err(map_io_err(path))?; + trace!(?output); + let version = version_from_output(output)?; + debug!(%version); + Ok(version) + } + + /// Sets zksolc's base path + pub fn with_base_path(mut self, base_path: impl Into) -> Self { + self.base_path = Some(base_path.into()); + self + } + + /// Compiles with `--standard-json` and deserializes the output as [`CompilerOutput`]. + pub fn compile(&self, input: &ZkSolcInput) -> Result { + // If solc is zksync solc, override the returned version to put the complete zksolc one + let output = self.compile_output(input)?; + // Only run UTF-8 validation once. + let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?; + + let mut compiler_output: ZkCompilerOutput = serde_json::from_str(output)?; + + // Add zksync version so that there's some way to identify if zksync solc was used + // by looking at build info + compiler_output.zksync_solc_version = self.solc_version_info.zksync_version.clone(); + Ok(compiler_output) + } + + pub fn solc_installed_versions() -> Vec { + if let Ok(dir) = Self::compilers_dir() { + let os = get_operating_system().unwrap(); + let solc_prefix = os.get_solc_prefix(); + let mut versions: Vec = walkdir::WalkDir::new(dir) + .max_depth(1) + .into_iter() + .filter_map(std::result::Result::ok) + .filter(|e| e.file_type().is_file()) + .filter_map(|e| e.file_name().to_str().map(|s| s.to_string())) + .filter(|e| e.ends_with(&ZKSYNC_SOLC_RELEASE.to_string())) + .filter_map(|e| { + e.strip_prefix(solc_prefix) + .and_then(|s| s.split('-').next()) + .and_then(|s| Version::parse(s).ok()) + }) + .collect(); + versions.sort(); + versions + } else { + vec![] + } + } + + pub fn solc_available_versions() -> Vec { + let mut ret = vec![]; + let min_max_patch_by_minor_versions = + vec![(4, 12, 26), (5, 0, 17), (6, 0, 12), (7, 0, 6), (8, 0, 28)]; + for (minor, min_patch, max_patch) in min_max_patch_by_minor_versions { + for i in min_patch..=max_patch { + ret.push(Version::new(0, minor, i)); + } + } + + ret + } + + /// Compiles with `--standard-json` and returns the raw `stdout` output. + #[instrument(name = "compile", level = "debug", skip_all)] + pub fn compile_output(&self, input: &ZkSolcInput) -> Result> { + let mut cmd = Command::new(&self.zksolc); + + if !self.allow_paths.is_empty() { + cmd.arg("--allow-paths"); + cmd.arg(self.allow_paths.iter().map(|p| p.display()).join(",")); + } + + if let Some(base_path) = &self.base_path { + for path in self.include_paths.iter().filter(|p| p.as_path() != base_path.as_path()) { + cmd.arg("--include-path").arg(path); + } + + cmd.arg("--base-path").arg(base_path); + + cmd.current_dir(base_path); + } + + // don't pass solc argument in yul mode (avoid verification) + if !input.is_yul() { + if let Some(solc) = &self.solc { + cmd.arg("--solc").arg(solc); + } + } + + cmd.arg("--standard-json"); + cmd.stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped()); + + trace!(input=%serde_json::to_string(input).unwrap_or_else(|e| e.to_string())); + debug!(?cmd, "compiling"); + + let mut child = cmd.spawn().map_err(map_io_err(&self.zksolc))?; + debug!("spawned"); + + let stdin = child.stdin.as_mut().unwrap(); + serde_json::to_writer(stdin, input)?; + debug!("wrote JSON input to stdin"); + + let output = child.wait_with_output().map_err(map_io_err(&self.zksolc))?; + debug!(%output.status, output.stderr = ?String::from_utf8_lossy(&output.stderr), "finished"); + + compile_output(output) + } + + fn compilers_dir() -> Result { + let mut compilers_dir = dirs::home_dir() + .ok_or(SolcError::msg("Could not build SolcManager - homedir not found"))?; + compilers_dir.push(".zksync"); + Ok(compilers_dir) + } + + fn compiler_path(version: &Version) -> Result { + let os = get_operating_system()?; + Ok(Self::compilers_dir()?.join(format!("{}v{}", os.get_zksolc_prefix(), version))) + } + + fn solc_path(version_str: &str) -> Result { + let os = get_operating_system()?; + Ok(Self::compilers_dir()?.join(format!( + "{}{}-{}", + os.get_solc_prefix(), + version_str, + ZKSYNC_SOLC_RELEASE + ))) + } + + /// Install zksolc version and block the thread + pub fn blocking_install(version: &Version) -> Result { + let os = get_operating_system()?; + let compiler_prefix = os.get_zksolc_prefix(); + let download_url = if version.pre.is_empty() { + format!( + "https://github.com/matter-labs/zksolc-bin/releases/download/v{version}/{compiler_prefix}v{version}", + ) + } else { + let pre = version.pre.as_str(); + // Use version as string without pre-release and build metadata + let version_str = version.to_string(); + let version_str = version_str.split('-').next().unwrap(); + // Matter Labs uses a different repositiry for pre-releases + format!( + "https://github.com/matter-labs/era-compiler-solidity/releases/download/{pre}/zksolc-{compiler_prefix}v{version_str}", + ) + }; + let compilers_dir = Self::compilers_dir()?; + if !compilers_dir.exists() { + create_dir_all(compilers_dir) + .map_err(|e| SolcError::msg(format!("Could not create compilers path: {e}")))?; + } + let compiler_path = Self::compiler_path(version)?; + let lock_path = lock_file_path("zksolc", &version.to_string()); + + let label = format!("zksolc-{version}"); + let install = compiler_blocking_install(compiler_path, lock_path, &download_url, &label); + + match install { + Ok(path) => { + //crate::report::solc_installation_success(version); + Ok(path) + } + Err(err) => { + //crate::report::solc_installation_error(version, &err.to_string()); + Err(err) + } + } + } + + /// Install zksync solc version and block the thread + pub fn solc_blocking_install(version_str: &str) -> Result { + let os = get_operating_system()?; + let solc_os_namespace = os.get_solc_prefix(); + let download_url = format!( + "https://github.com/matter-labs/era-solidity/releases/download/{version_str}-{ZKSYNC_SOLC_RELEASE}/{solc_os_namespace}{version_str}-{ZKSYNC_SOLC_RELEASE}", + ); + + let compilers_dir = Self::compilers_dir()?; + if !compilers_dir.exists() { + create_dir_all(compilers_dir) + .map_err(|e| SolcError::msg(format!("Could not create compilers path: {e}")))?; + } + let solc_path = Self::solc_path(version_str)?; + let lock_path = lock_file_path("solc", version_str); + + let label = format!("solc-{version_str}"); + compiler_blocking_install(solc_path, lock_path, &download_url, &label) + } + + pub fn find_installed_version(version: &Version) -> Result> { + let zksolc = Self::compiler_path(version)?; + + if !zksolc.is_file() { + return Ok(None); + } + Ok(Some(zksolc)) + } + + pub fn find_solc_installed_version(version_str: &str) -> Result> { + let solc = Self::solc_path(version_str)?; + + if !solc.is_file() { + return Ok(None); + } + Ok(Some(solc)) + } +} + +fn map_io_err(zksolc_path: &Path) -> impl FnOnce(std::io::Error) -> SolcError + '_ { + move |err| SolcError::io(err, zksolc_path) +} + +fn compile_output(output: Output) -> Result> { + if output.status.success() { + Ok(output.stdout) + } else { + Err(SolcError::solc_output(&output)) + } +} + +fn version_from_output(output: Output) -> Result { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let version = stdout + .lines() + .filter(|l| !l.trim().is_empty()) + .last() + .ok_or_else(|| SolcError::msg("Version not found in zksolc output"))?; + + version + .split_whitespace() + .find_map(|s| { + let trimmed = s.trim_start_matches('v'); + Version::from_str(trimmed).ok() + }) + .ok_or_else(|| SolcError::msg("Unable to retrieve version from zksolc output")) + } else { + Err(SolcError::solc_output(&output)) + } +} + +impl AsRef for ZkSolc { + fn as_ref(&self) -> &Path { + &self.zksolc + } +} + +impl CompilationError for Error { + fn is_warning(&self) -> bool { + self.severity.is_warning() + } + fn is_error(&self) -> bool { + self.severity.is_error() + } + + fn source_location(&self) -> Option { + self.source_location.clone() + } + + fn severity(&self) -> Severity { + self.severity + } + + fn error_code(&self) -> Option { + self.error_code + } +} + +fn compiler_blocking_install( + compiler_path: PathBuf, + lock_path: PathBuf, + download_url: &str, + label: &str, +) -> Result { + use foundry_compilers::utils::RuntimeOrHandle; + trace!("blocking installing {label}"); + //trace!("blocking installing {label}"); + // An async block is used because the underlying `reqwest::blocking::Client` does not behave + // well inside of a Tokio runtime. See: https://github.com/seanmonstar/reqwest/issues/1017 + RuntimeOrHandle::new().block_on(async { + let client = reqwest::Client::new(); + let response = client + .get(download_url) + .send() + .await + .map_err(|e| SolcError::msg(format!("Failed to download {label} file: {e}")))?; + + if response.status().is_success() { + let content = response + .bytes() + .await + .map_err(|e| SolcError::msg(format!("failed to download {label} file: {e}")))?; + trace!("downloaded {label}"); + + // lock file to indicate that installation of this compiler version will be in progress. + // wait until lock file is released, possibly by another parallel thread trying to + // install the same compiler version. + trace!("try to get lock for {label}"); + let _lock = try_lock_file(lock_path)?; + trace!("got lock for {label}"); + + // Only write to file if it is not there. The check is doneafter adquiring the lock + // to ensure the thread remains blocked until the required compiler is + // fully installed + if !compiler_path.exists() { + trace!("creating binary for {label}"); + //trace!("creating binary for {label}"); + let mut output_file = File::create(&compiler_path).map_err(|e| { + SolcError::msg(format!("Failed to create output {label} file: {e}")) + })?; + + output_file.write_all(&content).map_err(|e| { + SolcError::msg(format!("Failed to write the downloaded {label} file: {e}")) + })?; + + set_permissions(&compiler_path, PermissionsExt::from_mode(0o755)).map_err(|e| { + SolcError::msg(format!("Failed to set {label} permissions: {e}")) + })?; + } else { + trace!("found binary for {label}"); + } + } else { + return Err(SolcError::msg(format!( + "Failed to download {label} file: status code {}", + response.status() + ))); + } + trace!("{label} instalation completed"); + Ok(compiler_path) + }) +} + +/// Creates the file and locks it exclusively, this will block if the file is currently locked +fn try_lock_file(lock_path: PathBuf) -> Result { + use fs4::FileExt; + let _lock_file = std::fs::OpenOptions::new() + .create(true) + .truncate(true) + .read(true) + .write(true) + .open(&lock_path) + .map_err(|_| SolcError::msg("Error creating lock file"))?; + _lock_file.lock_exclusive().map_err(|_| SolcError::msg("Error taking the lock"))?; + Ok(LockFile { lock_path, _lock_file }) +} + +/// Represents a lockfile that's removed once dropped +struct LockFile { + _lock_file: File, + lock_path: PathBuf, +} + +impl Drop for LockFile { + fn drop(&mut self) { + let _ = fs::remove_file(&self.lock_path); + } +} + +/// Returns the lockfile to use for a specific file +fn lock_file_path(compiler: &str, version: &str) -> PathBuf { + ZkSolc::compilers_dir() + .expect("could not detect zksolc compilers directory") + .join(format!(".lock-{compiler}-{version}")) +} + +#[cfg(test)] +mod tests { + use similar_asserts::assert_eq; + + use foundry_compilers::solc::Solc; + + use super::*; + + fn zksolc() -> ZkSolc { + let zksolc_path = ZkSolc::get_path_for_version(&ZKSOLC_VERSION).unwrap(); + let solc_version = "0.8.27"; + + take_solc_installer_lock!(_lock); + let maybe_solc = ZkSolc::find_solc_installed_version(solc_version).unwrap(); + let solc_path = if let Some(solc) = maybe_solc { + solc + } else { + ZkSolc::solc_blocking_install(solc_version).unwrap() + }; + ZkSolc::new(zksolc_path, Some(solc_path)).unwrap() + } + + fn vanilla_solc() -> Solc { + if let Some(solc) = Solc::find_svm_installed_version(&Version::new(0, 8, 18)).unwrap() { + solc + } else { + Solc::blocking_install(&Version::new(0, 8, 18)).unwrap() + } + } + + #[test] + fn zksolc_version_works() { + ZkSolc::get_version_for_path(&zksolc().zksolc).unwrap(); + } + + #[test] + fn get_solc_type_and_version_works_for_zksync_solc() { + let zksolc = zksolc(); + let solc = zksolc.solc.unwrap(); + let solc_v = get_solc_version_info(&solc).unwrap(); + let zksync_v = solc_v.zksync_version.unwrap(); + let prerelease = Version::parse(zksync_v.pre.as_str()).unwrap(); + assert_eq!(solc_v.version.minor, 8); + assert_eq!(prerelease, ZKSYNC_SOLC_RELEASE); + } + + #[test] + fn get_solc_type_and_version_works_for_vanilla_solc() { + let solc = vanilla_solc(); + let solc_v = get_solc_version_info(&solc.solc).unwrap(); + assert_eq!(solc_v.version.minor, 8); + assert!(solc_v.zksync_version.is_none()); + } + + #[test] + fn zksolc_compile_works() { + let input = include_str!("../../../test-data/in/compiler-in-1.json"); + let input: ZkSolcInput = serde_json::from_str(input).unwrap(); + let out = zksolc().compile(&input).unwrap(); + assert!(!out.has_error()); + } + + #[test] + fn zksolc_can_compile_with_remapped_links() { + let input: ZkSolcInput = + serde_json::from_str(include_str!("../../../test-data/library-remapping-in.json")) + .unwrap(); + let out = zksolc().compile(&input).unwrap(); + let (_, mut contracts) = out.split(); + let contract = contracts.remove("LinkTest").unwrap(); + let bytecode = contract.bytecode().unwrap().object; + assert!(!bytecode.is_unlinked()); + } + + #[test] + fn zksolc_can_compile_with_remapped_links_temp_dir() { + let input: ZkSolcInput = + serde_json::from_str(include_str!("../../../test-data/library-remapping-in-2.json")) + .unwrap(); + let out = zksolc().compile(&input).unwrap(); + let (_, mut contracts) = out.split(); + let contract = contracts.remove("LinkTest").unwrap(); + let bytecode = contract.bytecode().unwrap().object; + assert!(!bytecode.is_unlinked()); + } +} diff --git a/crates/zksync/compilers/src/compilers/zksolc/settings.rs b/crates/zksync/compilers/src/compilers/zksolc/settings.rs new file mode 100644 index 000000000..ce7a9c2f8 --- /dev/null +++ b/crates/zksync/compilers/src/compilers/zksolc/settings.rs @@ -0,0 +1,442 @@ +use crate::artifacts::output_selection::OutputSelection as ZkOutputSelection; +use foundry_compilers::{ + artifacts::{serde_helpers, EvmVersion, Libraries}, + compilers::CompilerSettings, + solc, CompilerSettingsRestrictions, +}; +use foundry_compilers_artifacts_solc::{output_selection::OutputSelection, remappings::Remapping}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeSet, HashSet}, + fmt, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// +/// The Solidity compiler codegen. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Codegen { + /// The Yul IR. + #[default] + Yul, + /// The EVM legacy assembly IR. + EVMLA, +} + +/// `zksolc` warnings that can be suppressed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +pub enum ZkSolcWarning { + /// `txorigin` warning: Using `tx.origin` in place of `msg.sender`. + TxOrigin, +} + +impl FromStr for ZkSolcWarning { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "txorigin" => Ok(Self::TxOrigin), + s => Err(format!("Unknown zksolc warning: {s}")), + } + } +} + +/// `zksolc` errors that can be suppressed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +pub enum ZkSolcError { + /// `sendtransfer` error: Using `send()` or `transfer()` methods on `address payable` instead + /// of `call()`. + SendTransfer, +} + +impl FromStr for ZkSolcError { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "sendtransfer" => Ok(Self::SendTransfer), + s => Err(format!("Unknown zksolc error: {s}")), + } + } +} + +/// zksolc standard json input settings. See: +/// https://docs.zksync.io/zk-stack/components/compiler/toolchain/solidity.html#standard-json for differences +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ZkSettings { + // same + /// Change compilation pipeline to go through the Yul intermediate representation. This is + /// false by default. + #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")] + pub via_ir: Option, + /// The Solidity codegen. + #[serde(default)] + pub codegen: Codegen, + // TODO: era-compiler-solidity uses a BTreeSet of strings. In theory the serialization + // should be the same but maybe we should double check + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub remappings: Vec, + #[serde( + default, + with = "serde_helpers::display_from_str_opt", + skip_serializing_if = "Option::is_none" + )] + pub evm_version: Option, + + // check if the same (and use `compilers version`) + /// This field can be used to select desired outputs based + /// on file and contract names. + /// If this field is omitted, then the compiler loads and does type + /// checking, but will not generate any outputs apart from errors. + #[serde(default)] + pub output_selection: ZkOutputSelection, + + #[serde(default)] + pub optimizer: Optimizer, + /// Metadata settings + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(default)] + pub libraries: Libraries, + /// Switch to missing deployable libraries detection mode. + /// Contracts are not compiled in this mode, and all compilation artifacts are not included. + #[serde(default, rename = "detectMissingLibraries")] + pub detect_missing_libraries: bool, + // zksolc arguments + /// A flag indicating whether to enable the system contract compilation mode. + /// Whether to enable EraVM extensions. + #[serde(default, rename = "enableEraVMExtensions")] + pub enable_eravm_extensions: bool, + /// The extra LLVM options. + #[serde(default, rename = "LLVMOptions", skip_serializing_if = "Vec::is_empty")] + pub llvm_options: Vec, + /// Whether to compile via EVM assembly. + #[serde(default, rename = "forceEVMLA")] + pub force_evmla: bool, + /// Suppressed `zksolc` warnings. + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub suppressed_warnings: HashSet, + /// Suppressed `zksolc` errors. + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + pub suppressed_errors: HashSet, +} + +// Analogous to SolcSettings for Zk compiler +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ZkSolcSettings { + /// JSON settings expected by Solc + #[serde(flatten)] + pub settings: ZkSettings, + /// Additional CLI args configuration + #[serde(flatten)] + pub cli_settings: solc::CliSettings, +} + +impl ZkSettings { + /// Creates a new `Settings` instance with the given `output_selection` + pub fn new(output_selection: impl Into) -> Self { + Self { output_selection: output_selection.into(), ..Default::default() } + } + + /// Consumes the type and returns a [ZkSettings::sanitize] version + pub fn sanitized(mut self, solc_version: &Version) -> Self { + self.sanitize(solc_version); + self + } + + /// This will remove/adjust values in the settings that are not compatible with this version. + pub fn sanitize(&mut self, solc_version: &Version) { + if let Some(ref mut evm_version) = self.evm_version { + self.evm_version = evm_version.normalize_version_solc(solc_version); + } + } + + pub fn strip_prefix(&mut self, base: impl AsRef) { + let base = base.as_ref(); + self.remappings.iter_mut().for_each(|r| { + r.strip_prefix(base); + }); + + self.libraries.libs = std::mem::take(&mut self.libraries.libs) + .into_iter() + .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs)) + .collect(); + } + + /// Strips `base` from all paths + pub fn with_base_path(mut self, base: impl AsRef) -> Self { + let base = base.as_ref(); + self.remappings.iter_mut().for_each(|r| { + r.strip_prefix(base); + }); + + self.libraries.libs = self + .libraries + .libs + .into_iter() + .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs)) + .collect(); + + self + } +} + +impl Default for ZkSettings { + fn default() -> Self { + Self { + optimizer: Default::default(), + metadata: None, + output_selection: Default::default(), + evm_version: Some(EvmVersion::default()), + via_ir: None, + libraries: Default::default(), + remappings: Default::default(), + detect_missing_libraries: false, + enable_eravm_extensions: false, + llvm_options: Default::default(), + force_evmla: false, + codegen: Default::default(), + suppressed_errors: Default::default(), + suppressed_warnings: Default::default(), + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct ZkSolcRestrictions(); + +impl CompilerSettingsRestrictions for ZkSolcRestrictions { + fn merge(self, _other: Self) -> Option { + None + } +} + +impl CompilerSettings for ZkSolcSettings { + type Restrictions = ZkSolcRestrictions; + + fn update_output_selection(&mut self, _f: impl FnOnce(&mut OutputSelection) + Copy) { + // TODO: see how to support this, noop for now + //f(&mut self.output_selection) + } + + fn can_use_cached(&self, other: &Self) -> bool { + let Self { + settings: + ZkSettings { + via_ir, + remappings, + evm_version, + output_selection, + optimizer, + metadata, + libraries, + detect_missing_libraries, + enable_eravm_extensions, + llvm_options, + force_evmla, + codegen, + suppressed_warnings, + suppressed_errors, + }, + .. + } = self; + + *via_ir == other.settings.via_ir && + *remappings == other.settings.remappings && + *evm_version == other.settings.evm_version && + *output_selection == other.settings.output_selection && + *optimizer == other.settings.optimizer && + *metadata == other.settings.metadata && + *libraries == other.settings.libraries && + *detect_missing_libraries == other.settings.detect_missing_libraries && + *enable_eravm_extensions == other.settings.enable_eravm_extensions && + *llvm_options == other.settings.llvm_options && + *force_evmla == other.settings.force_evmla && + *codegen == other.settings.codegen && + *suppressed_warnings == other.settings.suppressed_warnings && + *suppressed_errors == other.settings.suppressed_errors + } + + fn with_remappings(mut self, remappings: &[Remapping]) -> Self { + self.settings.remappings = remappings.to_vec(); + + self + } + + fn with_allow_paths(mut self, allowed_paths: &BTreeSet) -> Self { + self.cli_settings.allow_paths.clone_from(allowed_paths); + self + } + + fn with_base_path(mut self, base_path: &Path) -> Self { + self.cli_settings.base_path = Some(base_path.to_path_buf()); + self + } + + fn with_include_paths(mut self, include_paths: &BTreeSet) -> Self { + self.cli_settings.include_paths.clone_from(include_paths); + self + } + + fn satisfies_restrictions(&self, _restrictions: &Self::Restrictions) -> bool { + // TODO + true + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Optimizer { + // TODO: does this have to be an option? + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + /// Switch optimizer components on or off in detail. + /// The "enabled" switch above provides two defaults which can be + /// tweaked here. If "details" is given, "enabled" can be omitted. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub details: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mode: Option, + /// Whether to try to recompile with -Oz if the bytecode is too large. + #[serde(skip_serializing_if = "Option::is_none")] + pub fallback_to_optimizing_for_size: Option, + /// Whether to disable the system request memoization. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_system_request_memoization: Option, + /// Set the jump table density threshold. + #[serde(skip_serializing_if = "Option::is_none")] + pub jump_table_density_threshold: Option, +} + +impl Optimizer { + pub fn disable(&mut self) { + self.enabled.take(); + } + + pub fn enable(&mut self) { + self.enabled = Some(true) + } +} + +impl Default for Optimizer { + fn default() -> Self { + Self { + enabled: Some(false), + mode: None, + fallback_to_optimizing_for_size: None, + disable_system_request_memoization: None, + jump_table_density_threshold: None, + details: None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct OptimizerDetails { + /// The peephole optimizer is always on if no details are given, + /// use details to switch it off. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub peephole: Option, + /// The inliner is always on if no details are given, + /// use details to switch it off. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub inliner: Option, + /// The unused jumpdest remover is always on if no details are given, + /// use details to switch it off. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub jumpdest_remover: Option, + /// Sometimes re-orders literals in commutative operations. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub order_literals: Option, + /// Removes duplicate code blocks + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deduplicate: Option, + /// Common subexpression elimination, this is the most complicated step but + /// can also provide the largest gain. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cse: Option, + /// Optimize representation of literal numbers and strings in code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub constant_optimizer: Option, +} + +impl OptimizerDetails { + /// Returns true if no settings are set. + pub fn is_empty(&self) -> bool { + self.peephole.is_none() && + self.inliner.is_none() && + self.jumpdest_remover.is_none() && + self.order_literals.is_none() && + self.deduplicate.is_none() && + self.cse.is_none() && + self.constant_optimizer.is_none() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct SettingsMetadata { + /// Use the given hash method for the metadata hash that is appended to the bytecode. + /// The metadata hash can be removed from the bytecode via option "none". + /// `zksolc` only supports keccak256 + #[serde( + default, + rename = "bytecodeHash", + skip_serializing_if = "Option::is_none", + with = "serde_helpers::display_from_str_opt" + )] + pub bytecode_hash: Option, +} + +impl SettingsMetadata { + pub fn new(hash: BytecodeHash) -> Self { + Self { bytecode_hash: Some(hash) } + } +} + +impl From for SettingsMetadata { + fn from(hash: BytecodeHash) -> Self { + Self { bytecode_hash: Some(hash) } + } +} + +/// Determines the hash method for the metadata hash that is appended to the bytecode. +/// Zksolc only supports keccak256 +#[derive(Clone, Debug, Default, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum BytecodeHash { + /// Do not include bytecode hash. + #[default] + #[serde(rename = "none")] + None, + /// The default keccak256 hash. + #[serde(rename = "keccak256")] + Keccak256, +} + +impl FromStr for BytecodeHash { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "none" => Ok(Self::None), + "keccak256" => Ok(Self::Keccak256), + s => Err(format!("Unknown bytecode hash: {s}")), + } + } +} + +impl fmt::Display for BytecodeHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + Self::Keccak256 => "keccak256", + Self::None => "none", + }; + f.write_str(s) + } +} diff --git a/crates/zksync/compiler/src/zksolc/mod.rs b/crates/zksync/compilers/src/dual_compiled_contracts.rs similarity index 97% rename from crates/zksync/compiler/src/zksolc/mod.rs rename to crates/zksync/compilers/src/dual_compiled_contracts.rs index f4e3b0699..ede4c2360 100644 --- a/crates/zksync/compiler/src/zksolc/mod.rs +++ b/crates/zksync/compilers/src/dual_compiled_contracts.rs @@ -6,15 +6,16 @@ use std::{ }; use foundry_compilers::{ - solc::SolcLanguage, zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, - Artifact, ArtifactId, ArtifactOutput, ConfigurableArtifacts, ProjectCompileOutput, - ProjectPathsConfig, + solc::SolcLanguage, Artifact, ArtifactId, ArtifactOutput, ConfigurableArtifacts, + ProjectCompileOutput, ProjectPathsConfig, }; use alloy_primitives::{keccak256, B256}; use tracing::debug; use zksync_types::H256; +use crate::compilers::{artifact_output::zk::ZkArtifactOutput, zksolc::ZkSolcCompiler}; + /// Represents the type of contract (ZK or EVM) #[derive(Debug, Clone, PartialEq, Eq)] pub enum ContractType { @@ -79,7 +80,7 @@ impl DualCompiledContracts { /// Creates a collection of `[DualCompiledContract]`s from the provided solc and zksolc output. pub fn new( output: &ProjectCompileOutput, - zk_output: &ZkProjectCompileOutput, + zk_output: &ProjectCompileOutput, layout: &ProjectPathsConfig, zk_layout: &ProjectPathsConfig, ) -> Self { diff --git a/crates/zksync/compilers/src/lib.rs b/crates/zksync/compilers/src/lib.rs new file mode 100644 index 000000000..dd8d1e571 --- /dev/null +++ b/crates/zksync/compilers/src/lib.rs @@ -0,0 +1,9 @@ +//! # foundry-zksync +//! +//! Foundry ZKSync compiler data structures and trait implementations. +#![warn(missing_docs, unused_crate_dependencies)] + +pub mod artifacts; +pub mod compilers; +pub mod dual_compiled_contracts; +pub mod libraries; diff --git a/crates/zksync/compiler/src/libraries.rs b/crates/zksync/compilers/src/libraries.rs similarity index 100% rename from crates/zksync/compiler/src/libraries.rs rename to crates/zksync/compilers/src/libraries.rs diff --git a/crates/zksync/compilers/test-data/in/compiler-in-1.json b/crates/zksync/compilers/test-data/in/compiler-in-1.json new file mode 100644 index 000000000..a74b01a52 --- /dev/null +++ b/crates/zksync/compilers/test-data/in/compiler-in-1.json @@ -0,0 +1 @@ +{"language":"Solidity","sources":{"contracts/Contract.sol":{"content":"pragma solidity >0.7.0;\n\ncontract Contract {\n}\n"}},"settings":{"optimizer":{"enabled":false},"outputSelection":{"*":{"*":["abi","evm.methodIdentifiers"],"":["ast"]}}}} diff --git a/crates/zksync/compilers/test-data/library-remapping-in-2.json b/crates/zksync/compilers/test-data/library-remapping-in-2.json new file mode 100644 index 000000000..8c4e897a4 --- /dev/null +++ b/crates/zksync/compilers/test-data/library-remapping-in-2.json @@ -0,0 +1 @@ +{"language":"Solidity","sources":{"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/can_create_on_goerli-0vd5EOs/lib/remapping/MyLib.sol":{"content":"\n// SPDX-License-Identifier: MIT\nlibrary MyLib {\n function foobar(uint256 a) public view returns (uint256) {\n \treturn a * 100;\n }\n}\n"},"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/can_create_on_goerli-0vd5EOs/src/LinkTest.sol":{"content":"\n// SPDX-License-Identifier: MIT\nimport \"remapping/MyLib.sol\";\ncontract LinkTest {\n function foo() public returns (uint256) {\n return MyLib.foobar(1);\n }\n}\n"}},"settings":{"remappings":["remapping/=/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/can_create_on_goerli-0vd5EOs/lib/remapping/","src/=/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/can_create_on_goerli-0vd5EOs/src/"],"optimizer":{"enabled":true},"metadata":{"bytecodeHash":"none"},"outputSelection":{"*":{"":["ast"],"*":["abi"]}},"evmVersion":"london","libraries":{"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/can_create_on_goerli-0vd5EOs/lib/remapping/MyLib.sol":{"MyLib":"0x0000000000000000000000000000000000000000"}}}} diff --git a/crates/zksync/compilers/test-data/library-remapping-in.json b/crates/zksync/compilers/test-data/library-remapping-in.json new file mode 100644 index 000000000..2a73f1377 --- /dev/null +++ b/crates/zksync/compilers/test-data/library-remapping-in.json @@ -0,0 +1 @@ +{"language":"Solidity","sources":{"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/lib/remapping/MyLib.sol":{"content":"\n// SPDX-License-Identifier: MIT\nlibrary MyLib {\n function foobar(uint256 a) public view returns (uint256) {\n \treturn a * 100;\n }\n}\n"},"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/src/LinkTest.sol":{"content":"\n// SPDX-License-Identifier: MIT\nimport \"remapping/MyLib.sol\";\ncontract LinkTest {\n function foo() public returns (uint256) {\n return MyLib.foobar(1);\n }\n}\n"}},"settings":{"remappings":["remapping/=/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/lib/remapping/"],"optimizer":{"enabled":false,"runs":200},"outputSelection":{"*":{"":["ast"],"*":["abi"]}},"evmVersion":"london","libraries":{"/private/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/tmp_dappPyXsdD/lib/remapping/MyLib.sol":{"MyLib":"0x0000000000000000000000000000000000000000"}}}} diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul new file mode 100644 index 000000000..cef5d9bc2 --- /dev/null +++ b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul @@ -0,0 +1,11 @@ +object "SimpleStore" { + code { + datacopy(0, dataoffset("SimpleStore_deployed"), datasize("SimpleStore_deployed")) + return(0, datasize("SimpleStore_deployed")) + } + object "SimpleStore_deployed" { + code { + calldatacopy(0, 0, 36) // write calldata to memory + } + } +} diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json new file mode 100644 index 000000000..09883da59 --- /dev/null +++ b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json @@ -0,0 +1 @@ +{"abi":null,"bytecode":{"object":"0x00000001002001900000000c0000c13d000000002101043c000000000010043f000000200100043d0000000701100197000000000202043b0000000802200197000000000112019f000000200010043f0000000001000019000000000001042d000000240000043f0000002001000039000001000010044300000120000004430000000601000041000000130001042e0000001200000432000000130001042e0000001400010430000000000000000000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e0e9492643af2958d06e962a0b194ca36037ef49d6167e028f1f053a54c571","is_unlinked":false,"missing_libraries":[]},"storageLayout":{"storage":[],"types":{}},"userdoc":{},"devdoc":{},"hash":"0100000b7acef21b9ea8b7897d4ebb51ecaec207df8038a10bc61b904c5c9e4b","factoryDependencies":{},"id":0} \ No newline at end of file From ddd361c29a91489b3b60226fd6aaf7e384f7a2ea Mon Sep 17 00:00:00 2001 From: elfedy Date: Tue, 17 Dec 2024 17:11:54 -0300 Subject: [PATCH 084/107] alloy 0.8 + some misc build errors --- Cargo.lock | 790 +++++------------- Cargo.toml | 3 +- crates/cheatcodes/src/config.rs | 4 +- crates/cheatcodes/src/fs.rs | 2 +- crates/cheatcodes/src/inspector.rs | 4 +- crates/cheatcodes/src/test.rs | 2 +- crates/cli/Cargo.toml | 3 + crates/cli/src/opts/build/zksync.rs | 4 +- crates/cli/src/utils/cmd.rs | 14 +- crates/config/src/lib.rs | 5 +- crates/config/src/zksync.rs | 5 +- crates/evm/core/src/utils.rs | 3 +- crates/evm/evm/src/executors/invariant/mod.rs | 6 +- crates/evm/evm/src/inspectors/stack.rs | 4 - crates/evm/traces/src/decoder/mod.rs | 2 +- crates/forge/bin/cmd/build.rs | 2 +- crates/forge/bin/cmd/coverage.rs | 11 +- crates/forge/bin/cmd/create.rs | 21 +- crates/forge/bin/cmd/test/mod.rs | 13 +- crates/forge/bin/main.rs | 2 +- crates/forge/src/gas_report.rs | 10 - crates/forge/src/multi_runner.rs | 21 +- crates/forge/tests/it/test_helpers.rs | 2 +- crates/script/src/build.rs | 10 +- crates/script/src/lib.rs | 2 +- crates/script/src/verify.rs | 3 +- crates/test-utils/src/lib.rs | 3 - crates/verify/src/etherscan/flatten.rs | 23 +- crates/verify/src/etherscan/standard_json.rs | 2 +- crates/verify/src/verify.rs | 6 +- crates/verify/src/zk_provider.rs | 8 +- crates/verify/src/zksync/mod.rs | 4 +- crates/verify/src/zksync/standard_json.rs | 4 +- crates/zksync/compilers/Cargo.toml | 3 +- crates/zksync/compilers/src/compilers/mod.rs | 96 +++ crates/zksync/core/src/lib.rs | 2 +- 36 files changed, 415 insertions(+), 684 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6aa7c7ec..0797afcb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,27 +96,27 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "0.6.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b524b8c28a7145d1fe4950f84360b5de3e307601679ff0558ddc20ea229399" +checksum = "e7e1758e5d759c0114140152ae72032eafcfdd7b599e995ebbc8eeafa2b4c977" dependencies = [ - "alloy-consensus 0.6.4", - "alloy-contract 0.6.4", + "alloy-consensus", + "alloy-contract", "alloy-core", - "alloy-eips 0.6.4", - "alloy-genesis 0.6.4", - "alloy-network 0.6.4", - "alloy-provider 0.6.4", - "alloy-pubsub 0.6.4", - "alloy-rpc-client 0.6.4", - "alloy-rpc-types 0.6.4", - "alloy-serde 0.6.4", - "alloy-signer 0.6.4", - "alloy-signer-local 0.6.4", - "alloy-transport 0.6.4", - "alloy-transport-http 0.6.4", - "alloy-transport-ipc 0.6.4", - "alloy-transport-ws 0.6.4", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", ] [[package]] @@ -131,33 +131,16 @@ dependencies = [ "strum", ] -[[package]] -name = "alloy-consensus" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae09ffd7c29062431dd86061deefe4e3c6f07fa0d674930095f8dcedb0baf02c" -dependencies = [ - "alloy-eips 0.6.4", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.6.4", - "auto_impl", - "c-kzg", - "derive_more 1.0.0", - "k256 0.13.4", - "serde", -] - [[package]] name = "alloy-consensus" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba14856660f31807ebb26ce8f667e814c72694e1077e97ef102e326ad580f3f" dependencies = [ - "alloy-eips 0.8.0", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.8.0", + "alloy-serde", "alloy-trie", "auto_impl", "c-kzg", @@ -172,35 +155,14 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28666307e76441e7af37a2b90cde7391c28112121bea59f4e0d804df8b20057e" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-eips 0.8.0", + "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.8.0", + "alloy-serde", "serde", ] -[[package]] -name = "alloy-contract" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66430a72d5bf5edead101c8c2f0a24bada5ec9f3cf9909b3e08b6d6899b4803e" -dependencies = [ - "alloy-dyn-abi", - "alloy-json-abi", - "alloy-network 0.6.4", - "alloy-network-primitives 0.6.4", - "alloy-primitives", - "alloy-provider 0.6.4", - "alloy-pubsub 0.6.4", - "alloy-rpc-types-eth 0.6.4", - "alloy-sol-types", - "alloy-transport 0.6.4", - "futures 0.3.31", - "futures-util", - "thiserror 1.0.69", -] - [[package]] name = "alloy-contract" version = "0.8.0" @@ -209,14 +171,14 @@ checksum = "f3510769905590b8991a8e63a5e0ab4aa72cf07a13ab5fbe23f12f4454d161da" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", - "alloy-network 0.8.0", - "alloy-network-primitives 0.8.0", + "alloy-network", + "alloy-network-primitives", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-pubsub 0.8.0", - "alloy-rpc-types-eth 0.8.0", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", "alloy-sol-types", - "alloy-transport 0.8.0", + "alloy-transport", "futures 0.3.31", "futures-util", "thiserror 2.0.6", @@ -284,24 +246,6 @@ dependencies = [ "serde", ] -[[package]] -name = "alloy-eips" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6aa3961694b30ba53d41006131a2fca3bdab22e4c344e46db2c639e7c2dfdd" -dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.6.4", - "c-kzg", - "derive_more 1.0.0", - "once_cell", - "serde", - "sha2 0.10.8", -] - [[package]] name = "alloy-eips" version = "0.8.0" @@ -312,7 +256,7 @@ dependencies = [ "alloy-eip7702", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.8.0", + "alloy-serde", "c-kzg", "derive_more 1.0.0", "once_cell", @@ -320,17 +264,6 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "alloy-genesis" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f7877ded3921d18a0a9556d55bedf84535567198c9edab2aa23106da91855" -dependencies = [ - "alloy-primitives", - "alloy-serde 0.6.4", - "serde", -] - [[package]] name = "alloy-genesis" version = "0.8.0" @@ -338,7 +271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dca170827a7ca156b43588faebf9e9d27c27d0fb07cab82cfd830345e2b24f5" dependencies = [ "alloy-primitives", - "alloy-serde 0.8.0", + "alloy-serde", "alloy-trie", "serde", ] @@ -355,20 +288,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "alloy-json-rpc" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3694b7e480728c0b3e228384f223937f14c10caef5a4c766021190fc8f283d35" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - [[package]] name = "alloy-json-rpc" version = "0.8.0" @@ -383,45 +302,22 @@ dependencies = [ "tracing", ] -[[package]] -name = "alloy-network" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea94b8ceb5c75d7df0a93ba0acc53b55a22b47b532b600a800a87ef04eb5b0b4" -dependencies = [ - "alloy-consensus 0.6.4", - "alloy-eips 0.6.4", - "alloy-json-rpc 0.6.4", - "alloy-network-primitives 0.6.4", - "alloy-primitives", - "alloy-rpc-types-eth 0.6.4", - "alloy-serde 0.6.4", - "alloy-signer 0.6.4", - "alloy-sol-types", - "async-trait", - "auto_impl", - "futures-utils-wasm", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "alloy-network" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad4e6ad4230df8c4a254c20f8d6a84ab9df151bfca13f463177dbc96571cc1f8" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-consensus-any", - "alloy-eips 0.8.0", - "alloy-json-rpc 0.8.0", - "alloy-network-primitives 0.8.0", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", - "alloy-rpc-types-eth 0.8.0", - "alloy-serde 0.8.0", - "alloy-signer 0.8.0", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", "alloy-sol-types", "async-trait", "auto_impl", @@ -431,29 +327,16 @@ dependencies = [ "thiserror 2.0.6", ] -[[package]] -name = "alloy-network-primitives" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9f3e281005943944d15ee8491534a1c7b3cbf7a7de26f8c433b842b93eb5f9" -dependencies = [ - "alloy-consensus 0.6.4", - "alloy-eips 0.6.4", - "alloy-primitives", - "alloy-serde 0.6.4", - "serde", -] - [[package]] name = "alloy-network-primitives" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4df88a2f8020801e0fefce79471d3946d39ca3311802dbbd0ecfdeee5e972e3" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-eips 0.8.0", + "alloy-consensus", + "alloy-eips", "alloy-primitives", - "alloy-serde 0.8.0", + "alloy-serde", "serde", ] @@ -463,7 +346,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2db5cefbc736b2b26a960dcf82279c70a03695dd11a0032a6dc27601eeb29182" dependencies = [ - "alloy-genesis 0.8.0", + "alloy-genesis", "alloy-primitives", "k256 0.13.4", "rand 0.8.5", @@ -506,46 +389,6 @@ dependencies = [ "tiny-keccak 2.0.2", ] -[[package]] -name = "alloy-provider" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c1f9eede27bf4c13c099e8e64d54efd7ce80ef6ea47478aa75d5d74e2dba3b" -dependencies = [ - "alloy-chains", - "alloy-consensus 0.6.4", - "alloy-eips 0.6.4", - "alloy-json-rpc 0.6.4", - "alloy-network 0.6.4", - "alloy-network-primitives 0.6.4", - "alloy-primitives", - "alloy-pubsub 0.6.4", - "alloy-rpc-client 0.6.4", - "alloy-rpc-types-eth 0.6.4", - "alloy-transport 0.6.4", - "alloy-transport-http 0.6.4", - "alloy-transport-ipc 0.6.4", - "alloy-transport-ws 0.6.4", - "async-stream", - "async-trait", - "auto_impl", - "dashmap 6.1.0", - "futures 0.3.31", - "futures-utils-wasm", - "lru", - "parking_lot 0.12.3", - "pin-project 1.1.7", - "reqwest 0.12.9", - "schnellru", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tracing", - "url", - "wasmtimer", -] - [[package]] name = "alloy-provider" version = "0.8.0" @@ -553,22 +396,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5115c74c037714e1b02a86f742289113afa5d494b5ea58308ba8aa378e739101" dependencies = [ "alloy-chains", - "alloy-consensus 0.8.0", - "alloy-eips 0.8.0", - "alloy-json-rpc 0.8.0", - "alloy-network 0.8.0", - "alloy-network-primitives 0.8.0", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", "alloy-primitives", - "alloy-pubsub 0.8.0", - "alloy-rpc-client 0.8.0", + "alloy-pubsub", + "alloy-rpc-client", "alloy-rpc-types-debug", - "alloy-rpc-types-eth 0.8.0", + "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-transport 0.8.0", - "alloy-transport-http 0.8.0", - "alloy-transport-ipc 0.8.0", - "alloy-transport-ws 0.8.0", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "async-stream", "async-trait", "auto_impl", @@ -589,34 +432,15 @@ dependencies = [ "wasmtimer", ] -[[package]] -name = "alloy-pubsub" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f1f34232f77341076541c405482e4ae12f0ee7153d8f9969fc1691201b2247" -dependencies = [ - "alloy-json-rpc 0.6.4", - "alloy-primitives", - "alloy-transport 0.6.4", - "bimap", - "futures 0.3.31", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tower 0.5.1", - "tracing", -] - [[package]] name = "alloy-pubsub" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b073afa409698d1b9a30522565815f3bf7010e5b47b997cf399209e6110df097" dependencies = [ - "alloy-json-rpc 0.8.0", + "alloy-json-rpc", "alloy-primitives", - "alloy-transport 0.8.0", + "alloy-transport", "bimap", "futures 0.3.31", "serde", @@ -649,45 +473,19 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "alloy-rpc-client" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374dbe0dc3abdc2c964f36b3d3edf9cdb3db29d16bda34aa123f03d810bec1dd" -dependencies = [ - "alloy-json-rpc 0.6.4", - "alloy-primitives", - "alloy-pubsub 0.6.4", - "alloy-transport 0.6.4", - "alloy-transport-http 0.6.4", - "alloy-transport-ipc 0.6.4", - "alloy-transport-ws 0.6.4", - "futures 0.3.31", - "pin-project 1.1.7", - "reqwest 0.12.9", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tower 0.5.1", - "tracing", - "url", - "wasmtimer", -] - [[package]] name = "alloy-rpc-client" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6a0bd0ce5660ac48e4f3bb0c7c5c3a94db287a0be94971599d83928476cbcd" dependencies = [ - "alloy-json-rpc 0.8.0", + "alloy-json-rpc", "alloy-primitives", - "alloy-pubsub 0.8.0", - "alloy-transport 0.8.0", - "alloy-transport-http 0.8.0", - "alloy-transport-ipc 0.8.0", - "alloy-transport-ws 0.8.0", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "futures 0.3.31", "pin-project 1.1.7", "reqwest 0.12.9", @@ -701,19 +499,6 @@ dependencies = [ "wasmtimer", ] -[[package]] -name = "alloy-rpc-types" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c74832aa474b670309c20fffc2a869fa141edab7c79ff7963fad0a08de60bae1" -dependencies = [ - "alloy-primitives", - "alloy-rpc-types-engine 0.6.4", - "alloy-rpc-types-eth 0.6.4", - "alloy-serde 0.6.4", - "serde", -] - [[package]] name = "alloy-rpc-types" version = "0.8.0" @@ -722,11 +507,11 @@ checksum = "374ac12e35bb90ebccd86e7c943ddba9590149a6e35cc4d9cd860d6635fd1018" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", - "alloy-rpc-types-engine 0.8.0", - "alloy-rpc-types-eth 0.8.0", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde 0.8.0", + "alloy-serde", "serde", ] @@ -737,8 +522,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b85a5f5f5d99047544f4ec31330ee15121dcb8ef5af3e791a5207e6b92b05b" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.8.0", - "alloy-serde 0.8.0", + "alloy-rpc-types-eth", + "alloy-serde", "serde", ] @@ -749,8 +534,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea98f81bcd759dbfa3601565f9d7a02220d8ef1d294ec955948b90aaafbfd857" dependencies = [ "alloy-consensus-any", - "alloy-rpc-types-eth 0.8.0", - "alloy-serde 0.8.0", + "alloy-rpc-types-eth", + "alloy-serde", ] [[package]] @@ -763,33 +548,17 @@ dependencies = [ "serde", ] -[[package]] -name = "alloy-rpc-types-engine" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56294dce86af23ad6ee8df46cf8b0d292eb5d1ff67dc88a0886051e32b1faf" -dependencies = [ - "alloy-consensus 0.6.4", - "alloy-eips 0.6.4", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.6.4", - "derive_more 1.0.0", - "serde", - "strum", -] - [[package]] name = "alloy-rpc-types-engine" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ca5898f753ff0d15a0dc955c169523d8fee57e05bb5a38a398b3451b0b988be" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-eips 0.8.0", + "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.8.0", + "alloy-serde", "derive_more 1.0.0", "jsonwebtoken", "rand 0.8.5", @@ -797,38 +566,19 @@ dependencies = [ "strum", ] -[[package]] -name = "alloy-rpc-types-eth" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a477281940d82d29315846c7216db45b15e90bcd52309da9f54bcf7ad94a11" -dependencies = [ - "alloy-consensus 0.6.4", - "alloy-eips 0.6.4", - "alloy-network-primitives 0.6.4", - "alloy-primitives", - "alloy-rlp", - "alloy-serde 0.6.4", - "alloy-sol-types", - "derive_more 1.0.0", - "itertools 0.13.0", - "serde", - "serde_json", -] - [[package]] name = "alloy-rpc-types-eth" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e518b0a7771e00728f18be0708f828b18a1cfc542a7153bef630966a26388e0" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-consensus-any", - "alloy-eips 0.8.0", - "alloy-network-primitives 0.8.0", + "alloy-eips", + "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.8.0", + "alloy-serde", "alloy-sol-types", "derive_more 1.0.0", "itertools 0.13.0", @@ -843,8 +593,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdff93fa38be6982f8613a060e18fa0a37ce440d69ed3b7f37c6c69036ce1c53" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.8.0", - "alloy-serde 0.8.0", + "alloy-rpc-types-eth", + "alloy-serde", "serde", "serde_json", "thiserror 2.0.6", @@ -857,20 +607,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d9dc647985db41fd164e807577134da1179b9f5ba0959f8698d6587eaa568f5" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.8.0", - "alloy-serde 0.8.0", - "serde", -] - -[[package]] -name = "alloy-serde" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dfa4a7ccf15b2492bb68088692481fd6b2604ccbee1d0d6c44c21427ae4df83" -dependencies = [ - "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", "serde", - "serde_json", ] [[package]] @@ -884,20 +623,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "alloy-signer" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e10aec39d60dc27edcac447302c7803d2371946fb737245320a05b78eb2fafd" -dependencies = [ - "alloy-primitives", - "async-trait", - "auto_impl", - "elliptic-curve 0.13.8", - "k256 0.13.4", - "thiserror 1.0.69", -] - [[package]] name = "alloy-signer" version = "0.8.0" @@ -920,10 +645,10 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe06d524ac84fefce1184f2d1273704e62faade7ff1f29c17ac9d493d3ffbdbf" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-network 0.8.0", + "alloy-consensus", + "alloy-network", "alloy-primitives", - "alloy-signer 0.8.0", + "alloy-signer", "async-trait", "aws-sdk-kms", "k256 0.13.4", @@ -938,10 +663,10 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "492cedcb4819a588aaef8d59edd5d65291f485d25f64b2aa0806dd86feeafd18" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-network 0.8.0", + "alloy-consensus", + "alloy-network", "alloy-primitives", - "alloy-signer 0.8.0", + "alloy-signer", "async-trait", "gcloud-sdk", "k256 0.13.4", @@ -956,11 +681,11 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "426409a02587b98e118d2fd32dda3f423805e264a32f9e247a65164163bc0e9b" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-signer 0.8.0", + "alloy-signer", "alloy-sol-types", "async-trait", "coins-ledger", @@ -970,32 +695,16 @@ dependencies = [ "tracing", ] -[[package]] -name = "alloy-signer-local" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8396f6dff60700bc1d215ee03d86ff56de268af96e2bf833a14d0bafcab9882" -dependencies = [ - "alloy-consensus 0.6.4", - "alloy-network 0.6.4", - "alloy-primitives", - "alloy-signer 0.6.4", - "async-trait", - "k256 0.13.4", - "rand 0.8.5", - "thiserror 1.0.69", -] - [[package]] name = "alloy-signer-local" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2184dab8c9493ab3e1c9f6bd3bdb563ed322b79023d81531935e84a4fdf7cf1" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-network 0.8.0", + "alloy-consensus", + "alloy-network", "alloy-primitives", - "alloy-signer 0.8.0", + "alloy-signer", "async-trait", "coins-bip32", "coins-bip39", @@ -1011,10 +720,10 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290ead62e020b751761de95f60056340faba341b20493ae929013d1357b9ba5b" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-network 0.8.0", + "alloy-consensus", + "alloy-network", "alloy-primitives", - "alloy-signer 0.8.0", + "alloy-signer", "async-trait", "semver 1.0.23", "thiserror 2.0.6", @@ -1095,33 +804,13 @@ dependencies = [ "serde", ] -[[package]] -name = "alloy-transport" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99acddb34000d104961897dbb0240298e8b775a7efffb9fda2a1a3efedd65b3" -dependencies = [ - "alloy-json-rpc 0.6.4", - "base64 0.22.1", - "futures-util", - "futures-utils-wasm", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tower 0.5.1", - "tracing", - "url", - "wasmtimer", -] - [[package]] name = "alloy-transport" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628be5b9b75e4f4c4f2a71d985bbaca4f23de356dc83f1625454c505f5eef4df" dependencies = [ - "alloy-json-rpc 0.8.0", + "alloy-json-rpc", "base64 0.22.1", "futures-util", "futures-utils-wasm", @@ -1135,29 +824,14 @@ dependencies = [ "wasmtimer", ] -[[package]] -name = "alloy-transport-http" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc013132e34eeadaa0add7e74164c1503988bfba8bae885b32e0918ba85a8a6" -dependencies = [ - "alloy-json-rpc 0.6.4", - "alloy-transport 0.6.4", - "reqwest 0.12.9", - "serde_json", - "tower 0.5.1", - "tracing", - "url", -] - [[package]] name = "alloy-transport-http" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e24412cf72f79c95cd9b1d9482e3a31f9d94c24b43c4b3b710cc8d4341eaab0" dependencies = [ - "alloy-json-rpc 0.8.0", - "alloy-transport 0.8.0", + "alloy-json-rpc", + "alloy-transport", "reqwest 0.12.9", "serde_json", "tower 0.5.1", @@ -1165,34 +839,15 @@ dependencies = [ "url", ] -[[package]] -name = "alloy-transport-ipc" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063edc0660e81260653cc6a95777c29d54c2543a668aa5da2359fb450d25a1ba" -dependencies = [ - "alloy-json-rpc 0.6.4", - "alloy-pubsub 0.6.4", - "alloy-transport 0.6.4", - "bytes", - "futures 0.3.31", - "interprocess", - "pin-project 1.1.7", - "serde_json", - "tokio", - "tokio-util 0.7.13", - "tracing", -] - [[package]] name = "alloy-transport-ipc" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0577a1f67ce70ece3f2b27cf1011da7222ef0a5701f7dcb558e5356278eeb531" dependencies = [ - "alloy-json-rpc 0.8.0", - "alloy-pubsub 0.8.0", - "alloy-transport 0.8.0", + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", "bytes", "futures 0.3.31", "interprocess", @@ -1205,32 +860,14 @@ dependencies = [ "tracing", ] -[[package]] -name = "alloy-transport-ws" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd170e600801116d5efe64f74a4fc073dbbb35c807013a7d0a388742aeebba0" -dependencies = [ - "alloy-pubsub 0.6.4", - "alloy-transport 0.6.4", - "futures 0.3.31", - "http 1.2.0", - "rustls 0.23.19", - "serde_json", - "tokio", - "tokio-tungstenite", - "tracing", - "ws_stream_wasm", -] - [[package]] name = "alloy-transport-ws" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca46272d17f9647fdb56080ed26c72b3ea5078416831130f5ed46f3b4be0ed6" dependencies = [ - "alloy-pubsub 0.8.0", - "alloy-transport 0.8.0", + "alloy-pubsub", + "alloy-transport", "futures 0.3.31", "http 1.2.0", "rustls 0.23.19", @@ -1259,11 +896,10 @@ dependencies = [ [[package]] name = "alloy-zksync" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1188e7fd94560a363fb32c12fc37109a92989dae4e40eabe39ac76119aa7f670" +version = "0.8.0" dependencies = [ "alloy", + "alloy-consensus-any", "async-trait", "chrono", "futures-utils-wasm", @@ -1377,27 +1013,27 @@ name = "anvil" version = "0.0.2" dependencies = [ "alloy-chains", - "alloy-consensus 0.8.0", - "alloy-contract 0.8.0", + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", - "alloy-eips 0.8.0", - "alloy-genesis 0.8.0", + "alloy-eips", + "alloy-genesis", "alloy-json-abi", - "alloy-json-rpc 0.8.0", - "alloy-network 0.8.0", + "alloy-json-rpc", + "alloy-network", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-pubsub 0.8.0", + "alloy-provider", + "alloy-pubsub", "alloy-rlp", - "alloy-rpc-client 0.8.0", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", - "alloy-signer 0.8.0", - "alloy-signer-local 0.8.0", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", "alloy-sol-types", - "alloy-transport 0.8.0", - "alloy-transport-ipc 0.8.0", - "alloy-transport-ws 0.8.0", + "alloy-transport", + "alloy-transport-ipc", + "alloy-transport-ws", "alloy-trie", "anvil-core", "anvil-rpc", @@ -1446,14 +1082,14 @@ dependencies = [ name = "anvil-core" version = "0.0.2" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 0.8.0", - "alloy-network 0.8.0", + "alloy-eips", + "alloy-network", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", + "alloy-rpc-types", + "alloy-serde", "alloy-trie", "bytes", "foundry-common", @@ -2840,22 +2476,22 @@ name = "cast" version = "0.0.2" dependencies = [ "alloy-chains", - "alloy-consensus 0.8.0", - "alloy-contract 0.8.0", + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", - "alloy-json-rpc 0.8.0", - "alloy-network 0.8.0", + "alloy-json-rpc", + "alloy-network", "alloy-node-bindings", "alloy-primitives", - "alloy-provider 0.8.0", + "alloy-provider", "alloy-rlp", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", - "alloy-signer 0.8.0", - "alloy-signer-local 0.8.0", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", "alloy-sol-types", - "alloy-transport 0.8.0", + "alloy-transport", "alloy-zksync", "anvil", "async-trait", @@ -2964,7 +2600,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "alloy-rpc-types 0.8.0", + "alloy-rpc-types", "clap", "dirs 5.0.1", "eyre", @@ -4917,19 +4553,19 @@ name = "forge" version = "0.0.2" dependencies = [ "alloy-chains", - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", "alloy-json-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", - "alloy-signer 0.8.0", - "alloy-signer-local 0.8.0", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", "alloy-sol-macro-expander", "alloy-sol-macro-input", - "alloy-transport 0.8.0", + "alloy-transport", "alloy-zksync", "anvil", "async-trait", @@ -5055,17 +4691,17 @@ name = "forge-script" version = "0.0.2" dependencies = [ "alloy-chains", - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 0.8.0", + "alloy-eips", "alloy-json-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", - "alloy-signer 0.8.0", - "alloy-transport 0.8.0", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-transport", "alloy-zksync", "clap", "dialoguer", @@ -5102,9 +4738,9 @@ dependencies = [ name = "forge-script-sequence" version = "0.0.2" dependencies = [ - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-rpc-types 0.8.0", + "alloy-rpc-types", "eyre", "foundry-common", "foundry-compilers", @@ -5140,8 +4776,8 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-rpc-types 0.8.0", + "alloy-provider", + "alloy-rpc-types", "async-trait", "ciborium", "clap", @@ -5199,17 +4835,17 @@ dependencies = [ name = "foundry-cheatcodes" version = "0.0.2" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-genesis 0.8.0", + "alloy-genesis", "alloy-json-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-provider 0.8.0", + "alloy-provider", "alloy-rlp", - "alloy-rpc-types 0.8.0", - "alloy-signer 0.8.0", - "alloy-signer-local 0.8.0", + "alloy-rpc-types", + "alloy-signer", + "alloy-signer-local", "alloy-sol-types", "base64 0.22.1", "chrono", @@ -5274,12 +4910,12 @@ version = "0.0.2" dependencies = [ "alloy-chains", "alloy-dyn-abi", - "alloy-eips 0.8.0", + "alloy-eips", "alloy-json-abi", "alloy-primitives", - "alloy-provider 0.8.0", + "alloy-provider", "alloy-rlp", - "alloy-transport 0.8.0", + "alloy-transport", "alloy-zksync", "clap", "color-eyre", @@ -5292,6 +4928,7 @@ dependencies = [ "foundry-debugger", "foundry-evm", "foundry-wallets", + "foundry-zksync-compilers", "futures 0.3.31", "indicatif", "rayon", @@ -5312,24 +4949,24 @@ dependencies = [ name = "foundry-common" version = "0.0.2" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-contract 0.8.0", + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", - "alloy-eips 0.8.0", + "alloy-eips", "alloy-json-abi", - "alloy-json-rpc 0.8.0", - "alloy-network 0.8.0", + "alloy-json-rpc", + "alloy-network", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-pubsub 0.8.0", - "alloy-rpc-client 0.8.0", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", "alloy-sol-types", - "alloy-transport 0.8.0", - "alloy-transport-http 0.8.0", - "alloy-transport-ipc 0.8.0", - "alloy-transport-ws 0.8.0", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "alloy-zksync", "anstream", "anstyle", @@ -5364,12 +5001,12 @@ dependencies = [ name = "foundry-common-fmt" version = "0.0.2" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", + "alloy-rpc-types", + "alloy-serde", "chrono", "comfy-table", "foundry-macros", @@ -5590,16 +5227,16 @@ dependencies = [ name = "foundry-evm-core" version = "0.0.2" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-genesis 0.8.0", + "alloy-genesis", "alloy-json-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-rpc-types 0.8.0", + "alloy-provider", + "alloy-rpc-types", "alloy-sol-types", - "alloy-transport 0.8.0", + "alloy-transport", "auto_impl", "eyre", "foundry-cheatcodes-spec", @@ -5700,12 +5337,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "491e9f9f138086b3627a8c406730dfbb6afcdcf688e6da0eb15df52f0c8ed163" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-primitives", - "alloy-provider 0.8.0", - "alloy-rpc-types 0.8.0", - "alloy-serde 0.8.0", - "alloy-transport 0.8.0", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-transport", "eyre", "futures 0.3.31", "parking_lot 0.12.3", @@ -5743,7 +5380,7 @@ name = "foundry-test-utils" version = "0.0.2" dependencies = [ "alloy-primitives", - "alloy-provider 0.8.0", + "alloy-provider", "era_test_node", "eyre", "fd-lock", @@ -5769,15 +5406,15 @@ dependencies = [ name = "foundry-wallets" version = "0.0.2" dependencies = [ - "alloy-consensus 0.8.0", + "alloy-consensus", "alloy-dyn-abi", - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", - "alloy-signer 0.8.0", + "alloy-signer", "alloy-signer-aws", "alloy-signer-gcp", "alloy-signer-ledger", - "alloy-signer-local 0.8.0", + "alloy-signer-local", "alloy-signer-trezor", "alloy-sol-types", "async-trait", @@ -5809,6 +5446,7 @@ dependencies = [ "foundry-compilers-artifacts-solc", "fs4 0.8.4", "itertools 0.13.0", + "path-slash", "reqwest 0.12.9", "semver 1.0.23", "serde", @@ -5824,10 +5462,10 @@ dependencies = [ name = "foundry-zksync-core" version = "0.0.2" dependencies = [ - "alloy-network 0.8.0", + "alloy-network", "alloy-primitives", "alloy-sol-types", - "alloy-transport 0.8.0", + "alloy-transport", "alloy-zksync", "ansiterm", "era_test_node", @@ -8836,11 +8474,11 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d95d0ec6457ad4d3d7fc0ad41db490b219587ed837ada87a26b28e535db15f" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-eips 0.8.0", + "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde 0.8.0", + "alloy-serde", "derive_more 1.0.0", "serde", "thiserror 2.0.6", @@ -8852,12 +8490,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eba1b44e2035ec04cc61762cb9b5457d0ecd29d9af631e1a1c107ef571ce2318" dependencies = [ - "alloy-consensus 0.8.0", - "alloy-eips 0.8.0", - "alloy-network-primitives 0.8.0", + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", "alloy-primitives", - "alloy-rpc-types-eth 0.8.0", - "alloy-serde 0.8.0", + "alloy-rpc-types-eth", + "alloy-serde", "derive_more 1.0.0", "op-alloy-consensus", "serde", @@ -10590,7 +10228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d056aaa21f36038ab35fe8ce940ee332903a0b4b992b8ca805fb60c85eb2086" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth 0.8.0", + "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-sol-types", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index ad57e73c8..45aebc6fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,7 +230,8 @@ syn-solidity = "0.8.14" alloy-chains = "0.1" alloy-rlp = "0.3" -alloy-zksync = "0.6.1" +#alloy-zksync = "0.6.1" +alloy-zksync = { path = "../alloy-zksync/" } alloy-trie = "0.7.0" diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index c71b9576e..9fe9cf381 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -8,7 +8,7 @@ use foundry_config::{ ResolvedRpcEndpoints, }; use foundry_evm_core::opts::EvmOpts; -use foundry_zksync_compiler::DualCompiledContracts; +use foundry_zksync_compilers::dual_compiled_contracts::DualCompiledContracts; use semver::Version; use std::{ path::{Path, PathBuf}, @@ -118,6 +118,8 @@ impl CheatsConfig { self.available_artifacts.clone(), self.running_contract.clone(), self.running_version.clone(), + self.dual_compiled_contracts.clone(), + self.use_zk, ) } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 436afa466..170db9462 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -12,7 +12,7 @@ use dialoguer::{Input, Password}; use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use foundry_zksync_compiler::ContractType; +use foundry_zksync_compilers::dual_compiled_contracts::ContractType; use revm::interpreter::CreateInputs; use revm_inspectors::tracing::types::CallKind; use semver::Version; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 6af6b4d26..83c3bfccf 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -43,7 +43,9 @@ use foundry_evm_core::{ }; use foundry_evm_traces::TracingInspectorConfig; use foundry_wallets::multi_wallet::MultiWallet; -use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; +use foundry_zksync_compilers::dual_compiled_contracts::{ + DualCompiledContract, DualCompiledContracts, +}; use foundry_zksync_core::{ convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, get_account_code_key, get_balance_key, get_nonce_key, Call, ZkPaymasterData, diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index c4b5f219c..9f732b340 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -5,7 +5,7 @@ use alloy_primitives::Address; use alloy_sol_types::SolValue; use chrono::DateTime; use foundry_evm_core::constants::MAGIC_SKIP; -use foundry_zksync_compiler::DualCompiledContract; +use foundry_zksync_compilers::dual_compiled_contracts::DualCompiledContract; use foundry_zksync_core::ZkPaymasterData; use std::env; diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ee1fc4c09..c08b5d066 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -51,6 +51,9 @@ yansi.workspace = true tracing-tracy = { version = "0.11", optional = true } +# zk +foundry-zksync-compilers.workspace = true + [dev-dependencies] tempfile.workspace = true diff --git a/crates/cli/src/opts/build/zksync.rs b/crates/cli/src/opts/build/zksync.rs index 216a37da6..14398a528 100644 --- a/crates/cli/src/opts/build/zksync.rs +++ b/crates/cli/src/opts/build/zksync.rs @@ -2,8 +2,8 @@ use std::{collections::HashSet, path::PathBuf}; use alloy_primitives::{hex, Address, Bytes}; use clap::Parser; -use foundry_compilers::zksolc::settings::{ZkSolcError, ZkSolcWarning}; -use foundry_config::ZkSyncConfig; +use foundry_config::zksync::ZkSyncConfig; +use foundry_zksync_compilers::compilers::zksolc::settings::{ZkSolcError, ZkSolcWarning}; use serde::Serialize; #[derive(Clone, Debug, Default, Serialize, Parser)] diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 07c42ad45..13f395f26 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -6,7 +6,6 @@ use foundry_compilers::{ artifacts::{CompactBytecode, Settings}, cache::{CacheEntry, CompilerCache}, utils::read_json_file, - zksync::artifact_output::zk::ZkContractArtifact, Artifact, ArtifactId, ProjectCompileOutput, }; use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; @@ -21,6 +20,10 @@ use foundry_evm::{ render_trace_arena_inner, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, }; +use foundry_zksync_compilers::compilers::{ + artifact_output::zk::{ZkArtifactOutput, ZkContractArtifact}, + zksolc::ZkSolcCompiler, +}; use std::{ fmt::Write, path::{Path, PathBuf}, @@ -75,7 +78,7 @@ pub fn remove_contract( /// Runtime Bytecode of the given contract. #[track_caller] pub fn remove_zk_contract( - output: &mut foundry_compilers::zksync::compile::output::ProjectCompileOutput, + output: &mut ProjectCompileOutput, path: &Path, name: &str, ) -> Result<(ZkContractArtifact, ArtifactId)> { @@ -222,11 +225,7 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool { /// True if it supports broadcasting in batches. pub fn has_batch_support(chain_id: u64) -> bool { if let Some(chain) = Chain::from(chain_id).named() { - if matches!( - chain, - NamedChain::ZkSync | - NamedChain::ZkSyncTestnet - ) { + if matches!(chain, NamedChain::ZkSync | NamedChain::ZkSyncTestnet) { return false }; return !chain.is_arbitrum(); @@ -553,6 +552,5 @@ pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf }); fs::write_json_file(&path, &cached_signatures)?; ->>>>>>> 59f354c179f4e7f6d7292acb3d068815c79286d1 Ok(()) } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 0981b058b..888beed79 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -116,14 +116,15 @@ use soldeer::{SoldeerConfig, SoldeerDependencyConfig}; mod vyper; use vyper::VyperConfig; -mod zksync; -pub use zksync::*; mod bind_json; use bind_json::BindJsonConfig; mod compilation; use compilation::{CompilationRestrictions, SettingsOverrides}; +pub mod zksync; +use zksync::ZkSyncConfig; + /// Foundry configuration /// /// # Defaults diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index d3f5397c1..d69a635e6 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -5,10 +5,7 @@ use foundry_compilers::{ Project, ProjectBuilder, ProjectPathsConfig, }; use foundry_zksync_compilers::{ - artifacts::{ - contract::Contract, - output_selection::{FileOutputSelection, OutputSelection, OutputSelectionFlag}, - }, + artifacts::output_selection::{FileOutputSelection, OutputSelection, OutputSelectionFlag}, compilers::{ artifact_output::zk::{ZkArtifactOutput, ZkContractArtifact}, zksolc::{ diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 889702d11..af678c70d 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -213,7 +213,8 @@ pub fn create2_handler_register( .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); // Sanity check that CREATE2 deployer exists. - let code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash; + // NOTE(zk): made mut to apply later check + let mut code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash; // NOTE(zk): We check which deployer we are using to separate the logic for zkSync // and original foundry. if call_inputs.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index ef702eb22..ac466adbc 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -8,10 +8,8 @@ use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_config::InvariantConfig; use foundry_evm_core::{ - constants::{ - CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME, - TEST_TIMEOUT, - }, + abi::HARDHAT_CONSOLE_ADDRESS, + constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, MAGIC_ASSUME, TEST_TIMEOUT}, precompiles::PRECOMPILES, }; use foundry_evm_fuzz::{ diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 0db809f5e..3e7680ecb 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1047,10 +1047,6 @@ impl InspectorExt for InspectorStackRefMut<'_> { )); } - fn is_alphanet(&self) -> bool { - self.inner.alphanet - } - fn is_odyssey(&self) -> bool { self.inner.odyssey } diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index bef41c7eb..38dddf802 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -15,6 +15,7 @@ use foundry_cheatcodes_spec::Vm; use foundry_common::{ abi::get_indexed_event, fmt::format_token, get_contract_name, ContractsByArtifact, SELECTOR_LEN, }; +use foundry_config::zksync::ZKSYNC_ARTIFACTS_DIR; use foundry_evm_core::{ abi::{Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS, HARDHAT_CONSOLE_SELECTOR_PATCHES}, constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, @@ -24,7 +25,6 @@ use foundry_evm_core::{ RIPEMD_160, SHA_256, }, }; -use foundry_zksync_compiler::ZKSYNC_ARTIFACTS_DIR; use itertools::Itertools; use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; use std::{ diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 045f04aac..758da4cc1 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -123,7 +123,7 @@ impl BuildArgs { Ok(()) } else { let zk_project = - foundry_zksync_compiler::config_create_project(&config, config.cache, false)?; + foundry_config::zksync::config_create_project(&config, config.cache, false)?; // Collect sources to compile if build subdirectories specified. let mut files = vec![]; diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index b892d0668..a49880fe3 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -23,7 +23,7 @@ use foundry_compilers::{ Artifact, ArtifactId, Project, ProjectCompileOutput, }; use foundry_config::{Config, SolcReq}; -use foundry_zksync_compiler::DualCompiledContracts; +use foundry_zksync_compilers::dual_compiled_contracts::DualCompiledContracts; use rayon::prelude::*; use semver::{Version, VersionReq}; use std::{ @@ -247,7 +247,14 @@ impl CoverageArgs { .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) .set_coverage(true) - .build::(&root, output, env, evm_opts)?; + .build::( + &root, + output, + None, + env, + evm_opts, + DualCompiledContracts::default(), + )?; let known_contracts = runner.known_contracts.clone(); diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index dc4ba7c60..45694252e 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -2,7 +2,9 @@ use crate::cmd::install; use alloy_chains::Chain; use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::{Constructor, JsonAbi}; -use alloy_network::{AnyNetwork, AnyTransactionReceipt, EthereumWallet, Network, ReceiptResponse, TransactionBuilder}; +use alloy_network::{ + AnyNetwork, AnyTransactionReceipt, EthereumWallet, Network, ReceiptResponse, TransactionBuilder, +}; use alloy_primitives::{hex, Address, Bytes}; use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder}; use alloy_rpc_types::TransactionRequest; @@ -26,8 +28,7 @@ use foundry_common::{ shell, }; use foundry_compilers::{ - artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, - zksync::artifact_output::zk::ZkContractArtifact, ArtifactId, + artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, ArtifactId, }; use foundry_config::{ figment::{ @@ -37,6 +38,7 @@ use foundry_config::{ }, merge_impl_figment_convert, Config, }; +use foundry_zksync_compilers::compilers::artifact_output::zk::ZkContractArtifact; use foundry_zksync_core::convert::ConvertH160; use serde_json::json; use std::{ @@ -161,7 +163,7 @@ impl CreateArgs { let config = self.opts.try_load_config_emit_warnings()?; let zk_project = - foundry_zksync_compiler::config_create_project(&config, config.cache, false)?; + foundry_config::zksync::config_create_project(&config, config.cache, false)?; let zk_compiler = ProjectCompiler::new().files([target_path.clone()]); let mut zk_output = zk_compiler.zksync_compile(&zk_project)?; @@ -570,7 +572,6 @@ impl CreateArgs { } return Ok(()); ->>>>>>> 59f354c179f4e7f6d7292acb3d068815c79286d1 } // Deploy the actual contract @@ -758,11 +759,6 @@ impl CreateArgs { None }; let verify = VerifyArgs { -<<<<<<< HEAD -======= ->>>>>>> 59f354c179f4e7f6d7292acb3d068815c79286d1 -======= ->>>>>>> main address, contract: Some(self.contract), compiler_version: None, @@ -783,12 +779,7 @@ impl CreateArgs { evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, guess_constructor_args: false, -<<<<<<< HEAD - // compilation_profile: Some(id.profile.to_string()), - compilation_profile: None, // TODO(zk): provide comp profile -======= compilation_profile: None, //TODO(zk): provide comp profile ->>>>>>> main zksync: self.opts.compiler.zk.enabled(), }; sh_println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier)?; diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index acb144755..bd3136e3e 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -41,7 +41,7 @@ use foundry_config::{ }; use foundry_debugger::Debugger; use foundry_evm::traces::identifier::TraceIdentifiers; -use foundry_zksync_compiler::DualCompiledContracts; +use foundry_zksync_compilers::dual_compiled_contracts::DualCompiledContracts; use regex::Regex; use std::{ collections::{BTreeMap, BTreeSet}, @@ -306,7 +306,7 @@ impl TestArgs { let (zk_output, dual_compiled_contracts) = if config.zksync.should_compile() { let zk_project = - foundry_zksync_compiler::config_create_project(&config, config.cache, false)?; + foundry_config::zksync::config_create_project(&config, config.cache, false)?; let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; let zk_compiler = ProjectCompiler::new().files(sources_to_compile); @@ -371,7 +371,14 @@ impl TestArgs { .with_fork(evm_opts.get_fork(&config, env.clone())) .enable_isolation(evm_opts.isolate) .odyssey(evm_opts.odyssey) - .build::(project_root, &output, env, evm_opts)?; + .build::( + project_root, + &output, + zk_output, + env, + evm_opts, + dual_compiled_contracts.unwrap_or_default(), + )?; let mut maybe_override_mt = |flag, maybe_regex: Option<&Option>| { if let Some(Some(regex)) = maybe_regex { diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index f652befdf..4b8080b42 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -90,7 +90,7 @@ fn run() -> Result<()> { let config = utils::load_config_with_root(root.as_deref()); let project = config.project()?; let zk_project = - foundry_zksync_compiler::config_create_project(&config, config.cache, false)?; + foundry_config::zksync::config_create_project(&config, config.cache, false)?; config.cleanup(&project)?; config.cleanup(&zk_project)?; Ok(()) diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 224a3eb14..044b28757 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -123,16 +123,6 @@ impl GasReport { if is_create_call { trace!(contract_name, "adding create gas info"); contract_info.gas = trace.gas_used; -<<<<<<< HEAD -======= - contract_info.size = trace.data.len(); - - if decoder.zk_contracts.contains(&node.trace.address) { - // Intercepted creates in zkvm mode will have the evm bytecode as input - // and the zkvm bytecode as output on the trace. - contract_info.size = trace.output.len(); - } ->>>>>>> main } else if let Some(DecodedCallData { signature, .. }) = decoded().await.call_data { let name = signature.split('(').next().unwrap(); // ignore any test/setup functions diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 2660dcda7..27ed25d55 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -13,7 +13,6 @@ use foundry_compilers::{ CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, Contract, Libraries, }, compilers::Compiler, - zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, Artifact, ArtifactId, ProjectCompileOutput, }; use foundry_config::{Config, InlineConfig}; @@ -28,7 +27,10 @@ use foundry_evm::{ traces::{InternalTraceMode, TraceMode}, }; use foundry_linking::{LinkOutput, Linker}; -use foundry_zksync_compiler::DualCompiledContracts; +use foundry_zksync_compilers::{ + compilers::{artifact_output::zk::ZkArtifactOutput, zksolc::ZkSolcCompiler}, + dual_compiled_contracts::DualCompiledContracts, +}; use rayon::prelude::*; use revm::primitives::SpecId; @@ -378,7 +380,7 @@ impl TestRunnerConfig { .odyssey(self.odyssey) .create2_deployer(self.evm_opts.create2_deployer) }) - .use_zk_vm(self.use_zk) + .use_zk_vm(use_zk) .spec_id(self.spec_id) .gas_limit(self.evm_opts.gas_limit()) .legacy_assertions(self.config.legacy_assertions) @@ -488,15 +490,17 @@ impl MultiContractRunnerBuilder { self, root: &Path, output: &ProjectCompileOutput, - zk_output: Option, + zk_output: Option>, env: revm::primitives::Env, evm_opts: EvmOpts, dual_compiled_contracts: DualCompiledContracts, ) -> Result { let use_zk = zk_output.is_some(); - let mut known_contracts = ContractsByArtifact::default(); - let output = output.with_stripped_file_prefixes(root); - let linker = Linker::new(root, output.artifact_ids().collect()); + let contracts = output + .artifact_ids() + .map(|(id, v)| (id.with_stripped_file_prefixes(root), v)) + .collect(); + let linker = Linker::new(root, contracts); // Build revert decoder from ABIs of all artifacts. let abis = linker @@ -535,6 +539,7 @@ impl MultiContractRunnerBuilder { } } + let mut known_contracts = ContractsByArtifact::default(); if !use_zk { known_contracts = ContractsByArtifact::new(linked_contracts); } else if let Some(zk_output) = zk_output { @@ -592,7 +597,7 @@ impl MultiContractRunnerBuilder { coverage: self.coverage, debug: self.debug, decode_internal: self.decode_internal, - inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), + inline_config: Arc::new(InlineConfig::new_parsed(&output, &self.config)?), isolation: self.isolation, odyssey: self.odyssey, diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index bf5b61966..bc0b224dd 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -88,7 +88,7 @@ impl ForgeTestProfile { pub fn zk_project(&self) -> ZkProject { let zk_config = self.zk_config(); let mut zk_project = - foundry_zksync_compiler::config_create_project(&zk_config, zk_config.cache, false) + foundry_config::zksync::config_create_project(&zk_config, zk_config.cache, false) .expect("failed creating zksync project"); zk_project.paths.artifacts = zk_config.root.as_ref().join("zk").join(ZKSYNC_ARTIFACTS_DIR); zk_project.paths.cache = zk_config diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index 4ea94b298..ba89cbfd0 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -19,12 +19,14 @@ use foundry_compilers::{ info::ContractInfo, solc::SolcLanguage, utils::source_files_iter, - zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, ArtifactId, ProjectCompileOutput, }; use foundry_evm::traces::debug::ContractSources; use foundry_linking::Linker; -use foundry_zksync_compiler::DualCompiledContracts; +use foundry_zksync_compilers::{ + compilers::{artifact_output::zk::ZkArtifactOutput, zksolc::ZkSolcCompiler}, + dual_compiled_contracts::DualCompiledContracts, +}; use std::{collections::BTreeMap, path::PathBuf, str::FromStr, sync::Arc}; /// Container for the compiled contracts. @@ -35,7 +37,7 @@ pub struct BuildData { /// The compiler output. pub output: ProjectCompileOutput, /// The zk compiler output - pub zk_output: Option, + pub zk_output: Option>, /// ID of target contract artifact. pub target: ArtifactId, pub dual_compiled_contracts: Option, @@ -234,7 +236,7 @@ impl PreprocessedState { let mut zk_output = None; // ZK let dual_compiled_contracts = if script_config.config.zksync.should_compile() { - let zk_project = foundry_zksync_compiler::config_create_project( + let zk_project = foundry_config::zksync::config_create_project( &script_config.config, script_config.config.cache, false, diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 9d7f883be..108fa05ec 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -55,7 +55,7 @@ use foundry_evm::{ traces::{TraceMode, Traces}, }; use foundry_wallets::MultiWalletOpts; -use foundry_zksync_compiler::DualCompiledContracts; +use foundry_zksync_compilers::dual_compiled_contracts::DualCompiledContracts; use serde::Serialize; use std::path::PathBuf; diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index a7e7fcdc7..bf224f041 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -10,8 +10,7 @@ use forge_verify::{provider::VerificationProviderType, RetryArgs, VerifierArgs, use foundry_cli::opts::{EtherscanOpts, ProjectPathsArgs}; use foundry_common::ContractsByArtifact; use foundry_compilers::{info::ContractInfo, Project}; -use foundry_config::{Chain, Config}; -use foundry_zksync_compiler::ZKSYNC_ARTIFACTS_DIR; +use foundry_config::{zksync::ZKSYNC_ARTIFACTS_DIR, Chain, Config}; use semver::Version; /// State after we have broadcasted the script. diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index eb37979cb..2b6495088 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -9,9 +9,6 @@ #[macro_use] extern crate foundry_common; -#[macro_use] -extern crate foundry_common; - #[macro_use] extern crate tracing; diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index 19b6feeb1..40ae50908 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -13,15 +13,12 @@ use foundry_compilers::{ Compiler, CompilerInput, }, solc::{CliSettings, Solc}, - zksolc::{ - input::{ZkSolcInput, ZkSolcVersionedInput}, - ZkSolc, ZkSolcCompiler, - }, - zksync::{ - compile::output::AggregatedCompilerOutput as ZkAggregatedCompilerOutput, raw_build_info_new, - }, AggregatedCompilerOutput, }; +use foundry_zksync_compilers::compilers::zksolc::{ + input::{ZkSolcInput, ZkSolcVersionedInput}, + ZkSolc, ZkSolcCompiler, +}; use semver::{BuildMetadata, Version}; use std::path::Path; @@ -74,7 +71,7 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default(); eyre::ensure!( - bch == foundry_compilers::zksolc::settings::BytecodeHash::Keccak256, + bch == foundry_zksync_compilers::compilers::zksolc::settings::BytecodeHash::Keccak256, "When using flattened source with zksync, bytecodeHash must be set to keccak256 because Etherscan uses Keccak256 in its Compiler Settings when re-compiling your code. BytecodeHash is currently: {}. Hint: Set the bytecodeHash key in your foundry.toml :)", bch, ); @@ -203,12 +200,12 @@ Diagnostics: {diags}", }; let zksolc_compiler = ZkSolcCompiler { zksolc: zksolc_path, solc: solc_compiler }; - let zksolc = zksolc_compiler.zksolc(&input)?; - let out = zksolc.compile(&input.input)?; - if out.has_error() { - let mut o = ZkAggregatedCompilerOutput::default(); - o.extend(solc_version, raw_build_info_new(&input, &out, false)?, "default", out); + let out = zksolc_compiler.compile(&input)?; + if out.errors.iter().any(|e| e.is_error()) { + let mut o: AggregatedCompilerOutput = + AggregatedCompilerOutput::default(); + o.extend(solc_version, RawBuildInfo::new(&input, &out, false)?, "default", out); let diags = o.diagnostics(&[], &[], Default::default()); eyre::bail!( diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 26a0bfcd1..e667909cb 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -58,7 +58,7 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { _args: &VerifyArgs, context: &ZkVerificationContext, ) -> Result<(String, String, CodeFormat)> { - let input = foundry_compilers::zksync::project_standard_json_input( + let input = foundry_zksync_compilers::compilers::project_standard_json_input( &context.project, &context.target_path, ) diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index d35ec90ce..932ec8b3f 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -375,7 +375,7 @@ impl VerifyArgs { let provider = utils::get_provider(&config)?; let code = provider.get_code_at(self.address).await?; - let output = ProjectCompiler::new().zksync_compile(&project)?; + let output = ProjectCompiler::new().compile(&project)?; let contracts = ContractsByArtifact::new( output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), ); @@ -408,7 +408,7 @@ impl VerifyArgs { let mut config = self.load_config_emit_warnings(); config.libraries.extend(self.libraries.clone()); - let project = foundry_zksync_compiler::config_create_project(&config, config.cache, false)?; + let project = foundry_config::zksync::config_create_project(&config, config.cache, false)?; if let Some(ref contract) = self.contract { let contract_path = if let Some(ref path) = contract.path { @@ -417,7 +417,7 @@ impl VerifyArgs { project.find_contract_path(&contract.name)? }; - let mut version = if let Some(ref version) = self.compiler_version { + let version = if let Some(ref version) = self.compiler_version { version.trim_start_matches('v').parse()? } else if let Some(ref solc) = config.solc { match solc { diff --git a/crates/verify/src/zk_provider.rs b/crates/verify/src/zk_provider.rs index d439daa2d..1f85797f4 100644 --- a/crates/verify/src/zk_provider.rs +++ b/crates/verify/src/zk_provider.rs @@ -8,11 +8,13 @@ use foundry_compilers::{ compilers::CompilerSettings, resolver::parse::SolData, solc::{Solc, SolcCompiler}, - zksolc::{self, ZkSolc, ZkSolcCompiler}, - zksync::artifact_output::zk::ZkArtifactOutput, Graph, Project, }; use foundry_config::Config; +use foundry_zksync_compilers::compilers::{ + artifact_output::zk::ZkArtifactOutput, + zksolc::{self, ZkSolc, ZkSolcCompiler}, +}; use semver::Version; use std::path::PathBuf; @@ -41,7 +43,7 @@ impl ZkVerificationContext { config: Config, ) -> Result { let mut project = - foundry_zksync_compiler::config_create_project(&config, config.cache, false)?; + foundry_config::zksync::config_create_project(&config, config.cache, false)?; project.no_artifacts = true; let zksolc_version = ZkSolc::get_version_for_path(&project.compiler.zksolc)?; diff --git a/crates/verify/src/zksync/mod.rs b/crates/verify/src/zksync/mod.rs index 164b13f64..b93e385bf 100644 --- a/crates/verify/src/zksync/mod.rs +++ b/crates/verify/src/zksync/mod.rs @@ -5,7 +5,7 @@ use alloy_primitives::hex; use eyre::{eyre, Result}; use foundry_cli::opts::EtherscanOpts; use foundry_common::{abi::encode_function_args, retry::Retry}; -use foundry_compilers::zksolc::input::StandardJsonCompilerInput; +use foundry_zksync_compilers::compilers::zksolc::input::StandardJsonCompilerInput; use futures::FutureExt; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, thread::sleep, time::Duration}; @@ -44,7 +44,7 @@ impl VerificationProvider for ZkVerificationProvider { let client = reqwest::Client::new(); - let retry: Retry = args.retry.into(); + let retry: Retry = args.retry.into_retry(); let verification_id: u64 = retry .run_async(|| { async { diff --git a/crates/verify/src/zksync/standard_json.rs b/crates/verify/src/zksync/standard_json.rs index ad5f7575f..b0939faa1 100644 --- a/crates/verify/src/zksync/standard_json.rs +++ b/crates/verify/src/zksync/standard_json.rs @@ -1,7 +1,7 @@ use super::ZksyncSourceProvider; use crate::zk_provider::ZkVerificationContext; use eyre::{Context, Result}; -use foundry_compilers::zksolc::input::StandardJsonCompilerInput; +use foundry_zksync_compilers::compilers::zksolc::input::StandardJsonCompilerInput; #[derive(Debug)] pub struct ZksyncStandardJsonSource; @@ -11,7 +11,7 @@ impl ZksyncSourceProvider for ZksyncStandardJsonSource { &self, context: &ZkVerificationContext, ) -> Result<(StandardJsonCompilerInput, String)> { - let mut input = foundry_compilers::zksync::project_standard_json_input( + let mut input = foundry_zksync_compilers::compilers::project_standard_json_input( &context.project, &context.target_path, ) diff --git a/crates/zksync/compilers/Cargo.toml b/crates/zksync/compilers/Cargo.toml index 6fc1c412c..0f3af0483 100644 --- a/crates/zksync/compilers/Cargo.toml +++ b/crates/zksync/compilers/Cargo.toml @@ -25,10 +25,11 @@ eyre.workspace = true walkdir.workspace = true reqwest.workspace = true yansi.workspace = true +similar-asserts.workspace = true fd-lock = "4.0.2" fs4 = "0.8.2" dirs = "5" -similar-asserts.workspace = true +path-slash = "0.2" # zk zksync_types.workspace = true diff --git a/crates/zksync/compilers/src/compilers/mod.rs b/crates/zksync/compilers/src/compilers/mod.rs index c7886bf97..a044344d7 100644 --- a/crates/zksync/compilers/src/compilers/mod.rs +++ b/crates/zksync/compilers/src/compilers/mod.rs @@ -1,2 +1,98 @@ pub mod artifact_output; pub mod zksolc; + +use std::path::{Path, PathBuf}; + +use alloy_primitives::map::HashSet; +use artifact_output::zk::ZkArtifactOutput; +use foundry_compilers::{ + error::{Result, SolcError}, + resolver::parse::SolData, + Graph, Project, +}; +use zksolc::{input::StandardJsonCompilerInput, ZkSolcCompiler, ZkSolcSettings}; + +// https://github.com/foundry-rs/compilers/blob/ff2a8d68a0d85d8f40c545a7a948e84d1bc2488e/crates/compilers/src/lib.rs#L165 +// TODO: foundry_compilers only implements this for compilers that impment Into. +// Maybe this works for us or maybe we can submit required changes upstream +pub fn project_standard_json_input( + project: &Project, + target: &Path, +) -> Result { + tracing::debug!(?target, "standard_json_input for zksync"); + let graph = Graph::::resolve(&project.paths)?; + let target_index = graph + .files() + .get(target) + .ok_or_else(|| SolcError::msg(format!("cannot resolve file at {:?}", target.display())))?; + + let mut sources = Vec::new(); + let mut unique_paths = HashSet::new(); + let (path, source) = graph.node(*target_index).unpack(); + unique_paths.insert(path.clone()); + sources.push((path, source)); + sources.extend( + graph + .all_imported_nodes(*target_index) + .map(|index| graph.node(index).unpack()) + .filter(|(p, _)| unique_paths.insert(p.to_path_buf())), + ); + + let root = project.root(); + let sources = sources + .into_iter() + .map(|(path, source)| (rebase_path(root, path), source.clone())) + .collect(); + + let mut zk_solc_settings: ZkSolcSettings = project.settings.clone(); + // strip the path to the project root from all remappings + zk_solc_settings.settings.remappings = project + .paths + .remappings + .clone() + .into_iter() + .map(|r| r.into_relative(project.root()).to_relative_remapping()) + .collect::>(); + + zk_solc_settings.settings.libraries.libs = zk_solc_settings + .settings + .libraries + .libs + .into_iter() + .map(|(f, libs)| (f.strip_prefix(project.root()).unwrap_or(&f).to_path_buf(), libs)) + .collect(); + + let input = StandardJsonCompilerInput::new(sources, zk_solc_settings.settings); + + Ok(input) +} + +// Copied from compilers/lib private method +fn rebase_path(base: &Path, path: &Path) -> PathBuf { + use path_slash::PathExt; + + let mut base_components = base.components(); + let mut path_components = path.components(); + + let mut new_path = PathBuf::new(); + + while let Some(path_component) = path_components.next() { + let base_component = base_components.next(); + + if Some(path_component) != base_component { + if base_component.is_some() { + new_path.extend( + std::iter::repeat(std::path::Component::ParentDir) + .take(base_components.count() + 1), + ); + } + + new_path.push(path_component); + new_path.extend(path_components); + + break; + } + } + + new_path.to_slash_lossy().into_owned().into() +} diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index 7f3d82c39..7218ff60e 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -23,7 +23,7 @@ use alloy_primitives::{address, hex, keccak256, Address, Bytes, U256 as rU256}; use alloy_transport::Transport; use alloy_zksync::{ network::transaction_request::TransactionRequest as ZkTransactionRequest, - provider::ZkyncProvider, + provider::ZksyncProvider, }; use convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}; use eyre::eyre; From 4a006a7ce4a0fdcb8e8e9257218df409e202c0e6 Mon Sep 17 00:00:00 2001 From: elfedy Date: Wed, 18 Dec 2024 09:44:26 -0300 Subject: [PATCH 085/107] some warnings and some test compilation errors --- Cargo.lock | 2 +- Cargo.toml | 3 +- crates/common/src/compile.rs | 1 - crates/config/src/providers/remappings.rs | 5 +- crates/config/src/zksync.rs | 2 +- crates/evm/core/src/backend/mod.rs | 3 +- crates/evm/core/src/opts.rs | 2 +- crates/evm/traces/Cargo.toml | 1 - crates/forge/tests/it/test_helpers.rs | 101 +++++++++--------- crates/zksync/compilers/src/artifacts/mod.rs | 37 +------ .../src/compilers/zksolc/settings.rs | 16 ++- 11 files changed, 77 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0797afcb0..20c0dd5ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,6 +897,7 @@ dependencies = [ [[package]] name = "alloy-zksync" version = "0.8.0" +source = "git+https://github.com/popzxc/alloy-zksync.git?rev=4554f91924efca588ad947c4c9ed2242e93d997d#4554f91924efca588ad947c4c9ed2242e93d997d" dependencies = [ "alloy", "alloy-consensus-any", @@ -5317,7 +5318,6 @@ dependencies = [ "foundry-config", "foundry-evm-core", "foundry-linking", - "foundry-zksync-compilers", "futures 0.3.31", "itertools 0.13.0", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 45aebc6fc..d6c3128b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,8 +230,7 @@ syn-solidity = "0.8.14" alloy-chains = "0.1" alloy-rlp = "0.3" -#alloy-zksync = "0.6.1" -alloy-zksync = { path = "../alloy-zksync/" } +alloy-zksync = { git = "https://github.com/popzxc/alloy-zksync.git", rev = "4554f91924efca588ad947c4c9ed2242e93d997d"} alloy-trie = "0.7.0" diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 3e6f465b9..9a408cdfc 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -6,7 +6,6 @@ use crate::{ term::SpinnerReporter, TestFunctionExt, }; -use alloy_zksync::provider::ZksyncProvider; use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; use eyre::Result; use foundry_block_explorers::contract::Metadata; diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index d96c15038..c941ed2c9 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -1,7 +1,4 @@ -use crate::{ - foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, utils::get_dir_remapping, - Config, -}; +use crate::{foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, Config}; use figment::{ value::{Dict, Map}, Error, Figment, Metadata, Profile, Provider, diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index d69a635e6..af6a6b84d 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -7,7 +7,7 @@ use foundry_compilers::{ use foundry_zksync_compilers::{ artifacts::output_selection::{FileOutputSelection, OutputSelection, OutputSelectionFlag}, compilers::{ - artifact_output::zk::{ZkArtifactOutput, ZkContractArtifact}, + artifact_output::zk::ZkArtifactOutput, zksolc::{ get_solc_version_info, settings::{ diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 6bb89f0c6..d5f3e6885 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -7,10 +7,9 @@ use crate::{ utils::{configure_tx_env, configure_tx_req_env, new_evm_with_inspector}, InspectorExt, }; -use alloy_consensus::Transaction as TransactionTrait; use alloy_genesis::GenesisAccount; use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse}; -use alloy_primitives::{keccak256, map::HashMap, uint, Address, TxKind, B256, U256}; +use alloy_primitives::{keccak256, map::HashMap, uint, Address, B256, U256}; use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; use eyre::Context; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index ad4304f6a..76be5937b 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -6,7 +6,7 @@ use eyre::WrapErr; use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; use foundry_config::{Chain, Config, GasLimit}; use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use std::fmt::Write; use url::Url; diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 8e9e2394c..7fae1414c 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -21,7 +21,6 @@ foundry-compilers.workspace = true foundry-linking.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true -foundry-zksync-compilers.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index bc0b224dd..a239fb87f 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -7,26 +7,21 @@ use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, compilers::multi::MultiCompiler, utils::RuntimeOrHandle, - zksolc::ZkSolcCompiler, - zksync::{ - artifact_output::zk::ZkArtifactOutput, - compile::output::ProjectCompileOutput as ZkProjectCompileOutput, - }, Project, ProjectCompileOutput, SolcConfig, Vyper, }; use foundry_config::{ - fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, - InvariantConfig, RpcEndpoint, RpcEndpoints, -}; -use foundry_evm::{ - constants::CALLER, - opts::{Env, EvmOpts}, + fs_permissions::PathPermission, + zksync::{ZKSYNC_ARTIFACTS_DIR, ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME}, + Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, InvariantConfig, RpcEndpoint, + RpcEndpoints, }; +use foundry_evm::{constants::CALLER, opts::EvmOpts}; use foundry_test_utils::{ fd_lock, init_tracing, rpc::next_rpc_endpoint, util::OutputExt, TestCommand, ZkSyncNode, }; -use foundry_zksync_compiler::{ - DualCompiledContracts, ZKSYNC_ARTIFACTS_DIR, ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME, +use foundry_zksync_compilers::{ + compilers::{artifact_output::zk::ZkArtifactOutput, zksolc::ZkSolcCompiler}, + dual_compiled_contracts::DualCompiledContracts, }; use semver::Version; use std::{ @@ -90,13 +85,9 @@ impl ForgeTestProfile { let mut zk_project = foundry_config::zksync::config_create_project(&zk_config, zk_config.cache, false) .expect("failed creating zksync project"); - zk_project.paths.artifacts = zk_config.root.as_ref().join("zk").join(ZKSYNC_ARTIFACTS_DIR); - zk_project.paths.cache = zk_config - .root - .as_ref() - .join("zk") - .join("cache") - .join(ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME); + zk_project.paths.artifacts = zk_config.root.join("zk").join(ZKSYNC_ARTIFACTS_DIR); + zk_project.paths.cache = + zk_config.root.join("zk").join("cache").join(ZKSYNC_SOLIDITY_FILES_CACHE_FILENAME); zk_project } @@ -153,6 +144,7 @@ impl ForgeTestProfile { failure_persist_file: Some("testfailure".to_string()), show_logs: false, timeout: None, + no_zksync_reserved_addresses: false, }; config.invariant = InvariantConfig { runs: 256, @@ -178,6 +170,7 @@ impl ForgeTestProfile { ), show_metrics: false, timeout: None, + no_zksync_reserved_addresses: false, }; config.sanitized() @@ -216,7 +209,7 @@ pub struct ZkTestData { pub zk_config: Config, pub zk_project: ZkProject, pub output: ProjectCompileOutput, - pub zk_output: ZkProjectCompileOutput, + pub zk_output: ProjectCompileOutput, } /// Container for test data for a specific test profile. @@ -234,7 +227,6 @@ impl ForgeTestData { /// Uses [get_compiled] to lazily compile the project. pub fn new(profile: ForgeTestProfile) -> Self { init_tracing(); -<<<<<<< HEAD // NOTE(zk): We need to manually install the crypto provider as zksync-era uses `aws-lc-rs` // provider, while foundry uses the `ring` provider. As a result, rustls cannot @@ -309,7 +301,14 @@ impl ForgeTestData { builder .enable_isolation(opts.isolate) .sender(config.sender) - .build::(root, &self.output, opts.local_evm_env(), opts) + .build::( + root, + &self.output, + None, + opts.local_evm_env(), + opts, + DualCompiledContracts::default(), + ) .unwrap() } @@ -323,7 +322,7 @@ impl ForgeTestData { zk_config.prompt_timeout = 0; let root = self.zk_test_data.zk_project.root(); - let mut opts = self.evm_opts.clone(); + let mut opts = config_evm_opts(&zk_config); if zk_config.isolate { opts.isolate = true; @@ -333,8 +332,6 @@ impl ForgeTestData { let output = self.zk_test_data.output.clone(); let zk_output = self.zk_test_data.zk_output.clone(); let dual_compiled_contracts = self.zk_test_data.dual_compiled_contracts.clone(); - let mut test_opts = self.test_opts.clone(); - test_opts.fuzz.no_zksync_reserved_addresses = zk_config.fuzz.no_zksync_reserved_addresses; let sender = zk_config.sender; let mut builder = self.base_runner(); @@ -342,8 +339,14 @@ impl ForgeTestData { builder .enable_isolation(opts.isolate) .sender(sender) - .with_test_options(test_opts) - .build(root, output, Some(zk_output), env, opts, dual_compiled_contracts) + .build::( + root, + &output, + Some(zk_output), + env, + opts, + dual_compiled_contracts, + ) .unwrap() } @@ -352,7 +355,14 @@ impl ForgeTestData { let mut opts = config_evm_opts(&self.config); opts.verbosity = 5; self.base_runner() - .build::(self.project.root(), &self.output, opts.local_evm_env(), opts) + .build::( + self.project.root(), + &self.output, + None, + opts.local_evm_env(), + opts, + DualCompiledContracts::default(), + ) .unwrap() } @@ -368,7 +378,14 @@ impl ForgeTestData { self.base_runner() .with_fork(fork) - .build::(self.project.root(), &self.output, env, opts) + .build::( + self.project.root(), + &self.output, + None, + env, + opts, + DualCompiledContracts::default(), + ) .unwrap() } } @@ -444,7 +461,9 @@ pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput { out } -pub fn get_zk_compiled(zk_project: &ZkProject) -> ZkProjectCompileOutput { +pub fn get_zk_compiled( + zk_project: &ZkProject, +) -> ProjectCompileOutput { let lock_file_path = zk_project.sources_path().join(".lock-zk"); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. @@ -468,7 +487,8 @@ pub fn get_zk_compiled(zk_project: &ZkProject) -> ZkProjectCompileOutput { write.write_all(b"1").unwrap(); } - let out: ZkProjectCompileOutput = out.expect("failed compiling zksync project"); + let out: ProjectCompileOutput = + out.expect("failed compiling zksync project"); if let Some(ref mut write) = write { write.write_all(b"1").unwrap(); @@ -476,23 +496,6 @@ pub fn get_zk_compiled(zk_project: &ZkProject) -> ZkProjectCompileOutput { out } -pub static EVM_OPTS: LazyLock = LazyLock::new(|| EvmOpts { - env: Env { - gas_limit: u64::MAX, - chain_id: None, - tx_origin: Config::DEFAULT_SENDER, - block_number: 1, - block_timestamp: 1, - ..Default::default() - }, - sender: Config::DEFAULT_SENDER, - initial_balance: U256::MAX, - ffi: true, - verbosity: 3, - memory_limit: 1 << 26, - ..Default::default() -}); - /// Default data for the tests group. pub static TEST_DATA_DEFAULT: LazyLock = LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::Default)); diff --git a/crates/zksync/compilers/src/artifacts/mod.rs b/crates/zksync/compilers/src/artifacts/mod.rs index edcda9550..41cd7e72d 100644 --- a/crates/zksync/compilers/src/artifacts/mod.rs +++ b/crates/zksync/compilers/src/artifacts/mod.rs @@ -1,3 +1,4 @@ +/// ZKsolc compiler artifacts to be used in foundry-compilers use foundry_compilers_artifacts_solc::{ BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles, }; @@ -21,10 +22,13 @@ pub type Contracts = FileToContractsMap; /// Output type `zksolc` produces #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] pub struct CompilerOutput { + /// `zksolc` compiler errors #[serde(default, skip_serializing_if = "Vec::is_empty")] pub errors: Vec, + /// sources that have been compiled #[serde(default)] pub sources: BTreeMap, + /// compiled contracts #[serde(default)] pub contracts: FileToContractsMap, /// The `solc` compiler version. @@ -53,39 +57,6 @@ impl CompilerOutput { pub fn split(self) -> (SourceFiles, OutputContracts) { (SourceFiles(self.sources), OutputContracts(self.contracts)) } - - /// Retains only those files the given iterator yields - /// - /// In other words, removes all contracts for files not included in the iterator - pub fn retain_files<'a, I>(&mut self, files: I) - where - I: IntoIterator, - { - // Note: use `to_lowercase` here because solc not necessarily emits the exact file name, - // e.g. `src/utils/upgradeProxy.sol` is emitted as `src/utils/UpgradeProxy.sol` - let files: HashSet<_> = - files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect(); - self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase())); - self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase())); - } - - pub fn merge(&mut self, other: Self) { - self.errors.extend(other.errors); - self.contracts.extend(other.contracts); - self.sources.extend(other.sources); - } - - pub fn join_all(&mut self, root: impl AsRef) { - let root = root.as_ref(); - self.contracts = std::mem::take(&mut self.contracts) - .into_iter() - .map(|(path, contracts)| (root.join(path), contracts)) - .collect(); - self.sources = std::mem::take(&mut self.sources) - .into_iter() - .map(|(path, source)| (root.join(path), source)) - .collect(); - } } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] diff --git a/crates/zksync/compilers/src/compilers/zksolc/settings.rs b/crates/zksync/compilers/src/compilers/zksolc/settings.rs index ce7a9c2f8..c79aaff99 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/settings.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/settings.rs @@ -80,6 +80,7 @@ pub struct ZkSettings { pub codegen: Codegen, // TODO: era-compiler-solidity uses a BTreeSet of strings. In theory the serialization // should be the same but maybe we should double check + /// Solidity remappings #[serde(default, skip_serializing_if = "Vec::is_empty")] pub remappings: Vec, #[serde( @@ -87,6 +88,7 @@ pub struct ZkSettings { with = "serde_helpers::display_from_str_opt", skip_serializing_if = "Option::is_none" )] + /// EVM version pub evm_version: Option, // check if the same (and use `compilers version`) @@ -98,11 +100,13 @@ pub struct ZkSettings { pub output_selection: ZkOutputSelection, #[serde(default)] + /// Optimizer options pub optimizer: Optimizer, /// Metadata settings #[serde(default, skip_serializing_if = "Option::is_none")] pub metadata: Option, #[serde(default)] + /// Libraries pub libraries: Libraries, /// Switch to missing deployable libraries detection mode. /// Contracts are not compiled in this mode, and all compilation artifacts are not included. @@ -127,7 +131,7 @@ pub struct ZkSettings { pub suppressed_errors: HashSet, } -// Analogous to SolcSettings for Zk compiler +/// Analogous to SolcSettings for zksolc compiler #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ZkSolcSettings { @@ -158,6 +162,7 @@ impl ZkSettings { } } + /// Removes prefix from all paths pub fn strip_prefix(&mut self, base: impl AsRef) { let base = base.as_ref(); self.remappings.iter_mut().for_each(|r| { @@ -210,6 +215,7 @@ impl Default for ZkSettings { } #[derive(Debug, Clone, Copy, Default)] +/// Restrictions for zksolc pub struct ZkSolcRestrictions(); impl CompilerSettingsRestrictions for ZkSolcRestrictions { @@ -292,8 +298,10 @@ impl CompilerSettings for ZkSolcSettings { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// Optimizer settings pub struct Optimizer { // TODO: does this have to be an option? + /// Enable the optimizer #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, /// Switch optimizer components on or off in detail. @@ -301,6 +309,7 @@ pub struct Optimizer { /// tweaked here. If "details" is given, "enabled" can be omitted. #[serde(default, skip_serializing_if = "Option::is_none")] pub details: Option, + /// Optimizer mode #[serde(default, skip_serializing_if = "Option::is_none")] pub mode: Option, /// Whether to try to recompile with -Oz if the bytecode is too large. @@ -315,10 +324,12 @@ pub struct Optimizer { } impl Optimizer { + /// Disable optimizer pub fn disable(&mut self) { self.enabled.take(); } + /// Enable optimizer pub fn enable(&mut self) { self.enabled = Some(true) } @@ -337,6 +348,7 @@ impl Default for Optimizer { } } +/// Optimizer details #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct OptimizerDetails { @@ -380,6 +392,7 @@ impl OptimizerDetails { } } +/// Settings metadata #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SettingsMetadata { /// Use the given hash method for the metadata hash that is appended to the bytecode. @@ -395,6 +408,7 @@ pub struct SettingsMetadata { } impl SettingsMetadata { + /// New SettingsMetadata pub fn new(hash: BytecodeHash) -> Self { Self { bytecode_hash: Some(hash) } } From c259f1530d9041343ed1442f0a1ba9ffc5746602 Mon Sep 17 00:00:00 2001 From: elfedy Date: Wed, 18 Dec 2024 09:53:58 -0300 Subject: [PATCH 086/107] adapt to zk_env --- crates/cheatcodes/src/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 744287ce0..6c72f4167 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -126,6 +126,7 @@ impl CheatsConfig { self.running_version.clone(), self.dual_compiled_contracts.clone(), self.use_zk, + self.zk_env.clone(), ) } From 72f1520f9b54584eb388345428b3eefcdc6a0839 Mon Sep 17 00:00:00 2001 From: elfedy Date: Wed, 18 Dec 2024 10:59:17 -0300 Subject: [PATCH 087/107] testing wip --- crates/forge/tests/it/zk/repros.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/forge/tests/it/zk/repros.rs b/crates/forge/tests/it/zk/repros.rs index 3221ef683..edbdf0c52 100644 --- a/crates/forge/tests/it/zk/repros.rs +++ b/crates/forge/tests/it/zk/repros.rs @@ -12,16 +12,12 @@ use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; // zk-specific repros configuration -async fn repro_config( - issue: usize, - should_fail: bool, - sender: Option
, - test_data: &ForgeTestData, -) -> TestConfig { +async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { foundry_test_utils::init_tracing(); let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); - let mut config = test_data.config.clone(); + let test_data = &TEST_DATA_DEFAULT; + let mut config = test_data.clone().config.to_owned(); config.fs_permissions = FsPermissions::new(vec![ PathPermission::read("./fixtures/zk"), PathPermission::read("zkout"), From 9e37759e6bb117ef060696e9536aaf91ceccfc1d Mon Sep 17 00:00:00 2001 From: elfedy Date: Wed, 18 Dec 2024 14:39:09 -0300 Subject: [PATCH 088/107] make tests compile --- crates/forge/tests/cli/build.rs | 6 +++-- crates/forge/tests/it/zk/basic.rs | 6 ++--- crates/forge/tests/it/zk/cheats.rs | 32 ++++++++++++------------ crates/forge/tests/it/zk/constructor.rs | 2 +- crates/forge/tests/it/zk/contracts.rs | 16 ++++++------ crates/forge/tests/it/zk/factory.rs | 6 ++--- crates/forge/tests/it/zk/factory_deps.rs | 2 +- crates/forge/tests/it/zk/fork.rs | 4 +-- crates/forge/tests/it/zk/fuzz.rs | 2 +- crates/forge/tests/it/zk/invariant.rs | 13 +++++++--- crates/forge/tests/it/zk/logs.rs | 4 +-- crates/forge/tests/it/zk/nonce.rs | 2 +- crates/forge/tests/it/zk/ownership.rs | 4 +-- crates/forge/tests/it/zk/repros.rs | 21 ++++++++-------- crates/forge/tests/it/zk/traces.rs | 4 +-- 15 files changed, 66 insertions(+), 58 deletions(-) diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 840e591e5..76dd718c6 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -200,8 +200,10 @@ contract ValidContract {} ) .unwrap(); - let config = - Config { skip: vec![Glob::new("src/InvalidContract.sol").unwrap()], ..Default::default() }; + let config = Config { + skip: vec![Glob::new("src/InvalidContract.sol").unwrap().into()], + ..Default::default() + }; prj.write_config(config); cmd.args(["build"]).assert_success(); diff --git a/crates/forge/tests/it/zk/basic.rs b/crates/forge/tests/it/zk/basic.rs index 62c82b792..b2f3cdd26 100644 --- a/crates/forge/tests/it/zk/basic.rs +++ b/crates/forge/tests/it/zk/basic.rs @@ -10,7 +10,7 @@ async fn test_zk_block_information_is_consistent() { let filter = Filter::new("testZkBasicBlockNumber|testZkBasicBlockTimestamp", "ZkBasicTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -18,7 +18,7 @@ async fn test_zk_address_balance_is_consistent() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkBasicAddressBalance", "ZkBasicTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -30,5 +30,5 @@ async fn test_zk_propagated_block_env_is_consistent() { ".*", ); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/zk/cheats.rs b/crates/forge/tests/it/zk/cheats.rs index b76c98bc1..eccf45401 100644 --- a/crates/forge/tests/it/zk/cheats.rs +++ b/crates/forge/tests/it/zk/cheats.rs @@ -15,7 +15,7 @@ async fn test_zk_cheat_roll_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkCheatcodesRoll", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -26,7 +26,7 @@ async fn test_zk_cheat_get_code() { let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(zk_config); let filter = Filter::new("testZkCheatcodesGetCode", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -34,7 +34,7 @@ async fn test_zk_cheat_warp_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkCheatcodesWarp", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -42,7 +42,7 @@ async fn test_zk_cheat_deal_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkCheatcodesDeal", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -50,7 +50,7 @@ async fn test_zk_cheat_set_nonce_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkCheatcodesSetNonce", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -60,7 +60,7 @@ async fn test_zk_cheat_etch_works() { let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(zk_config); let filter = Filter::new("testZkCheatcodesEtch", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -68,7 +68,7 @@ async fn test_zk_cheat_record_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testRecord", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -76,7 +76,7 @@ async fn test_zk_cheat_expect_emit_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testExpectEmit", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -84,7 +84,7 @@ async fn test_zk_cheat_mock_with_value_function() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkCheatcodesValueFunctionMockReturn", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -96,7 +96,7 @@ async fn test_zk_cheat_mock_calls() { ".*", ); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -104,14 +104,14 @@ async fn test_zk_cheat_works_after_fork() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkCheatcodesCanBeUsedAfterFork", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] async fn test_zk_eravm_force_return_feature() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new(".*", "ZkRetTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -119,7 +119,7 @@ async fn test_zk_can_mock_modifiers() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new(".*", "MockedModifierTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -127,7 +127,7 @@ async fn test_zk_record_logs() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("RecordLogs", "ZkCheatcodesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -135,7 +135,7 @@ async fn test_zk_cheatcodes_in_zkvm() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new(".*", "ZkCheatcodesInZkVmTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -143,7 +143,7 @@ async fn test_zk_zk_vm_skip_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new(".*", "ZkCheatcodeZkVmSkipTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } forgetest_async!(test_zk_use_factory_dep, |prj, cmd| { diff --git a/crates/forge/tests/it/zk/constructor.rs b/crates/forge/tests/it/zk/constructor.rs index 508e6f8bf..0cb078f3b 100644 --- a/crates/forge/tests/it/zk/constructor.rs +++ b/crates/forge/tests/it/zk/constructor.rs @@ -12,7 +12,7 @@ async fn test_zk_constructor_works() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkConstructor", "ZkConstructorTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } forgetest_async!(test_zk_constructor_works_in_script, |prj, cmd| { diff --git a/crates/forge/tests/it/zk/contracts.rs b/crates/forge/tests/it/zk/contracts.rs index 891e5849f..9dd70150a 100644 --- a/crates/forge/tests/it/zk/contracts.rs +++ b/crates/forge/tests/it/zk/contracts.rs @@ -17,7 +17,7 @@ async fn test_zk_contract_can_call_function() { ".*", ); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -25,7 +25,7 @@ async fn test_zk_contract_persisted_contracts_after_fork() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkContractsPersistedDeployedContractNoArgs|testZkContractsPersistedDeployedContractArgs", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -33,7 +33,7 @@ async fn test_zk_contract_deployment() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkContractsInlineDeployedContractNoArgs|testZkContractsInlineDeployedContractComplexArgs", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -42,7 +42,7 @@ async fn test_zk_contract_deployment_balance() { let filter = Filter::new("testZkContractsInlineDeployedContractBalance", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -50,7 +50,7 @@ async fn test_zk_contract_deployment_balance_transfer() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkContractsExpectedBalances", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -87,7 +87,7 @@ async fn test_zk_contract_can_call_system_contracts() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkContractsCallSystemContract", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -95,7 +95,7 @@ async fn test_zk_contract_deployed_in_setup_can_be_mocked() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkContractsDeployedInSetupAreMockable", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -103,5 +103,5 @@ async fn test_zk_contract_static_calls_keep_nonce_consistent() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkStaticCalls", "ZkContractsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/zk/factory.rs b/crates/forge/tests/it/zk/factory.rs index 58caefe3d..001d72418 100644 --- a/crates/forge/tests/it/zk/factory.rs +++ b/crates/forge/tests/it/zk/factory.rs @@ -13,7 +13,7 @@ async fn test_zk_can_deploy_in_method() { let runner = TEST_DATA_DEFAULT.runner_zksync(); { let filter = Filter::new("testClassicFactory|testNestedFactory", "ZkFactoryTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } } @@ -26,7 +26,7 @@ async fn test_zk_can_deploy_in_constructor() { "ZkFactoryTest", ".*", ); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } } @@ -35,7 +35,7 @@ async fn test_zk_can_use_predeployed_factory() { let runner = TEST_DATA_DEFAULT.runner_zksync(); { let filter = Filter::new("testUser.*", "ZkFactoryTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } } diff --git a/crates/forge/tests/it/zk/factory_deps.rs b/crates/forge/tests/it/zk/factory_deps.rs index 9fce45f85..be569fc1d 100644 --- a/crates/forge/tests/it/zk/factory_deps.rs +++ b/crates/forge/tests/it/zk/factory_deps.rs @@ -15,7 +15,7 @@ async fn test_zk_can_deploy_large_factory_deps() { let runner = TEST_DATA_DEFAULT.runner_zksync(); { let filter = Filter::new(".*", "ZkLargeFactoryDependenciesTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } } diff --git a/crates/forge/tests/it/zk/fork.rs b/crates/forge/tests/it/zk/fork.rs index e8477753e..3e91e6b54 100644 --- a/crates/forge/tests/it/zk/fork.rs +++ b/crates/forge/tests/it/zk/fork.rs @@ -10,7 +10,7 @@ async fn test_zk_setup_fork_failure() { let filter = Filter::new("testFail_ZkSetupForkFailureExecutesTest", "ZkSetupForkFailureTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -18,5 +18,5 @@ async fn test_zk_immutable_vars_persist_after_fork() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new(".*", "ZkForkImmutableVarsTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/zk/fuzz.rs b/crates/forge/tests/it/zk/fuzz.rs index e75dcb9ff..0203a3aa3 100644 --- a/crates/forge/tests/it/zk/fuzz.rs +++ b/crates/forge/tests/it/zk/fuzz.rs @@ -9,5 +9,5 @@ async fn test_zk_fuzz_avoid_system_addresses() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkFuzzAvoidSystemAddresses", "ZkFuzzTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index eb7bb23db..308ca0d86 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -1,7 +1,10 @@ //! Invariant tests +use std::sync::Arc; + use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::revm::primitives::SpecId; +use foundry_config::Config; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] @@ -9,11 +12,13 @@ async fn test_zk_invariant_deposit() { let mut runner = TEST_DATA_DEFAULT.runner_zksync(); // FIXME: just use the inline config - runner.test_options.invariant.no_zksync_reserved_addresses = true; - runner.test_options.invariant.fail_on_revert = true; - runner.test_options.invariant.runs = 10; + let mut config = Config::clone(&runner.config); + config.invariant.no_zksync_reserved_addresses = true; + config.invariant.fail_on_revert = true; + config.invariant.runs = 10; + runner.config = Arc::new(config); let filter = Filter::new(".*", "ZkInvariantTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/zk/logs.rs b/crates/forge/tests/it/zk/logs.rs index ce0036457..498183aad 100644 --- a/crates/forge/tests/it/zk/logs.rs +++ b/crates/forge/tests/it/zk/logs.rs @@ -11,7 +11,7 @@ async fn test_zk_logs_work_in_call() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkConsoleOutputDuringCall", "ZkConsoleTest", ".*"); - let results = TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).test(); + let results = TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).test(); assert_multiple( &results, BTreeMap::from([( @@ -39,7 +39,7 @@ async fn test_zk_logs_work_in_create() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkConsoleOutputDuringCreate", "ZkConsoleTest", ".*"); - let results = TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).test(); + let results = TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).test(); assert_multiple( &results, BTreeMap::from([( diff --git a/crates/forge/tests/it/zk/nonce.rs b/crates/forge/tests/it/zk/nonce.rs index e0d1dd5d8..57870d5df 100644 --- a/crates/forge/tests/it/zk/nonce.rs +++ b/crates/forge/tests/it/zk/nonce.rs @@ -23,7 +23,7 @@ async fn test_zk_contract_nonce_mismatch() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testTxOriginNonceDoesNotUpdate", "NonceMismatchTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } fn setup_deploy_prj(prj: &mut TestProject) { diff --git a/crates/forge/tests/it/zk/ownership.rs b/crates/forge/tests/it/zk/ownership.rs index 924693285..d3149c752 100644 --- a/crates/forge/tests/it/zk/ownership.rs +++ b/crates/forge/tests/it/zk/ownership.rs @@ -8,7 +8,7 @@ async fn test_zk_ownership() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkOwnership", "ZkOwnershipTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } #[tokio::test(flavor = "multi_thread")] @@ -16,5 +16,5 @@ async fn test_zk_ownership_delegate_call() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkOwnershipDelegateCall", "ZkOwnershipTest", ".*"); - TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/zk/repros.rs b/crates/forge/tests/it/zk/repros.rs index edbdf0c52..4d5d3c145 100644 --- a/crates/forge/tests/it/zk/repros.rs +++ b/crates/forge/tests/it/zk/repros.rs @@ -2,13 +2,11 @@ //! //! Issue list: https://github.com/matter-labs/foundry-zksync/issues -use crate::{ - config::*, - repros::test_repro, - test_helpers::{ForgeTestData, TEST_DATA_DEFAULT}, -}; +use std::sync::Arc; + +use crate::{config::*, repros::test_repro, test_helpers::TEST_DATA_DEFAULT}; use alloy_primitives::Address; -use foundry_config::{fs_permissions::PathPermission, FsPermissions}; +use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; use foundry_test_utils::Filter; // zk-specific repros configuration @@ -17,7 +15,7 @@ async fn repro_config(issue: usize, should_fail: bool, sender: Option
) let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); let test_data = &TEST_DATA_DEFAULT; - let mut config = test_data.clone().config.to_owned(); + let mut config = Config::clone(&test_data.config); config.fs_permissions = FsPermissions::new(vec![ PathPermission::read("./fixtures/zk"), PathPermission::read("zkout"), @@ -35,9 +33,12 @@ test_repro!(497); test_repro!(565; |cfg| { // FIXME: just use the inline config - cfg.runner.test_options.invariant.no_zksync_reserved_addresses = true; - cfg.runner.test_options.invariant.fail_on_revert = true; - cfg.runner.test_options.invariant.runs = 2; + let mut config = Config::clone(&cfg.runner.config); + config.invariant.no_zksync_reserved_addresses = true; + config.invariant.fail_on_revert = true; + config.invariant.runs = 2; + + cfg.runner.config = Arc::new(config); }); // https://github.com/matter-labs/foundry-zksync/issues/687 diff --git a/crates/forge/tests/it/zk/traces.rs b/crates/forge/tests/it/zk/traces.rs index e02d3cc6f..fc6afab85 100644 --- a/crates/forge/tests/it/zk/traces.rs +++ b/crates/forge/tests/it/zk/traces.rs @@ -66,7 +66,7 @@ async fn test_zk_traces_work_during_call() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkTraceOutputDuringCall", "ZkTraceTest", ".*"); - let results = TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).test(); + let results = TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).test(); let traces = &results["zk/Trace.t.sol:ZkTraceTest"].test_results["testZkTraceOutputDuringCall()"].traces; @@ -160,7 +160,7 @@ async fn test_zk_traces_work_during_create() { let runner = TEST_DATA_DEFAULT.runner_zksync(); let filter = Filter::new("testZkTraceOutputDuringCreate", "ZkTraceTest", ".*"); - let results = TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).test(); + let results = TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).test(); let traces = results["zk/Trace.t.sol:ZkTraceTest"].test_results ["testZkTraceOutputDuringCreate()"] .traces From 94d263f63b8409df4b19fe3cf9fbe7dea4ab9843 Mon Sep 17 00:00:00 2001 From: elfedy Date: Wed, 18 Dec 2024 15:42:09 -0300 Subject: [PATCH 089/107] fix most clippy warnings --- crates/config/src/zksync.rs | 2 +- crates/evm/coverage/src/lib.rs | 3 --- crates/forge/src/multi_runner.rs | 2 +- crates/zksync/compilers/src/artifacts/contract.rs | 7 +++++++ crates/zksync/compilers/src/artifacts/error.rs | 1 + crates/zksync/compilers/src/artifacts/mod.rs | 10 +++++----- .../compilers/src/artifacts/output_selection.rs | 2 ++ .../src/compilers/artifact_output/mod.rs | 1 + .../compilers/src/compilers/artifact_output/zk.rs | 14 ++++++++++++++ .../src/compilers/artifact_output/zk/bytecode.rs | 4 ++++ crates/zksync/compilers/src/compilers/mod.rs | 2 ++ .../compilers/src/compilers/zksolc/input.rs | 15 +++++++++++++++ .../zksync/compilers/src/compilers/zksolc/mod.rs | 13 +++++++++++++ .../compilers/src/compilers/zksolc/settings.rs | 1 + 14 files changed, 67 insertions(+), 10 deletions(-) diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index af6a6b84d..56ce76072 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -210,7 +210,7 @@ pub fn config_create_project( ZkSolc::blocking_install(&default_version)?; zksolc = ZkSolc::find_installed_version(&default_version)?; } - zksolc.unwrap_or_else(|| panic!("Could not install zksolc v{}", default_version)) + zksolc.unwrap_or_else(|| panic!("Could not install zksolc v{default_version}")) } else { "zksolc".into() }; diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index c55fec62f..52ec329ad 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -5,9 +5,6 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#[macro_use] -extern crate foundry_common; - #[macro_use] extern crate tracing; diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index a1c9ab633..5da39b005 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -598,7 +598,7 @@ impl MultiContractRunnerBuilder { coverage: self.coverage, debug: self.debug, decode_internal: self.decode_internal, - inline_config: Arc::new(InlineConfig::new_parsed(&output, &self.config)?), + inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, odyssey: self.odyssey, diff --git a/crates/zksync/compilers/src/artifacts/contract.rs b/crates/zksync/compilers/src/artifacts/contract.rs index 04d1f4243..7ec459da9 100644 --- a/crates/zksync/compilers/src/artifacts/contract.rs +++ b/crates/zksync/compilers/src/artifacts/contract.rs @@ -12,11 +12,15 @@ use std::{borrow::Cow, collections::BTreeMap}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Contract { + /// The contract abi pub abi: Option, + /// The contract metadata #[serde(default, skip_serializing_if = "Option::is_none")] pub metadata: Option, + /// The contract userdoc #[serde(default)] pub userdoc: UserDoc, + /// The contract devdoc #[serde(default)] pub devdoc: DevDoc, /// The contract optimized IR code. @@ -44,10 +48,12 @@ fn storage_layout_is_empty(storage_layout: &StorageLayout) -> bool { } impl Contract { + /// Returns true if contract is not linked pub fn is_unlinked(&self) -> bool { self.hash.is_none() || !self.missing_libraries.is_empty() } + /// takes missing libraries output and transforms into link references pub fn missing_libs_to_link_references( missing_libraries: &[String], ) -> BTreeMap>> { @@ -72,6 +78,7 @@ impl Contract { Self::missing_libs_to_link_references(self.missing_libraries.as_slice()) } + /// Get bytecode pub fn bytecode(&self) -> Option { self.eravm.as_ref().and_then(|eravm| eravm.bytecode(self.is_unlinked())).map(|object| { let mut bytecode: Bytecode = object.into(); diff --git a/crates/zksync/compilers/src/artifacts/error.rs b/crates/zksync/compilers/src/artifacts/error.rs index 72c5a40a9..6097518a6 100644 --- a/crates/zksync/compilers/src/artifacts/error.rs +++ b/crates/zksync/compilers/src/artifacts/error.rs @@ -1,3 +1,4 @@ +//! zksolc error from std json output use foundry_compilers_artifacts_solc::error::{Severity, SourceLocation}; use foundry_compilers_artifacts_solc::serde_helpers; diff --git a/crates/zksync/compilers/src/artifacts/mod.rs b/crates/zksync/compilers/src/artifacts/mod.rs index 41cd7e72d..d21befa2d 100644 --- a/crates/zksync/compilers/src/artifacts/mod.rs +++ b/crates/zksync/compilers/src/artifacts/mod.rs @@ -1,14 +1,11 @@ -/// ZKsolc compiler artifacts to be used in foundry-compilers +//! zksolc artifacts to be used in `foundry-compilers` use foundry_compilers_artifacts_solc::{ BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles, }; use semver::Version; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashSet}, - path::{Path, PathBuf}, -}; +use std::{collections::BTreeMap, path::PathBuf}; pub mod contract; pub mod error; @@ -59,6 +56,7 @@ impl CompilerOutput { } } +/// `zksolc` eravm output field #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct EraVM { @@ -72,6 +70,7 @@ pub struct EraVM { } impl EraVM { + /// Get bytecode object pub fn bytecode(&self, should_be_unlinked: bool) -> Option { self.bytecode.as_ref().map(|object| match (should_be_unlinked, object) { (true, BytecodeObject::Bytecode(bc)) => { @@ -88,6 +87,7 @@ impl EraVM { }) } + /// Get bytecode object ref // TODO: tmp to make compiler abstraction sample work, needs some thought on // how do transform linked/to unlinked pub fn bytecode_ref(&self) -> Option<&BytecodeObject> { diff --git a/crates/zksync/compilers/src/artifacts/output_selection.rs b/crates/zksync/compilers/src/artifacts/output_selection.rs index e05b27515..7db51d0fe 100644 --- a/crates/zksync/compilers/src/artifacts/output_selection.rs +++ b/crates/zksync/compilers/src/artifacts/output_selection.rs @@ -1,3 +1,4 @@ +//! zksolc output selection use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -11,6 +12,7 @@ pub struct OutputSelection { pub all: FileOutputSelection, } +/// The `solc --standard-json` expected output selection value. #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct FileOutputSelection { /// The per-file output selections. diff --git a/crates/zksync/compilers/src/compilers/artifact_output/mod.rs b/crates/zksync/compilers/src/compilers/artifact_output/mod.rs index fc4887264..6dc8fa538 100644 --- a/crates/zksync/compilers/src/compilers/artifact_output/mod.rs +++ b/crates/zksync/compilers/src/compilers/artifact_output/mod.rs @@ -1 +1,2 @@ +//! `foundry-compilers` `ArtifactOutput` implementations for ZK Sync pub mod zk; diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs index b28f4b0e3..58409e44f 100644 --- a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs @@ -1,3 +1,4 @@ +//! ZK Sync artifact output use crate::artifacts::contract::Contract; use alloy_json_abi::JsonAbi; use foundry_compilers::{ @@ -15,26 +16,37 @@ use std::{borrow::Cow, collections::BTreeMap, path::Path}; mod bytecode; pub use bytecode::ZkArtifactBytecode; +/// Artifact representing a compiled contract #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ZkContractArtifact { + /// contract abi pub abi: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + /// contract bytecodee pub bytecode: Option, + /// contract assembly #[serde(default, skip_serializing_if = "Option::is_none")] pub assembly: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + /// contract metadata pub metadata: Option, + /// contract storage layout #[serde(default, skip_serializing_if = "Option::is_none")] pub storage_layout: Option, + /// contract userdoc #[serde(default, skip_serializing_if = "Option::is_none")] pub userdoc: Option, + /// contract devdoc #[serde(default, skip_serializing_if = "Option::is_none")] pub devdoc: Option, + /// contract optimized IR code #[serde(default, skip_serializing_if = "Option::is_none")] pub ir_optimized: Option, + /// contract hash #[serde(default, skip_serializing_if = "Option::is_none")] pub hash: Option, + /// contract factory dependencies #[serde(default, skip_serializing_if = "Option::is_none")] pub factory_dependencies: Option>, /// The identifier of the source file @@ -43,6 +55,7 @@ pub struct ZkContractArtifact { } impl ZkContractArtifact { + /// Get contract missing libraries pub fn missing_libraries(&self) -> Option<&Vec> { self.bytecode.as_ref().map(|bc| &bc.missing_libraries) } @@ -90,6 +103,7 @@ impl From for CompactContract { } } +/// ZK Sync ArtifactOutput #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] pub struct ZkArtifactOutput(); diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs index 19c2045bf..ef0473c74 100644 --- a/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs @@ -7,16 +7,19 @@ use foundry_compilers_artifacts_solc::{ }; use serde::{Deserialize, Serialize}; +/// Bytecode compiled by zksolc #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct ZkArtifactBytecode { object: Bytes, is_unlinked: bool, + /// Bytecode missing libraries #[serde(default)] pub missing_libraries: Vec, } impl ZkArtifactBytecode { + /// Get Bytecode from parts pub fn with_object( object: BytecodeObject, is_unlinked: bool, @@ -35,6 +38,7 @@ impl ZkArtifactBytecode { Contract::missing_libs_to_link_references(self.missing_libraries.as_slice()) } + /// Get bytecode object pub fn object(&self) -> BytecodeObject { if self.is_unlinked { // convert to unlinked diff --git a/crates/zksync/compilers/src/compilers/mod.rs b/crates/zksync/compilers/src/compilers/mod.rs index a044344d7..7a847ea64 100644 --- a/crates/zksync/compilers/src/compilers/mod.rs +++ b/crates/zksync/compilers/src/compilers/mod.rs @@ -1,3 +1,4 @@ +//! `foundry-compilers` core trait implementations and overrides for ZK Sync pub mod artifact_output; pub mod zksolc; @@ -12,6 +13,7 @@ use foundry_compilers::{ }; use zksolc::{input::StandardJsonCompilerInput, ZkSolcCompiler, ZkSolcSettings}; +/// zksolc specific standard_json_input to be used in verification // https://github.com/foundry-rs/compilers/blob/ff2a8d68a0d85d8f40c545a7a948e84d1bc2488e/crates/compilers/src/lib.rs#L165 // TODO: foundry_compilers only implements this for compilers that impment Into. // Maybe this works for us or maybe we can submit required changes upstream diff --git a/crates/zksync/compilers/src/compilers/zksolc/input.rs b/crates/zksync/compilers/src/compilers/zksolc/input.rs index fe8de50c6..3172e518c 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/input.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/input.rs @@ -1,3 +1,4 @@ +//! zksolc input use super::{ settings::{ZkSolcError, ZkSolcSettings, ZkSolcWarning}, ZkSettings, @@ -16,11 +17,15 @@ use std::{ }; use tracing::warn; +/// Versioned input for zksolc #[derive(Debug, Clone, Serialize)] pub struct ZkSolcVersionedInput { + /// zksolc json input #[serde(flatten)] pub input: ZkSolcInput, + /// solc version to be used along zksolc pub solc_version: Version, + /// zksolc cli settings pub cli_settings: solc::CliSettings, } @@ -72,14 +77,19 @@ impl CompilerInput for ZkSolcVersionedInput { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ZkSolcInput { + /// source code language pub language: SolcLanguage, + /// sources to compile pub sources: Sources, + /// compiler settings set by the user pub settings: ZkSettings, + /// suppressed warnings // For `zksolc` versions <1.5.7, suppressed warnings / errors were specified on the same level // as `settings`. For `zksolc` 1.5.7+, they are specified inside `settings`. Since we want to // support both options at the time, we duplicate fields from `settings` here. #[serde(default, skip_serializing_if = "HashSet::is_empty")] pub suppressed_warnings: HashSet, + /// suppressed errors #[serde(default, skip_serializing_if = "HashSet::is_empty")] pub suppressed_errors: HashSet, } @@ -125,6 +135,7 @@ impl ZkSolcInput { self } + /// Add remappings to settings pub fn with_remappings(mut self, remappings: Vec) -> Self { if self.language == SolcLanguage::Yul { if !remappings.is_empty() { @@ -146,13 +157,17 @@ impl ZkSolcInput { /// the verified contracts #[derive(Clone, Debug, Serialize, Deserialize)] pub struct StandardJsonCompilerInput { + /// compiler language pub language: SolcLanguage, + /// sources to compile #[serde(with = "serde_helpers::tuple_vec_map")] pub sources: Vec<(PathBuf, Source)>, + /// compiler settings pub settings: ZkSettings, } impl StandardJsonCompilerInput { + /// new StandardJsonCompilerInput pub fn new(sources: Vec<(PathBuf, Source)>, settings: ZkSettings) -> Self { Self { language: SolcLanguage::Solidity, sources, settings } } diff --git a/crates/zksync/compilers/src/compilers/zksolc/mod.rs b/crates/zksync/compilers/src/compilers/zksolc/mod.rs index 8518249f7..1a4935573 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/mod.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/mod.rs @@ -1,3 +1,4 @@ +//! foundry-compilers trait implementations for zksolc use self::input::{ZkSolcInput, ZkSolcVersionedInput}; use crate::artifacts::{contract::Contract, error::Error, CompilerOutput as ZkCompilerOutput}; use alloy_json_abi::JsonAbi; @@ -34,8 +35,11 @@ pub mod input; pub mod settings; pub use settings::{ZkSettings, ZkSolcSettings}; +/// zksolc command pub const ZKSOLC: &str = "zksolc"; +/// ZKsync solc release used for all ZKsync solc versions pub const ZKSYNC_SOLC_RELEASE: Version = Version::new(1, 0, 1); +/// Default zksolc version pub const ZKSOLC_VERSION: Version = Version::new(1, 5, 7); #[cfg(test)] @@ -116,9 +120,12 @@ impl ZkSolcOS { } } +/// ZkSolc compiler #[derive(Debug, Clone)] pub struct ZkSolcCompiler { + /// zksolc path pub zksolc: PathBuf, + /// solc compiler to use along zksolc pub solc: SolcCompiler, } @@ -204,6 +211,7 @@ impl Compiler for ZkSolcCompiler { } impl ZkSolcCompiler { + /// Get zksolc command wrapper pub fn zksolc(&self, input: &ZkSolcVersionedInput) -> Result { let solc = match &self.solc { SolcCompiler::Specific(solc) => Some(solc.solc.clone()), @@ -321,6 +329,7 @@ impl ZkSolc { }) } + /// Get zksolc path for a given version pub fn get_path_for_version(version: &Version) -> Result { let maybe_zksolc = Self::find_installed_version(version)?; @@ -363,6 +372,7 @@ impl ZkSolc { Ok(compiler_output) } + /// Get installed versions of zksync solc pub fn solc_installed_versions() -> Vec { if let Ok(dir) = Self::compilers_dir() { let os = get_operating_system().unwrap(); @@ -387,6 +397,7 @@ impl ZkSolc { } } + /// Get available zksync solc versions pub fn solc_available_versions() -> Vec { let mut ret = vec![]; let min_max_patch_by_minor_versions = @@ -529,6 +540,7 @@ impl ZkSolc { compiler_blocking_install(solc_path, lock_path, &download_url, &label) } + /// Get path for installed zksolc version. Returns `Ok(None)` if not installed pub fn find_installed_version(version: &Version) -> Result> { let zksolc = Self::compiler_path(version)?; @@ -538,6 +550,7 @@ impl ZkSolc { Ok(Some(zksolc)) } + /// Get path for installed ZKsync solc version. Returns `Ok(None)` if not installed pub fn find_solc_installed_version(version_str: &str) -> Result> { let solc = Self::solc_path(version_str)?; diff --git a/crates/zksync/compilers/src/compilers/zksolc/settings.rs b/crates/zksync/compilers/src/compilers/zksolc/settings.rs index c79aaff99..c10cca0c0 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/settings.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/settings.rs @@ -1,3 +1,4 @@ +//! zksolc settings use crate::artifacts::output_selection::OutputSelection as ZkOutputSelection; use foundry_compilers::{ artifacts::{serde_helpers, EvmVersion, Libraries}, From b6fc37256fc1f71b379a8e000770fe4068d15497 Mon Sep 17 00:00:00 2001 From: elfedy Date: Wed, 18 Dec 2024 16:15:21 -0300 Subject: [PATCH 090/107] use released alloy-zksync --- Cargo.lock | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20c0dd5ab..c1e887882 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,7 +897,8 @@ dependencies = [ [[package]] name = "alloy-zksync" version = "0.8.0" -source = "git+https://github.com/popzxc/alloy-zksync.git?rev=4554f91924efca588ad947c4c9ed2242e93d997d#4554f91924efca588ad947c4c9ed2242e93d997d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13856958a09fecdedde2379b2f53eee29d68eb767021412ade4a92e051c74e9" dependencies = [ "alloy", "alloy-consensus-any", diff --git a/Cargo.toml b/Cargo.toml index d6c3128b1..1e48e0faa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,7 +230,7 @@ syn-solidity = "0.8.14" alloy-chains = "0.1" alloy-rlp = "0.3" -alloy-zksync = { git = "https://github.com/popzxc/alloy-zksync.git", rev = "4554f91924efca588ad947c4c9ed2242e93d997d"} +alloy-zksync = "0.8.0" alloy-trie = "0.7.0" From bde4b63249768c0c68c4cb8e1d2e1334c22cd92b Mon Sep 17 00:00:00 2001 From: elfedy Date: Thu, 19 Dec 2024 13:57:07 -0300 Subject: [PATCH 091/107] Add compilers integration tests --- Cargo.lock | 2 + crates/zksync/compilers/.lock | 0 crates/zksync/compilers/Cargo.toml | 7 +- .../compilers/src/compilers/zksolc/mod.rs | 1 - crates/zksync/compilers/src/lib.rs | 7 + .../dapp-sample/lib/ds-test/demo/demo.sol | 223 +++++++ .../dapp-sample/lib/ds-test/src/test.sol | 434 +++++++++++++ .../test-data/dapp-sample/src/Dapp.sol | 5 + .../test-data/dapp-sample/src/Dapp.t.sol | 22 + .../test-data/yul-sample/SimpleStore.yul.json | 1 - crates/zksync/compilers/tests/zksync_tests.rs | 596 ++++++++++++++++++ 11 files changed, 1294 insertions(+), 4 deletions(-) create mode 100644 crates/zksync/compilers/.lock create mode 100644 crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol create mode 100644 crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol create mode 100644 crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol create mode 100644 crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol delete mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json create mode 100644 crates/zksync/compilers/tests/zksync_tests.rs diff --git a/Cargo.lock b/Cargo.lock index c1e887882..2644ccab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5445,6 +5445,7 @@ dependencies = [ "fd-lock", "foundry-compilers", "foundry-compilers-artifacts-solc", + "foundry-test-utils", "fs4 0.8.4", "itertools 0.13.0", "path-slash", @@ -5453,6 +5454,7 @@ dependencies = [ "serde", "serde_json", "similar-asserts", + "tempfile", "tracing", "walkdir", "yansi", diff --git a/crates/zksync/compilers/.lock b/crates/zksync/compilers/.lock new file mode 100644 index 000000000..e69de29bb diff --git a/crates/zksync/compilers/Cargo.toml b/crates/zksync/compilers/Cargo.toml index 0f3af0483..40c345eb5 100644 --- a/crates/zksync/compilers/Cargo.toml +++ b/crates/zksync/compilers/Cargo.toml @@ -25,8 +25,6 @@ eyre.workspace = true walkdir.workspace = true reqwest.workspace = true yansi.workspace = true -similar-asserts.workspace = true -fd-lock = "4.0.2" fs4 = "0.8.2" dirs = "5" path-slash = "0.2" @@ -34,3 +32,8 @@ path-slash = "0.2" # zk zksync_types.workspace = true +[dev-dependencies] +similar-asserts.workspace = true +fd-lock = "4.0.2" +tempfile.workspace = true +foundry-test-utils.workspace = true diff --git a/crates/zksync/compilers/src/compilers/zksolc/mod.rs b/crates/zksync/compilers/src/compilers/zksolc/mod.rs index 1a4935573..368b8f70e 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/mod.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/mod.rs @@ -129,7 +129,6 @@ pub struct ZkSolcCompiler { pub solc: SolcCompiler, } -#[cfg(feature = "project-util")] impl Default for ZkSolcCompiler { fn default() -> Self { let zksolc = diff --git a/crates/zksync/compilers/src/lib.rs b/crates/zksync/compilers/src/lib.rs index dd8d1e571..4316c45df 100644 --- a/crates/zksync/compilers/src/lib.rs +++ b/crates/zksync/compilers/src/lib.rs @@ -7,3 +7,10 @@ pub mod artifacts; pub mod compilers; pub mod dual_compiled_contracts; pub mod libraries; + +// TODO: Used in integration tests. +// find out why cargo complains about unused dev_dependency for these cases +#[cfg(test)] +use foundry_test_utils as _; +#[cfg(test)] +use tempfile as _; diff --git a/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol new file mode 100644 index 000000000..d3a7d81fc --- /dev/null +++ b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/demo/demo.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.4.23; + +import "../src/test.sol"; + +contract DemoTest is DSTest { + function test_this() public pure { + require(true); + } + function test_logs() public { + emit log("-- log(string)"); + emit log("a string"); + + emit log("-- log_named_uint(string, uint)"); + log_named_uint("uint", 512); + + emit log("-- log_named_int(string, int)"); + log_named_int("int", -512); + + emit log("-- log_named_address(string, address)"); + log_named_address("address", address(this)); + + emit log("-- log_named_bytes32(string, bytes32)"); + log_named_bytes32("bytes32", "a string"); + + emit log("-- log_named_bytes(string, bytes)"); + log_named_bytes("bytes", hex"cafefe"); + + emit log("-- log_named_string(string, string)"); + log_named_string("string", "a string"); + + emit log("-- log_named_decimal_uint(string, uint, uint)"); + log_named_decimal_uint("decimal uint", 1.0e18, 18); + + emit log("-- log_named_decimal_int(string, int, uint)"); + log_named_decimal_int("decimal int", -1.0e18, 18); + } + event log_old_named_uint(bytes32,uint); + function test_old_logs() public { + log_old_named_uint("key", 500); + log_named_bytes32("bkey", "val"); + } + function test_trace() public view { + this.echo("string 1", "string 2"); + } + function test_multiline() public { + emit log("a multiline\\n" "string"); + emit log("a multiline " "string"); + log_bytes("a string"); + log_bytes("a multiline\n" "string"); + log_bytes("a multiline\\n" "string"); + emit log(unicode"Ώ"); + logs(hex"0000"); + log_named_bytes("0x0000", hex"0000"); + logs(hex"ff"); + } + function echo(string memory s1, string memory s2) public pure + returns (string memory, string memory) + { + return (s1, s2); + } + + function prove_this(uint x) public { + log_named_uint("sym x", x); + assertGt(x + 1, 0); + } + + function test_logn() public { + assembly { + log0(0x01, 0x02) + log1(0x01, 0x02, 0x03) + log2(0x01, 0x02, 0x03, 0x04) + log3(0x01, 0x02, 0x03, 0x04, 0x05) + } + } + + event MyEvent(uint, uint indexed, uint, uint indexed); + function test_events() public { + emit MyEvent(1, 2, 3, 4); + } + + function test_asserts() public { + string memory err = "this test has failed!"; + emit log("## assertTrue(bool)\n"); + assertTrue(false); + emit log("\n"); + assertTrue(false, err); + + emit log("\n## assertEq(address,address)\n"); + assertEq(address(this), msg.sender); + emit log("\n"); + assertEq(address(this), msg.sender, err); + + emit log("\n## assertEq32(bytes32,bytes32)\n"); + assertEq32("bytes 1", "bytes 2"); + emit log("\n"); + assertEq32("bytes 1", "bytes 2", err); + + emit log("\n## assertEq(bytes32,bytes32)\n"); + assertEq32("bytes 1", "bytes 2"); + emit log("\n"); + assertEq32("bytes 1", "bytes 2", err); + + emit log("\n## assertEq(uint,uint)\n"); + assertEq(uint(0), 1); + emit log("\n"); + assertEq(uint(0), 1, err); + + emit log("\n## assertEq(int,int)\n"); + assertEq(-1, -2); + emit log("\n"); + assertEq(-1, -2, err); + + emit log("\n## assertEqDecimal(int,int,uint)\n"); + assertEqDecimal(-1.0e18, -1.1e18, 18); + emit log("\n"); + assertEqDecimal(-1.0e18, -1.1e18, 18, err); + + emit log("\n## assertEqDecimal(uint,uint,uint)\n"); + assertEqDecimal(uint(1.0e18), 1.1e18, 18); + emit log("\n"); + assertEqDecimal(uint(1.0e18), 1.1e18, 18, err); + + emit log("\n## assertGt(uint,uint)\n"); + assertGt(uint(0), 0); + emit log("\n"); + assertGt(uint(0), 0, err); + + emit log("\n## assertGt(int,int)\n"); + assertGt(-1, -1); + emit log("\n"); + assertGt(-1, -1, err); + + emit log("\n## assertGtDecimal(int,int,uint)\n"); + assertGtDecimal(-2.0e18, -1.1e18, 18); + emit log("\n"); + assertGtDecimal(-2.0e18, -1.1e18, 18, err); + + emit log("\n## assertGtDecimal(uint,uint,uint)\n"); + assertGtDecimal(uint(1.0e18), 1.1e18, 18); + emit log("\n"); + assertGtDecimal(uint(1.0e18), 1.1e18, 18, err); + + emit log("\n## assertGe(uint,uint)\n"); + assertGe(uint(0), 1); + emit log("\n"); + assertGe(uint(0), 1, err); + + emit log("\n## assertGe(int,int)\n"); + assertGe(-1, 0); + emit log("\n"); + assertGe(-1, 0, err); + + emit log("\n## assertGeDecimal(int,int,uint)\n"); + assertGeDecimal(-2.0e18, -1.1e18, 18); + emit log("\n"); + assertGeDecimal(-2.0e18, -1.1e18, 18, err); + + emit log("\n## assertGeDecimal(uint,uint,uint)\n"); + assertGeDecimal(uint(1.0e18), 1.1e18, 18); + emit log("\n"); + assertGeDecimal(uint(1.0e18), 1.1e18, 18, err); + + emit log("\n## assertLt(uint,uint)\n"); + assertLt(uint(0), 0); + emit log("\n"); + assertLt(uint(0), 0, err); + + emit log("\n## assertLt(int,int)\n"); + assertLt(-1, -1); + emit log("\n"); + assertLt(-1, -1, err); + + emit log("\n## assertLtDecimal(int,int,uint)\n"); + assertLtDecimal(-1.0e18, -1.1e18, 18); + emit log("\n"); + assertLtDecimal(-1.0e18, -1.1e18, 18, err); + + emit log("\n## assertLtDecimal(uint,uint,uint)\n"); + assertLtDecimal(uint(2.0e18), 1.1e18, 18); + emit log("\n"); + assertLtDecimal(uint(2.0e18), 1.1e18, 18, err); + + emit log("\n## assertLe(uint,uint)\n"); + assertLe(uint(1), 0); + emit log("\n"); + assertLe(uint(1), 0, err); + + emit log("\n## assertLe(int,int)\n"); + assertLe(0, -1); + emit log("\n"); + assertLe(0, -1, err); + + emit log("\n## assertLeDecimal(int,int,uint)\n"); + assertLeDecimal(-1.0e18, -1.1e18, 18); + emit log("\n"); + assertLeDecimal(-1.0e18, -1.1e18, 18, err); + + emit log("\n## assertLeDecimal(uint,uint,uint)\n"); + assertLeDecimal(uint(2.0e18), 1.1e18, 18); + emit log("\n"); + assertLeDecimal(uint(2.0e18), 1.1e18, 18, err); + + emit log("\n## assertEq(string,string)\n"); + string memory s1 = "string 1"; + string memory s2 = "string 2"; + assertEq(s1, s2); + emit log("\n"); + assertEq(s1, s2, err); + + emit log("\n## assertEq0(bytes,bytes)\n"); + assertEq0(hex"abcdef01", hex"abcdef02"); + log("\n"); + assertEq0(hex"abcdef01", hex"abcdef02", err); + } +} + +contract DemoTestWithSetUp { + function setUp() public { + } + function test_pass() public pure { + } +} diff --git a/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol new file mode 100644 index 000000000..96d3c1543 --- /dev/null +++ b/crates/zksync/compilers/test-data/dapp-sample/lib/ds-test/src/test.sol @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.4.23; + +contract DSTest { + event log (string); + event logs (bytes); + + event log_address (address); + event log_bytes32 (bytes32); + event log_int (int); + event log_uint (uint); + event log_bytes (bytes); + event log_string (string); + + event log_named_address (string key, address val); + event log_named_bytes32 (string key, bytes32 val); + event log_named_decimal_int (string key, int val, uint decimals); + event log_named_decimal_uint (string key, uint val, uint decimals); + event log_named_int (string key, int val); + event log_named_uint (string key, uint val); + event log_named_bytes (string key, bytes val); + event log_named_string (string key, string val); + + bool public IS_TEST = true; + bool public failed; + + address constant HEVM_ADDRESS = + address(bytes20(uint160(uint256(keccak256('hevm cheat code'))))); + + modifier mayRevert() { _; } + modifier testopts(string memory) { _; } + + function fail() internal { + failed = true; + } + + modifier logs_gas() { + uint startGas = gasleft(); + _; + uint endGas = gasleft(); + emit log_named_uint("gas", startGas - endGas); + } + + function assertTrue(bool condition) internal { + if (!condition) { + emit log("Error: Assertion Failed"); + fail(); + } + } + + function assertTrue(bool condition, string memory err) internal { + if (!condition) { + emit log_named_string("Error", err); + assertTrue(condition); + } + } + + function assertEq(address a, address b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [address]"); + emit log_named_address(" Expected", b); + emit log_named_address(" Actual", a); + fail(); + } + } + function assertEq(address a, address b, string memory err) internal { + if (a != b) { + emit log_named_string ("Error", err); + assertEq(a, b); + } + } + + function assertEq(bytes32 a, bytes32 b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [bytes32]"); + emit log_named_bytes32(" Expected", b); + emit log_named_bytes32(" Actual", a); + fail(); + } + } + function assertEq(bytes32 a, bytes32 b, string memory err) internal { + if (a != b) { + emit log_named_string ("Error", err); + assertEq(a, b); + } + } + function assertEq32(bytes32 a, bytes32 b) internal { + assertEq(a, b); + } + function assertEq32(bytes32 a, bytes32 b, string memory err) internal { + assertEq(a, b, err); + } + + function assertEq(int a, int b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [int]"); + emit log_named_int(" Expected", b); + emit log_named_int(" Actual", a); + fail(); + } + } + function assertEq(int a, int b, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + function assertEq(uint a, uint b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + fail(); + } + } + function assertEq(uint a, uint b, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + function assertEqDecimal(int a, int b, uint decimals) internal { + if (a != b) { + emit log("Error: a == b not satisfied [decimal int]"); + emit log_named_decimal_int(" Expected", b, decimals); + emit log_named_decimal_int(" Actual", a, decimals); + fail(); + } + } + function assertEqDecimal(int a, int b, uint decimals, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEqDecimal(a, b, decimals); + } + } + function assertEqDecimal(uint a, uint b, uint decimals) internal { + if (a != b) { + emit log("Error: a == b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Expected", b, decimals); + emit log_named_decimal_uint(" Actual", a, decimals); + fail(); + } + } + function assertEqDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEqDecimal(a, b, decimals); + } + } + + function assertGt(uint a, uint b) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertGt(uint a, uint b, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGt(a, b); + } + } + function assertGt(int a, int b) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertGt(int a, int b, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGt(a, b); + } + } + function assertGtDecimal(int a, int b, uint decimals) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertGtDecimal(int a, int b, uint decimals, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGtDecimal(a, b, decimals); + } + } + function assertGtDecimal(uint a, uint b, uint decimals) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertGtDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGtDecimal(a, b, decimals); + } + } + + function assertGe(uint a, uint b) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertGe(uint a, uint b, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGe(a, b); + } + } + function assertGe(int a, int b) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertGe(int a, int b, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGe(a, b); + } + } + function assertGeDecimal(int a, int b, uint decimals) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertGeDecimal(int a, int b, uint decimals, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGeDecimal(a, b, decimals); + } + } + function assertGeDecimal(uint a, uint b, uint decimals) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertGeDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGeDecimal(a, b, decimals); + } + } + + function assertLt(uint a, uint b) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertLt(uint a, uint b, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLt(a, b); + } + } + function assertLt(int a, int b) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertLt(int a, int b, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLt(a, b); + } + } + function assertLtDecimal(int a, int b, uint decimals) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertLtDecimal(int a, int b, uint decimals, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLtDecimal(a, b, decimals); + } + } + function assertLtDecimal(uint a, uint b, uint decimals) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertLtDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLtDecimal(a, b, decimals); + } + } + + function assertLe(uint a, uint b) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertLe(uint a, uint b, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertLe(a, b); + } + } + function assertLe(int a, int b) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertLe(int a, int b, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertLe(a, b); + } + } + function assertLeDecimal(int a, int b, uint decimals) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertLeDecimal(int a, int b, uint decimals, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertLeDecimal(a, b, decimals); + } + } + function assertLeDecimal(uint a, uint b, uint decimals) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertLeDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertGeDecimal(a, b, decimals); + } + } + + function assertEq(string memory a, string memory b) internal { + if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { + emit log("Error: a == b not satisfied [string]"); + emit log_named_string(" Value a", a); + emit log_named_string(" Value b", b); + fail(); + } + } + function assertEq(string memory a, string memory b, string memory err) internal { + if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + + function checkEq0(bytes memory a, bytes memory b) internal pure returns (bool ok) { + ok = true; + if (a.length == b.length) { + for (uint i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + ok = false; + } + } + } else { + ok = false; + } + } + function assertEq0(bytes memory a, bytes memory b) internal { + if (!checkEq0(a, b)) { + emit log("Error: a == b not satisfied [bytes]"); + emit log_named_bytes(" Expected", a); + emit log_named_bytes(" Actual", b); + fail(); + } + } + function assertEq0(bytes memory a, bytes memory b, string memory err) internal { + if (!checkEq0(a, b)) { + emit log_named_string("Error", err); + assertEq0(a, b); + } + } +} diff --git a/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol new file mode 100644 index 000000000..906f3070b --- /dev/null +++ b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.6.6; + +contract Dapp { +} diff --git a/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol new file mode 100644 index 000000000..e0f78a35a --- /dev/null +++ b/crates/zksync/compilers/test-data/dapp-sample/src/Dapp.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.6.6; + +import "ds-test/test.sol"; + +import "./Dapp.sol"; + +contract DappTest is DSTest { + Dapp dapp; + + function setUp() public { + dapp = new Dapp(); + } + + function testFail_basic_sanity() public { + assertTrue(false); + } + + function test_basic_sanity() public { + assertTrue(true); + } +} diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json deleted file mode 100644 index 09883da59..000000000 --- a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json +++ /dev/null @@ -1 +0,0 @@ -{"abi":null,"bytecode":{"object":"0x00000001002001900000000c0000c13d000000002101043c000000000010043f000000200100043d0000000701100197000000000202043b0000000802200197000000000112019f000000200010043f0000000001000019000000000001042d000000240000043f0000002001000039000001000010044300000120000004430000000601000041000000130001042e0000001200000432000000130001042e0000001400010430000000000000000000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e0e9492643af2958d06e962a0b194ca36037ef49d6167e028f1f053a54c571","is_unlinked":false,"missing_libraries":[]},"storageLayout":{"storage":[],"types":{}},"userdoc":{},"devdoc":{},"hash":"0100000b7acef21b9ea8b7897d4ebb51ecaec207df8038a10bc61b904c5c9e4b","factoryDependencies":{},"id":0} \ No newline at end of file diff --git a/crates/zksync/compilers/tests/zksync_tests.rs b/crates/zksync/compilers/tests/zksync_tests.rs new file mode 100644 index 000000000..274569458 --- /dev/null +++ b/crates/zksync/compilers/tests/zksync_tests.rs @@ -0,0 +1,596 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + path::PathBuf, + str::FromStr, +}; + +use foundry_compilers_artifacts_solc::Remapping; +use foundry_test_utils::foundry_compilers::{ + buildinfo::BuildInfo, cache::CompilerCache, project_util::*, resolver::parse::SolData, + CompilerOutput, Graph, ProjectBuilder, ProjectPathsConfig, +}; + +use foundry_zksync_compilers::{ + artifacts::{contract::Contract, error::Error}, + compilers::{ + artifact_output::zk::ZkArtifactOutput, + zksolc::{ + input::ZkSolcInput, + settings::{ZkSolcError, ZkSolcWarning}, + ZkSolc, ZkSolcCompiler, ZkSolcSettings, + }, + }, +}; + +#[test] +fn zksync_can_compile_dapp_sample() { + // let _ = tracing_subscriber::fmt() + // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + // .try_init() + // .ok(); + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample"); + let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib")); + let project = TempProject::::new(paths).unwrap(); + + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("Dapp").is_some()); + compiled.assert_success(); + + // nothing to compile + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("Dapp").is_some()); + assert!(compiled.is_unchanged()); + + let cache = CompilerCache::::read(project.cache_path()).unwrap(); + + // delete artifacts + std::fs::remove_dir_all(&project.paths().artifacts).unwrap(); + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("Dapp").is_some()); + assert!(!compiled.is_unchanged()); + + let updated_cache = CompilerCache::::read(project.cache_path()).unwrap(); + assert_eq!(cache, updated_cache); +} + +fn test_zksync_can_compile_contract_with_suppressed_errors(compiler: ZkSolcCompiler) { + // let _ = tracing_subscriber::fmt() + // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + // .try_init() + // .ok(); + let mut project = TempProject::::dapptools().unwrap(); + project.project_mut().compiler = compiler; + + project + .add_source( + "Erroneous", + r#" + // SPDX-License-Identifier: MIT OR Apache-2.0 + pragma solidity ^0.8.10; + contract Erroneous { + function distribute(address payable recipient) public { + recipient.send(1); + recipient.transfer(1); + } + } + "#, + ) + .unwrap(); + + let compiled = project.compile().unwrap(); + assert!(compiled.has_compiler_errors()); + + project.project_mut().settings.settings.suppressed_errors = + HashSet::from([ZkSolcError::SendTransfer]); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + assert!(compiled.find_first("Erroneous").is_some()); +} + +#[test] +fn zksync_can_compile_contract_with_suppressed_errors() { + test_zksync_can_compile_contract_with_suppressed_errors(ZkSolcCompiler::default()); +} + +#[test] +fn zksync_pre_1_5_7_can_compile_contract_with_suppressed_errors() { + let compiler = ZkSolcCompiler { + zksolc: ZkSolc::get_path_for_version(&semver::Version::new(1, 5, 6)).unwrap(), + solc: Default::default(), + }; + test_zksync_can_compile_contract_with_suppressed_errors(compiler); +} + +fn test_zksync_can_compile_contract_with_suppressed_warnings(compiler: ZkSolcCompiler) { + // let _ = tracing_subscriber::fmt() + // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + // .try_init() + // .ok(); + let mut project = TempProject::::dapptools().unwrap(); + project.project_mut().compiler = compiler; + + project + .add_source( + "Warning", + r#" + // SPDX-License-Identifier: MIT OR Apache-2.0 + pragma solidity ^0.8.10; + contract Warning { + function test() public view { + require(tx.origin != address(0), "what"); + } + } + "#, + ) + .unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + assert!( + compiled + .output() + .errors + .iter() + .any(|err| err.is_warning() && err.message.contains("tx.origin")), + "{:#?}", + compiled.output().errors + ); + + project.project_mut().settings.settings.suppressed_warnings = + HashSet::from([ZkSolcWarning::TxOrigin]); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + assert!(compiled.find_first("Warning").is_some()); + assert!( + !compiled + .output() + .errors + .iter() + .any(|err| err.is_warning() && err.message.contains("tx.origin")), + "{:#?}", + compiled.output().errors + ); +} + +#[test] +fn zksync_can_compile_contract_with_suppressed_warnings() { + test_zksync_can_compile_contract_with_suppressed_warnings(ZkSolcCompiler::default()); +} + +#[test] +fn zksync_pre_1_5_7_can_compile_contract_with_suppressed_warnings() { + let compiler = ZkSolcCompiler { + zksolc: ZkSolc::get_path_for_version(&semver::Version::new(1, 5, 6)).unwrap(), + solc: Default::default(), + }; + test_zksync_can_compile_contract_with_suppressed_warnings(compiler); +} + +#[test] +fn zksync_can_compile_dapp_detect_changes_in_libs() { + // let _ = tracing_subscriber::fmt() + // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + // .try_init() + // .ok(); + let mut project = TempProject::::dapptools().unwrap(); + + let remapping = project.paths().libraries[0].join("remapping"); + project + .paths_mut() + .remappings + .push(Remapping::from_str(&format!("remapping/={}/", remapping.display())).unwrap()); + + let src = project + .add_source( + "Foo", + r#" + pragma solidity ^0.8.10; + import "remapping/Bar.sol"; + + contract Foo {} + "#, + ) + .unwrap(); + + let lib = project + .add_lib( + "remapping/Bar", + r" + pragma solidity ^0.8.10; + + contract Bar {} + ", + ) + .unwrap(); + + let graph = Graph::::resolve(project.paths()).unwrap(); + assert_eq!(graph.files().len(), 2); + assert_eq!(graph.files().clone(), HashMap::from([(src, 0), (lib, 1),])); + + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("Foo").is_some()); + assert!(compiled.find_first("Bar").is_some()); + compiled.assert_success(); + + // nothing to compile + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("Foo").is_some()); + assert!(compiled.is_unchanged()); + + let cache = CompilerCache::::read(&project.paths().cache).unwrap(); + assert_eq!(cache.files.len(), 2); + + // overwrite lib + project + .add_lib( + "remapping/Bar", + r" + pragma solidity ^0.8.10; + + // changed lib + contract Bar {} + ", + ) + .unwrap(); + + let graph = Graph::::resolve(project.paths()).unwrap(); + assert_eq!(graph.files().len(), 2); + + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("Foo").is_some()); + assert!(compiled.find_first("Bar").is_some()); + // ensure change is detected + assert!(!compiled.is_unchanged()); +} + +#[test] +fn zksync_can_compile_dapp_detect_changes_in_sources() { + let project = TempProject::::dapptools().unwrap(); + + let src = project + .add_source( + "DssSpell.t", + r#" + pragma solidity ^0.8.10; + import "./DssSpell.t.base.sol"; + + contract DssSpellTest is DssSpellTestBase { } + "#, + ) + .unwrap(); + + let base = project + .add_source( + "DssSpell.t.base", + r" + pragma solidity ^0.8.10; + + contract DssSpellTestBase { + address deployed_spell; + function setUp() public { + deployed_spell = address(0xA867399B43aF7790aC800f2fF3Fa7387dc52Ec5E); + } + } + ", + ) + .unwrap(); + + let graph = Graph::::resolve(project.paths()).unwrap(); + assert_eq!(graph.files().len(), 2); + assert_eq!(graph.files().clone(), HashMap::from([(base, 0), (src, 1),])); + assert_eq!(graph.imported_nodes(1).to_vec(), vec![0]); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + assert!(compiled.find_first("DssSpellTest").is_some()); + assert!(compiled.find_first("DssSpellTestBase").is_some()); + + // nothing to compile + let compiled = project.compile().unwrap(); + assert!(compiled.is_unchanged()); + assert!(compiled.find_first("DssSpellTest").is_some()); + assert!(compiled.find_first("DssSpellTestBase").is_some()); + + let cache = CompilerCache::::read(&project.paths().cache).unwrap(); + assert_eq!(cache.files.len(), 2); + + let artifacts = compiled.into_artifacts().collect::>(); + + // overwrite import + let _ = project + .add_source( + "DssSpell.t.base", + r" + pragma solidity ^0.8.10; + + contract DssSpellTestBase { + address deployed_spell; + function setUp() public { + deployed_spell = address(0); + } + } + ", + ) + .unwrap(); + let graph = Graph::::resolve(project.paths()).unwrap(); + assert_eq!(graph.files().len(), 2); + + let compiled = project.compile().unwrap(); + assert!(compiled.find_first("DssSpellTest").is_some()); + assert!(compiled.find_first("DssSpellTestBase").is_some()); + // ensure change is detected + assert!(!compiled.is_unchanged()); + + // and all recompiled artifacts are different + for (p, artifact) in compiled.into_artifacts() { + let other = artifacts + .iter() + .find(|(id, _)| id.name == p.name && id.version == p.version && id.source == p.source) + .unwrap() + .1; + assert_ne!(artifact, *other); + } +} + +#[test] +fn zksync_can_emit_build_info() { + let mut project = TempProject::::dapptools().unwrap(); + project.project_mut().build_info = true; + project + .add_source( + "A", + r#" +pragma solidity ^0.8.10; +import "./B.sol"; +contract A { } +"#, + ) + .unwrap(); + + project + .add_source( + "B", + r" +pragma solidity ^0.8.10; +contract B { } +", + ) + .unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + + let info_dir = project.project().build_info_path(); + assert!(info_dir.exists()); + + let mut build_info_count = 0; + for entry in fs::read_dir(info_dir).unwrap() { + let info = + BuildInfo::>::read(&entry.unwrap().path()) + .unwrap(); + assert!(info.output.metadata.contains_key("zksyncSolcVersion")); + build_info_count += 1; + } + assert_eq!(build_info_count, 1); +} + +#[test] +fn zksync_can_clean_build_info() { + let mut project = TempProject::::dapptools().unwrap(); + + project.project_mut().build_info = true; + project.project_mut().paths.build_infos = project.project_mut().paths.root.join("build-info"); + project + .add_source( + "A", + r#" +pragma solidity ^0.8.10; +import "./B.sol"; +contract A { } +"#, + ) + .unwrap(); + + project + .add_source( + "B", + r" +pragma solidity ^0.8.10; +contract B { } +", + ) + .unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + + let info_dir = project.project().build_info_path(); + assert!(info_dir.exists()); + + let mut build_info_count = 0; + for entry in fs::read_dir(info_dir).unwrap() { + let _info = + BuildInfo::>::read(&entry.unwrap().path()) + .unwrap(); + build_info_count += 1; + } + assert_eq!(build_info_count, 1); + + project.project().cleanup().unwrap(); + + assert!(!project.project().build_info_path().exists()); +} + +#[test] +fn zksync_cant_compile_a_file_outside_allowed_paths() { + // For this test we should create the following directory structure: + // project_root/ + // ├── outer/ + // │ ├── Util.sol + // │ └── Helper.sol + // └── contracts/ + // ├── src/ + // │ └── Main.sol + + let tmp_dir = tempfile::tempdir().unwrap(); + let project_root = tmp_dir.path().to_path_buf(); + let contracts_dir = tempfile::tempdir_in(&project_root).unwrap(); + + fs::create_dir_all(contracts_dir.path().join("src")).unwrap(); + fs::create_dir_all(project_root.join("outer")).unwrap(); + + fs::write( + contracts_dir.path().join("src/Main.sol"), + r#" +pragma solidity ^0.8.0; +import "@outer/Helper.sol"; +contract Main { + Helper helper = new Helper(); + function run() public {} +} +"#, + ) + .unwrap(); + + fs::write( + project_root.join("outer/Helper.sol"), + r#" +pragma solidity ^0.8.0; +import "./Util.sol"; +contract Helper { + Util util = new Util(); +} +"#, + ) + .unwrap(); + + fs::write( + project_root.join("outer/Util.sol"), + r#" +pragma solidity ^0.8.0; +contract Util {} +"#, + ) + .unwrap(); + + let root = contracts_dir.path().to_path_buf(); + let paths = ProjectPathsConfig::builder() + .root(root.clone()) + .sources(root.join("src")) + .remappings(vec![Remapping::from_str("@outer/=../outer/").unwrap()]) + .build() + .unwrap(); + + let inner = ProjectBuilder::::new(Default::default()) + .paths(paths) + .build(Default::default()) + .unwrap(); + let project = + TempProject::::create_new(contracts_dir, inner).unwrap(); + + let compiled = project.compile().unwrap(); + assert!(compiled.has_compiler_errors()); + assert!(compiled.output().errors.iter().any(|error| error + .formatted_message + .as_ref() + .map_or(false, |msg| msg.contains("File outside of allowed directories")))); +} + +#[test] +fn zksync_can_compile_a_file_in_allowed_paths_successfully() { + let tmp_dir = tempfile::tempdir().unwrap(); + let project_root = tmp_dir.path().to_path_buf(); + let contracts_dir = tempfile::tempdir_in(&project_root).unwrap(); + + fs::create_dir_all(contracts_dir.path().join("src")).unwrap(); + fs::create_dir_all(project_root.join("outer")).unwrap(); + + fs::write( + contracts_dir.path().join("src/Main.sol"), + r#" +pragma solidity ^0.8.0; +import "@outer/Helper.sol"; +contract Main { + Helper helper = new Helper(); + function run() public {} +} +"#, + ) + .unwrap(); + + fs::write( + project_root.join("outer/Helper.sol"), + r#" +pragma solidity ^0.8.0; +import "./Util.sol"; +contract Helper { + Util util = new Util(); +} +"#, + ) + .unwrap(); + + fs::write( + project_root.join("outer/Util.sol"), + r#" +pragma solidity ^0.8.0; +contract Util {} +"#, + ) + .unwrap(); + + let root = contracts_dir.path().to_path_buf(); + let paths = ProjectPathsConfig::builder() + .root(root.clone()) + .sources(root.join("src")) + .allowed_paths(vec!["../"]) + .remappings(vec![Remapping::from_str("@outer/=../outer/").unwrap()]) + .build() + .unwrap(); + + let inner = ProjectBuilder::::new(Default::default()) + .paths(paths) + .build(Default::default()) + .unwrap(); + let project = + TempProject::::create_new(contracts_dir, inner).unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); +} + +#[test] +fn zksync_can_compile_yul_sample() { + // let _ = tracing_subscriber::fmt() + // .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + // .try_init() + // .ok(); + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/yul-sample"); + let paths = ProjectPathsConfig::builder().sources(root); + let project = TempProject::::new(paths).unwrap(); + + let compiled = project.compile().unwrap(); + compiled.assert_success(); + + let simple_store_artifact = compiled + .compiled_artifacts() + .values() + .find_map(|contracts| { + contracts + .iter() + .find(|(name, _)| name.ends_with("SimpleStore.yul")) + .and_then(|(_, artifacts)| artifacts.first()) + }) + .expect("SimpleStore.yul artifact not found") + .artifact + .bytecode + .clone() + .unwrap(); + + let yul_bytecode = simple_store_artifact.object().into_bytes().unwrap(); + + assert!(!yul_bytecode.is_empty(), "SimpleStore.yul bytecode is empty"); +} From 419506b19696952cb011b83284f0007ed7826a7a Mon Sep 17 00:00:00 2001 From: elfedy Date: Thu, 19 Dec 2024 14:48:28 -0300 Subject: [PATCH 092/107] adapt gas test to new table layout --- crates/forge/tests/cli/zk_cmd.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/forge/tests/cli/zk_cmd.rs b/crates/forge/tests/cli/zk_cmd.rs index 644d9c810..f8f8c2c8e 100644 --- a/crates/forge/tests/cli/zk_cmd.rs +++ b/crates/forge/tests/cli/zk_cmd.rs @@ -58,15 +58,15 @@ contract ContractOneTest is DSTest { .stdout_lossy(); let mut cells = out.split('|'); - let deployment_cost: u64 = cells.nth(22).unwrap().trim().parse().unwrap(); + let deployment_cost: u64 = cells.nth(17).unwrap().trim().parse().unwrap(); let deployment_size: u64 = cells.next().unwrap().trim().parse().unwrap(); - let function = cells.nth(12).unwrap().trim(); + let function = cells.nth(25).unwrap().trim(); let gas: u64 = cells.next().unwrap().trim().parse().unwrap(); let mut cells_zk = out_zk.split('|'); - let deployment_cost_zk: u64 = cells_zk.nth(22).unwrap().trim().parse().unwrap(); + let deployment_cost_zk: u64 = cells_zk.nth(17).unwrap().trim().parse().unwrap(); let deployment_size_zk: u64 = cells_zk.next().unwrap().trim().parse().unwrap(); - let function_zk = cells_zk.nth(12).unwrap().trim(); + let function_zk = cells_zk.nth(25).unwrap().trim(); let gas_zk: u64 = cells_zk.next().unwrap().trim().parse().unwrap(); assert!(deployment_cost_zk > deployment_cost); From ededd23b4d56ed135d07b34ac547a8696592a49b Mon Sep 17 00:00:00 2001 From: elfedy Date: Thu, 19 Dec 2024 14:55:40 -0300 Subject: [PATCH 093/107] bring back zk methods in testdata --- testdata/cheats/Vm.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index bdbb68e37..20c28d893 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -513,4 +513,9 @@ interface Vm { function writeLine(string calldata path, string calldata data) external; function writeToml(string calldata json, string calldata path) external; function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + function zkRegisterContract(string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode) external pure; + function zkUseFactoryDep(string calldata name) external pure; + function zkUsePaymaster(address paymaster_address, bytes calldata paymaster_input) external pure; + function zkVm(bool enable) external pure; + function zkVmSkip() external pure; } From d94c52b2a1b282c2523e9b0196f3d3bed21adad7 Mon Sep 17 00:00:00 2001 From: elfedy Date: Thu, 19 Dec 2024 14:59:26 -0300 Subject: [PATCH 094/107] update ci rust version --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b99917c9..a7cf342f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ concurrency: env: CARGO_TERM_COLOR: always - TARGET_RUST_VERSION: "nightly-2024-09-01" + TARGET_RUST_VERSION: "nightly-2024-09-05" jobs: nextest: @@ -175,4 +175,4 @@ jobs: ./install-foundry-zksync - name: Verify anvil-zksync installation - run: anvil-zksync --version \ No newline at end of file + run: anvil-zksync --version From 7f3aadbf86aefbb78bf8d94b4201a7b7440007a5 Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 08:16:24 -0300 Subject: [PATCH 095/107] fix: use verbosity 5 in tracing tests --- crates/forge/tests/it/zk/traces.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/forge/tests/it/zk/traces.rs b/crates/forge/tests/it/zk/traces.rs index fc6afab85..d2c4500d4 100644 --- a/crates/forge/tests/it/zk/traces.rs +++ b/crates/forge/tests/it/zk/traces.rs @@ -63,7 +63,9 @@ fn get_zk_artifact_bytecode + std::fmt::Debug>(path: P) -> Vec Date: Fri, 20 Dec 2024 14:25:31 -0300 Subject: [PATCH 096/107] fix some typos --- crates/zksync/compilers/src/artifacts/contract.rs | 2 +- crates/zksync/compilers/src/compilers/artifact_output/zk.rs | 2 +- crates/zksync/compilers/src/compilers/zksolc/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/zksync/compilers/src/artifacts/contract.rs b/crates/zksync/compilers/src/artifacts/contract.rs index 7ec459da9..3b9be91ca 100644 --- a/crates/zksync/compilers/src/artifacts/contract.rs +++ b/crates/zksync/compilers/src/artifacts/contract.rs @@ -90,7 +90,7 @@ impl Contract { // CompactContract variants // TODO: for zkEvm, the distinction between bytecode and deployed_bytecode makes little sense, -// and there some fields that the ouptut doesn't provide (e.g: source_map) +// and there some fields that the output doesn't provide (e.g: source_map) // However, we implement these because we get the Artifact trait and can reuse lots of // the crate's helpers without needing to duplicate everything. Maybe there's a way // we can get all these without having to add the same bytecode twice on each struct. diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs index 58409e44f..0ece4b670 100644 --- a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs @@ -63,7 +63,7 @@ impl ZkContractArtifact { // CompactContract variants // TODO: for zkEvm, the distinction between bytecode and deployed_bytecode makes little sense, -// and there some fields that the ouptut doesn't provide (e.g: source_map) +// and there some fields that the output doesn't provide (e.g: source_map) // However, we implement these because we get the Artifact trait and can reuse lots of // the crate's helpers without needing to duplicate everything. Maybe there's a way // we can get all these without having to add the same bytecode twice on each struct. diff --git a/crates/zksync/compilers/src/compilers/zksolc/mod.rs b/crates/zksync/compilers/src/compilers/zksolc/mod.rs index 368b8f70e..203a0fd45 100644 --- a/crates/zksync/compilers/src/compilers/zksolc/mod.rs +++ b/crates/zksync/compilers/src/compilers/zksolc/mod.rs @@ -653,7 +653,7 @@ fn compiler_blocking_install( let _lock = try_lock_file(lock_path)?; trace!("got lock for {label}"); - // Only write to file if it is not there. The check is doneafter adquiring the lock + // Only write to file if it is not there. The check is done after acquiring the lock // to ensure the thread remains blocked until the required compiler is // fully installed if !compiler_path.exists() { @@ -679,7 +679,7 @@ fn compiler_blocking_install( response.status() ))); } - trace!("{label} instalation completed"); + trace!("{label} installation completed"); Ok(compiler_path) }) } From d2311c289a1e20a5b61d4d4b919f8977146d5aa4 Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 14:55:34 -0300 Subject: [PATCH 097/107] do not check against create2 deployer evm hash for zk --- crates/evm/core/src/utils.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index af678c70d..b7e9513e8 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -217,9 +217,13 @@ pub fn create2_handler_register( let mut code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash; // NOTE(zk): We check which deployer we are using to separate the logic for zkSync // and original foundry. + // TODO(zk): adding this check to skip comparing to evm create2 deployer + // hash, should we compare vs zkevm one? + let mut zk_is_create2_deployer = false; if call_inputs.target_address == DEFAULT_CREATE2_DEPLOYER_ZKSYNC { code_hash = ctx.evm.load_account(call_inputs.target_address)?.info.code_hash; - }; + zk_is_create2_deployer = true; + } if code_hash == KECCAK_EMPTY { return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { result: InterpreterResult { @@ -229,7 +233,7 @@ pub fn create2_handler_register( }, memory_offset: 0..0, }))) - } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH && !zk_is_create2_deployer { return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { result: InterpreterResult { result: InstructionResult::Revert, From 01da3d4db26ccec069657d2d9be5f964e95154a8 Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 15:01:38 -0300 Subject: [PATCH 098/107] cargo cheats --- crates/cheatcodes/assets/cheatcodes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index d2c9ea2ce..6bd4a67b3 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -10500,4 +10500,4 @@ "safety": "safe" } ] -} +} \ No newline at end of file From c012874ddcdc5bcf6e1fd3e53c9009f161e1abc1 Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 15:15:56 -0300 Subject: [PATCH 099/107] update transact/inspect evm failure prompt --- crates/evm/evm/src/executors/strategy.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/evm/evm/src/executors/strategy.rs b/crates/evm/evm/src/executors/strategy.rs index 2d20b4159..502f9ede0 100644 --- a/crates/evm/evm/src/executors/strategy.rs +++ b/crates/evm/evm/src/executors/strategy.rs @@ -99,7 +99,7 @@ impl ExecutorStrategy for EvmExecutorStrategy { ) -> eyre::Result { let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); - let res = evm.transact().wrap_err("backend: failed while inspecting")?; + let res = evm.transact().wrap_err("EVM error")?; env.env = evm.context.evm.inner.env; @@ -120,7 +120,7 @@ impl ExecutorStrategy for EvmExecutorStrategy { ) -> eyre::Result { let mut evm = crate::utils::new_evm_with_inspector(db, env.clone(), inspector); - let res = evm.transact().wrap_err("backend: failed while inspecting")?; + let res = evm.transact().wrap_err("EVM error")?; env.env = evm.context.evm.inner.env; From 980b02df7112e51cee3f0a22587ce0d7ffd3c79a Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 15:43:23 -0300 Subject: [PATCH 100/107] Try bigger nextest timeout --- .config/nextest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 56f40c364..21c36243b 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,6 +1,6 @@ [profile.default] retries = { backoff = "exponential", count = 2, delay = "2s", jitter = true } -slow-timeout = { period = "1m", terminate-after = 3 } +slow-timeout = { period = "2m", terminate-after = 3 } [[profile.default.overrides]] filter = "test(/ext_integration|can_test_forge_std/)" From bc75c6daed88348fd850fa5b1d931e90ef102b2b Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 16:35:50 -0300 Subject: [PATCH 101/107] comment out invariant test --- crates/forge/tests/it/zk/invariant.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index 308ca0d86..98be007c8 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -7,6 +7,7 @@ use forge::revm::primitives::SpecId; use foundry_config::Config; use foundry_test_utils::Filter; +/* #[tokio::test(flavor = "multi_thread")] async fn test_zk_invariant_deposit() { let mut runner = TEST_DATA_DEFAULT.runner_zksync(); @@ -15,10 +16,11 @@ async fn test_zk_invariant_deposit() { let mut config = Config::clone(&runner.config); config.invariant.no_zksync_reserved_addresses = true; config.invariant.fail_on_revert = true; - config.invariant.runs = 10; + config.invariant.runs = 1; runner.config = Arc::new(config); let filter = Filter::new(".*", "ZkInvariantTest", ".*"); TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } +*/ From 3b14a3572e65ed33d8d10b3239cff57cb48b12b6 Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 16:36:39 -0300 Subject: [PATCH 102/107] comment out bis --- crates/forge/tests/it/zk/invariant.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index 98be007c8..74dcd223a 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -1,5 +1,6 @@ //! Invariant tests +/* use std::sync::Arc; use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; @@ -7,7 +8,6 @@ use forge::revm::primitives::SpecId; use foundry_config::Config; use foundry_test_utils::Filter; -/* #[tokio::test(flavor = "multi_thread")] async fn test_zk_invariant_deposit() { let mut runner = TEST_DATA_DEFAULT.runner_zksync(); From 0e5ad05c5161ff6d26d31afc850c415e646eebe7 Mon Sep 17 00:00:00 2001 From: elfedy Date: Fri, 20 Dec 2024 17:49:39 -0300 Subject: [PATCH 103/107] Use lower default depth for zksync test config and revert slow-timeout change --- .config/nextest.toml | 2 +- crates/forge/tests/it/test_helpers.rs | 1 + crates/forge/tests/it/zk/invariant.rs | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 21c36243b..56f40c364 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,6 +1,6 @@ [profile.default] retries = { backoff = "exponential", count = 2, delay = "2s", jitter = true } -slow-timeout = { period = "2m", terminate-after = 3 } +slow-timeout = { period = "1m", terminate-after = 3 } [[profile.default.overrides]] filter = "test(/ext_integration|can_test_forge_std/)" diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 2a01c5029..a9a717e29 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -203,6 +203,7 @@ impl ForgeTestProfile { zk_config.zksync.optimizer_mode = '3'; zk_config.zksync.zksolc = Some(foundry_config::SolcReq::Version(Version::new(1, 5, 7))); zk_config.fuzz.no_zksync_reserved_addresses = true; + zk_config.invariant.depth = 15; zk_config } diff --git a/crates/forge/tests/it/zk/invariant.rs b/crates/forge/tests/it/zk/invariant.rs index 74dcd223a..308ca0d86 100644 --- a/crates/forge/tests/it/zk/invariant.rs +++ b/crates/forge/tests/it/zk/invariant.rs @@ -1,6 +1,5 @@ //! Invariant tests -/* use std::sync::Arc; use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; @@ -16,11 +15,10 @@ async fn test_zk_invariant_deposit() { let mut config = Config::clone(&runner.config); config.invariant.no_zksync_reserved_addresses = true; config.invariant.fail_on_revert = true; - config.invariant.runs = 1; + config.invariant.runs = 10; runner.config = Arc::new(config); let filter = Filter::new(".*", "ZkInvariantTest", ".*"); TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; } -*/ From f54949de7ffee54cb0004f83da06a4ae081bc04e Mon Sep 17 00:00:00 2001 From: elfedy Date: Tue, 24 Dec 2024 09:10:23 -0300 Subject: [PATCH 104/107] make test_helpers compile --- crates/forge/tests/it/test_helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 7da206659..d7472c6a7 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -4,7 +4,7 @@ use alloy_chains::NamedChain; use alloy_primitives::U256; use forge::{ executors::strategy::ExecutorStrategy, revm::primitives::SpecId, MultiContractRunner, - MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, + MultiContractRunnerBuilder, }; use foundry_cli::utils; use foundry_compilers::{ From 6d12151c716746cb4d551b4f447a55b7213c7439 Mon Sep 17 00:00:00 2001 From: elfedy Date: Tue, 24 Dec 2024 09:42:03 -0300 Subject: [PATCH 105/107] fix EVM error --- crates/evm/core/src/backend/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/core/src/backend/strategy.rs b/crates/evm/core/src/backend/strategy.rs index 2160da098..3025fdfbc 100644 --- a/crates/evm/core/src/backend/strategy.rs +++ b/crates/evm/core/src/backend/strategy.rs @@ -145,7 +145,7 @@ impl BackendStrategyRunner for EvmBackendStrategyRunner { ) -> Result { let mut evm = crate::utils::new_evm_with_inspector(backend, env.clone(), inspector); - let res = evm.transact().wrap_err("backend: failed while inspecting")?; + let res = evm.transact().wrap_err("EVM error")?; env.env = evm.context.evm.inner.env; From c557dc8d1abeec51eaa8091214f1709ec7e0f2cd Mon Sep 17 00:00:00 2001 From: elfedy Date: Tue, 24 Dec 2024 16:12:14 -0300 Subject: [PATCH 106/107] bring back latest linking fixes --- .../compilers/src/artifacts/contract.rs | 29 +++++++++++---- crates/zksync/compilers/src/artifacts/mod.rs | 23 +++++++++++- .../src/compilers/artifact_output/zk.rs | 2 ++ .../compilers/artifact_output/zk/bytecode.rs | 36 ++++++++++++++++++- .../test-data/yul-sample/SimpleStore.yul | 11 ------ .../test-data/yul-sample/SimpleStore.yul.json | 1 + 6 files changed, 82 insertions(+), 20 deletions(-) delete mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul create mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json diff --git a/crates/zksync/compilers/src/artifacts/contract.rs b/crates/zksync/compilers/src/artifacts/contract.rs index 3b9be91ca..0e64e2ab7 100644 --- a/crates/zksync/compilers/src/artifacts/contract.rs +++ b/crates/zksync/compilers/src/artifacts/contract.rs @@ -3,7 +3,7 @@ use crate::artifacts::EraVM; use alloy_json_abi::JsonAbi; use foundry_compilers_artifacts_solc::{ Bytecode, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow, - CompactContractRef, CompactDeployedBytecode, DevDoc, Offsets, StorageLayout, UserDoc, + CompactContractRef, CompactDeployedBytecode, DevDoc, Evm, Offsets, StorageLayout, UserDoc, }; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, collections::BTreeMap}; @@ -35,9 +35,12 @@ pub struct Contract { /// The contract factory dependencies. #[serde(default, skip_serializing_if = "Option::is_none")] pub factory_dependencies: Option>, - /// EVM-related outputs + /// EraVM-related outputs #[serde(default, skip_serializing_if = "Option::is_none")] pub eravm: Option, + /// EVM-related outputs (deprecated) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub evm: Option, /// The contract's unlinked libraries #[serde(default)] pub missing_libraries: Vec, @@ -80,11 +83,21 @@ impl Contract { /// Get bytecode pub fn bytecode(&self) -> Option { - self.eravm.as_ref().and_then(|eravm| eravm.bytecode(self.is_unlinked())).map(|object| { - let mut bytecode: Bytecode = object.into(); - bytecode.link_references = self.link_references(); - bytecode - }) + self.eravm + .as_ref() + .and_then(|eravm| eravm.bytecode(self.is_unlinked())) + .or_else(|| { + self.evm + .as_ref() + .and_then(|evm| evm.bytecode.as_ref()) + .map(|bytecode| &bytecode.object) + .cloned() + }) + .map(|object| { + let mut bytecode: Bytecode = object.into(); + bytecode.link_references = self.link_references(); + bytecode + }) } } @@ -126,6 +139,8 @@ impl<'a> From<&'a Contract> for CompactContractRef<'a> { fn from(c: &'a Contract) -> Self { let (bin, bin_runtime) = if let Some(ref eravm) = c.eravm { (eravm.bytecode.as_ref(), eravm.bytecode.as_ref()) + } else if let Some(ref evm) = c.evm { + (evm.bytecode.as_ref().map(|c| &c.object), evm.bytecode.as_ref().map(|c| &c.object)) } else { (None, None) }; diff --git a/crates/zksync/compilers/src/artifacts/mod.rs b/crates/zksync/compilers/src/artifacts/mod.rs index d21befa2d..104a70c5c 100644 --- a/crates/zksync/compilers/src/artifacts/mod.rs +++ b/crates/zksync/compilers/src/artifacts/mod.rs @@ -1,6 +1,6 @@ //! zksolc artifacts to be used in `foundry-compilers` use foundry_compilers_artifacts_solc::{ - BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles, + Bytecode, BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles, }; use semver::Version; @@ -56,6 +56,27 @@ impl CompilerOutput { } } +/// Evm zksolc output field (deprecated) +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Evm { + /// The contract EraVM assembly code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub assembly: Option, + /// The contract EVM legacy assembly code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub legacy_assembly: Option, + /// The contract bytecode. + /// Is reset by that of EraVM before yielding the compiled project artifacts. + pub bytecode: Option, + /// The list of function hashes + #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] + pub method_identifiers: BTreeMap, + /// The extra EVMLA metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub extra_metadata: Option, +} + /// `zksolc` eravm output field #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs index 0ece4b670..ec146cfe0 100644 --- a/crates/zksync/compilers/src/compilers/artifact_output/zk.rs +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk.rs @@ -126,6 +126,7 @@ impl ArtifactOutput for ZkArtifactOutput { devdoc, storage_layout, eravm, + evm, ir_optimized, hash, factory_dependencies, @@ -134,6 +135,7 @@ impl ArtifactOutput for ZkArtifactOutput { let (bytecode, assembly) = eravm .map(|eravm| (eravm.bytecode(is_unlinked), eravm.assembly)) + .or_else(|| evm.map(|evm| (evm.bytecode.map(|bc| bc.object), evm.assembly))) .unwrap_or_else(|| (None, None)); let bytecode = bytecode .map(|object| ZkArtifactBytecode::with_object(object, is_unlinked, missing_libraries)); diff --git a/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs b/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs index ef0473c74..48088aa45 100644 --- a/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs +++ b/crates/zksync/compilers/src/compilers/artifact_output/zk/bytecode.rs @@ -7,9 +7,21 @@ use foundry_compilers_artifacts_solc::{ }; use serde::{Deserialize, Serialize}; +/// This will serialize the bytecode data without a `0x` prefix +/// +/// Equivalent of solc artifact bytecode's +/// [`serialize_bytecode_without_prefix`](foundry_compilers_artifacts::solc::bytecode::serialize_bytecode_without_prefix) +pub fn serialize_bytes_without_prefix(code: &Bytes, s: S) -> Result +where + S: serde::Serializer, +{ + s.serialize_str(&alloy_primitives::hex::encode(code)) +} + /// Bytecode compiled by zksolc #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct ZkArtifactBytecode { + #[serde(serialize_with = "serialize_bytes_without_prefix")] object: Bytes, is_unlinked: bool, @@ -34,7 +46,8 @@ impl ZkArtifactBytecode { Self { object, is_unlinked, missing_libraries } } - fn link_references(&self) -> BTreeMap>> { + /// Get link references + pub fn link_references(&self) -> BTreeMap>> { Contract::missing_libs_to_link_references(self.missing_libraries.as_slice()) } @@ -65,3 +78,24 @@ impl From for CompactDeployedBytecode { Self { bytecode: Some(bcode.into()), immutable_references: BTreeMap::default() } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialized_bytecode_is_not_prefixed() { + let object = Bytes::from(vec![0xDEu8, 0xAD, 0xBE, 0xEF]); + let sample = ZkArtifactBytecode { object, is_unlinked: false, missing_libraries: vec![] }; + + let json_str = + serde_json::to_string(&sample).expect("able to serialize artifact bytecode as json"); + + let deserialized: serde_json::Value = + serde_json::from_str(&json_str).expect("able to deserialize json"); + + let bytecode_str = deserialized["object"].as_str().expect(".object to be a string"); + + assert!(!bytecode_str.starts_with("0x")); + } +} diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul deleted file mode 100644 index cef5d9bc2..000000000 --- a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul +++ /dev/null @@ -1,11 +0,0 @@ -object "SimpleStore" { - code { - datacopy(0, dataoffset("SimpleStore_deployed"), datasize("SimpleStore_deployed")) - return(0, datasize("SimpleStore_deployed")) - } - object "SimpleStore_deployed" { - code { - calldatacopy(0, 0, 36) // write calldata to memory - } - } -} diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json new file mode 100644 index 000000000..5966bcf94 --- /dev/null +++ b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json @@ -0,0 +1 @@ +{"abi":null,"bytecode":{"object":"00000001002001900000000c0000c13d000000002101043c000000000010043f000000200100043d0000000701100197000000000202043b0000000802200197000000000112019f000000200010043f0000000001000019000000000001042d000000240000043f0000002001000039000001000010044300000120000004430000000601000041000000130001042e0000001200000432000000130001042e0000001400010430000000000000000000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e0e9492643af2958d06e962a0b194ca36037ef49d6167e028f1f053a54c571","is_unlinked":false,"missing_libraries":[]},"storageLayout":{"storage":[],"types":{}},"userdoc":{},"devdoc":{},"hash":"0100000b7acef21b9ea8b7897d4ebb51ecaec207df8038a10bc61b904c5c9e4b","factoryDependencies":{},"id":0} \ No newline at end of file From 778786cff4c59d4eceefb7efa8e86f535f0ebbae Mon Sep 17 00:00:00 2001 From: elfedy Date: Tue, 24 Dec 2024 16:39:00 -0300 Subject: [PATCH 107/107] honey I deleted the wrong file --- .../compilers/test-data/yul-sample/SimpleStore.yul | 11 +++++++++++ .../test-data/yul-sample/SimpleStore.yul.json | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul delete mode 100644 crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul new file mode 100644 index 000000000..cef5d9bc2 --- /dev/null +++ b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul @@ -0,0 +1,11 @@ +object "SimpleStore" { + code { + datacopy(0, dataoffset("SimpleStore_deployed"), datasize("SimpleStore_deployed")) + return(0, datasize("SimpleStore_deployed")) + } + object "SimpleStore_deployed" { + code { + calldatacopy(0, 0, 36) // write calldata to memory + } + } +} diff --git a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json b/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json deleted file mode 100644 index 5966bcf94..000000000 --- a/crates/zksync/compilers/test-data/yul-sample/SimpleStore.yul.json +++ /dev/null @@ -1 +0,0 @@ -{"abi":null,"bytecode":{"object":"00000001002001900000000c0000c13d000000002101043c000000000010043f000000200100043d0000000701100197000000000202043b0000000802200197000000000112019f000000200010043f0000000001000019000000000001042d000000240000043f0000002001000039000001000010044300000120000004430000000601000041000000130001042e0000001200000432000000130001042e0000001400010430000000000000000000000000000000000000000000000000000000020000000000000000000000000000004000000100000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004e0e9492643af2958d06e962a0b194ca36037ef49d6167e028f1f053a54c571","is_unlinked":false,"missing_libraries":[]},"storageLayout":{"storage":[],"types":{}},"userdoc":{},"devdoc":{},"hash":"0100000b7acef21b9ea8b7897d4ebb51ecaec207df8038a10bc61b904c5c9e4b","factoryDependencies":{},"id":0} \ No newline at end of file