diff --git a/Cargo.lock b/Cargo.lock index b262dd4564c86..9ce73bc689315 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1890,7 +1890,7 @@ dependencies = [ [[package]] name = "ethers" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -1905,7 +1905,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "ethers-core", "once_cell", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -1934,7 +1934,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "Inflector", "const-hex", @@ -1957,7 +1957,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "Inflector", "const-hex", @@ -1972,7 +1972,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "arrayvec", "bytes", @@ -2001,7 +2001,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "ethers-core", "ethers-solc", @@ -2016,7 +2016,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "async-trait", "auto_impl", @@ -2042,7 +2042,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "async-trait", "auto_impl", @@ -2080,7 +2080,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "async-trait", "coins-bip32", @@ -2107,7 +2107,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#179891d45cc7b50941f2b8c30b206355ab76d94c" +source = "git+https://github.com/gakonst/ethers-rs#7603af021be920ca39b610f88c0012328b337cd6" dependencies = [ "cfg-if", "const-hex", diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs index a64cd67299184..c5e5a9be61f3e 100644 --- a/crates/config/src/inline/conf_parser.rs +++ b/crates/config/src/inline/conf_parser.rs @@ -71,11 +71,8 @@ where fn validate_configs(natspec: &NatSpec) -> Result<(), InlineConfigError> { let config_key = Self::config_key(); - let configs = natspec - .config_lines() - .into_iter() - .filter(|l| l.contains(&config_key)) - .collect::>(); + let configs = + natspec.config_lines().filter(|l| l.contains(&config_key)).collect::>(); Self::default().try_merge(&configs).map_err(|e| { let line = natspec.debug_context(); diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 36d8cefeca2b6..eda0a1fec078b 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -1,12 +1,10 @@ -use std::{collections::BTreeMap, path::Path}; - +use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; use ethers_solc::{ artifacts::{ast::NodeType, Node}, ProjectCompileOutput, }; use serde_json::Value; - -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; +use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations pub struct NatSpec { @@ -26,21 +24,19 @@ impl NatSpec { /// Factory function that extracts a vector of [`NatSpec`] instances from /// a solc compiler output. The root path is to express contract base dirs. /// That is essential to match per-test configs at runtime. - pub fn parse

(output: &ProjectCompileOutput, root: &P) -> Vec - where - P: AsRef, - { + pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { let mut natspecs: Vec = vec![]; - let output = output.clone(); - for artifact in output.with_stripped_file_prefixes(root).into_artifacts() { - if let Some(ast) = artifact.1.ast.as_ref() { - let contract: String = artifact.0.identifier(); - if let Some(node) = contract_root_node(&ast.nodes, &contract) { - apply(&mut natspecs, &contract, node) - } - } + for (id, artifact) in output.artifact_ids() { + let Some(ast) = &artifact.ast else { continue }; + let path = id.source.as_path(); + let path = path.strip_prefix(root).unwrap_or(path); + // id.identifier + let contract = format!("{}:{}", path.display(), id.name); + let Some(node) = contract_root_node(&ast.nodes, &contract) else { continue }; + apply(&mut natspecs, &contract, node) } + natspecs } @@ -52,24 +48,21 @@ impl NatSpec { } /// Returns a list of configuration lines that match the current profile - pub fn current_profile_configs(&self) -> Vec { - let prefix: &str = INLINE_CONFIG_PREFIX_SELECTED_PROFILE.as_ref(); - self.config_lines_with_prefix(prefix) + pub fn current_profile_configs(&self) -> impl Iterator + '_ { + self.config_lines_with_prefix(INLINE_CONFIG_PREFIX_SELECTED_PROFILE.as_str()) } /// Returns a list of configuration lines that match a specific string prefix - pub fn config_lines_with_prefix>(&self, prefix: S) -> Vec { - let prefix: String = prefix.into(); - self.config_lines().into_iter().filter(|l| l.starts_with(&prefix)).collect() + pub fn config_lines_with_prefix<'a>( + &'a self, + prefix: &'a str, + ) -> impl Iterator + 'a { + self.config_lines().filter(move |l| l.starts_with(prefix)) } /// Returns a list of all the configuration lines available in the natspec - pub fn config_lines(&self) -> Vec { - self.docs - .split('\n') - .map(remove_whitespaces) - .filter(|line| line.contains(INLINE_CONFIG_PREFIX)) - .collect::>() + pub fn config_lines(&self) -> impl Iterator + '_ { + self.docs.lines().map(remove_whitespaces).filter(|line| line.contains(INLINE_CONFIG_PREFIX)) } } @@ -137,7 +130,7 @@ fn get_fn_docs(fn_data: &BTreeMap) -> Option<(String, String)> { let mut src_line = fn_docs .get("src") .map(|src| src.to_string()) - .unwrap_or(String::from("")); + .unwrap_or_else(|| String::from("")); src_line.retain(|c| c != '"'); return Some((comment.into(), src_line)) @@ -158,7 +151,7 @@ mod tests { let natspec = natspec(); let config_lines = natspec.config_lines(); assert_eq!( - config_lines, + config_lines.collect::>(), vec![ "forge-config:default.fuzz.runs=600".to_string(), "forge-config:ci.fuzz.runs=500".to_string(), @@ -173,7 +166,7 @@ mod tests { let config_lines = natspec.current_profile_configs(); assert_eq!( - config_lines, + config_lines.collect::>(), vec![ "forge-config:default.fuzz.runs=600".to_string(), "forge-config:default.invariant.runs=1".to_string() @@ -186,9 +179,9 @@ mod tests { use super::INLINE_CONFIG_PREFIX; let natspec = natspec(); let prefix = format!("{INLINE_CONFIG_PREFIX}:default"); - let config_lines = natspec.config_lines_with_prefix(prefix); + let config_lines = natspec.config_lines_with_prefix(&prefix); assert_eq!( - config_lines, + config_lines.collect::>(), vec![ "forge-config:default.fuzz.runs=600".to_string(), "forge-config:default.invariant.runs=1".to_string() diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index e5a0b560acbe8..6b95bc788c0ed 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -165,9 +165,8 @@ impl TestArgs { let test_options: TestOptions = TestOptionsBuilder::default() .fuzz(config.fuzz) .invariant(config.invariant) - .compile_output(&output) .profiles(profiles) - .build(project_root)?; + .build(&output, project_root)?; // Determine print verbosity and executor verbosity let verbosity = evm_opts.verbosity; diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 35537bde685a2..24bebfd7c1380 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -47,6 +47,47 @@ pub struct TestOptions { } impl TestOptions { + /// Tries to create a new instance by detecting inline configurations from the project compile + /// output. + pub fn new( + output: &ProjectCompileOutput, + root: &Path, + profiles: Vec, + base_fuzz: FuzzConfig, + base_invariant: InvariantConfig, + ) -> Result { + let natspecs: Vec = NatSpec::parse(output, root); + let mut inline_invariant = InlineConfig::::default(); + let mut inline_fuzz = InlineConfig::::default(); + + for natspec in natspecs { + // Perform general validation + validate_profiles(&natspec, &profiles)?; + FuzzConfig::validate_configs(&natspec)?; + InvariantConfig::validate_configs(&natspec)?; + + // Apply in-line configurations for the current profile + let configs: Vec = natspec.current_profile_configs().collect(); + let c: &str = &natspec.contract; + let f: &str = &natspec.function; + let line: String = natspec.debug_context(); + + match base_fuzz.try_merge(&configs) { + Ok(Some(conf)) => inline_fuzz.insert(c, f, conf), + Ok(None) => { /* No inline config found, do nothing */ } + Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, + } + + match base_invariant.try_merge(&configs) { + Ok(Some(conf)) => inline_invariant.insert(c, f, conf), + Ok(None) => { /* No inline config found, do nothing */ } + Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, + } + } + + Ok(Self { fuzz: base_fuzz, invariant: base_invariant, inline_fuzz, inline_invariant }) + } + /// Returns a "fuzz" test runner instance. Parameters are used to select tight scoped fuzz /// configs that apply for a contract-function pair. A fallback configuration is applied /// if no specific setup is found for a given input. @@ -127,83 +168,23 @@ impl TestOptions { } } -impl<'a, P> TryFrom<(&'a ProjectCompileOutput, &'a P, Vec, FuzzConfig, InvariantConfig)> - for TestOptions -where - P: AsRef, -{ - type Error = InlineConfigError; - - /// Tries to create an instance of `Self`, detecting inline configurations from the project - /// compile output. - /// - /// Param is a tuple, whose elements are: - /// 1. Solidity compiler output, essential to extract natspec test configs. - /// 2. Root path to express contract base dirs. This is essential to match inline configs at - /// runtime. 3. List of available configuration profiles - /// 4. Reference to a fuzz base configuration. - /// 5. Reference to an invariant base configuration. - fn try_from( - value: (&'a ProjectCompileOutput, &'a P, Vec, FuzzConfig, InvariantConfig), - ) -> Result { - let output = value.0; - let root = value.1; - let profiles = &value.2; - let base_fuzz: FuzzConfig = value.3; - let base_invariant: InvariantConfig = value.4; - - let natspecs: Vec = NatSpec::parse(output, root); - let mut inline_invariant = InlineConfig::::default(); - let mut inline_fuzz = InlineConfig::::default(); - - for natspec in natspecs { - // Perform general validation - validate_profiles(&natspec, profiles)?; - FuzzConfig::validate_configs(&natspec)?; - InvariantConfig::validate_configs(&natspec)?; - - // Apply in-line configurations for the current profile - let configs: Vec = natspec.current_profile_configs(); - let c: &str = &natspec.contract; - let f: &str = &natspec.function; - let line: String = natspec.debug_context(); - - match base_fuzz.try_merge(&configs) { - Ok(Some(conf)) => inline_fuzz.insert(c, f, conf), - Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, - _ => { /* No inline config found, do nothing */ } - } - - match base_invariant.try_merge(&configs) { - Ok(Some(conf)) => inline_invariant.insert(c, f, conf), - Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, - _ => { /* No inline config found, do nothing */ } - } - } - - Ok(Self { fuzz: base_fuzz, invariant: base_invariant, inline_fuzz, inline_invariant }) - } -} - /// Builder utility to create a [`TestOptions`] instance. #[derive(Default)] +#[must_use = "builders do nothing unless you call `build` on them"] pub struct TestOptionsBuilder { fuzz: Option, invariant: Option, profiles: Option>, - output: Option, } impl TestOptionsBuilder { /// Sets a [`FuzzConfig`] to be used as base "fuzz" configuration. - #[must_use = "A base 'fuzz' config must be provided"] pub fn fuzz(mut self, conf: FuzzConfig) -> Self { self.fuzz = Some(conf); self } /// Sets a [`InvariantConfig`] to be used as base "invariant" configuration. - #[must_use = "A base 'invariant' config must be provided"] pub fn invariant(mut self, conf: InvariantConfig) -> Self { self.invariant = Some(conf); self @@ -216,40 +197,21 @@ impl TestOptionsBuilder { self } - /// Sets a project compiler output instance. This is used to extract - /// inline test configurations that override `self.fuzz` and `self.invariant` - /// specs when necessary. - pub fn compile_output(mut self, output: &ProjectCompileOutput) -> Self { - self.output = Some(output.clone()); - self - } - /// Creates an instance of [`TestOptions`]. This takes care of creating "fuzz" and /// "invariant" fallbacks, and extracting all inline test configs, if available. /// /// `root` is a reference to the user's project root dir. This is essential /// to determine the base path of generated contract identifiers. This is to provide correct /// matchers for inline test configs. - pub fn build(self, root: impl AsRef) -> Result { - let default_profiles = vec![Config::selected_profile().into()]; - let profiles: Vec = self.profiles.unwrap_or(default_profiles); + pub fn build( + self, + output: &ProjectCompileOutput, + root: &Path, + ) -> Result { + let profiles: Vec = + self.profiles.unwrap_or_else(|| vec![Config::selected_profile().into()]); let base_fuzz = self.fuzz.unwrap_or_default(); let base_invariant = self.invariant.unwrap_or_default(); - - match self.output { - Some(compile_output) => Ok(TestOptions::try_from(( - &compile_output, - &root, - profiles, - base_fuzz, - base_invariant, - ))?), - None => Ok(TestOptions { - fuzz: base_fuzz, - invariant: base_invariant, - inline_fuzz: InlineConfig::default(), - inline_invariant: InlineConfig::default(), - }), - } + TestOptions::new(output, root, profiles, base_fuzz, base_invariant) } } diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 4f76391373a76..fb32da49c4351 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -76,9 +76,8 @@ fn build_test_options() { let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) .profiles(profiles) - .build(root); + .build(&COMPILED, root); assert!(build_result.is_ok()); } @@ -90,9 +89,8 @@ fn build_test_options_just_one_valid_profile() { let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) .profiles(valid_profiles) - .build(root); + .build(&COMPILED, root); // We expect an error, since COMPILED contains in-line // per-test configs for "default" and "ci" profiles @@ -104,7 +102,6 @@ fn test_options() -> TestOptions { TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) - .build(root) + .build(&COMPILED, root) .expect("Config loaded") }