From 8a60077c9e0de08cd753d1bbdb627cccf5a70a81 Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Thu, 22 Feb 2024 14:30:12 -1000 Subject: [PATCH 01/15] [cast] Fix cast wallet verify (#7215) * Fix cast wallet verify * fmt * fix(cast): use recover_address_from_msg * chore: add test, abstract addr recovery for testing --------- Co-authored-by: Enrique Ortiz --- crates/cast/bin/cmd/wallet/mod.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 240134ef933b..c5c045b75c80 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{Address, Signature, B256}; +use alloy_primitives::{Address, Signature}; use alloy_signer::{ coins_bip39::{English, Mnemonic}, LocalWallet, MnemonicBuilder, Signer as AlloySigner, @@ -12,7 +12,7 @@ use foundry_config::Config; use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; use rand::thread_rng; use serde_json::json; -use std::{path::Path, str::FromStr}; +use std::path::Path; use yansi::Paint; pub mod vanity; @@ -273,9 +273,8 @@ impl WalletSubcommands { println!("0x{sig}"); } WalletSubcommands::Verify { message, signature, address } => { - let recovered_address = - signature.recover_address_from_prehash(&B256::from_str(&message)?)?; - if recovered_address == address { + let recovered_address = Self::recover_address_from_message(&message, &signature)?; + if address == recovered_address { println!("Validation succeeded. Address {address} signed this message."); } else { println!("Validation failed. Address {address} did not sign this message."); @@ -355,6 +354,11 @@ flag to set your key via: Ok(()) } + /// Recovers an address from the specified message and signature + fn recover_address_from_message(message: &str, signature: &Signature) -> Result
{ + Ok(signature.recover_address_from_msg(message)?) + } + fn hex_str_to_bytes(s: &str) -> Result> { Ok(match s.strip_prefix("0x") { Some(data) => hex::decode(data).wrap_err("Could not decode 0x-prefixed string.")?, @@ -365,6 +369,10 @@ flag to set your key via: #[cfg(test)] mod tests { + use std::str::FromStr; + + use alloy_primitives::address; + use super::*; #[test] @@ -393,6 +401,17 @@ mod tests { } } + #[test] + fn can_verify_signed_hex_message() { + let message = "hello"; + let signature = Signature::from_str("f2dd00eac33840c04b6fc8a5ec8c4a47eff63575c2bc7312ecb269383de0c668045309c423484c8d097df306e690c653f8e1ec92f7f6f45d1f517027771c3e801c").unwrap(); + let address = address!("28A4F420a619974a2393365BCe5a7b560078Cc13"); + let recovered_address = + WalletSubcommands::recover_address_from_message(message, &signature); + assert!(recovered_address.is_ok()); + assert_eq!(address, recovered_address.unwrap()); + } + #[test] fn can_parse_wallet_sign_data() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "--data", "{ ... }"]); From 524721aba933ee69429babd39dddf0c60c7fa923 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 23 Feb 2024 19:18:51 +0400 Subject: [PATCH 02/15] fix(forge): prefer --from if specified for `cast call` (#7218) * fix(forge): use --from if specified for call * Update crates/wallets/src/wallet.rs Co-authored-by: Enrique * fmt --------- Co-authored-by: Enrique --- crates/wallets/src/wallet.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index a15f805b93fc..0cb06980df65 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -130,12 +130,18 @@ of the unlocked account you want to use, or provide the --from flag with the add Ok(signer) } - /// Returns the sender address of the signer or `from`. + /// This function prefers the `from` field and may return a different address from the + /// configured signer + /// If from is specified, returns it + /// If from is not specified, but there is a signer configured, returns the signer's address + /// If from is not specified and there is no signer configured, returns zero address pub async fn sender(&self) -> Address { - if let Ok(signer) = self.signer().await { + if let Some(from) = self.from { + from + } else if let Ok(signer) = self.signer().await { signer.address().to_alloy() } else { - self.from.unwrap_or(Address::ZERO) + Address::ZERO } } } From cf1ca626863228e4944bc981a19b77031195ed76 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:06:23 +0200 Subject: [PATCH 03/15] feat: don't request Solc JSON AST unless absolutely necessary (#7197) * feat: don't request Solc JSON AST unless absolutely necessary * fix: don't require AST just for the path of a file * feat: add `--abi` and `abi = ` config values * fmt * fix config * fix: keep AST in build_info --- crates/chisel/src/session_source.rs | 13 ++---- crates/cli/src/opts/build/core.rs | 4 ++ crates/cli/src/opts/build/mod.rs | 5 +++ crates/cli/src/utils/mod.rs | 2 +- crates/common/src/compile.rs | 30 ++++++++++++- crates/common/src/contracts.rs | 10 ++--- crates/config/README.md | 1 + crates/config/src/lib.rs | 53 +++++++++++++--------- crates/forge/bin/cmd/coverage.rs | 67 +++++++++++++--------------- crates/forge/bin/cmd/flatten.rs | 9 ++-- crates/forge/bin/cmd/script/build.rs | 39 +++------------- crates/forge/bin/cmd/script/cmd.rs | 2 +- crates/forge/bin/cmd/test/mod.rs | 25 +++-------- crates/forge/tests/cli/config.rs | 3 +- 14 files changed, 130 insertions(+), 133 deletions(-) diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index c827454e97c4..47660ba091ae 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -318,15 +318,10 @@ impl SessionSource { let mut sources = Sources::new(); sources.insert(self.file_name.clone(), Source::new(self.to_repl_source())); + let remappings = self.config.foundry_config.get_all_remappings().collect::>(); + // Include Vm.sol if forge-std remapping is not available - if !self.config.no_vm && - !self - .config - .foundry_config - .get_all_remappings() - .into_iter() - .any(|r| r.name.starts_with("forge-std")) - { + if !self.config.no_vm && !remappings.iter().any(|r| r.name.starts_with("forge-std")) { sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE)); } @@ -337,7 +332,7 @@ impl SessionSource { .expect("Solidity source not found"); // get all remappings from the config - compiler_input.settings.remappings = self.config.foundry_config.get_all_remappings(); + compiler_input.settings.remappings = remappings; // We also need to enforce the EVM version that the user has specified. compiler_input.settings.evm_version = Some(self.config.foundry_config.evm_version); diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index 02c14a90fa6e..dd01710bd2f2 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -216,6 +216,10 @@ impl Provider for CoreBuildArgs { dict.insert("build_info".to_string(), self.build_info.into()); } + if self.compiler.ast { + dict.insert("ast".to_string(), true.into()); + } + if self.compiler.optimize { dict.insert("optimizer".to_string(), self.compiler.optimize.into()); } diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index d502de2d41b9..0b97ed2dfe89 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -15,6 +15,11 @@ pub use self::paths::ProjectPathsArgs; #[derive(Clone, Debug, Default, Serialize, Parser)] #[clap(next_help_heading = "Compiler options")] pub struct CompilerArgs { + /// Includes the AST as JSON in the compiler output. + #[clap(long, help_heading = "Compiler options")] + #[serde(skip)] + pub ast: bool, + /// The target EVM version. #[clap(long, value_name = "VERSION")] #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index f900c50b071d..99fcceb3e5fd 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -30,7 +30,7 @@ pub use foundry_config::utils::*; /// Deterministic fuzzer seed used for gas snapshots and coverage reports. /// /// The keccak256 hash of "foundry rulez" -pub static STATIC_FUZZ_SEED: [u8; 32] = [ +pub const STATIC_FUZZ_SEED: [u8; 32] = [ 0x01, 0x00, 0xfa, 0x69, 0xa5, 0xf1, 0x71, 0x0a, 0x95, 0xcd, 0xef, 0x94, 0x88, 0x9b, 0x02, 0x84, 0x5d, 0x64, 0x0b, 0x19, 0xad, 0xf0, 0xe3, 0x57, 0xb8, 0xd4, 0xbe, 0x7d, 0x49, 0xee, 0x70, 0xe6, ]; diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 15debf8a539b..459ae516e90e 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -2,10 +2,10 @@ use crate::{compact_to_contract, glob::GlobMatcher, term::SpinnerReporter, TestFunctionExt}; use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Table}; -use eyre::Result; +use eyre::{Context, Result}; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ - artifacts::{BytecodeObject, ContractBytecodeSome}, + artifacts::{BytecodeObject, CompactContractBytecode, ContractBytecodeSome}, remappings::Remapping, report::{BasicStdoutReporter, NoReporter, Report}, Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, ProjectPathsConfig, @@ -281,6 +281,32 @@ pub struct ContractSources { } impl ContractSources { + /// Collects the contract sources and artifacts from the project compile output. + pub fn from_project_output( + output: &ProjectCompileOutput, + root: &Path, + ) -> Result { + let mut sources = ContractSources::default(); + for (id, artifact) in output.artifact_ids() { + if let Some(file_id) = artifact.id { + let abs_path = root.join(&id.path); + let source_code = std::fs::read_to_string(abs_path).wrap_err_with(|| { + format!("failed to read artifact source file for `{}`", id.identifier()) + })?; + let compact = CompactContractBytecode { + abi: artifact.abi.clone(), + bytecode: artifact.bytecode.clone(), + deployed_bytecode: artifact.deployed_bytecode.clone(), + }; + let contract = compact_to_contract(compact)?; + sources.insert(&id, file_id, source_code, contract); + } else { + warn!(id = id.identifier(), "source not found"); + } + } + Ok(sources) + } + /// Inserts a contract into the sources. pub fn insert( &mut self, diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index ed6c6ae20f71..a1b251b768dd 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -2,6 +2,7 @@ use alloy_json_abi::{Event, Function, JsonAbi}; use alloy_primitives::{hex, Address, Selector, B256}; +use eyre::Result; use foundry_compilers::{ artifacts::{CompactContractBytecode, ContractBytecodeSome}, ArtifactId, ProjectPathsConfig, @@ -33,10 +34,7 @@ impl ContractsByArtifact { /// Finds a contract which has the same contract name or identifier as `id`. If more than one is /// found, return error. - pub fn find_by_name_or_identifier( - &self, - id: &str, - ) -> eyre::Result> { + pub fn find_by_name_or_identifier(&self, id: &str) -> Result> { let contracts = self .iter() .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) @@ -203,9 +201,7 @@ pub fn get_artifact_path(paths: &ProjectPathsConfig, path: &str) -> PathBuf { } /// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome -pub fn compact_to_contract( - contract: CompactContractBytecode, -) -> eyre::Result { +pub fn compact_to_contract(contract: CompactContractBytecode) -> Result { Ok(ContractBytecodeSome { abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?, bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(), diff --git a/crates/config/README.md b/crates/config/README.md index 004ade3851b3..880149f6220d 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -139,6 +139,7 @@ extra_output_files = [] names = false sizes = false via_ir = false +ast = false # caches storage retrieved locally for certain chains and endpoints # can also be restricted to `chains = ["optimism", "mainnet"]` # by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 3c7cc1d5e376..1aa9841e0de2 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -320,6 +320,8 @@ pub struct Config { /// If set to true, changes compilation pipeline to go through the Yul intermediate /// representation. pub via_ir: bool, + /// Whether to include the AST as JSON in the compiler output. + pub ast: bool, /// RPC storage caching settings determines what chains and endpoints to cache pub rpc_storage_caching: StorageCachingConfig, /// Disables storage caching entirely. This overrides any settings made in @@ -670,7 +672,7 @@ impl Config { .set_auto_detect(self.is_auto_detect()) .set_offline(self.offline) .set_cached(cached) - .set_build_info(cached & self.build_info) + .set_build_info(cached && self.build_info) .set_no_artifacts(no_artifacts) .build()?; @@ -770,7 +772,7 @@ impl Config { .tests(&self.test) .scripts(&self.script) .artifacts(&self.out) - .libs(self.libs.clone()) + .libs(self.libs.iter()) .remappings(self.get_all_remappings()); if let Some(build_info_path) = &self.build_info_path { @@ -798,8 +800,8 @@ impl Config { /// contracts/tokens/token.sol /// contracts/math/math.sol /// ``` - pub fn get_all_remappings(&self) -> Vec { - self.remappings.iter().map(|m| m.clone().into()).collect() + pub fn get_all_remappings(&self) -> impl Iterator + '_ { + self.remappings.iter().map(|m| m.clone().into()) } /// Returns the configured rpc jwt secret @@ -1027,6 +1029,7 @@ impl Config { /// `extra_output` fields pub fn configured_artifacts_handler(&self) -> ConfigurableArtifacts { let mut extra_output = self.extra_output.clone(); + // Sourcify verification requires solc metadata output. Since, it doesn't // affect the UX & performance of the compiler, output the metadata files // by default. @@ -1036,7 +1039,7 @@ impl Config { extra_output.push(ContractOutputSelection::Metadata); } - ConfigurableArtifacts::new(extra_output, self.extra_output_files.clone()) + ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().cloned()) } /// Parses all libraries in the form of @@ -1045,28 +1048,30 @@ impl Config { Libraries::parse(&self.libraries) } + /// Returns all libraries with applied remappings. Same as `self.solc_settings()?.libraries`. + pub fn libraries_with_remappings(&self) -> Result { + Ok(self.parsed_libraries()?.with_applied_remappings(&self.project_paths())) + } + /// Returns the configured `solc` `Settings` that includes: - /// - all libraries - /// - the optimizer (including details, if configured) - /// - evm version + /// - all libraries + /// - the optimizer (including details, if configured) + /// - evm version pub fn solc_settings(&self) -> Result { - let libraries = self.parsed_libraries()?.with_applied_remappings(&self.project_paths()); - let optimizer = self.optimizer(); - // By default if no targets are specifically selected the model checker uses all targets. // This might be too much here, so only enable assertion checks. // If users wish to enable all options they need to do so explicitly. let mut model_checker = self.model_checker.clone(); - if let Some(ref mut model_checker_settings) = model_checker { + if let Some(model_checker_settings) = &mut model_checker { if model_checker_settings.targets.is_none() { model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); } } let mut settings = Settings { - optimizer, + libraries: self.libraries_with_remappings()?, + optimizer: self.optimizer(), evm_version: Some(self.evm_version), - libraries, metadata: Some(SettingsMetadata { use_literal_content: Some(self.use_literal_content), bytecode_hash: Some(self.bytecode_hash), @@ -1074,16 +1079,23 @@ impl Config { }), debug: self.revert_strings.map(|revert_strings| DebuggingSettings { revert_strings: Some(revert_strings), + // Not used. debug_info: Vec::new(), }), model_checker, - ..Default::default() + via_ir: Some(self.via_ir), + // Not used. + stop_after: None, + // Set in project paths. + remappings: Vec::new(), + // Set with `with_extra_output` below. + output_selection: Default::default(), } - .with_extra_output(self.configured_artifacts_handler().output_selection()) - .with_ast(); + .with_extra_output(self.configured_artifacts_handler().output_selection()); - if self.via_ir { - settings = settings.with_via_ir(); + // We're keeping AST in `--build-info` for backwards compatibility with HardHat. + if self.ast || self.build_info { + settings = settings.with_ast(); } Ok(settings) @@ -1877,6 +1889,7 @@ impl Default for Config { ignored_file_paths: vec![], deny_warnings: false, via_ir: false, + ast: false, rpc_storage_caching: Default::default(), rpc_endpoints: Default::default(), etherscan: Default::default(), @@ -2885,7 +2898,7 @@ mod tests { // contains additional remapping to the source dir assert_eq!( - config.get_all_remappings(), + config.get_all_remappings().collect::>(), vec![ Remapping::from_str("ds-test/=lib/ds-test/src/").unwrap(), Remapping::from_str("env-lib/=lib/env-lib/").unwrap(), diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 9d2bef5e0edf..3505fc318247 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -88,6 +88,9 @@ impl CoverageArgs { // Set fuzz seed so coverage reports are deterministic config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Coverage analysis requires the Solc AST output. + config.ast = true; + let (project, output) = self.build(&config)?; p_println!(!self.opts.silent => "Analysing contracts..."); let report = self.prepare(&config, output.clone())?; @@ -99,47 +102,41 @@ impl CoverageArgs { /// Builds the project. fn build(&self, config: &Config) -> Result<(Project, ProjectCompileOutput)> { // Set up the project - let project = { - let mut project = config.ephemeral_no_artifacts_project()?; - - if self.ir_minimum { - // TODO: How to detect solc version if the user does not specify a solc version in - // config case1: specify local installed solc ? - // case2: multiple solc versions used and auto_detect_solc == true - if let Some(SolcReq::Version(version)) = &config.solc { - if *version < Version::new(0, 8, 13) { - return Err(eyre::eyre!( + let mut project = config.ephemeral_no_artifacts_project()?; + if self.ir_minimum { + // TODO: How to detect solc version if the user does not specify a solc version in + // config case1: specify local installed solc ? + // case2: multiple solc versions used and auto_detect_solc == true + if let Some(SolcReq::Version(version)) = &config.solc { + if *version < Version::new(0, 8, 13) { + return Err(eyre::eyre!( "viaIR with minimum optimization is only available in Solidity 0.8.13 and above." )); - } } + } - // print warning message - p_println!(!self.opts.silent => "{}", - Paint::yellow( - concat!( - "Warning! \"--ir-minimum\" flag enables viaIR with minimum optimization, which can result in inaccurate source mappings.\n", + // print warning message + let msg = Paint::yellow(concat!( + "Warning! \"--ir-minimum\" flag enables viaIR with minimum optimization, \ + which can result in inaccurate source mappings.\n", "Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n", "Note that \"viaIR\" is only available in Solidity 0.8.13 and above.\n", - "See more:\n", - "https://github.com/foundry-rs/foundry/issues/3357\n" - ))); - - // Enable viaIR with minimum optimization - // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 - // And also in new releases of solidity: - // https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 - project.solc_config.settings = - project.solc_config.settings.with_via_ir_minimum_optimization() - } else { - project.solc_config.settings.optimizer.disable(); - project.solc_config.settings.optimizer.runs = None; - project.solc_config.settings.optimizer.details = None; - project.solc_config.settings.via_ir = None; - } - - project - }; + "See more: https://github.com/foundry-rs/foundry/issues/3357", + )); + p_println!(!self.opts.silent => "{msg}"); + + // Enable viaIR with minimum optimization + // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + // And also in new releases of solidity: + // https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 + project.solc_config.settings = + project.solc_config.settings.with_via_ir_minimum_optimization() + } else { + project.solc_config.settings.optimizer.disable(); + project.solc_config.settings.optimizer.runs = None; + project.solc_config.settings.optimizer.details = None; + project.solc_config.settings.via_ir = None; + } let output = ProjectCompiler::default() .compile(&project)? diff --git a/crates/forge/bin/cmd/flatten.rs b/crates/forge/bin/cmd/flatten.rs index 9831e17cecce..b4c1edcd9c5e 100644 --- a/crates/forge/bin/cmd/flatten.rs +++ b/crates/forge/bin/cmd/flatten.rs @@ -36,13 +36,12 @@ impl FlattenArgs { // flatten is a subset of `BuildArgs` so we can reuse that to get the config let build_args = CoreBuildArgs { project_paths, ..Default::default() }; - - let config = build_args.try_load_config_emit_warnings()?; - - let target_path = dunce::canonicalize(target_path)?; - + let mut config = build_args.try_load_config_emit_warnings()?; + // `Flattener` uses the typed AST for better flattening results. + config.ast = true; let project = config.ephemeral_no_artifacts_project()?; + let target_path = dunce::canonicalize(target_path)?; let compiler_output = ProjectCompiler::new().files([target_path.clone()]).compile(&project); let flattened = match compiler_output { diff --git a/crates/forge/bin/cmd/script/build.rs b/crates/forge/bin/cmd/script/build.rs index ebd502a92a79..4b188d955323 100644 --- a/crates/forge/bin/cmd/script/build.rs +++ b/crates/forge/bin/cmd/script/build.rs @@ -3,11 +3,7 @@ use alloy_primitives::{Address, Bytes}; use eyre::{Context, ContextCompat, Result}; use forge::link::{LinkOutput, Linker}; use foundry_cli::utils::get_cached_entry_by_name; -use foundry_common::{ - compact_to_contract, - compile::{self, ContractSources, ProjectCompiler}, - fs, -}; +use foundry_common::compile::{self, ContractSources, ProjectCompiler}; use foundry_compilers::{ artifacts::{ContractBytecode, ContractBytecodeSome, Libraries}, cache::SolFilesCache, @@ -28,38 +24,15 @@ impl ScriptArgs { /// Compiles the file with auto-detection and compiler params. pub fn build(&mut self, script_config: &mut ScriptConfig) -> Result { let (project, output) = self.get_project_and_output(script_config)?; - let output = output.with_stripped_file_prefixes(project.root()); - - let mut sources: ContractSources = Default::default(); - - let contracts = output - .into_artifacts() - .map(|(id, artifact)| -> Result<_> { - // Sources are only required for the debugger, but it *might* mean that there's - // something wrong with the build and/or artifacts. - if let Some(source) = artifact.source_file() { - let path = source - .ast - .ok_or_else(|| eyre::eyre!("source from artifact has no AST"))? - .absolute_path; - let abs_path = project.root().join(path); - let source_code = fs::read_to_string(abs_path).wrap_err_with(|| { - format!("failed to read artifact source file for `{}`", id.identifier()) - })?; - let contract = artifact.clone().into_contract_bytecode(); - let source_contract = compact_to_contract(contract)?; - sources.insert(&id, source.id, source_code, source_contract); - } else { - warn!(?id, "source not found"); - } - Ok((id, artifact)) - }) - .collect::>()?; + let root = project.root(); + let output = output.with_stripped_file_prefixes(root); + let sources = ContractSources::from_project_output(&output, root)?; + let contracts = output.into_artifacts().collect(); let target = self.find_target(&project, &contracts)?.clone(); script_config.target_contract = Some(target.clone()); - let libraries = script_config.config.solc_settings()?.libraries; + let libraries = script_config.config.libraries_with_remappings()?; let linker = Linker::new(project.root(), contracts); let (highlevel_known_contracts, libraries, predeploy_libraries) = self.link_script_target( diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index c1e0fefb0de3..70fce668017f 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -332,7 +332,7 @@ impl ScriptArgs { script_config.sender_nonce = nonce; let target = script_config.target_contract(); - let libraries = script_config.config.solc_settings()?.libraries; + let libraries = script_config.config.libraries_with_remappings()?; let (highlevel_known_contracts, libraries, predeploy_libraries) = self.link_script_target(&linker, libraries, new_sender, nonce, target.clone())?; diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 4f0f9f29940a..d84817659da7 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -18,7 +18,6 @@ use foundry_cli::{ utils::{self, LoadConfig}, }; use foundry_common::{ - compact_to_contract, compile::{ContractSources, ProjectCompiler}, evm::EvmArgs, shell, @@ -33,7 +32,7 @@ use foundry_config::{ }; use foundry_debugger::Debugger; use regex::Regex; -use std::{collections::BTreeMap, fs, sync::mpsc::channel}; +use std::{collections::BTreeMap, sync::mpsc::channel}; use watchexec::config::{InitConfig, RuntimeConfig}; use yansi::Paint; @@ -213,28 +212,16 @@ impl TestArgs { let outcome = self.run_tests(runner, config, verbosity, &filter, test_options).await?; if should_debug { - let mut sources = ContractSources::default(); - for (id, artifact) in output_clone.unwrap().into_artifacts() { - // Sources are only required for the debugger, but it *might* mean that there's - // something wrong with the build and/or artifacts. - if let Some(source) = artifact.source_file() { - let path = source - .ast - .ok_or_else(|| eyre::eyre!("Source from artifact has no AST."))? - .absolute_path; - let abs_path = project.root().join(&path); - let source_code = fs::read_to_string(abs_path)?; - let contract = artifact.clone().into_contract_bytecode(); - let source_contract = compact_to_contract(contract)?; - sources.insert(&id, source.id, source_code, source_contract); - } - } - // There is only one test. let Some(test) = outcome.into_tests_cloned().next() else { return Err(eyre::eyre!("no tests were executed")); }; + let sources = ContractSources::from_project_output( + output_clone.as_ref().unwrap(), + project.root(), + )?; + // Run the debugger. let mut builder = Debugger::builder() .debug_arenas(test.result.debug.as_slice()) diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 2e4be0c6a8b5..4a2037af87e1 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -99,6 +99,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { ignored_file_paths: vec![], deny_warnings: false, via_ir: true, + ast: false, rpc_storage_caching: StorageCachingConfig { chains: CachedChains::None, endpoints: CachedEndpoints::Remote, @@ -548,7 +549,7 @@ forgetest_init!( pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); let config = cmd.config(); - let remappings = config.get_all_remappings(); + let remappings = config.get_all_remappings().collect::>(); pretty_assertions::assert_eq!( remappings, vec![ From afc87e5da9a2adc97b6be8613ec95c7325532ab3 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:12:22 +0200 Subject: [PATCH 04/15] test: move forge-std test to external tests (#7227) --- .github/scripts/matrices.py | 6 ----- crates/config/src/lib.rs | 6 ++--- crates/forge/tests/cli/ext_integration.rs | 8 +++++++ crates/forge/tests/cli/test_cmd.rs | 27 +---------------------- 4 files changed, 11 insertions(+), 36 deletions(-) diff --git a/.github/scripts/matrices.py b/.github/scripts/matrices.py index 0a47c96a2195..3fdcea115421 100755 --- a/.github/scripts/matrices.py +++ b/.github/scripts/matrices.py @@ -92,12 +92,6 @@ def __init__( n_partitions=2, pr_cross_platform=False, ), - Case( - name="integration / forge-std", - filter="package(=forge) & test(~forge_std)", - n_partitions=1, - pr_cross_platform=False, - ), Case( name="integration / external", filter="package(=forge) & test(~ext_integration)", diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 1aa9841e0de2..f582322a9ae9 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -354,10 +354,8 @@ pub struct Config { /// included in solc's output selection, see also /// [OutputSelection](foundry_compilers::artifacts::output_selection::OutputSelection) pub sparse_mode: bool, - /// Whether to emit additional build info files - /// - /// If set to `true`, `ethers-solc` will generate additional build info json files for every - /// new build, containing the `CompilerInput` and `CompilerOutput` + /// Generates additional build info json files for every new build, containing the + /// `CompilerInput` and `CompilerOutput`. pub build_info: bool, /// The path to the `build-info` directory that contains the build info json files. pub build_info_path: Option, diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index 76c497837be3..3483708f2c37 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -1,5 +1,13 @@ use foundry_test_utils::util::ExtTester; +#[test] +fn forge_std() { + ExtTester::new("foundry-rs", "forge-std", "1d0766bc5d814f117c7b1e643828f7d85024fb51") + // Skip fork tests. + .args(["--nmc", "Fork"]) + .run(); +} + #[test] fn solmate() { ExtTester::new("transmissions11", "solmate", "c892309933b25c03d32b1b0d674df7ae292ba925").run(); diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 25a30a1fd040..58b9b1eb85de 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2,7 +2,7 @@ use foundry_common::rpc; use foundry_config::Config; use foundry_test_utils::util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}; -use std::{path::PathBuf, process::Command, str::FromStr}; +use std::{path::PathBuf, str::FromStr}; // tests that test filters are handled correctly forgetest!(can_set_filter_values, |prj, cmd| { @@ -259,31 +259,6 @@ contract ContractTest is DSTest { ); }); -// checks that we can test forge std successfully -// `forgetest_init!` will install with `forge-std` under `lib/forge-std` -forgetest_init!( - #[serial_test::serial] - can_test_forge_std, - |prj, cmd| { - let forge_std_dir = prj.root().join("lib/forge-std"); - let status = Command::new("git") - .current_dir(&forge_std_dir) - .args(["pull", "origin", "master"]) - .status() - .unwrap(); - if !status.success() { - panic!("failed to update forge-std"); - } - - // execute in subdir - cmd.cmd().current_dir(forge_std_dir); - cmd.args(["test", "--root", "."]); - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("[PASS]"), "No tests passed:\n{stdout}"); - assert!(!stdout.contains("[FAIL]"), "Tests failed:\n{stdout}"); - } -); - // tests that libraries are handled correctly in multiforking mode forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { prj.wipe_contracts(); From 739736f4b208c52f2a3ba96a691ce1e4ed67695c Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Feb 2024 06:20:47 +0200 Subject: [PATCH 05/15] test: remove serial_test from forge tests (#7226) --- Cargo.lock | 9 +- crates/chisel/Cargo.toml | 2 +- crates/forge/Cargo.toml | 1 - crates/forge/tests/cli/cmd.rs | 33 ++- crates/forge/tests/cli/config.rs | 390 ++++++++++++++----------------- crates/forge/tests/cli/create.rs | 312 ++++++++++++------------- crates/forge/tests/cli/script.rs | 343 ++++++++++++--------------- 7 files changed, 502 insertions(+), 588 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddd120c385d4..a488b1aad732 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2944,7 +2944,6 @@ dependencies = [ "semver 1.0.21", "serde", "serde_json", - "serial_test", "similar", "solang-parser", "strum 0.26.1", @@ -6885,9 +6884,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" dependencies = [ "dashmap", "futures", @@ -6899,9 +6898,9 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" dependencies = [ "proc-macro2", "quote", diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 22d78e64e611..589beaa3ef7f 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -53,7 +53,7 @@ tracing.workspace = true [dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } once_cell = "1" -serial_test = "2" +serial_test = "3" tracing-subscriber.workspace = true [features] diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index cf4640bb8ab4..516575ce543f 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -92,7 +92,6 @@ globset = "0.4" paste = "1.0" path-slash = "0.2" pretty_assertions.workspace = true -serial_test = "2" svm = { package = "svm-rs", version = "0.3", default-features = false, features = ["rustls"] } tempfile = "3" tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index e5d7f712d364..18877265772c 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -614,15 +614,12 @@ contract Foo { }); // test that `forge snapshot` commands work -forgetest!( - #[serial_test::serial] - can_check_snapshot, - |prj, cmd| { - prj.insert_ds_test(); +forgetest!(can_check_snapshot, |prj, cmd| { + prj.insert_ds_test(); - prj.add_source( - "ATest.t.sol", - r#" + prj.add_source( + "ATest.t.sol", + r#" import "./test.sol"; contract ATest is DSTest { function testExample() public { @@ -630,20 +627,18 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("snapshot"); + cmd.arg("snapshot"); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_check_snapshot.stdout"), - ); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_check_snapshot.stdout"), + ); - cmd.arg("--check"); - let _ = cmd.output(); - } -); + cmd.arg("--check"); + let _ = cmd.output(); +}); // test that `forge build` does not print `(with warnings)` if file path is ignored forgetest!(can_compile_without_warnings_ignored_file_paths, |prj, cmd| { diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 4a2037af87e1..f6fe89efb2a0 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -130,195 +130,172 @@ forgetest!(can_extract_config_values, |prj, cmd| { }); // tests config gets printed to std out -forgetest!( - #[serial_test::serial] - can_show_config, - |prj, cmd| { - cmd.arg("config"); - let expected = - Config::load_with_root(prj.root()).to_string_pretty().unwrap().trim().to_string(); - assert_eq!(expected, cmd.stdout_lossy().trim().to_string()); - } -); +forgetest!(can_show_config, |prj, cmd| { + cmd.arg("config"); + let expected = + Config::load_with_root(prj.root()).to_string_pretty().unwrap().trim().to_string(); + assert_eq!(expected, cmd.stdout_lossy().trim().to_string()); +}); // checks that config works // - foundry.toml is properly generated // - paths are resolved properly // - config supports overrides from env, and cli -forgetest_init!( - #[serial_test::serial] - can_override_config, - |prj, cmd| { - cmd.set_current_dir(prj.root()); - let foundry_toml = prj.root().join(Config::FILE_NAME); - assert!(foundry_toml.exists()); - - let profile = Config::load_with_root(prj.root()); - // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - - // ensure remappings contain test - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - // the loaded config has resolved, absolute paths - assert_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", - Remapping::from(profile.remappings[0].clone()).to_string() - ); - - cmd.arg("config"); - let expected = profile.to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - - // remappings work - let remappings_txt = - prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - assert_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-file").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - // env vars work - std::env::set_var("DAPP_REMAPPINGS", "ds-test/=lib/forge-std/lib/ds-test/from-env/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - assert_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-env").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - let config = - prj.config_from_output(["--remappings", "ds-test/=lib/forge-std/lib/ds-test/from-cli"]); - assert_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-cli").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - let config = prj.config_from_output(["--remappings", "other-key/=lib/other/"]); - assert_eq!(config.remappings.len(), 3); - assert_eq!( - format!("other-key/={}/", prj.root().join("lib/other").to_slash_lossy()), - // As CLI has the higher priority, it'll be found at the first slot. - Remapping::from(config.remappings[0].clone()).to_string() - ); - - std::env::remove_var("DAPP_REMAPPINGS"); - pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); - - cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); - let expected = profile.into_basic().to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - } -); - -forgetest_init!( - #[serial_test::serial] - can_parse_remappings_correctly, - |prj, cmd| { - cmd.set_current_dir(prj.root()); - let foundry_toml = prj.root().join(Config::FILE_NAME); - assert!(foundry_toml.exists()); - - let profile = Config::load_with_root(prj.root()); - // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - let [r, _] = &profile.remappings[..] else { unreachable!() }; - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", r.to_string()); - - // the loaded config has resolved, absolute paths - assert_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", - Remapping::from(r.clone()).to_string() - ); - - cmd.arg("config"); - let expected = profile.to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - - let install = |cmd: &mut TestCommand, dep: &str| { - cmd.forge_fuse().args(["install", dep, "--no-commit"]); - cmd.assert_non_empty_stdout(); - }; - - install(&mut cmd, "transmissions11/solmate"); - let profile = Config::load_with_root(prj.root()); - // remappings work - let remappings_txt = prj.create_file( - "remappings.txt", - "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", - ); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - // trailing slashes are removed on windows `to_slash_lossy` - let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); - #[cfg(windows)] - let path = path + "/"; - assert_eq!( - format!("solmate/={path}"), - Remapping::from(config.remappings[0].clone()).to_string() - ); - // As this is an user-generated remapping, it is not removed, even if it points to the same - // location. - assert_eq!( - format!("solmate-contracts/={path}"), - Remapping::from(config.remappings[1].clone()).to_string() - ); - pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); - - cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); - let expected = profile.into_basic().to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - } -); - -forgetest_init!( - #[serial_test::serial] - can_detect_config_vals, - |prj, _cmd| { - let url = "http://127.0.0.1:8545"; - let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); - assert!(!config.auto_detect_solc); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - - let mut config = Config::load_with_root(prj.root()); - config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); - config.auto_detect_solc = false; - // write to `foundry.toml` - prj.create_file( - Config::FILE_NAME, - &config.to_string_pretty().unwrap().replace("eth_rpc_url", "eth-rpc-url"), - ); - let config = prj.config_from_output(["--force"]); - assert!(!config.auto_detect_solc); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - } -); +forgetest_init!(can_override_config, |prj, cmd| { + cmd.set_current_dir(prj.root()); + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(foundry_toml.exists()); + + let profile = Config::load_with_root(prj.root()); + // ensure that the auto-generated internal remapping for forge-std's ds-test exists + assert_eq!(profile.remappings.len(), 2); + assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); + + // ensure remappings contain test + assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); + // the loaded config has resolved, absolute paths + assert_eq!( + "ds-test/=lib/forge-std/lib/ds-test/src/", + Remapping::from(profile.remappings[0].clone()).to_string() + ); + + cmd.arg("config"); + let expected = profile.to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); + + // remappings work + let remappings_txt = + prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); + let config = forge_utils::load_config_with_root(Some(prj.root().into())); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-file").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + // env vars work + std::env::set_var("DAPP_REMAPPINGS", "ds-test/=lib/forge-std/lib/ds-test/from-env/"); + let config = forge_utils::load_config_with_root(Some(prj.root().into())); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-env").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + let config = + prj.config_from_output(["--remappings", "ds-test/=lib/forge-std/lib/ds-test/from-cli"]); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-cli").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + let config = prj.config_from_output(["--remappings", "other-key/=lib/other/"]); + assert_eq!(config.remappings.len(), 3); + assert_eq!( + format!("other-key/={}/", prj.root().join("lib/other").to_slash_lossy()), + // As CLI has the higher priority, it'll be found at the first slot. + Remapping::from(config.remappings[0].clone()).to_string() + ); + + std::env::remove_var("DAPP_REMAPPINGS"); + pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); + + cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); + let expected = profile.into_basic().to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); +}); + +forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { + cmd.set_current_dir(prj.root()); + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(foundry_toml.exists()); + + let profile = Config::load_with_root(prj.root()); + // ensure that the auto-generated internal remapping for forge-std's ds-test exists + assert_eq!(profile.remappings.len(), 2); + let [r, _] = &profile.remappings[..] else { unreachable!() }; + assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", r.to_string()); + + // the loaded config has resolved, absolute paths + assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", Remapping::from(r.clone()).to_string()); + + cmd.arg("config"); + let expected = profile.to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); + + let install = |cmd: &mut TestCommand, dep: &str| { + cmd.forge_fuse().args(["install", dep, "--no-commit"]); + cmd.assert_non_empty_stdout(); + }; + + install(&mut cmd, "transmissions11/solmate"); + let profile = Config::load_with_root(prj.root()); + // remappings work + let remappings_txt = prj.create_file( + "remappings.txt", + "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", + ); + let config = forge_utils::load_config_with_root(Some(prj.root().into())); + // trailing slashes are removed on windows `to_slash_lossy` + let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); + #[cfg(windows)] + let path = path + "/"; + assert_eq!( + format!("solmate/={path}"), + Remapping::from(config.remappings[0].clone()).to_string() + ); + // As this is an user-generated remapping, it is not removed, even if it points to the same + // location. + assert_eq!( + format!("solmate-contracts/={path}"), + Remapping::from(config.remappings[1].clone()).to_string() + ); + pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); + + cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); + let expected = profile.into_basic().to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); +}); + +forgetest_init!(can_detect_config_vals, |prj, _cmd| { + let url = "http://127.0.0.1:8545"; + let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); + assert!(!config.auto_detect_solc); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); + + let mut config = Config::load_with_root(prj.root()); + config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); + config.auto_detect_solc = false; + // write to `foundry.toml` + prj.create_file( + Config::FILE_NAME, + &config.to_string_pretty().unwrap().replace("eth_rpc_url", "eth-rpc-url"), + ); + let config = prj.config_from_output(["--force"]); + assert!(!config.auto_detect_solc); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); +}); // checks that `clean` removes dapptools style paths -forgetest_init!( - #[serial_test::serial] - can_get_evm_opts, - |prj, _cmd| { - let url = "http://127.0.0.1:8545"; - let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - assert!(config.ffi); - - std::env::set_var("FOUNDRY_ETH_RPC_URL", url); - let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); - let evm_opts: EvmOpts = figment.extract().unwrap(); - assert_eq!(evm_opts.fork_url, Some(url.to_string())); - std::env::remove_var("FOUNDRY_ETH_RPC_URL"); - } -); +forgetest_init!(can_get_evm_opts, |prj, _cmd| { + let url = "http://127.0.0.1:8545"; + let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); + assert!(config.ffi); + + std::env::set_var("FOUNDRY_ETH_RPC_URL", url); + let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); + let evm_opts: EvmOpts = figment.extract().unwrap(); + assert_eq!(evm_opts.fork_url, Some(url.to_string())); + std::env::remove_var("FOUNDRY_ETH_RPC_URL"); +}); // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { @@ -533,33 +510,28 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { // test remappings with closer paths are prioritised // so that `dep/=lib/a/src` will take precedent over `dep/=lib/a/lib/b/src` -forgetest_init!( - #[serial_test::serial] - can_prioritise_closer_lib_remappings, - |prj, cmd| { - let config = cmd.config(); - - // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` - let mut config = config; - config.remappings = - vec![Remapping::from_str("forge-std/=lib/forge-std/src/").unwrap().into()]; - let nested = prj.paths().libraries[0].join("dep1"); - pretty_err(&nested, fs::create_dir_all(&nested)); - let toml_file = nested.join("foundry.toml"); - pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); - - let config = cmd.config(); - let remappings = config.get_all_remappings().collect::>(); - pretty_assertions::assert_eq!( - remappings, - vec![ - "dep1/=lib/dep1/src/".parse().unwrap(), - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), - "forge-std/=lib/forge-std/src/".parse().unwrap() - ] - ); - } -); +forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { + let config = cmd.config(); + + // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` + let mut config = config; + config.remappings = vec![Remapping::from_str("forge-std/=lib/forge-std/src/").unwrap().into()]; + let nested = prj.paths().libraries[0].join("dep1"); + pretty_err(&nested, fs::create_dir_all(&nested)); + let toml_file = nested.join("foundry.toml"); + pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); + + let config = cmd.config(); + let remappings = config.get_all_remappings().collect::>(); + pretty_assertions::assert_eq!( + remappings, + vec![ + "dep1/=lib/dep1/src/".parse().unwrap(), + "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), + "forge-std/=lib/forge-std/src/".parse().unwrap() + ] + ); +}); // test to check that foundry.toml libs section updates on install forgetest!(can_update_libs_section, |prj, cmd| { diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 9cb15954989f..20f0aa7cca5a 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -129,98 +129,87 @@ forgetest!(can_create_oracle_on_mumbai, |prj, cmd| { }); // tests that we can deploy the template contract -forgetest_async!( - #[serial_test::serial] - can_create_template_contract, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - cmd.forge_fuse().args([ - "create", - format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract.stdout"), - ); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract-2nd.stdout"), - ); - } -); +forgetest_async!(can_create_template_contract, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.signer().to_bytes()); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_template_contract.stdout"), + ); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_template_contract-2nd.stdout"), + ); +}); // tests that we can deploy the template contract -forgetest_async!( - #[serial_test::serial] - can_create_using_unlocked, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let dev = handle.dev_accounts().next().unwrap(); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - cmd.forge_fuse().args([ - "create", - format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), - "--rpc-url", - rpc.as_str(), - "--from", - format!("{dev:?}").as_str(), - "--unlocked", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked.stdout"), - ); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked-2nd.stdout"), - ); - } -); +forgetest_async!(can_create_using_unlocked, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let dev = handle.dev_accounts().next().unwrap(); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--from", + format!("{dev:?}").as_str(), + "--unlocked", + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_using_unlocked.stdout"), + ); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_using_unlocked-2nd.stdout"), + ); +}); // tests that we can deploy with constructor args -forgetest_async!( - #[serial_test::serial] - can_create_with_constructor_args, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - prj.add_source( - "ConstructorContract", - r#" +forgetest_async!(can_create_with_constructor_args, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.signer().to_bytes()); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + prj.add_source( + "ConstructorContract", + r#" contract ConstructorContract { string public name; @@ -229,28 +218,28 @@ contract ConstructorContract { } } "#, - ) - .unwrap(); - - cmd.forge_fuse().args([ - "create", - "./src/ConstructorContract.sol:ConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "My Constructor", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_constructor_args.stdout"), - ); - - prj.add_source( - "TupleArrayConstructorContract", - r#" + ) + .unwrap(); + + cmd.forge_fuse().args([ + "create", + "./src/ConstructorContract.sol:ConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "My Constructor", + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_with_constructor_args.stdout"), + ); + + prj.add_source( + "TupleArrayConstructorContract", + r#" struct Point { uint256 x; uint256 y; @@ -260,46 +249,42 @@ contract TupleArrayConstructorContract { constructor(Point[] memory _points) {} } "#, - ) - .unwrap(); - - cmd.forge_fuse().args([ - "create", - "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "[(1,2), (2,3), (3,4)]", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_tuple_constructor_args.stdout"), - ); - } -); + ) + .unwrap(); + + cmd.forge_fuse().args([ + "create", + "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "[(1,2), (2,3), (3,4)]", + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_with_tuple_constructor_args.stdout"), + ); +}); // -forgetest_async!( - #[serial_test::serial] - can_create_and_call, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - prj.add_source( - "UniswapV2Swap", - r#" +forgetest_async!(can_create_and_call, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.signer().to_bytes()); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + prj.add_source( + "UniswapV2Swap", + r#" contract UniswapV2Swap { function pairInfo() public view returns (uint reserveA, uint reserveB, uint totalSupply) { @@ -308,19 +293,18 @@ contract UniswapV2Swap { } "#, - ) - .unwrap(); - - cmd.forge_fuse().args([ - "create", - "./src/UniswapV2Swap.sol:UniswapV2Swap", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - ]); - - let (stdout, _) = cmd.output_lossy(); - assert!(stdout.contains("Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3")); - } -); + ) + .unwrap(); + + cmd.forge_fuse().args([ + "create", + "./src/UniswapV2Swap.sol:UniswapV2Swap", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + ]); + + let (stdout, _) = cmd.output_lossy(); + assert!(stdout.contains("Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3")); +}); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index bcd53afcf3d0..b45ce52cf445 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -444,97 +444,75 @@ forgetest_async!(can_deploy_script_with_lib, |prj, cmd| { .await; }); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_private_key, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_addresses(&[ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployPrivateKey()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(&[( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 3, - )]) - .await; - } -); +forgetest_async!(can_deploy_script_private_key, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_unlocked, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()) - .unlocked() - .add_sig("BroadcastTest", "deployOther()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast); - } -); + tester + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployPrivateKey()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 3, + )]) + .await; +}); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_remember_key, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_addresses(&[ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployRememberKey()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(&[( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 2, - )]) - .await; - } -); +forgetest_async!(can_deploy_unlocked, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_remember_key_and_resume, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .add_deployer(0) - .load_addresses(&[ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployRememberKeyResume()") - .simulate(ScriptOutcome::OkSimulation) - .resume(ScriptOutcome::MissingWallet) - // load missing wallet - .load_private_keys(&[0]) - .await - .run(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(&[( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 1, - )]) - .await - .assert_nonce_increment(&[(0, 2)]) - .await; - } -); + tester + .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()) + .unlocked() + .add_sig("BroadcastTest", "deployOther()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast); +}); + +forgetest_async!(can_deploy_script_remember_key, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployRememberKey()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 2, + )]) + .await; +}); + +forgetest_async!(can_deploy_script_remember_key_and_resume, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .add_deployer(0) + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployRememberKeyResume()") + .simulate(ScriptOutcome::OkSimulation) + .resume(ScriptOutcome::MissingWallet) + // load missing wallet + .load_private_keys(&[0]) + .await + .run(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 1, + )]) + .await + .assert_nonce_increment(&[(0, 2)]) + .await; +}); forgetest_async!(can_resume_script, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; @@ -621,41 +599,33 @@ forgetest_async!(can_deploy_with_create2, |prj, cmd| { .run(ScriptOutcome::ScriptFailed); }); -forgetest_async!( - #[serial_test::serial] - 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()); - - tester - .load_private_keys(&[0]) - .await - .add_sig("BroadcastTestNoLinking", "deployMany()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(&[(0, 25)]) - .await; - } -); +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()); -forgetest_async!( - #[serial_test::serial] - can_deploy_and_simulate_mixed_broadcast_modes, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_private_keys(&[0]) - .await - .add_sig("BroadcastMix", "deployMix()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(&[(0, 15)]) - .await; - } -); + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestNoLinking", "deployMany()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 25)]) + .await; +}); + +forgetest_async!(can_deploy_and_simulate_mixed_broadcast_modes, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastMix", "deployMix()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 15)]) + .await; +}); forgetest_async!(deploy_with_setup, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; @@ -682,81 +652,76 @@ forgetest_async!(fail_broadcast_staticcall, |prj, cmd| { .simulate(ScriptOutcome::StaticCallNotAllowed); }); -forgetest_async!( - #[serial_test::serial] - check_broadcast_log, - |prj, cmd| { - let (api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - // Prepare CREATE2 Deployer - let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); - let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); - api.anvil_set_code(addr, code).await.unwrap(); - - tester - .load_private_keys(&[0]) - .await - .add_sig("BroadcastTestSetup", "run()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(&[(0, 6)]) - .await; - - // Uncomment to recreate the broadcast log - // std::fs::copy( - // "broadcast/Broadcast.t.sol/31337/run-latest.json", - // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast. - // log. json" ), ); - - // Check broadcast logs - // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice, - // transactionIndex and logIndex values since they can change inbetween runs - let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap(); - - let fixtures_log = std::fs::read_to_string( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/fixtures/broadcast.log.json"), - ) - .unwrap(); - let _fixtures_log = re.replace_all(&fixtures_log, ""); +forgetest_async!(check_broadcast_log, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - let run_log = - std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); - let _run_log = re.replace_all(&run_log, ""); + // Prepare CREATE2 Deployer + let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); + let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); + api.anvil_set_code(addr, code).await.unwrap(); - // pretty_assertions::assert_eq!(fixtures_log, run_log); + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestSetup", "run()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 6)]) + .await; - // Uncomment to recreate the sensitive log - // std::fs::copy( - // "cache/Broadcast.t.sol/31337/run-latest.json", - // PathBuf::from(env!("CARGO_MANIFEST_DIR")) - // .join("../../testdata/fixtures/broadcast.sensitive.log.json"), - // ); + // Uncomment to recreate the broadcast log + // std::fs::copy( + // "broadcast/Broadcast.t.sol/31337/run-latest.json", + // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast. + // log. json" ), ); - // Check sensitive logs - // Ignore port number since it can change inbetween runs - let re = Regex::new(r":[0-9]+").unwrap(); + // Check broadcast logs + // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice, + // transactionIndex and logIndex values since they can change inbetween runs + let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap(); - let fixtures_log = std::fs::read_to_string( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/fixtures/broadcast.sensitive.log.json"), - ) - .unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, ""); + let fixtures_log = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/fixtures/broadcast.log.json"), + ) + .unwrap(); + let _fixtures_log = re.replace_all(&fixtures_log, ""); - let run_log = - std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap(); - let run_log = re.replace_all(&run_log, ""); + let run_log = + std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); + let _run_log = re.replace_all(&run_log, ""); - // Clean up carriage return OS differences - let re = Regex::new(r"\r\n").unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, "\n"); - let run_log = re.replace_all(&run_log, "\n"); + // pretty_assertions::assert_eq!(fixtures_log, run_log); - pretty_assertions::assert_eq!(fixtures_log, run_log); - } -); + // Uncomment to recreate the sensitive log + // std::fs::copy( + // "cache/Broadcast.t.sol/31337/run-latest.json", + // PathBuf::from(env!("CARGO_MANIFEST_DIR")) + // .join("../../testdata/fixtures/broadcast.sensitive.log.json"), + // ); + + // Check sensitive logs + // Ignore port number since it can change inbetween runs + let re = Regex::new(r":[0-9]+").unwrap(); + + let fixtures_log = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/fixtures/broadcast.sensitive.log.json"), + ) + .unwrap(); + let fixtures_log = re.replace_all(&fixtures_log, ""); + + let run_log = std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap(); + let run_log = re.replace_all(&run_log, ""); + + // Clean up carriage return OS differences + let re = Regex::new(r"\r\n").unwrap(); + let fixtures_log = re.replace_all(&fixtures_log, "\n"); + let run_log = re.replace_all(&run_log, "\n"); + + pretty_assertions::assert_eq!(fixtures_log, run_log); +}); forgetest_async!(test_default_sender_balance, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; From 18d85533cc28d4c3ea28e9107668122979cb0973 Mon Sep 17 00:00:00 2001 From: RPate97 Date: Sat, 24 Feb 2024 03:24:41 -0800 Subject: [PATCH 06/15] fix(cheatcodes): Properly record call to create2 factory in state diff (#7207) --- crates/cheatcodes/src/inspector.rs | 50 ++++++++++++++-- crates/forge/tests/it/repros.rs | 7 +++ testdata/repros/Issue6634.t.sol | 95 ++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 testdata/repros/Issue6634.t.sol diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index a036ffa597e0..878a037ab976 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1237,7 +1237,13 @@ impl Inspector for Cheatcodes { // Apply the Create2 deployer if self.broadcast.is_some() || self.config.always_use_create_2_factory { - match apply_create2_deployer(data, call, self.prank.as_ref(), self.broadcast.as_ref()) { + match apply_create2_deployer( + data, + call, + self.prank.as_ref(), + self.broadcast.as_ref(), + self.recorded_account_diffs_stack.as_mut(), + ) { Ok(_) => {} Err(err) => return (InstructionResult::Revert, None, gas, Error::encode(err)), }; @@ -1249,6 +1255,15 @@ impl Inspector for Cheatcodes { let address = self.allow_cheatcodes_on_create(data, call); // If `recordAccountAccesses` has been called, record the create if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + // If the create scheme is create2, and the caller is the DEFAULT_CREATE2_DEPLOYER then + // we must add 1 to the depth to account for the call to the create2 factory. + let mut depth = data.journaled_state.depth(); + if let CreateScheme::Create2 { salt: _ } = call.scheme { + if call.caller == DEFAULT_CREATE2_DEPLOYER { + depth += 1; + } + } + // Record the create context as an account access and create a new vector to record all // subsequent account accesses recorded_account_diffs_stack.push(vec![AccountAccess { @@ -1269,7 +1284,7 @@ impl Inspector for Cheatcodes { deployedCode: vec![], // updated on create_end storageAccesses: vec![], // updated on create_end }, - depth: data.journaled_state.depth(), + depth, }]); } @@ -1432,17 +1447,44 @@ fn apply_create2_deployer( call: &mut CreateInputs, prank: Option<&Prank>, broadcast: Option<&Broadcast>, + diffs_stack: Option<&mut Vec>>, ) -> Result<(), DB::Error> { - if let CreateScheme::Create2 { salt: _ } = call.scheme { + if let CreateScheme::Create2 { salt } = call.scheme { let mut base_depth = 1; if let Some(prank) = &prank { base_depth = prank.depth; } else if let Some(broadcast) = &broadcast { base_depth = broadcast.depth; } + // If the create scheme is Create2 and the depth equals the broadcast/prank/default // depth, then use the default create2 factory as the deployer if data.journaled_state.depth() == base_depth { + // Record the call to the create2 factory in the state diff + if let Some(recorded_account_diffs_stack) = diffs_stack { + let calldata = [&salt.to_be_bytes::<32>()[..], &call.init_code[..]].concat(); + recorded_account_diffs_stack.push(vec![AccountAccess { + access: crate::Vm::AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: data.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(data.env.cfg.chain_id), + }, + accessor: call.caller, + account: DEFAULT_CREATE2_DEPLOYER, + kind: crate::Vm::AccountAccessKind::Call, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + value: call.value, + data: calldata, + reverted: false, + deployedCode: vec![], // updated on create_end + storageAccesses: vec![], // updated on create_end + }, + depth: data.journaled_state.depth(), + }]) + } + // Sanity checks for our CREATE2 deployer let info = &data.journaled_state.load_account(DEFAULT_CREATE2_DEPLOYER, data.db)?.0.info; @@ -1475,9 +1517,9 @@ fn process_broadcast_create( data: &mut EVMData<'_, DB>, call: &mut CreateInputs, ) -> (Bytes, Option
, u64) { + call.caller = broadcast_sender; match call.scheme { CreateScheme::Create => { - call.caller = broadcast_sender; (bytecode, None, data.journaled_state.account(broadcast_sender).info.nonce) } CreateScheme::Create2 { salt } => { diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 7323d7ef131a..8506a248de01 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -310,3 +310,10 @@ test_repro!(5529; |config| { cheats_config.always_use_create_2_factory = true; config.runner.cheats_config = std::sync::Arc::new(cheats_config); }); + +// https://github.com/foundry-rs/foundry/issues/6634 +test_repro!(6634; |config| { + let mut cheats_config = config.runner.cheats_config.as_ref().clone(); + cheats_config.always_use_create_2_factory = true; + config.runner.cheats_config = std::sync::Arc::new(cheats_config); +}); diff --git a/testdata/repros/Issue6634.t.sol b/testdata/repros/Issue6634.t.sol new file mode 100644 index 000000000000..3b1acb9c18aa --- /dev/null +++ b/testdata/repros/Issue6634.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "../cheats/Vm.sol"; +import "../logs/console.sol"; + +contract Box { + uint256 public number; + + constructor(uint256 _number) { + number = _number; + } +} + +// https://github.com/foundry-rs/foundry/issues/6634 +contract Issue6634Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_Create2FactoryCallRecordedInStandardTest() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess account is incorrect"); + assertEq(called[0].accessor, address(this), "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } + + function test_Create2FactoryCallRecordedWhenPranking() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + address accessor = address(0x5555); + + vm.startPrank(accessor); + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess accout is incorrect"); + assertEq(called[0].accessor, accessor, "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } + + function test_Create2FactoryCallRecordedWhenBroadcasting() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + address accessor = address(0x5555); + + vm.startBroadcast(accessor); + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess accout is incorrect"); + assertEq(called[0].accessor, accessor, "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } +} From 8d5f92ed06bf87a185200f8ca437b32a5b5572a4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 24 Feb 2024 16:03:30 +0100 Subject: [PATCH 07/15] fix: handle rpc error response (#7229) --- crates/anvil/server/src/lib.rs | 2 +- crates/anvil/src/eth/api.rs | 49 +++++------------------- crates/anvil/src/eth/backend/mem/mod.rs | 50 ++++++------------------- crates/anvil/src/eth/error.rs | 11 +++++- crates/anvil/tests/it/fork.rs | 30 ++++++++++++++- 5 files changed, 60 insertions(+), 82 deletions(-) diff --git a/crates/anvil/server/src/lib.rs b/crates/anvil/server/src/lib.rs index 898d3a7659d5..5ff287c96b3c 100644 --- a/crates/anvil/server/src/lib.rs +++ b/crates/anvil/server/src/lib.rs @@ -117,7 +117,7 @@ pub trait RpcHandler: Clone + Send + Sync + 'static { /// **Note**: override this function if the expected `Request` deviates from `{ "method" : /// "", "params": "" }` async fn on_call(&self, call: RpcMethodCall) -> RpcResponse { - trace!(target: "rpc", id = ?call.id , method = ?call.method, "received method call"); + trace!(target: "rpc", id = ?call.id , method = ?call.method, params = ?call.params, "received method call"); let RpcMethodCall { method, params, id, .. } = call; let params: serde_json::Value = params.into(); diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index e78e2cb19ba2..59bbcdd19b99 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -606,10 +606,7 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - return fork - .get_balance(address, number) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.get_balance(address, number).await?) } } } @@ -634,9 +631,7 @@ impl EthApi { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { return Ok(B256::from( - fork.storage_at(address, index, Some(BlockNumber::Number(number))) - .await - .map_err(|_| BlockchainError::DataUnavailable)?, + fork.storage_at(address, index, Some(BlockNumber::Number(number))).await?, )); } } @@ -768,10 +763,7 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - return fork - .get_code(address, number) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.get_code(address, number).await?) } } } @@ -796,10 +788,7 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return fork - .get_proof(address, keys, Some(number.into())) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.get_proof(address, keys, Some(number.into())).await?) } } } @@ -994,10 +983,7 @@ impl EthApi { "not available on past forked blocks".to_string(), )); } - return fork - .call(&request, Some(number.into())) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.call(&request, Some(number.into())).await?) } } } @@ -1044,10 +1030,7 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - return fork - .create_access_list(&request, Some(number.into())) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.create_access_list(&request, Some(number.into())).await?) } } } @@ -1189,10 +1172,7 @@ impl EthApi { self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return fork - .uncle_by_block_hash_and_index(block_hash, idx.into()) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.uncle_by_block_hash_and_index(block_hash, idx.into()).await?) } } // It's impossible to have uncles outside of fork mode @@ -1211,10 +1191,7 @@ impl EthApi { let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return fork - .uncle_by_block_number_and_index(number, idx.into()) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?); } } // It's impossible to have uncles outside of fork mode @@ -2194,10 +2171,7 @@ impl EthApi { "not available on past forked blocks".to_string(), )); } - return fork - .estimate_gas(&request, Some(number.into())) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.estimate_gas(&request, Some(number.into())).await?) } } } @@ -2576,10 +2550,7 @@ impl EthApi { if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return fork - .get_nonce(address, number) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.get_nonce(address, number).await?); } } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index f7234d9cd7c0..202cc880e929 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -393,8 +393,7 @@ impl Backend { let fork_block_number = fork.block_number(); let fork_block = fork .block_by_number(fork_block_number) - .await - .map_err(|_| BlockchainError::DataUnavailable)? + .await? .ok_or(BlockchainError::BlockNotFound)?; // update all settings related to the forked block { @@ -1248,7 +1247,7 @@ impl Backend { } if let Some(fork) = self.get_fork() { - return fork.logs(&filter).await.map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.logs(&filter).await?); } Ok(Vec::new()) @@ -1335,8 +1334,7 @@ impl Backend { if fork.predates_fork(from) { // this data is only available on the forked client let filter = filter.clone().from_block(from).to_block(to_on_fork); - all_logs = - fork.logs(&filter).await.map_err(|_| BlockchainError::DataUnavailable)?; + all_logs = fork.logs(&filter).await?; // update the range from = fork.block_number() + 1; @@ -1379,7 +1377,7 @@ impl Backend { } if let Some(fork) = self.get_fork() { - return fork.block_by_hash(hash).await.map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.block_by_hash(hash).await?); } Ok(None) @@ -1395,10 +1393,7 @@ impl Backend { } if let Some(fork) = self.get_fork() { - return fork - .block_by_hash_full(hash) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.block_by_hash_full(hash).await?) } Ok(None) @@ -1452,10 +1447,7 @@ impl Backend { if let Some(fork) = self.get_fork() { let number = self.convert_block_number(Some(number)); if fork.predates_fork_inclusive(number) { - return fork - .block_by_number(number) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.block_by_number(number).await?) } } @@ -1474,10 +1466,7 @@ impl Backend { if let Some(fork) = self.get_fork() { let number = self.convert_block_number(Some(number)); if fork.predates_fork_inclusive(number) { - return fork - .block_by_number_full(number) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.block_by_number_full(number).await?) } } @@ -1891,10 +1880,7 @@ impl Backend { } if let Some(fork) = self.get_fork() { - return fork.debug_trace_transaction(hash, opts).await.map_err(|err| { - warn!(target: "backend", "error delegating debug_traceTransaction: {:?}", err); - BlockchainError::DataUnavailable - }) + return Ok(fork.debug_trace_transaction(hash, opts).await?) } Ok(GethTrace::Default(Default::default())) @@ -1920,10 +1906,7 @@ impl Backend { if let Some(fork) = self.get_fork() { if fork.predates_fork(number) { - return fork.trace_block(number).await.map_err(|err| { - warn!(target: "backend", "error delegating trace_block: {:?}", err); - BlockchainError::DataUnavailable - }) + return Ok(fork.trace_block(number).await?) } } @@ -1939,10 +1922,7 @@ impl Backend { } if let Some(fork) = self.get_fork() { - let receipt = fork - .transaction_receipt(hash) - .await - .map_err(|_| BlockchainError::DataUnavailable)?; + let receipt = fork.transaction_receipt(hash).await?; let number = self.convert_block_number( receipt .clone() @@ -2116,10 +2096,7 @@ impl Backend { if let Some(fork) = self.get_fork() { let number = self.convert_block_number(Some(number)); if fork.predates_fork(number) { - return fork - .transaction_by_block_number_and_index(number, index.into()) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.transaction_by_block_number_and_index(number, index.into()).await?) } } @@ -2136,10 +2113,7 @@ impl Backend { } if let Some(fork) = self.get_fork() { - return fork - .transaction_by_block_hash_and_index(hash, index.into()) - .await - .map_err(|_| BlockchainError::DataUnavailable); + return Ok(fork.transaction_by_block_hash_and_index(hash, index.into()).await?) } Ok(None) diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 37e0fce54920..cc7af40ac515 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -359,8 +359,15 @@ impl ToRpcResponseResult for Result { "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`", ), BlockchainError::AlloyForkProvider(err) => { - error!(%err, "alloy fork provider error"); - RpcError::internal_error_with(format!("Fork Error: {err:?}")) + error!(target: "backend", %err, "fork provider error"); + match err { + TransportError::ErrorResp(err) => RpcError { + code: ErrorCode::from(err.code), + message: err.message.into(), + data: err.data.and_then(|data| serde_json::to_value(data).ok()), + }, + err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), + } } err @ BlockchainError::EvmError(_) => { RpcError::internal_error_with(err.to_string()) diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index ceeeb16e7e1e..8399dc85ad31 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -4,9 +4,12 @@ use crate::{ abi::*, utils::{self, ethers_http_provider}, }; -use alloy_primitives::U256 as rU256; +use alloy_primitives::{address, U256 as rU256}; use alloy_providers::provider::TempProvider; -use alloy_rpc_types::{request::TransactionRequest as CallRequest, BlockNumberOrTag}; +use alloy_rpc_types::{ + request::{TransactionInput, TransactionRequest as CallRequest}, + BlockNumberOrTag, +}; use alloy_signer::Signer as AlloySigner; use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; use anvil_core::types::Forking; @@ -1119,3 +1122,26 @@ async fn test_arbitrum_fork_dev_balance() { assert_eq!(balance, rU256::from(100000000000000000000u128)); } } + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_execution_reverted() { + let target = 16681681u64; + let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(target + 1))).await; + + let resp = api + .call( + CallRequest { + to: Some(address!("Fd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377")), + input: TransactionInput::new("0x8f283b3c".as_bytes().into()), + ..Default::default() + }, + Some(target.into()), + None, + ) + .await; + + assert!(resp.is_err()); + let err = resp.unwrap_err(); + assert!(err.to_string().contains("execution reverted")); +} From 35fa86d5a8bca479906b137db70748f525b115a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 25 Feb 2024 13:07:01 +0100 Subject: [PATCH 08/15] chore(deps): weekly `cargo update` (#7235) Updating git repository `https://github.com/gakonst/ethers-rs` Updating git repository `https://github.com/bluealloy/revm` Updating git repository `https://github.com/alloy-rs/alloy` Updating git repository `https://github.com/paradigmxyz/evm-inspectors` Updating ahash v0.8.8 -> v0.8.9 Updating alloy-consensus v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-eips v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-genesis v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-json-rpc v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-network v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-providers v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-pubsub v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-rpc-client v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-rpc-trace-types v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-rpc-types v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-signer v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-transport v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-transport-http v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-transport-ipc v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating alloy-transport-ws v0.1.0 (https://github.com/alloy-rs/alloy#5062eaf1) -> #29a7886e Updating anstream v0.6.11 -> v0.6.12 Updating anyhow v1.0.79 -> v1.0.80 Updating bstr v1.9.0 -> v1.9.1 Updating bumpalo v3.15.0 -> v3.15.3 Updating c-kzg v0.4.1 -> v0.4.2 Updating cc v1.0.83 -> v1.0.87 Updating const-hex v1.11.0 -> v1.11.1 Updating darling v0.20.6 -> v0.20.8 Updating darling_core v0.20.6 -> v0.20.8 Updating darling_macro v0.20.6 -> v0.20.8 Updating event-listener v5.0.0 -> v5.1.0 Updating futures-timer v3.0.2 -> v3.0.3 Updating hermit-abi v0.3.6 -> v0.3.8 Removing jobserver v0.1.28 Updating lru v0.12.2 -> v0.12.3 Updating openssl v0.10.63 -> v0.10.64 Updating openssl-sys v0.9.99 -> v0.9.101 Updating ryu v1.0.16 -> v1.0.17 Updating semver v1.0.21 -> v1.0.22 Updating serde v1.0.196 -> v1.0.197 Updating serde_derive v1.0.196 -> v1.0.197 Updating serde_json v1.0.113 -> v1.0.114 Updating socket2 v0.5.5 -> v0.5.6 Updating syn v2.0.49 -> v2.0.50 Updating thread_local v1.1.7 -> v1.1.8 Updating unicode-normalization v0.1.22 -> v0.1.23 Updating windows-targets v0.52.0 -> v0.52.3 Updating windows_aarch64_gnullvm v0.52.0 -> v0.52.3 Updating windows_aarch64_msvc v0.52.0 -> v0.52.3 Updating windows_i686_gnu v0.52.0 -> v0.52.3 Updating windows_i686_msvc v0.52.0 -> v0.52.3 Updating windows_x86_64_gnu v0.52.0 -> v0.52.3 Updating windows_x86_64_gnullvm v0.52.0 -> v0.52.3 Updating windows_x86_64_msvc v0.52.0 -> v0.52.3 Updating winnow v0.6.1 -> v0.6.2 note: pass `--verbose` to see 179 unchanged dependencies behind latest Co-authored-by: mattsse --- Cargo.lock | 322 ++++++++++++++++++++++++++--------------------------- 1 file changed, 156 insertions(+), 166 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a488b1aad732..3246374c4ba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ "cfg-if", "once_cell", @@ -90,7 +90,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-eips", "alloy-network", @@ -122,7 +122,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -133,7 +133,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -155,7 +155,7 @@ dependencies = [ [[package]] name = "alloy-json-rpc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-primitives", "serde", @@ -166,7 +166,7 @@ dependencies = [ [[package]] name = "alloy-network" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-eips", "alloy-json-rpc", @@ -205,7 +205,7 @@ dependencies = [ [[package]] name = "alloy-providers" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-network", "alloy-primitives", @@ -224,7 +224,7 @@ dependencies = [ [[package]] name = "alloy-pubsub" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -257,13 +257,13 @@ checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] name = "alloy-rpc-client" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -280,7 +280,7 @@ dependencies = [ [[package]] name = "alloy-rpc-trace-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -304,7 +304,7 @@ dependencies = [ [[package]] name = "alloy-signer" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-network", "alloy-primitives", @@ -335,7 +335,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.49", + "syn 2.0.50", "syn-solidity", "tiny-keccak", ] @@ -365,7 +365,7 @@ dependencies = [ [[package]] name = "alloy-transport" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-json-rpc", "base64 0.21.7", @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "alloy-transport-http" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -395,7 +395,7 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "alloy-transport-ws" version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#5062eaf1af2733a7115bc0536c9708a42bfb53ca" +source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -462,9 +462,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" dependencies = [ "anstyle", "anstyle-parse", @@ -632,9 +632,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arbitrary" @@ -798,7 +798,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 5.0.0", + "event-listener 5.1.0", "event-listener-strategy 0.5.0", "futures-core", "pin-project-lite", @@ -832,7 +832,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -849,7 +849,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -902,7 +902,7 @@ checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -1116,9 +1116,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "regex-automata 0.4.5", @@ -1142,9 +1142,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.15.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byte-slice-cast" @@ -1196,9 +1196,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d8c306be83ec04bf5f73710badd8edf56dea23f2f0d8b7f9fe4644d371c758" +checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" dependencies = [ "blst", "cc", @@ -1234,7 +1234,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -1292,7 +1292,7 @@ dependencies = [ "rpassword", "rusoto_core", "rusoto_kms", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "tempfile", @@ -1310,11 +1310,10 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" dependencies = [ - "jobserver", "libc", ] @@ -1348,7 +1347,7 @@ dependencies = [ "reqwest", "revm", "rustyline", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "serial_test", @@ -1372,7 +1371,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -1465,7 +1464,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -1654,9 +1653,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d59688ad0945eaf6b84cb44fedbe93484c81b48970e98f09db8a22832d7961" +checksum = "efbd12d49ab0eaf8193ba9175e45f56bbc2e4b27d57b8cfe62aa47942a46b9a9" dependencies = [ "cfg-if", "cpufeatures", @@ -1897,9 +1896,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -1907,27 +1906,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] name = "darling_macro" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -1988,7 +1987,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2009,7 +2008,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2019,7 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2261,7 +2260,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2469,7 +2468,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.49", + "syn 2.0.50", "toml 0.8.10", "walkdir", ] @@ -2486,7 +2485,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2511,7 +2510,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.49", + "syn 2.0.50", "tempfile", "thiserror", "tiny-keccak", @@ -2526,7 +2525,7 @@ dependencies = [ "chrono", "ethers-core", "reqwest", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -2616,7 +2615,7 @@ dependencies = [ "rand 0.8.5", "rusoto_core", "rusoto_kms", - "semver 1.0.21", + "semver 1.0.22", "sha2 0.10.8", "spki", "thiserror", @@ -2645,7 +2644,7 @@ dependencies = [ "rand 0.8.5", "rayon", "regex", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "sha2 0.10.8", @@ -2680,9 +2679,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1" +checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27" dependencies = [ "concurrent-queue", "parking", @@ -2705,7 +2704,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" dependencies = [ - "event-listener 5.0.0", + "event-listener 5.1.0", "pin-project-lite", ] @@ -2765,7 +2764,7 @@ dependencies = [ "bytes", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -2941,7 +2940,7 @@ dependencies = [ "regex", "reqwest", "revm-inspectors", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "similar", @@ -3020,7 +3019,7 @@ dependencies = [ "alloy-primitives", "foundry-compilers", "reqwest", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -3143,7 +3142,7 @@ dependencies = [ "pretty_assertions", "rand 0.8.5", "reqwest", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "tempfile", @@ -3178,7 +3177,7 @@ dependencies = [ "rand 0.8.5", "rayon", "regex", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "sha2 0.10.8", @@ -3214,7 +3213,7 @@ dependencies = [ "regex", "reqwest", "revm-primitives", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "serde_regex", @@ -3318,7 +3317,7 @@ dependencies = [ "foundry-compilers", "foundry-evm-core", "revm", - "semver 1.0.21", + "semver 1.0.22", "tracing", ] @@ -3382,7 +3381,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -3549,7 +3548,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -3566,9 +3565,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", "send_wrapper 0.4.0", @@ -3975,7 +3974,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.8", + "ahash 0.8.9", "allocator-api2", "serde", ] @@ -3997,9 +3996,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" [[package]] name = "hex" @@ -4449,15 +4448,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.68" @@ -4652,9 +4642,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ "hashbrown 0.14.3", ] @@ -4813,7 +4803,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5079,7 +5069,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5162,9 +5152,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", "cfg-if", @@ -5183,7 +5173,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5194,9 +5184,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -5393,7 +5383,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5451,7 +5441,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5554,7 +5544,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5592,7 +5582,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5703,7 +5693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -5798,7 +5788,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", "version_check", "yansi 1.0.0-rc.1", ] @@ -6472,7 +6462,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.21", + "semver 1.0.22", ] [[package]] @@ -6586,9 +6576,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "salsa20" @@ -6759,9 +6749,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] @@ -6789,22 +6779,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -6820,9 +6810,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "indexmap", "itoa", @@ -6858,7 +6848,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -6904,7 +6894,7 @@ checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7070,12 +7060,12 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7201,7 +7191,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7214,7 +7204,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7247,7 +7237,7 @@ dependencies = [ "hex", "once_cell", "reqwest", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "sha2 0.10.8", @@ -7264,7 +7254,7 @@ checksum = "aa64b5e8eecd3a8af7cfc311e29db31a268a62d5953233d3e8243ec77a71c4e3" dependencies = [ "build_const", "hex", - "semver 1.0.21", + "semver 1.0.22", "serde_json", "svm-rs", ] @@ -7277,7 +7267,7 @@ checksum = "5b8d3c94c4d3337336f58493471b98d712c267c66977b0fbe48efd6cbf69ffd0" dependencies = [ "build_const", "hex", - "semver 1.0.21", + "semver 1.0.22", "serde_json", "svm-rs", ] @@ -7295,9 +7285,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.49" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -7313,7 +7303,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7435,14 +7425,14 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -7557,7 +7547,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7708,7 +7698,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.1", + "winnow 0.6.2", ] [[package]] @@ -7790,7 +7780,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -7995,9 +7985,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -8156,7 +8146,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", "wasm-bindgen-shared", ] @@ -8190,7 +8180,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8333,7 +8323,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -8360,7 +8350,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -8395,17 +8385,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -8422,9 +8412,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -8440,9 +8430,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -8458,9 +8448,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -8476,9 +8466,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -8494,9 +8484,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -8512,9 +8502,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -8530,9 +8520,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "winnow" @@ -8545,9 +8535,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401" +checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" dependencies = [ "memchr", ] @@ -8625,7 +8615,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -8645,7 +8635,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] From b4a9259341c2caf78c0a843806941b4fa5d2316b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:59:57 +0200 Subject: [PATCH 09/15] fix(forge): total duration is not the sum of individual runs (#7228) * fix(forge): total duration is not the sum of individual runs * updates * update * chore: clippy --- crates/forge/bin/cmd/test/mod.rs | 8 +++-- crates/forge/src/result.rs | 29 ++++++++++----- crates/forge/src/runner.rs | 36 +++++++++---------- .../tests/fixtures/can_check_snapshot.stdout | 2 +- .../can_run_test_in_custom_test_folder.stdout | 2 +- .../tests/fixtures/can_test_repeatedly.stdout | 2 +- .../can_use_libs_in_multi_fork.stdout | 2 +- .../include_custom_types_in_traces.stdout | 2 +- crates/forge/tests/fixtures/repro_6531.stdout | 2 +- ...xactly_once_with_changed_versions.1.stdout | 2 +- ...xactly_once_with_changed_versions.2.stdout | 2 +- crates/test-utils/src/util.rs | 2 +- 12 files changed, 53 insertions(+), 38 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index d84817659da7..ba928f6ea313 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -32,7 +32,7 @@ use foundry_config::{ }; use foundry_debugger::Debugger; use regex::Regex; -use std::{collections::BTreeMap, sync::mpsc::channel}; +use std::{sync::mpsc::channel, time::Instant}; use watchexec::config::{InitConfig, RuntimeConfig}; use yansi::Paint; @@ -296,6 +296,7 @@ impl TestArgs { // Run tests. let (tx, rx) = channel::<(String, SuiteResult)>(); + let timer = Instant::now(); let handle = tokio::task::spawn({ let filter = filter.clone(); async move { runner.test(&filter, tx, test_options).await } @@ -419,6 +420,7 @@ impl TestArgs { break; } } + let duration = timer.elapsed(); trace!(target: "forge::test", len=outcome.results.len(), %any_test_failed, "done with results"); @@ -429,7 +431,7 @@ impl TestArgs { } if !outcome.results.is_empty() { - shell::println(outcome.summary())?; + shell::println(outcome.summary(duration))?; if self.summary { let mut summary_table = TestSummaryReporter::new(self.detailed); @@ -515,7 +517,7 @@ fn list( } } } - Ok(TestOutcome::new(BTreeMap::new(), false)) + Ok(TestOutcome::empty(false)) } #[cfg(test)] diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index cb53756551d7..1605f72fe8f9 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -42,7 +42,7 @@ impl TestOutcome { /// Creates a new empty test outcome. pub fn empty(allow_failure: bool) -> Self { - Self { results: BTreeMap::new(), allow_failure, decoder: None } + Self::new(BTreeMap::new(), allow_failure) } /// Returns an iterator over all individual succeeding tests and their names. @@ -112,13 +112,15 @@ impl TestOutcome { self.failures().count() } - /// Calculates the total duration of all test suites. - pub fn duration(&self) -> Duration { + /// Sums up all the durations of all individual test suites. + /// + /// Note that this is not necessarily the wall clock time of the entire test run. + pub fn total_time(&self) -> Duration { self.results.values().map(|suite| suite.duration).sum() } /// Formats the aggregated summary of all test suites into a string (for printing). - pub fn summary(&self) -> String { + pub fn summary(&self, wall_clock_time: Duration) -> String { let num_test_suites = self.results.len(); let suites = if num_test_suites == 1 { "suite" } else { "suites" }; let total_passed = self.passed(); @@ -126,10 +128,11 @@ impl TestOutcome { let total_skipped = self.skipped(); let total_tests = total_passed + total_failed + total_skipped; format!( - "\nRan {} test {} in {:.2?}: {} tests passed, {} failed, {} skipped ({} total tests)", + "\nRan {} test {} in {:.2?} ({:.2?} CPU time): {} tests passed, {} failed, {} skipped ({} total tests)", num_test_suites, suites, - self.duration(), + wall_clock_time, + self.total_time(), Paint::green(total_passed), Paint::red(total_failed), Paint::yellow(total_skipped), @@ -180,7 +183,7 @@ impl TestOutcome { /// A set of test results for a single test suite, which is all the tests in a single contract. #[derive(Clone, Debug, Serialize)] pub struct SuiteResult { - /// Total duration of the test run for this block of tests. + /// Wall clock time it took to execute all tests in this suite. pub duration: Duration, /// Individual test results: `test fn signature -> TestResult`. pub test_results: BTreeMap, @@ -242,17 +245,25 @@ impl SuiteResult { self.test_results.len() } + /// Sums up all the durations of all individual tests in this suite. + /// + /// Note that this is not necessarily the wall clock time of the entire test suite. + pub fn total_time(&self) -> Duration { + self.test_results.values().map(|result| result.duration).sum() + } + /// Returns the summary of a single test suite. pub fn summary(&self) -> String { let failed = self.failed(); let result = if failed == 0 { Paint::green("ok") } else { Paint::red("FAILED") }; format!( - "Test result: {}. {} passed; {} failed; {} skipped; finished in {:.2?}", + "Suite result: {}. {} passed; {} failed; {} skipped; finished in {:.2?} ({:.2?} CPU time)", result, Paint::green(self.passed()), Paint::red(failed), Paint::yellow(self.skipped()), self.duration, + self.total_time(), ) } } @@ -357,6 +368,8 @@ pub struct TestResult { /// The debug nodes of the call pub debug: Option, + pub duration: Duration, + /// pc breakpoint char map pub breakpoints: Breakpoints, } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 912fbb895d20..356248a61c62 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -386,6 +386,7 @@ impl<'a> ContractRunner<'a> { traces, labeled_addresses, kind: TestKind::Standard(0), + duration: start.elapsed(), ..Default::default() } } @@ -397,6 +398,7 @@ impl<'a> ContractRunner<'a> { traces, labeled_addresses, kind: TestKind::Standard(0), + duration: start.elapsed(), ..Default::default() } } @@ -410,13 +412,8 @@ impl<'a> ContractRunner<'a> { ); // Record test execution time - debug!( - duration = ?start.elapsed(), - gas, - reverted, - should_fail, - success, - ); + let duration = start.elapsed(); + debug!(?duration, gas, reverted, should_fail, success); TestResult { status: match success { @@ -433,6 +430,7 @@ impl<'a> ContractRunner<'a> { labeled_addresses, debug: debug_arena, breakpoints, + duration, } } @@ -452,6 +450,7 @@ impl<'a> ContractRunner<'a> { let TestSetup { address, logs, traces, labeled_addresses, coverage, .. } = setup; // First, run the test normally to see if it needs to be skipped. + let start = Instant::now(); if let Err(EvmError::SkipError) = self.executor.clone().execute_test::<_, _>( self.sender, address, @@ -468,6 +467,7 @@ impl<'a> ContractRunner<'a> { labeled_addresses, kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, coverage, + duration: start.elapsed(), ..Default::default() } }; @@ -495,6 +495,7 @@ impl<'a> ContractRunner<'a> { traces, labeled_addresses, kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, + duration: start.elapsed(), ..Default::default() } } @@ -542,12 +543,6 @@ impl<'a> ContractRunner<'a> { } } - let kind = TestKind::Invariant { - runs: cases.len(), - calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), - reverts, - }; - TestResult { status: match success { true => TestStatus::Success, @@ -557,10 +552,15 @@ impl<'a> ContractRunner<'a> { counterexample, decoded_logs: decode_console_logs(&logs), logs, - kind, + kind: TestKind::Invariant { + runs: cases.len(), + calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), + reverts, + }, coverage, traces, labeled_addresses: labeled_addresses.clone(), + duration: start.elapsed(), ..Default::default() // TODO collect debug traces on the last run or error } } @@ -612,6 +612,7 @@ impl<'a> ContractRunner<'a> { debug, breakpoints, coverage, + duration: start.elapsed(), ..Default::default() } } @@ -668,10 +669,8 @@ impl<'a> ContractRunner<'a> { coverage = merge_coverages(coverage, result.coverage); // Record test execution time - debug!( - duration = ?start.elapsed(), - success = %result.success - ); + let duration = start.elapsed(); + debug!(?duration, success = %result.success); TestResult { status: match result.success { @@ -688,6 +687,7 @@ impl<'a> ContractRunner<'a> { labeled_addresses, debug, breakpoints, + duration, } } } diff --git a/crates/forge/tests/fixtures/can_check_snapshot.stdout b/crates/forge/tests/fixtures/can_check_snapshot.stdout index 1846bd5778b6..bce1c6972521 100644 --- a/crates/forge/tests/fixtures/can_check_snapshot.stdout +++ b/crates/forge/tests/fixtures/can_check_snapshot.stdout @@ -4,6 +4,6 @@ Compiler run successful! Ran 1 test for src/ATest.t.sol:ATest [PASS] testExample() (gas: 168) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.42ms +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.42ms Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout b/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout index 1701d90029b9..cd92d6ebeed8 100644 --- a/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout +++ b/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout @@ -4,6 +4,6 @@ Compiler run successful! Ran 1 test for src/nested/forge-tests/MyTest.t.sol:MyTest [PASS] testTrue() (gas: 168) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.93ms +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.93ms Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/can_test_repeatedly.stdout b/crates/forge/tests/fixtures/can_test_repeatedly.stdout index 3d782aa35872..dbab2812511d 100644 --- a/crates/forge/tests/fixtures/can_test_repeatedly.stdout +++ b/crates/forge/tests/fixtures/can_test_repeatedly.stdout @@ -3,6 +3,6 @@ No files changed, compilation skipped Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 26521, ~: 28387) [PASS] test_Increment() (gas: 28379) -Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms Ran 1 test suite: 2 tests passed, 0 failed, 0 skipped (2 total tests) diff --git a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout b/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout index 5a5b2f621811..70c72887aaea 100644 --- a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout +++ b/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout @@ -4,6 +4,6 @@ Compiler run successful! Ran 1 test for test/Contract.t.sol:ContractTest [PASS] test() (gas: 70360) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.21s +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.21s Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout index 19771e0920f5..786679a6736b 100644 --- a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout +++ b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout @@ -14,7 +14,7 @@ Traces: ├─ emit MyEvent(a: 100) └─ ← () -Test result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 3.88ms +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 3.88ms Ran 1 test suite: 1 tests passed, 1 failed, 0 skipped (2 total tests) diff --git a/crates/forge/tests/fixtures/repro_6531.stdout b/crates/forge/tests/fixtures/repro_6531.stdout index 1dfca09d72ff..159c6476eb98 100644 --- a/crates/forge/tests/fixtures/repro_6531.stdout +++ b/crates/forge/tests/fixtures/repro_6531.stdout @@ -14,6 +14,6 @@ Traces: │ └─ ← "USD Coin" └─ ← () -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.43s +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.43s Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout index c46d083296fa..c98d9f93e42f 100644 --- a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout +++ b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout @@ -4,6 +4,6 @@ Compiler run successful! Ran 1 test for src/Contract.t.sol:ContractTest [PASS] testExample() (gas: 190) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout index 28dbffcc86c3..abfd712db4c1 100644 --- a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout +++ b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout @@ -4,6 +4,6 @@ Compiler run successful! Ran 1 test for src/Contract.t.sol:ContractTest [PASS] testExample() (gas: 190) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index f5beda778563..a26936489314 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1054,7 +1054,7 @@ static IGNORE_IN_FIXTURES: Lazy = Lazy::new(|| { // solc runs r"runs: \d+, μ: \d+, ~: \d+", // elapsed time - "(?:finished)? ?in .*?s", + r"(?:finished)? ?in .*?s(?: \(.*?s CPU time\))?", // file paths r"-->.*\.sol", r"Location(.|\n)*\.rs(.|\n)*Backtrace", From 8064e1986f254b89e5c848740a8f6ec503d7b053 Mon Sep 17 00:00:00 2001 From: reptarrat Date: Mon, 26 Feb 2024 07:00:08 -0500 Subject: [PATCH 10/15] feat(forge bind): add option to skip json derives (#7233) * chore: bump ethers deps * feat(forge bind): add option to skip json derives --------- Co-authored-by: Matthias Seitz --- Cargo.lock | 54 ++++++++++++++++++------------------ Cargo.toml | 16 +++++------ crates/forge/bin/cmd/bind.rs | 22 +++++++++++---- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3246374c4ba9..cd8deb311764 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1551,9 +1551,9 @@ dependencies = [ [[package]] name = "coins-ledger" -version = "0.9.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a3eedd46b3c8c0b9fbe6359078d375008c11824fadc4b7462491bb445e8904" +checksum = "9e076e6e5d9708f0b90afe2dbe5a8ba406b5c794347661e6e44618388c7e3a31" dependencies = [ "async-trait", "byteorder", @@ -2236,9 +2236,9 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enr" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" +checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" dependencies = [ "base64 0.21.7", "bytes", @@ -2408,8 +2408,8 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2423,8 +2423,8 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "ethers-core", "once_cell", @@ -2434,8 +2434,8 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2452,8 +2452,8 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "Inflector", "const-hex", @@ -2475,8 +2475,8 @@ dependencies = [ [[package]] name = "ethers-contract-derive" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "Inflector", "const-hex", @@ -2490,8 +2490,8 @@ dependencies = [ [[package]] name = "ethers-core" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "arrayvec", "bytes", @@ -2509,7 +2509,7 @@ dependencies = [ "rlp", "serde", "serde_json", - "strum 0.25.0", + "strum 0.26.1", "syn 2.0.50", "tempfile", "thiserror", @@ -2519,8 +2519,8 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "chrono", "ethers-core", @@ -2534,8 +2534,8 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "async-trait", "auto_impl", @@ -2559,8 +2559,8 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "async-trait", "auto_impl", @@ -2597,8 +2597,8 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "async-trait", "coins-bip32", @@ -2625,8 +2625,8 @@ dependencies = [ [[package]] name = "ethers-solc" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" dependencies = [ "cfg-if", "const-hex", diff --git a/Cargo.toml b/Cargo.toml index fc1b78661eac..e7e40baf7edb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,14 +201,14 @@ tower-http = "0.4" #ethers-solc = { path = "../ethers-rs/ethers-solc" } [patch.crates-io] -ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-core = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-contract = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-providers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-signers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-solc = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } +ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-core = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-contract = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-providers = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-signers = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } +ethers-solc = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index c4fb92bd6e08..93d188260dc8 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -77,6 +77,10 @@ pub struct BindArgs { #[clap(long)] skip_build: bool, + /// Don't add any additional derives to generated bindings + #[clap(long)] + skip_extra_derives: bool, + #[clap(flatten)] build_args: CoreBuildArgs, } @@ -161,10 +165,13 @@ impl BindArgs { }) .map(|path| { trace!(?path, "parsing Abigen from file"); - Abigen::from_file(&path) - .wrap_err_with(|| format!("failed to parse Abigen from file: {:?}", path))? - .add_derive("serde::Serialize")? - .add_derive("serde::Deserialize") + let abi = Abigen::from_file(&path) + .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).with_filter(self.get_filter()); @@ -207,11 +214,14 @@ No contract artifacts found. Hint: Have you built your contracts yet? `forge bin /// Generate the bindings fn generate_bindings(&self, artifacts: impl AsRef) -> Result<()> { - let bindings = self.get_multi(&artifacts)?.build()?; + let mut bindings = self.get_multi(&artifacts)?.build()?; println!("Generating bindings for {} contracts", bindings.len()); if !self.module { trace!(single_file = self.single_file, "generating crate"); - bindings.dependencies([r#"serde = "1""#]).write_to_crate( + if !self.skip_extra_derives { + bindings = bindings.dependencies([r#"serde = "1""#]) + } + bindings.write_to_crate( &self.crate_name, &self.crate_version, self.bindings_root(&artifacts), From 8b18a13b8f343db7d7d062b18e11d6b787e686fe Mon Sep 17 00:00:00 2001 From: Carter Carlson Date: Mon, 26 Feb 2024 14:07:24 -0800 Subject: [PATCH 11/15] build(cast): fraxtal genesis blocks (#7241) * build(cast): fraxtal genesis blocks * docs(etherscan): chain id example * chore: fmt --- crates/cast/src/lib.rs | 4 ++++ crates/config/README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 7d701cb5095c..ca6eb83bec30 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -436,6 +436,10 @@ where "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { "optimism-kovan" } + "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", + "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { + "fraxtal-testnet" + } "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { "arbitrum-mainnet" } diff --git a/crates/config/README.md b/crates/config/README.md index 880149f6220d..8e4bdf70ec0a 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -252,7 +252,7 @@ The optional `url` attribute can be used to explicitly set the Etherscan API url [etherscan] mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } mainnet2 = { key = "ABCDEFG", chain = "mainnet" } -optimism = { key = "1234576" } +optimism = { key = "1234576", chain = 42 } unknownchain = { key = "ABCDEFG", url = "https://" } ``` From ccb2331ea6da579651452055bdf139ace10ae9c4 Mon Sep 17 00:00:00 2001 From: RPate97 Date: Mon, 26 Feb 2024 22:17:33 -0700 Subject: [PATCH 12/15] feat: Add call depth to `vm.stopAndReturnStateDiff()` results (#7234) * Added depth member to AccountAccess * Combine AccountAccess structs --------- Co-authored-by: ercembu --- crates/cheatcodes/assets/cheatcodes.json | 5 + crates/cheatcodes/spec/src/vm.rs | 2 + crates/cheatcodes/src/evm.rs | 1 - crates/cheatcodes/src/inspector.rs | 147 +++++++++----------- testdata/cheats/RecordAccountAccesses.t.sol | 89 ++++++++---- testdata/cheats/Vm.sol | 2 +- 6 files changed, 132 insertions(+), 114 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index c4f6cc8d738b..8e300ad1a08c 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -372,6 +372,11 @@ "name": "storageAccesses", "ty": "StorageAccess[]", "description": "An ordered list of storage accesses made during an account access operation." + }, + { + "name": "depth", + "ty": "uint64", + "description": "Call depth traversed during the recording of state differences" } ] }, diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 370c8b27a4cf..f9e0ca51d705 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -203,6 +203,8 @@ interface Vm { bool reverted; /// An ordered list of storage accesses made during an account access operation. StorageAccess[] storageAccesses; + /// Call depth traversed during the recording of state differences + uint64 depth; } /// The storage accessed during an `AccountAccess`. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 27eb17733ae8..2fec645036cc 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -543,7 +543,6 @@ fn get_state_diff(state: &mut Cheatcodes) -> Result { .unwrap_or_default() .into_iter() .flatten() - .map(|record| record.access) .collect::>(); Ok(res.abi_encode()) } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 878a037ab976..889cc2446e72 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -13,6 +13,7 @@ use crate::{ ExpectedRevert, ExpectedRevertKind, }, CheatsConfig, CheatsCtxt, Error, Result, Vm, + Vm::AccountAccess, }; use alloy_primitives::{Address, Bytes, B256, U256, U64}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; @@ -83,14 +84,6 @@ pub struct BroadcastableTransaction { /// List of transactions that can be broadcasted. pub type BroadcastableTransactions = VecDeque; -#[derive(Clone, Debug)] -pub struct AccountAccess { - /// The account access. - pub access: crate::Vm::AccountAccess, - /// The call depth the account was accessed. - pub depth: u64, -} - /// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. /// /// Cheatcodes can be called by contracts during execution to modify the VM environment, such as @@ -434,10 +427,11 @@ impl Inspector for Cheatcodes { reverted: false, deployedCode: vec![], storageAccesses: vec![], + depth: data.journaled_state.depth(), }; // Ensure that we're not selfdestructing a context recording was initiated on if let Some(last) = account_accesses.last_mut() { - last.push(AccountAccess { access, depth: data.journaled_state.depth() }); + last.push(access); } } } @@ -541,18 +535,14 @@ impl Inspector for Cheatcodes { reverted: false, deployedCode: vec![], storageAccesses: vec![], - }; - let access = AccountAccess { - access: account_access, - // use current depth; EXT* opcodes are not creating new contexts depth: data.journaled_state.depth(), }; // Record the EXT* call as an account access at the current depth // (future storage accesses will be recorded in a new "Resume" context) if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.push(access); + last.push(account_access); } else { - recorded_account_diffs_stack.push(vec![access]); + recorded_account_diffs_stack.push(vec![account_access]); } } _ => (), @@ -883,23 +873,21 @@ impl Inspector for Cheatcodes { // updated with the revert status of this call, since the EVM does not mark accounts // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { - access: crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), - }, - accessor: call.context.caller, - account: call.contract, - kind, - initialized, - oldBalance: old_balance, - newBalance: U256::ZERO, // updated on call_end - value: call.transfer.value, - data: call.input.to_vec(), - reverted: false, - deployedCode: vec![], - storageAccesses: vec![], // updated on step + chainInfo: crate::Vm::ChainInfo { + forkId: data.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(data.env.cfg.chain_id), }, + accessor: call.context.caller, + account: call.contract, + kind, + initialized, + oldBalance: old_balance, + newBalance: U256::ZERO, // updated on call_end + value: call.transfer.value, + data: call.input.to_vec(), + reverted: false, + deployedCode: vec![], + storageAccesses: vec![], // updated on step depth: data.journaled_state.depth(), }]); } @@ -1004,9 +992,8 @@ impl Inspector for Cheatcodes { // accordance with EVM behavior if status.is_revert() { last_recorded_depth.iter_mut().for_each(|element| { - element.access.reverted = true; + element.reverted = true; element - .access .storageAccesses .iter_mut() .for_each(|storage_access| storage_access.reverted = true); @@ -1019,8 +1006,8 @@ impl Inspector for Cheatcodes { if call_access.depth == data.journaled_state.depth() { if let Ok((acc, _)) = data.journaled_state.load_account(call.contract, data.db) { - debug_assert!(access_is_call(call_access.access.kind)); - call_access.access.newBalance = acc.info.balance; + debug_assert!(access_is_call(call_access.kind)); + call_access.newBalance = acc.info.balance; } } // Merge the last depth's AccountAccesses into the AccountAccesses at the current @@ -1267,23 +1254,21 @@ impl Inspector for Cheatcodes { // Record the create context as an account access and create a new vector to record all // subsequent account accesses recorded_account_diffs_stack.push(vec![AccountAccess { - access: crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), - }, - accessor: call.caller, - account: address, - kind: crate::Vm::AccountAccessKind::Create, - initialized: true, - oldBalance: U256::ZERO, // updated on create_end - newBalance: U256::ZERO, // updated on create_end - value: call.value, - data: call.init_code.to_vec(), - reverted: false, - deployedCode: vec![], // updated on create_end - storageAccesses: vec![], // updated on create_end + chainInfo: crate::Vm::ChainInfo { + forkId: data.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(data.env.cfg.chain_id), }, + accessor: call.caller, + account: address, + kind: crate::Vm::AccountAccessKind::Create, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + value: call.value, + data: call.init_code.to_vec(), + reverted: false, + deployedCode: vec![], // updated on create_end + storageAccesses: vec![], // updated on create_end depth, }]); } @@ -1357,9 +1342,8 @@ impl Inspector for Cheatcodes { // accordance with EVM behavior if status.is_revert() { last_depth.iter_mut().for_each(|element| { - element.access.reverted = true; + element.reverted = true; element - .access .storageAccesses .iter_mut() .for_each(|storage_access| storage_access.reverted = true); @@ -1372,15 +1356,15 @@ impl Inspector for Cheatcodes { // percolated up to a higher depth. if create_access.depth == data.journaled_state.depth() { debug_assert_eq!( - create_access.access.kind as u8, + create_access.kind as u8, crate::Vm::AccountAccessKind::Create as u8 ); if let Some(address) = address { if let Ok((created_acc, _)) = data.journaled_state.load_account(address, data.db) { - create_access.access.newBalance = created_acc.info.balance; - create_access.access.deployedCode = created_acc + create_access.newBalance = created_acc.info.balance; + create_access.deployedCode = created_acc .info .code .clone() @@ -1464,23 +1448,21 @@ fn apply_create2_deployer( if let Some(recorded_account_diffs_stack) = diffs_stack { let calldata = [&salt.to_be_bytes::<32>()[..], &call.init_code[..]].concat(); recorded_account_diffs_stack.push(vec![AccountAccess { - access: crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), - }, - accessor: call.caller, - account: DEFAULT_CREATE2_DEPLOYER, - kind: crate::Vm::AccountAccessKind::Call, - initialized: true, - oldBalance: U256::ZERO, // updated on create_end - newBalance: U256::ZERO, // updated on create_end - value: call.value, - data: calldata, - reverted: false, - deployedCode: vec![], // updated on create_end - storageAccesses: vec![], // updated on create_end + chainInfo: crate::Vm::ChainInfo { + forkId: data.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(data.env.cfg.chain_id), }, + accessor: call.caller, + account: DEFAULT_CREATE2_DEPLOYER, + kind: crate::Vm::AccountAccessKind::Call, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + value: call.value, + data: calldata, + reverted: false, + deployedCode: vec![], // updated on create_end + storageAccesses: vec![], // updated on create_end depth: data.journaled_state.depth(), }]) } @@ -1589,32 +1571,33 @@ fn append_storage_access( // 2. If there's an existing Resume record, then add the storage access to it. // 3. Otherwise, create a new Resume record based on the current context. if last.len() == 1 { - last.first_mut().unwrap().access.storageAccesses.push(storage_access); + last.first_mut().unwrap().storageAccesses.push(storage_access); } else { let last_record = last.last_mut().unwrap(); - if last_record.access.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { - last_record.access.storageAccesses.push(storage_access); + if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { + last_record.storageAccesses.push(storage_access); } else { let entry = last.first().unwrap(); let resume_record = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: entry.access.chainInfo.forkId, - chainId: entry.access.chainInfo.chainId, + forkId: entry.chainInfo.forkId, + chainId: entry.chainInfo.chainId, }, - accessor: entry.access.accessor, - account: entry.access.account, + accessor: entry.accessor, + account: entry.account, kind: crate::Vm::AccountAccessKind::Resume, - initialized: entry.access.initialized, + initialized: entry.initialized, storageAccesses: vec![storage_access], - reverted: entry.access.reverted, + reverted: entry.reverted, // The remaining fields are defaults oldBalance: U256::ZERO, newBalance: U256::ZERO, value: U256::ZERO, data: vec![], deployedCode: vec![], + depth: entry.depth, }; - last.push(AccountAccess { access: resume_record, depth: entry.depth }); + last.push(resume_record); } } } diff --git a/testdata/cheats/RecordAccountAccesses.t.sol b/testdata/cheats/RecordAccountAccesses.t.sol index 15c1780b1a35..a86361a757eb 100644 --- a/testdata/cheats/RecordAccountAccesses.t.sol +++ b/testdata/cheats/RecordAccountAccesses.t.sol @@ -348,7 +348,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }) ); @@ -366,7 +367,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); assertEq( @@ -383,7 +385,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "hello world", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }) ); assertEq( @@ -400,7 +403,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); assertEq( @@ -417,7 +421,8 @@ contract RecordAccountAccessesTest is DSTest { value: 2 ether, data: abi.encodePacked(type(SelfCaller).creationCode, abi.encode("hello2 world2")), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); assertEq( @@ -434,7 +439,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.2 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); } @@ -461,7 +467,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodeCall(this.revertingCall, (address(1234), "")), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }) ); assertEq( @@ -478,7 +485,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.1 ether, data: "", reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); } @@ -503,7 +511,7 @@ contract RecordAccountAccessesTest is DSTest { Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 7 + toUint(expectFirstCall), "incorrect length"); - uint256 startingIndex = toUint(expectFirstCall); + uint64 startingIndex = uint64(toUint(expectFirstCall)); if (expectFirstCall) { assertEq( called[0], @@ -519,7 +527,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex }) ); } @@ -551,7 +560,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodeCall(NestedRunner.run, (shouldRevert)), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex }), false ); @@ -583,7 +593,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.1 ether, data: abi.encodeCall(Reverter.run, ()), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 1 }), false ); @@ -615,7 +626,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.01 ether, data: abi.encodeCall(Doer.run, ()), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 2 }), false ); @@ -647,7 +659,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.001 ether, data: abi.encodeCall(Doer.doStuff, ()), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 3 }), false ); @@ -679,7 +692,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.1 ether, data: abi.encodeCall(Succeeder.run, ()), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 4 }), false ); @@ -711,7 +725,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.01 ether, data: abi.encodeCall(Doer.run, ()), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 5 }), false ); @@ -743,7 +758,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.001 ether, data: abi.encodeCall(Doer.doStuff, ()), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 6 }), false ); @@ -782,7 +798,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: abi.encodeCall(NestedStorer.run, ()), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }), false ); @@ -826,7 +843,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: abi.encodeCall(NestedStorer.run2, ()), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }), false ); @@ -858,7 +876,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }), false ); @@ -903,7 +922,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(false)), reverted: false, - storageAccesses: storageAccesses + storageAccesses: storageAccesses, + depth: 0 }) ); @@ -925,7 +945,8 @@ contract RecordAccountAccessesTest is DSTest { (bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) ), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); @@ -953,7 +974,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: creationCode, reverted: true, - storageAccesses: storageAccesses + storageAccesses: storageAccesses, + depth: 2 }) ); } @@ -998,7 +1020,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: creationCode, reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); assertEq( @@ -1015,7 +1038,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }) ); } @@ -1044,7 +1068,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(this))), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); assertEq( @@ -1061,7 +1086,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }) ); assertEq( @@ -1078,7 +1104,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(bytes20("doesn't exist yet")))), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); assertEq( @@ -1095,7 +1122,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 4 }) ); } @@ -1190,7 +1218,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: expected.reverted, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }), false ); diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 4bc85d457ce0..f28c719231fd 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -16,7 +16,7 @@ interface Vm { struct Wallet { address addr; uint256 publicKeyX; uint256 publicKeyY; uint256 privateKey; } struct FfiResult { int32 exitCode; bytes stdout; bytes stderr; } struct ChainInfo { uint256 forkId; uint256 chainId; } - struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; } + struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } function _expectCheatcodeRevert() external; function _expectCheatcodeRevert(bytes4 revertData) external; From 811dfe345df392a196432f66abf0587639c9ddb0 Mon Sep 17 00:00:00 2001 From: malik Date: Tue, 27 Feb 2024 15:50:26 +0100 Subject: [PATCH 13/15] test: add more positive fuzz test cases (#7101) * changes * changes * proposed fix * format * fixed * Update crates/forge/tests/it/fuzz.rs Co-authored-by: Oliver Nordbjerg --------- Co-authored-by: Oliver Nordbjerg --- crates/forge/tests/it/fuzz.rs | 35 +++++++++++++++++++++++++++++++- testdata/fuzz/FuzzPositive.t.sol | 18 ++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 testdata/fuzz/FuzzPositive.t.sol diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 66f2b4d38287..2eefa530d623 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -13,7 +13,7 @@ async fn test_fuzz() { let suite_result = runner .test_collect( &Filter::new(".*", ".*", ".*fuzz/") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)|testSuccessChecker\(uint256\)|testSuccessChecker2\(int256\)|testSuccessChecker3\(uint32\)") .exclude_paths("invariant"), test_opts(), ) @@ -46,6 +46,39 @@ async fn test_fuzz() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_successful_fuzz_cases() { + let mut runner = runner().await; + + let suite_result = runner + .test_collect( + &Filter::new(".*", ".*", ".*fuzz/FuzzPositive") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") + .exclude_paths("invariant"), + test_opts(), + ) + .await; + + assert!(!suite_result.is_empty()); + + for (_, SuiteResult { test_results, .. }) in suite_result { + for (test_name, result) in test_results { + match test_name.as_str() { + "testSuccessChecker(uint256)" | + "testSuccessChecker2(int256)" | + "testSuccessChecker3(uint32)" => assert!( + result.status == TestStatus::Success, + "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", + test_name, + result.reason, + result.decoded_logs.join("\n") + ), + _ => {} + } + } + } +} + /// Test that showcases PUSH collection on normal fuzzing. Ignored until we collect them in a /// smarter way. #[tokio::test(flavor = "multi_thread")] diff --git a/testdata/fuzz/FuzzPositive.t.sol b/testdata/fuzz/FuzzPositive.t.sol new file mode 100644 index 000000000000..952a3b699227 --- /dev/null +++ b/testdata/fuzz/FuzzPositive.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +contract FuzzPositive is DSTest { + function testSuccessChecker(uint256 val) public { + assertTrue(true); + } + + function testSuccessChecker2(int256 val) public { + assert(val == val); + } + + function testSuccessChecker3(uint32 val) public { + assert(val + 0 == val); + } +} From d65768ac3b58e32482b0552b9621c4c74efe86b2 Mon Sep 17 00:00:00 2001 From: galois Date: Wed, 28 Feb 2024 12:15:33 +0800 Subject: [PATCH 14/15] fix(cast): fix to address reveal problem (#7254) --- crates/common/src/fmt/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/fmt/ui.rs b/crates/common/src/fmt/ui.rs index 4c21328cea88..5549f36abc5f 100644 --- a/crates/common/src/fmt/ui.rs +++ b/crates/common/src/fmt/ui.rs @@ -180,7 +180,7 @@ type {}", ); if let Some(to) = to { - pretty.push_str(&format!("\nto {}", to)); + pretty.push_str(&format!("\nto {}", to.pretty())); } // additional captured fields From cb99bace7a06fa3871c3673c3eead61d53ad5245 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 28 Feb 2024 11:15:05 +0400 Subject: [PATCH 15/15] feat(forge): isolated execution (#7186) * [wip] feat(forge): isolated execution * small fixes * don't panic on transaction error + fixture fix * stricter call scheme check * refactor and more fixes * wip * fix * wip * wip * rm cheatcodes check * clippy * update commit logic * opt-in * enable in gas reports * --isolate * isolation tests * smaller diff * fmt * simplify logic * docs * fmt * enable isolation properly for --gas-report * change nonce incrementing * document why we touch --- crates/cheatcodes/src/inspector.rs | 6 +- crates/common/src/evm.rs | 11 + crates/config/src/lib.rs | 6 + crates/evm/core/src/backend/fuzz.rs | 10 +- crates/evm/core/src/opts.rs | 3 + crates/evm/evm/src/inspectors/stack.rs | 337 +++++++++++++++++++++--- crates/forge/bin/cmd/script/executor.rs | 17 +- crates/forge/bin/cmd/test/mod.rs | 6 + crates/forge/src/gas_report.rs | 11 + crates/forge/src/multi_runner.rs | 11 + crates/forge/tests/cli/config.rs | 1 + crates/forge/tests/cli/test_cmd.rs | 83 ++++++ testdata/repros/Issue3653.t.sol | 4 +- 13 files changed, 460 insertions(+), 46 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 889cc2446e72..f6627114f5c9 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -839,6 +839,9 @@ impl Inspector for Cheatcodes { debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); let prev = account.info.nonce; + + // Touch account to ensure that incremented nonce is committed + account.mark_touch(); account.info.nonce += 1; debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce"); } else if broadcast.single_call { @@ -1509,9 +1512,10 @@ fn process_broadcast_create( // by the create2_deployer let account = data.journaled_state.state().get_mut(&broadcast_sender).unwrap(); let prev = account.info.nonce; + // Touch account to ensure that incremented nonce is committed + account.mark_touch(); account.info.nonce += 1; debug!(target: "cheatcodes", address=%broadcast_sender, nonce=prev+1, prev, "incremented nonce in create2"); - // Proxy deployer requires the data to be `salt ++ init_code` let calldata = [&salt.to_be_bytes::<32>()[..], &bytecode[..]].concat(); (calldata.into(), Some(DEFAULT_CREATE2_DEPLOYER), prev) diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index a924fe5251a1..2230bd760874 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -144,6 +144,13 @@ pub struct EvmArgs { #[clap(flatten)] #[serde(flatten)] pub env: EnvArgs, + + /// Whether to enable isolation of calls. + /// 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. + #[clap(long)] + #[serde(skip)] + pub isolate: bool, } // Make this set of options a `figment::Provider` so that it can be merged into the `Config` @@ -166,6 +173,10 @@ impl Provider for EvmArgs { dict.insert("ffi".to_string(), self.ffi.into()); } + if self.isolate { + dict.insert("isolate".to_string(), self.isolate.into()); + } + if self.always_use_create_2_factory { dict.insert( "always_use_create_2_factory".to_string(), diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f582322a9ae9..32c713230492 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -374,6 +374,11 @@ pub struct Config { /// Should be removed once EvmVersion Cancun is supported by solc pub cancun: bool, + /// Whether to enable call isolation. + /// + /// Useful for more correct gas accounting and EVM behavior in general. + pub isolate: bool, + /// Address labels pub labels: HashMap, @@ -1820,6 +1825,7 @@ impl Default for Config { profile: Self::DEFAULT_PROFILE, fs_permissions: FsPermissions::new([PathPermission::read("out")]), cancun: false, + isolate: false, __root: Default::default(), src: "src".into(), test: "test".into(), diff --git a/crates/evm/core/src/backend/fuzz.rs b/crates/evm/core/src/backend/fuzz.rs index f4d362ec8d84..11857fa4ab64 100644 --- a/crates/evm/core/src/backend/fuzz.rs +++ b/crates/evm/core/src/backend/fuzz.rs @@ -11,8 +11,8 @@ use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; use revm::{ db::DatabaseRef, - primitives::{AccountInfo, Bytecode, Env, ResultAndState}, - Database, Inspector, JournaledState, + primitives::{Account, AccountInfo, Bytecode, Env, HashMap as Map, ResultAndState}, + Database, DatabaseCommit, Inspector, JournaledState, }; use std::{borrow::Cow, collections::HashMap}; @@ -279,3 +279,9 @@ impl<'a> Database for FuzzBackendWrapper<'a> { DatabaseRef::block_hash_ref(self, number) } } + +impl<'a> DatabaseCommit for FuzzBackendWrapper<'a> { + fn commit(&mut self, changes: Map) { + self.backend.to_mut().commit(changes) + } +} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 49aaa0a2e284..510f14254ad7 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -61,6 +61,9 @@ pub struct EvmOpts { /// The memory limit per EVM execution in bytes. /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. pub memory_limit: u64, + + /// Whether to enable isolation of calls. + pub isolate: bool, } impl EvmOpts { diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index a1fc13d70619..2a66ac76280d 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -3,15 +3,21 @@ use super::{ StackSnapshotType, TracePrinter, TracingInspector, TracingInspectorConfig, }; use alloy_primitives::{Address, Bytes, Log, B256, U256}; -use foundry_evm_core::{backend::DatabaseExt, debug::DebugArena}; +use foundry_evm_core::{ + backend::DatabaseExt, + debug::DebugArena, + utils::{eval_to_instruction_result, halt_to_instruction_result}, +}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use revm::{ + evm_inner, interpreter::{ - return_revert, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, Stack, + return_revert, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, + Stack, }, - primitives::{BlockEnv, Env}, - EVMData, Inspector, + primitives::{BlockEnv, Env, ExecutionResult, Output, State, TransactTo}, + DatabaseCommit, EVMData, Inspector, }; use std::{collections::HashMap, sync::Arc}; @@ -44,6 +50,10 @@ pub struct InspectorStackBuilder { pub print: Option, /// The chisel state inspector. pub chisel_state: Option, + /// Whether to enable call isolation. + /// 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, } impl InspectorStackBuilder { @@ -123,6 +133,14 @@ impl InspectorStackBuilder { self } + /// Set whether to enable the call isolation. + /// For description of call isolation, see [`InspectorStack::enable_isolation`]. + #[inline] + pub fn enable_isolation(mut self, yes: bool) -> Self { + self.enable_isolation = yes; + self + } + /// Builds the stack of inspectors to use when transacting/committing on the EVM. /// /// See also [`revm::Evm::inspect_ref`] and [`revm::Evm::commit_ref`]. @@ -138,6 +156,7 @@ impl InspectorStackBuilder { coverage, print, chisel_state, + enable_isolation, } = self; let mut stack = InspectorStack::new(); @@ -157,6 +176,8 @@ impl InspectorStackBuilder { stack.print(print.unwrap_or(false)); stack.tracing(trace.unwrap_or(false)); + stack.enable_isolation(enable_isolation); + // environment, must come after all of the inspectors if let Some(block) = block { stack.set_block(&block); @@ -180,6 +201,38 @@ macro_rules! call_inspectors { )+}} } +/// Same as [call_inspectors] macro, but with depth adjustment for isolated execution. +macro_rules! call_inspectors_adjust_depth { + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { + if $self.in_inner_context { + $data.journaled_state.depth += 1; + } + {$( + if let Some($id) = $inspector { + if let Some(result) = $call { + if $self.in_inner_context { + $data.journaled_state.depth -= 1; + } + return result; + } + } + )+} + if $self.in_inner_context { + $data.journaled_state.depth -= 1; + } + } +} + +/// Helper method which updates data in the state with the data from the database. +fn update_state(state: &mut State, db: &mut DB) { + for (addr, acc) in state.iter_mut() { + acc.info = db.basic(*addr).unwrap().unwrap_or_default(); + for (key, val) in acc.storage.iter_mut() { + val.present_value = db.storage(*addr, *key).unwrap(); + } + } +} + /// The collected results of [`InspectorStack`]. pub struct InspectorData { pub logs: Vec, @@ -191,6 +244,24 @@ pub struct InspectorData { pub chisel_state: Option<(Stack, Vec, InstructionResult)>, } +/// Contains data about the state of outer/main EVM which created and invoked the inner EVM context. +/// Used to adjust EVM state while in inner context. +/// +/// We need this to avoid breaking changes due to EVM behavior differences in isolated vs +/// non-isolated mode. For descriptions and workarounds for those changes see: https://github.com/foundry-rs/foundry/pull/7186#issuecomment-1959102195 +#[derive(Debug, Clone)] +pub struct InnerContextData { + /// The sender of the inner EVM context. + /// It is also an origin of the transaction that created the inner EVM context. + sender: Address, + /// Nonce of the sender before invocation of the inner EVM context. + original_sender_nonce: u64, + /// Origin of the transaction in the outer EVM context. + original_origin: Address, + /// Whether the inner context was created by a CREATE transaction. + is_create: bool, +} + /// An inspector that calls multiple inspectors in sequence. /// /// If a call to an inspector returns a value other than [InstructionResult::Continue] (or @@ -205,6 +276,11 @@ pub struct InspectorStack { pub log_collector: Option, pub printer: Option, pub tracer: Option, + pub enable_isolation: bool, + + /// Flag marking if we are in the inner EVM context. + pub in_inner_context: bool, + pub inner_context_data: Option, } impl InspectorStack { @@ -271,6 +347,12 @@ impl InspectorStack { self.debugger = yes.then(Default::default); } + /// Set whether to enable call isolation. + #[inline] + pub fn enable_isolation(&mut self, yes: bool) { + self.enable_isolation = yes; + } + /// Set whether to enable the log collector. #[inline] pub fn collect_logs(&mut self, yes: bool) { @@ -327,7 +409,7 @@ impl InspectorStack { status: InstructionResult, retdata: Bytes, ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( + call_inspectors_adjust_depth!( [ &mut self.fuzzer, &mut self.debugger, @@ -346,19 +428,136 @@ impl InspectorStack { if new_status != status || (new_status == InstructionResult::Revert && new_retdata != retdata) { - return (new_status, new_gas, new_retdata); + Some((new_status, new_gas, new_retdata)) + } else { + None } - } + }, + self, + data ); - (status, remaining_gas, retdata) } + + fn transact_inner( + &mut self, + data: &mut EVMData<'_, DB>, + transact_to: TransactTo, + caller: Address, + input: Bytes, + gas_limit: u64, + value: U256, + ) -> (InstructionResult, Option
, Gas, Bytes) { + data.db.commit(data.journaled_state.state.clone()); + + let nonce = data + .journaled_state + .load_account(caller, data.db) + .expect("failed to load caller") + .0 + .info + .nonce; + + let cached_env = data.env.clone(); + + data.env.block.basefee = U256::ZERO; + data.env.tx.caller = caller; + data.env.tx.transact_to = transact_to.clone(); + data.env.tx.data = input; + data.env.tx.value = value; + data.env.tx.nonce = Some(nonce); + // Add 21000 to the gas limit to account for the base cost of transaction. + // We might have modified block gas limit earlier and revm will reject tx with gas limit > + // block gas limit, so we adjust. + data.env.tx.gas_limit = std::cmp::min(gas_limit + 21000, data.env.block.gas_limit.to()); + data.env.tx.gas_price = U256::ZERO; + + self.inner_context_data = Some(InnerContextData { + sender: data.env.tx.caller, + original_origin: cached_env.tx.caller, + original_sender_nonce: nonce, + is_create: matches!(transact_to, TransactTo::Create(_)), + }); + self.in_inner_context = true; + let res = evm_inner(data.env, data.db, Some(self)).transact(); + self.in_inner_context = false; + self.inner_context_data = None; + + data.env.tx = cached_env.tx; + data.env.block.basefee = cached_env.block.basefee; + + let mut gas = Gas::new(gas_limit); + + let Ok(mut res) = res else { + // Should we match, encode and propagate error as a revert reason? + return (InstructionResult::Revert, None, gas, Bytes::new()); + }; + + // Commit changes after transaction + data.db.commit(res.state.clone()); + + // Update both states with new DB data after commit. + update_state(&mut data.journaled_state.state, data.db); + update_state(&mut res.state, data.db); + + // Merge transaction journal into the active journal. + for (addr, acc) in res.state { + if let Some(acc_mut) = data.journaled_state.state.get_mut(&addr) { + acc_mut.status |= acc.status; + for (key, val) in acc.storage { + if !acc_mut.storage.contains_key(&key) { + acc_mut.storage.insert(key, val); + } + } + } else { + data.journaled_state.state.insert(addr, acc); + } + } + + match res.result { + ExecutionResult::Success { reason, gas_used, gas_refunded, logs: _, output } => { + gas.set_refund(gas_refunded as i64); + gas.record_cost(gas_used); + let address = match output { + Output::Create(_, address) => address, + Output::Call(_) => None, + }; + (eval_to_instruction_result(reason), address, gas, output.into_data()) + } + ExecutionResult::Halt { reason, gas_used } => { + gas.record_cost(gas_used); + (halt_to_instruction_result(reason), None, gas, Bytes::new()) + } + ExecutionResult::Revert { gas_used, output } => { + gas.record_cost(gas_used); + (InstructionResult::Revert, None, gas, output) + } + } + } + + /// Adjusts the EVM data for the inner EVM context. + /// Should be called on the top-level call of inner context (depth == 0 && + /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility + /// Updates tx.origin to the value before entering inner context + fn adjust_evm_data_for_inner_context(&mut self, data: &mut EVMData<'_, DB>) { + let inner_context_data = + self.inner_context_data.as_ref().expect("should be called in inner context"); + let sender_acc = data + .journaled_state + .state + .get_mut(&inner_context_data.sender) + .expect("failed to load sender"); + if !inner_context_data.is_create { + sender_acc.info.nonce = inner_context_data.original_sender_nonce; + } + data.env.tx.caller = inner_context_data.original_origin; + } } -impl Inspector for InspectorStack { +impl Inspector for InspectorStack { fn initialize_interp(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { let res = interpreter.instruction_result; - call_inspectors!( + call_inspectors_adjust_depth!( [ &mut self.debugger, &mut self.coverage, @@ -372,16 +571,19 @@ impl Inspector for InspectorStack { // Allow inspectors to exit early if interpreter.instruction_result != res { - #[allow(clippy::needless_return)] - return; + Some(()) + } else { + None } - } + }, + self, + data ); } fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { let res = interpreter.instruction_result; - call_inspectors!( + call_inspectors_adjust_depth!( [ &mut self.fuzzer, &mut self.debugger, @@ -396,10 +598,13 @@ impl Inspector for InspectorStack { // Allow inspectors to exit early if interpreter.instruction_result != res { - #[allow(clippy::needless_return)] - return; + Some(()) + } else { + None } - } + }, + self, + data ); } @@ -410,17 +615,20 @@ impl Inspector for InspectorStack { topics: &[B256], data: &Bytes, ) { - call_inspectors!( + call_inspectors_adjust_depth!( [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], |inspector| { inspector.log(evm_data, address, topics, data); - } + None + }, + self, + evm_data ); } fn step_end(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { let res = interpreter.instruction_result; - call_inspectors!( + call_inspectors_adjust_depth!( [ &mut self.debugger, &mut self.tracer, @@ -434,10 +642,13 @@ impl Inspector for InspectorStack { // Allow inspectors to exit early if interpreter.instruction_result != res { - #[allow(clippy::needless_return)] - return; + Some(()) + } else { + None } - } + }, + self, + data ); } @@ -446,7 +657,12 @@ impl Inspector for InspectorStack { data: &mut EVMData<'_, DB>, call: &mut CallInputs, ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( + if self.in_inner_context && data.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(data); + return (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()); + } + + call_inspectors_adjust_depth!( [ &mut self.fuzzer, &mut self.debugger, @@ -460,13 +676,32 @@ impl Inspector for InspectorStack { let (status, gas, retdata) = inspector.call(data, call); // Allow inspectors to exit early - #[allow(clippy::needless_return)] if status != InstructionResult::Continue { - return (status, gas, retdata); + Some((status, gas, retdata)) + } else { + None } - } + }, + self, + data ); + if self.enable_isolation && + call.context.scheme == CallScheme::Call && + !self.in_inner_context && + data.journaled_state.depth == 1 + { + let (res, _, gas, output) = self.transact_inner( + data, + TransactTo::Call(call.contract), + call.context.caller, + call.input.clone(), + call.gas_limit, + call.transfer.value, + ); + return (res, gas, output); + } + (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) } @@ -478,8 +713,13 @@ impl Inspector for InspectorStack { status: InstructionResult, retdata: Bytes, ) -> (InstructionResult, Gas, Bytes) { - let res = self.do_call_end(data, call, remaining_gas, status, retdata); + // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. + // Avoid processing twice. + if self.in_inner_context && data.journaled_state.depth == 0 { + return (status, remaining_gas, retdata); + } + let res = self.do_call_end(data, call, remaining_gas, status, retdata); if matches!(res.0, return_revert!()) { // Encountered a revert, since cheatcodes may have altered the evm state in such a way // that violates some constraints, e.g. `deal`, we need to manually roll back on revert @@ -497,7 +737,12 @@ impl Inspector for InspectorStack { data: &mut EVMData<'_, DB>, call: &mut CreateInputs, ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!( + if self.in_inner_context && data.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(data); + return (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()); + } + + call_inspectors_adjust_depth!( [ &mut self.debugger, &mut self.tracer, @@ -511,11 +756,26 @@ impl Inspector for InspectorStack { // Allow inspectors to exit early if status != InstructionResult::Continue { - return (status, addr, gas, retdata); + Some((status, addr, gas, retdata)) + } else { + None } - } + }, + self, + data ); + if self.enable_isolation && !self.in_inner_context && data.journaled_state.depth == 1 { + return self.transact_inner( + data, + TransactTo::Create(call.scheme), + call.caller, + call.init_code.clone(), + call.gas_limit, + call.value, + ); + } + (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) } @@ -528,7 +788,12 @@ impl Inspector for InspectorStack { remaining_gas: Gas, retdata: Bytes, ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!( + // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. + // Avoid processing twice. + if self.in_inner_context && data.journaled_state.depth == 0 { + return (status, address, remaining_gas, retdata); + } + call_inspectors_adjust_depth!( [ &mut self.debugger, &mut self.tracer, @@ -548,9 +813,13 @@ impl Inspector for InspectorStack { ); if new_status != status { - return (new_status, new_address, new_gas, new_retdata); + Some((new_status, new_address, new_gas, new_retdata)) + } else { + None } - } + }, + self, + data ); (status, address, remaining_gas, retdata) diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index e0f8ed8ece1b..071cbcbb219d 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -303,14 +303,17 @@ impl ScriptArgs { if let SimulationStage::Local = stage { builder = builder.inspectors(|stack| { - stack.debug(self.debug).cheatcodes( - CheatsConfig::new( - &script_config.config, - script_config.evm_opts.clone(), - script_wallets, + stack + .debug(self.debug) + .cheatcodes( + CheatsConfig::new( + &script_config.config, + script_config.evm_opts.clone(), + script_wallets, + ) + .into(), ) - .into(), - ) + .enable_isolation(script_config.evm_opts.isolate) }); } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index ba928f6ea313..b9ca859b8e5a 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -142,6 +142,11 @@ impl TestArgs { // Merge all configs let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + // Explicitly enable isolation for gas reports for more correct gas accounting + if self.gas_report { + evm_opts.isolate = true; + } + // Set up the project. let mut project = config.project()?; @@ -196,6 +201,7 @@ impl TestArgs { .with_fork(evm_opts.get_fork(&config, env.clone())) .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone(), None)) .with_test_options(test_options.clone()) + .enable_isolation(evm_opts.isolate) .build(project_root, output, env, evm_opts)?; if let Some(debug_test_pattern) = &self.debug { diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index afe41f76dfa5..f6c269d7029e 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -7,6 +7,7 @@ use crate::{ }; use comfy_table::{presets::ASCII_MARKDOWN, *}; use foundry_common::{calc, TestFunctionExt}; +use foundry_evm::traces::CallKind; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Display}; @@ -75,6 +76,16 @@ impl GasReport { return; } + // Only include top-level calls which accout for calldata and base (21.000) cost. + // Only include Calls and Creates as only these calls are isolated in inspector. + if trace.depth != 1 && + (trace.kind == CallKind::Call || + trace.kind == CallKind::Create || + trace.kind == CallKind::Create2) + { + return; + } + let decoded = decoder.decode_function(&node.trace).await; let Some(name) = &decoded.contract else { return }; diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 0651e82d6184..699df0da0566 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -60,6 +60,8 @@ pub struct MultiContractRunner { pub debug: bool, /// Settings related to fuzz and/or invariant tests pub test_options: TestOptions, + /// Whether to enable call isolation + pub isolation: bool, } impl MultiContractRunner { @@ -179,6 +181,7 @@ impl MultiContractRunner { .trace(self.evm_opts.verbosity >= 3 || self.debug) .debug(self.debug) .coverage(self.coverage) + .enable_isolation(self.isolation) }) .spec(self.evm_spec) .gas_limit(self.evm_opts.gas_limit()) @@ -256,6 +259,8 @@ pub struct MultiContractRunnerBuilder { pub coverage: bool, /// Whether or not to collect debug info pub debug: bool, + /// Whether to enable call isolation + pub isolation: bool, /// Settings related to fuzz and/or invariant tests pub test_options: Option, } @@ -301,6 +306,11 @@ impl MultiContractRunnerBuilder { self } + pub fn enable_isolation(mut self, enable: bool) -> Self { + self.isolation = enable; + self + } + /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm pub fn build( @@ -381,6 +391,7 @@ impl MultiContractRunnerBuilder { coverage: self.coverage, debug: self.debug, test_options: self.test_options.unwrap_or_default(), + isolation: self.isolation, }) } } diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index f6fe89efb2a0..d8f9db3106d9 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -121,6 +121,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { fs_permissions: Default::default(), labels: Default::default(), cancun: true, + isolate: true, __non_exhaustive: (), __warnings: vec![], }; diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 58b9b1eb85de..61cbab489e77 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -407,3 +407,86 @@ contract CustomTypesTest is Test { .join("tests/fixtures/include_custom_types_in_traces.stdout"), ); }); + +forgetest_init!(can_test_selfdestruct_with_isolation, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Destructing { + function destruct() public { + selfdestruct(payable(address(0))); + } +} + +contract SelfDestructTest is Test { + function test() public { + Destructing d = new Destructing(); + vm.store(address(d), bytes32(0), bytes32(uint256(1))); + d.destruct(); + assertEq(address(d).code.length, 0); + assertEq(vm.load(address(d), bytes32(0)), bytes32(0)); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate"]).assert_success(); +}); + +forgetest_init!(can_test_transient_storage_with_isolation, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract TransientTester { + function locked() public view returns (bool isLocked) { + assembly { + isLocked := tload(0) + } + } + + modifier lock() { + require(!locked(), "locked"); + assembly { + tstore(0, 1) + } + _; + } + + function maybeReentrant(address target, bytes memory data) public lock { + (bool success, bytes memory ret) = target.call(data); + if (!success) { + // forwards revert reason + assembly { + let ret_size := mload(ret) + revert(add(32, ret), ret_size) + } + } + } +} + +contract TransientTest is Test { + function test() public { + TransientTester t = new TransientTester(); + vm.expectRevert(bytes("locked")); + t.maybeReentrant(address(t), abi.encodeCall(TransientTester.maybeReentrant, (address(0), new bytes(0)))); + + t.maybeReentrant(address(0), new bytes(0)); + assertEq(t.locked(), false); + } +} + + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate", "--evm-version", "cancun"]).assert_success(); +}); diff --git a/testdata/repros/Issue3653.t.sol b/testdata/repros/Issue3653.t.sol index 6e52c49f8aa5..5022af67859f 100644 --- a/testdata/repros/Issue3653.t.sol +++ b/testdata/repros/Issue3653.t.sol @@ -11,13 +11,13 @@ contract Issue3653Test is DSTest { Token token; constructor() { - fork = vm.createSelectFork("rpcAlias", 10); + fork = vm.createSelectFork("rpcAlias", 1000000); token = new Token(); vm.makePersistent(address(token)); } function testDummy() public { - assertEq(block.number, 10); + assertEq(block.number, 1000000); } }