From 590c463e563000b728d027beb4a38de8058a32e9 Mon Sep 17 00:00:00 2001 From: Matthew Wiriyathananon-Smith Date: Sat, 19 Feb 2022 01:22:57 +0700 Subject: [PATCH] Expand forge test --match interface (again) (#662) * Added test_path method to TestFilter * Added path regex to test interface * Added source path filtering to MultiContractRunner * Updated test Filter and reorganiezed test_helpers * Updated tests to use new filter * Fixed test filter * Use new into_artifacts * Path filtering requires absolute path * Formatting * Fixed warnings * Minor refactoring * Minor refactoring * Bumped semver to 1.0.5 for dev compatibility with ethers-rs * Added passing test for foundry_utils::link * Renamed test * chore: bump ethers for latest artifacts update https://github.com/gakonst/ethers-rs/pull/882 driveby fixes: https://github.com/gakonst/ethers-rs/pull/930 https://github.com/gakonst/ethers-rs/pull/928 Co-authored-by: Georgios Konstantopoulos --- Cargo.lock | 55 ++++++++-------- cli/Cargo.toml | 2 +- cli/src/cmd/mod.rs | 12 +--- cli/src/cmd/test.rs | 41 ++++++++++-- cli/tests/cmd.rs | 2 +- config/Cargo.toml | 2 +- forge/Cargo.toml | 2 +- forge/src/lib.rs | 57 +++++++++++------ forge/src/multi_runner.rs | 36 +++++++---- forge/src/runner.rs | 12 ++-- utils/Cargo.toml | 7 +- utils/src/lib.rs | 99 ++++++++++++++++++++++++++++- utils/testdata/linking/LinkTest.sol | 66 +++++++++++++++++++ 13 files changed, 309 insertions(+), 84 deletions(-) create mode 100644 utils/testdata/linking/LinkTest.sol diff --git a/Cargo.lock b/Cargo.lock index dd649fe78598..af16ed961ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -836,11 +836,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array 0.14.5", + "typenum", ] [[package]] @@ -940,13 +941,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.0", "crypto-common", - "generic-array 0.14.5", "subtle", ] @@ -1071,7 +1071,7 @@ checksum = "aff9543a3535519a0d2ebbb6ae0afd58175258162e2fa4c1b57981edaad60fcb" dependencies = [ "aes", "ctr", - "digest 0.10.1", + "digest 0.10.3", "hex", "hmac 0.12.0", "pbkdf2 0.10.0", @@ -1154,7 +1154,7 @@ dependencies = [ [[package]] name = "ethers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -1169,7 +1169,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "0.1.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "ethers-core", "once_cell", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "Inflector", "cfg-if 1.0.0", @@ -1221,7 +1221,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "arrayvec 0.7.2", "bytes", @@ -1263,7 +1263,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "0.2.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "ethers-core", "ethers-solc", @@ -1277,7 +1277,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "async-trait", "ethers-contract", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "async-trait", "auto_impl", @@ -1332,7 +1332,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "async-trait", "coins-bip32", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "0.3.0" -source = "git+https://github.com/gakonst/ethers-rs#16b9de513497c0c0d165011fcb1d7f920af6d6a8" +source = "git+https://github.com/gakonst/ethers-rs#5b2c1fa6f8758945aceab895c6ed5f3f3868cec2" dependencies = [ "colored", "dunce", @@ -1675,6 +1675,7 @@ dependencies = [ name = "foundry-utils" version = "0.1.0" dependencies = [ + "ethers", "ethers-addressbook", "ethers-core", "ethers-etherscan", @@ -1976,7 +1977,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", ] [[package]] @@ -2380,11 +2381,11 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "md-5" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", ] [[package]] @@ -2765,7 +2766,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4628cc3cf953b82edcd3c1388c5715401420ce5524fedbab426bd5aba017434" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", "hmac 0.12.0", "password-hash 0.3.2", "sha2 0.10.1", @@ -3293,7 +3294,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea54b64a1a8410c48395c154adadbad7e1bcd02debca79fc3694386cf73e5799" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", ] [[package]] @@ -3647,7 +3648,7 @@ checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.1", + "digest 0.10.3", ] [[package]] @@ -3690,7 +3691,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", "keccak", ] @@ -4132,9 +4133,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b67f2d696025..812d180554df 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -50,7 +50,7 @@ serde = "1.0.133" sputnik = { package = "evm", git = "https://github.com/rust-blockchain/evm", optional = true } proptest = "1.0.0" glob = "0.3.0" -semver = "1.0.4" +semver = "1.0.5" once_cell = "1.9.0" locate-cargo-manifest = "0.2.2" walkdir = "2.3.2" diff --git a/cli/src/cmd/mod.rs b/cli/src/cmd/mod.rs index b10fd632c788..50a74b432c20 100644 --- a/cli/src/cmd/mod.rs +++ b/cli/src/cmd/mod.rs @@ -152,16 +152,8 @@ fn get_artifact_from_name( let mut has_found_contract = false; let mut contract_artifact = None; - for (name, artifact) in compiled.into_artifacts() { - // if the contract name - let mut split = name.split(':'); - let mut artifact_contract_name = - split.next().ok_or_else(|| eyre::Error::msg("no contract name provided"))?; - if let Some(new_name) = split.next() { - artifact_contract_name = new_name; - }; - - if artifact_contract_name == contract.name { + for (artifact_id, artifact) in compiled.into_artifacts() { + if artifact_id.name == contract.name { if has_found_contract { eyre::bail!("contract with duplicate name. pass path") } diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/test.rs index 8f9fb60b31f5..cd3fc602eb9b 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/test.rs @@ -12,14 +12,15 @@ use evm_adapters::{ }; use forge::{MultiContractRunnerBuilder, TestFilter}; use foundry_config::{figment::Figment, Config}; -use std::collections::BTreeMap; +use regex::Regex; +use std::{collections::BTreeMap, str::FromStr}; #[derive(Debug, Clone, Parser)] pub struct Filter { #[clap( long = "match", short = 'm', - help = "only run test methods matching regex (deprecated, see --match-test, --match-contract)" + help = "only run test methods matching regex (deprecated, see --match-test)" )] pattern: Option, @@ -54,11 +55,28 @@ pub struct Filter { conflicts_with = "pattern" )] contract_pattern_inverse: Option, + + #[clap( + long = "match-path", + alias = "mp", + help = "only run test methods in source files at path matching regex. Requires absolute path", + conflicts_with = "pattern" + )] + path_pattern: Option, + + #[clap( + long = "no-match-path", + alias = "nmp", + help = "only run test methods in source files at path not matching regex. Requires absolute path", + conflicts_with = "pattern" + )] + path_pattern_inverse: Option, } impl TestFilter for Filter { - fn matches_test(&self, test_name: &str) -> bool { + fn matches_test(&self, test_name: impl AsRef) -> bool { let mut ok = true; + let test_name = test_name.as_ref(); // Handle the deprecated option match if let Some(re) = &self.pattern { ok &= re.is_match(test_name); @@ -72,8 +90,9 @@ impl TestFilter for Filter { ok } - fn matches_contract(&self, contract_name: &str) -> bool { + fn matches_contract(&self, contract_name: impl AsRef) -> bool { let mut ok = true; + let contract_name = contract_name.as_ref(); if let Some(re) = &self.contract_pattern { ok &= re.is_match(contract_name); } @@ -82,6 +101,20 @@ impl TestFilter for Filter { } ok } + + fn matches_path(&self, path: impl AsRef) -> bool { + let mut ok = true; + let path = path.as_ref(); + if let Some(re) = &self.path_pattern { + let re = Regex::from_str(&format!("^{}", re.as_str())).unwrap(); + ok &= re.is_match(path); + } + if let Some(re) = &self.path_pattern_inverse { + let re = Regex::from_str(&format!("^{}", re.as_str())).unwrap(); + ok &= !re.is_match(path); + } + ok + } } // Loads project's figment and merges the build cli arguments into it diff --git a/cli/tests/cmd.rs b/cli/tests/cmd.rs index f2a1a397e150..ec8c1cfe70ba 100644 --- a/cli/tests/cmd.rs +++ b/cli/tests/cmd.rs @@ -259,7 +259,7 @@ forgetest_init!(can_emit_extra_output, |prj: TestProject, mut cmd: TestCommand| cmd.assert_non_empty_stdout(); let metadata_path = prj.paths().artifacts.join("Contract.sol/Contract.metadata.json"); - let artifact: Metadata = ethers::solc::utils::read_json_file(metadata_path).unwrap(); + let _artifact: Metadata = ethers::solc::utils::read_json_file(metadata_path).unwrap(); }); // test against a local checkout, useful to debug with local ethers-rs patch diff --git a/config/Cargo.toml b/config/Cargo.toml index c152a4e92895..e1ad2e945c1a 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" [dependencies] dirs-next = "2.0.0" -semver = { version = "1.0.4", features = ["serde"] } +semver = { version = "1.0.5", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.73" toml = "0.5" diff --git a/forge/Cargo.toml b/forge/Cargo.toml index de06271276aa..d8dcbcc8417d 100644 --- a/forge/Cargo.toml +++ b/forge/Cargo.toml @@ -12,7 +12,7 @@ evm-adapters = { path = "./../evm-adapters", features = ["sputnik", "sputnik-hel ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = false, features = ["solc-full"] } eyre = "0.6.5" -semver = "1.0.4" +semver = "1.0.5" serde_json = "1.0.67" serde = "1.0.130" regex = { version = "1.5.4", default-features = false } diff --git a/forge/src/lib.rs b/forge/src/lib.rs index 41864a4096cb..6d288439f72f 100644 --- a/forge/src/lib.rs +++ b/forge/src/lib.rs @@ -5,8 +5,9 @@ mod multi_runner; pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder}; pub trait TestFilter { - fn matches_test(&self, test_name: &str) -> bool; - fn matches_contract(&self, contract_name: &str) -> bool; + fn matches_test(&self, test_name: impl AsRef) -> bool; + fn matches_contract(&self, contract_name: impl AsRef) -> bool; + fn matches_path(&self, path: impl AsRef) -> bool; } #[cfg(test)] @@ -22,7 +23,6 @@ pub mod test_helpers { sputnik::helpers::VICINITY, FAUCET_ACCOUNT, }; - use regex::Regex; use sputnik::backend::MemoryBackend; pub static COMPILED: Lazy = Lazy::new(|| { @@ -49,27 +49,46 @@ pub mod test_helpers { backend }); - pub struct Filter { - test_regex: Regex, - contract_regex: Regex, - } + pub mod filter { + use super::*; + use regex::Regex; - impl Filter { - pub fn new(test_pattern: &str, contract_pattern: &str) -> Self { - Filter { - test_regex: Regex::new(test_pattern).unwrap(), - contract_regex: Regex::new(contract_pattern).unwrap(), - } + pub struct Filter { + test_regex: Regex, + contract_regex: Regex, + path_regex: Regex, } - } - impl TestFilter for Filter { - fn matches_test(&self, test_name: &str) -> bool { - self.test_regex.is_match(test_name) + impl Filter { + pub fn new(test_pattern: &str, contract_pattern: &str, path_pattern: &str) -> Self { + Filter { + test_regex: Regex::new(test_pattern).unwrap(), + contract_regex: Regex::new(contract_pattern).unwrap(), + path_regex: Regex::new(path_pattern).unwrap(), + } + } + + pub fn matches_all() -> Self { + Filter { + test_regex: Regex::new(".*").unwrap(), + contract_regex: Regex::new(".*").unwrap(), + path_regex: Regex::new(".*").unwrap(), + } + } } - fn matches_contract(&self, contract_name: &str) -> bool { - self.contract_regex.is_match(contract_name) + impl TestFilter for Filter { + fn matches_test(&self, test_name: impl AsRef) -> bool { + self.test_regex.is_match(test_name.as_ref()) + } + + fn matches_contract(&self, contract_name: impl AsRef) -> bool { + self.contract_regex.is_match(contract_name.as_ref()) + } + + fn matches_path(&self, path: impl AsRef) -> bool { + self.path_regex.is_match(path.as_ref()) + } } } } diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 90d3cb4d4e28..7cc411dbc342 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -1,5 +1,4 @@ use crate::{runner::TestResult, ContractRunner, TestFilter}; -use ethers::prelude::artifacts::CompactContractBytecode; use evm_adapters::{ evm_opts::{BackendKind, EvmOpts}, sputnik::cheatcodes::{CONSOLE_ABI, HEVMCONSOLE_ABI, HEVM_ABI}, @@ -7,12 +6,10 @@ use evm_adapters::{ use foundry_utils::PostLinkInput; use sputnik::{backend::Backend, Config}; -use ethers::solc::Artifact; - use ethers::{ abi::{Abi, Event, Function}, - prelude::ArtifactOutput, - solc::Project, + prelude::{artifacts::CompactContractBytecode, ArtifactId, ArtifactOutput}, + solc::{Artifact, Project}, types::{Address, H256, U256}, }; @@ -62,8 +59,19 @@ impl MultiContractRunnerBuilder { // artifacts let contracts = output .into_artifacts() - .map(|(n, c)| (n, c.into_contract_bytecode())) + .map(|(i, c)| (i, c.into_contract_bytecode())) + .collect::>(); + + let source_paths = contracts + .iter() + .map(|(i, _)| (i.slug(), i.source.to_string_lossy().into())) + .collect::>(); + + let contracts = contracts + .into_iter() + .map(|(i, c)| (i.slug(), c)) .collect::>(); + let mut known_contracts: BTreeMap)> = Default::default(); // create a mapping of name => (abi, deployment code, Vec) @@ -129,6 +137,7 @@ impl MultiContractRunnerBuilder { sender: self.sender, fuzzer: self.fuzzer, execution_info, + source_paths, }) } @@ -177,6 +186,8 @@ pub struct MultiContractRunner { fuzzer: Option, /// The address which will be used as the `from` field in all EVM calls sender: Option
, + /// A map of contract names to absolute source file paths + source_paths: BTreeMap, } impl MultiContractRunner { @@ -189,9 +200,11 @@ impl MultiContractRunner { let vicinity = self.evm_opts.vicinity()?; let backend = self.evm_opts.backend(&vicinity)?; + let source_paths = self.source_paths.clone(); let results = contracts .par_iter() + .filter(|(name, _)| filter.matches_path(source_paths.get(*name).unwrap())) .filter(|(name, _)| filter.matches_contract(name)) .map(|(name, (abi, deploy_code, libs))| { // unavoidable duplication here? @@ -247,7 +260,7 @@ impl MultiContractRunner { #[cfg(test)] mod tests { use super::*; - use crate::test_helpers::{Filter, EVM_OPTS}; + use crate::test_helpers::{filter::Filter, EVM_OPTS}; use ethers::solc::ProjectPathsConfig; use std::path::PathBuf; @@ -272,7 +285,7 @@ mod tests { fn test_multi_runner() { let mut runner = runner(); - let results = runner.test(&Filter::new(".*", ".*")).unwrap(); + let results = runner.test(&Filter::matches_all()).unwrap(); // 9 contracts being built assert_eq!(results.keys().len(), 9); @@ -287,7 +300,8 @@ mod tests { } // can also filter - let only_gm = runner.test(&Filter::new("testGm.*", ".*")).unwrap(); + let filter = Filter::new("testGm.*", ".*", ".*"); + let only_gm = runner.test(&filter).unwrap(); assert_eq!(only_gm.len(), 1); assert_eq!(only_gm["GmTest.json:GmTest"].len(), 1); @@ -296,7 +310,7 @@ mod tests { fn test_abstract_contract() { let mut runner = runner(); - let results = runner.test(&Filter::new(".*", ".*")).unwrap(); + let results = runner.test(&Filter::matches_all()).unwrap(); assert!(results.get("Tests.json:Tests").is_none()); assert!(results.get("ATests.json:ATests").is_some()); assert!(results.get("BTests.json:BTests").is_some()); @@ -309,7 +323,7 @@ mod tests { #[test] fn test_sputnik_debug_logs() { let mut runner = runner(); - let results = runner.test(&Filter::new(".*", ".*")).unwrap(); + let results = runner.test(&Filter::matches_all()).unwrap(); let reasons = results["DebugLogsTest.json:DebugLogsTest"] .iter() diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 505a371249ca..80d508b701f1 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -592,7 +592,7 @@ fn revert + evm_adapters::Evm, T>(_evm: #[cfg(test)] mod tests { use super::*; - use crate::test_helpers::{Filter, BACKEND, COMPILED, EVM_OPTS}; + use crate::test_helpers::{filter::Filter, BACKEND, COMPILED, EVM_OPTS}; use ethers::solc::artifacts::CompactContractRef; mod sputnik { @@ -628,8 +628,8 @@ mod tests { let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; let fuzzer = TestRunner::new(cfg); - let results = - runner.run_tests(&Filter::new("testGreeting", ".*"), Some(fuzzer), None).unwrap(); + let filter = Filter::new("testGreeting", ".*", ".*"); + let results = runner.run_tests(&filter, Some(fuzzer), None).unwrap(); assert!(results["testGreeting()"].success); assert!(results["testGreeting(string)"].success); assert!(results["testGreeting(string,string)"].success); @@ -645,8 +645,8 @@ mod tests { let mut cfg = FuzzConfig::default(); cfg.failure_persistence = None; let fuzzer = TestRunner::new(cfg); - let results = - runner.run_tests(&Filter::new("testFuzz.*", ".*"), Some(fuzzer), None).unwrap(); + let filter = Filter::new("testFuzz.*", ".*", ".*"); + let results = runner.run_tests(&filter, Some(fuzzer), None).unwrap(); for (_, res) in results { assert!(!res.success); assert!(res.counterexample.is_some()); @@ -713,7 +713,7 @@ mod tests { let mut libs = vec![]; let runner = sputnik::runner(compiled.abi.as_ref().unwrap(), code, &mut libs); - let res = runner.run_tests(&Filter::new(".*", ".*"), None, None).unwrap(); + let res = runner.run_tests(&Filter::matches_all(), None, None).unwrap(); assert!(!res.is_empty()); assert!(res.iter().all(|(_, result)| result.success)); } diff --git a/utils/Cargo.toml b/utils/Cargo.toml index fbe88f8a4e0f..3bd0c8ea14c4 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -25,5 +25,10 @@ serde_json = { version = "1.0.67", default-features = false } tokio = { version = "1.12.0", features = ["rt-multi-thread", "macros"] } rlp = "0.5.1" + +[dev-dependencies] +ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = false, features = ["solc-full"] } + + [features] -test = ["tracing-subscriber"] +test = ["tracing-subscriber"] \ No newline at end of file diff --git a/utils/src/lib.rs b/utils/src/lib.rs index ee1db00923e6..e0c2c52ab62f 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -806,6 +806,7 @@ pub fn abi_to_solidity(contract_abi: &Abi, mut contract_name: &str) -> Result { pub contract: CompactContractBytecode, pub known_contracts: &'a mut BTreeMap, @@ -906,7 +907,7 @@ pub fn link( } /// Enables tracing -#[cfg(any(feature = "test", test))] +#[cfg(any(feature = "test"))] pub fn init_tracing_subscriber() { let _ = tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) @@ -917,7 +918,101 @@ pub fn init_tracing_subscriber() { #[cfg(test)] mod tests { use super::*; - use ethers_core::abi::Abi; + use ethers::{ + abi::Abi, + solc::{artifacts::CompactContractBytecode, Project, ProjectPathsConfig}, + types::{Address, Bytes}, + }; + use std::path::PathBuf; + + #[test] + #[ignore] // TODO: This needs to be re-enabled one it's fixed in ethers-solc. + fn test_linking() { + let lib_test_json_lib_test = "6101d1610053600b82828239805160001a607314610046577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c806314ba3f121461003a575b600080fd5b610054600480360381019061004f91906100bb565b61006a565b60405161006191906100f7565b60405180910390f35b60006064826100799190610141565b9050919050565b600080fd5b6000819050919050565b61009881610085565b81146100a357600080fd5b50565b6000813590506100b58161008f565b92915050565b6000602082840312156100d1576100d0610080565b5b60006100df848285016100a6565b91505092915050565b6100f181610085565b82525050565b600060208201905061010c60008301846100e8565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061014c82610085565b915061015783610085565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156101905761018f610112565b5b82820290509291505056fea264697066735822122089bbb5614fb9e62f207b40682b397b25f2000c514857bf7959055b0d9b5dcfbf64736f6c634300080b0033"; + let lib_test_nested_json_lib_test_nested = "610266610053600b82828239805160001a607314610046577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100355760003560e01c80639acc23361461003a575b600080fd5b610054600480360381019061004f9190610116565b61006a565b604051610061919061015c565b60405180910390f35b60007347e9fbef8c83a1714f1951f142132e6e90f5fa5d6314ba3f1260656040518263ffffffff1660e01b81526004016100a491906101bc565b602060405180830381865af41580156100c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e59190610203565b9050919050565b600080fd5b600381106100fe57600080fd5b50565b600081359050610110816100f1565b92915050565b60006020828403121561012c5761012b6100ec565b5b600061013a84828501610101565b91505092915050565b6000819050919050565b61015681610143565b82525050565b6000602082019050610171600083018461014d565b92915050565b6000819050919050565b6000819050919050565b60006101a66101a161019c84610177565b610181565b610143565b9050919050565b6101b68161018b565b82525050565b60006020820190506101d160008301846101ad565b92915050565b6101e081610143565b81146101eb57600080fd5b50565b6000815190506101fd816101d7565b92915050565b600060208284031215610219576102186100ec565b5b6000610227848285016101ee565b9150509291505056fea26469706673582212204d96467c5d42f97ecaa460cca5137364d61ae850ee0be7c2d9c5ffb045bf8dc364736f6c634300080b0033"; + let contract_names = [ + "DsTestMini.json:DsTestMini", + "LibLinkingTest.json:LibLinkingTest", + "LibTest.json:LibTest", + "LibTestNested.json:LibTestNested", + "Main.json:Main", + ]; + + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/linking"); + let paths = ProjectPathsConfig::builder().root(&root).sources(&root).build().unwrap(); + + let project = Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap(); + + let output = project.compile().unwrap(); + let contracts = output + .into_artifacts() + .map(|(i, c)| (i.slug(), c.into_contract_bytecode())) + .collect::>(); + + let mut known_contracts: BTreeMap)> = Default::default(); + let mut deployable_contracts: BTreeMap)> = + Default::default(); + + assert_eq!(&contracts.keys().collect::>()[..], &contract_names[..]); + + link( + &contracts, + &mut known_contracts, + Address::default(), + &mut deployable_contracts, + |file, key| (format!("{}.json:{}", key, key), file, key), + |post_link_input| { + match post_link_input.fname.as_str() { + "DsTestMini.json:DsTestMini" => { + assert_eq!(post_link_input.dependencies.len(), 0); + } + "LibLinkingTest.json:LibLinkingTest" => { + assert_eq!(post_link_input.dependencies.len(), 3); + assert_eq!( + hex::encode(post_link_input.dependencies[0].clone()), + lib_test_json_lib_test + ); + assert_eq!( + hex::encode(post_link_input.dependencies[1].clone()), + lib_test_json_lib_test + ); + assert_eq!( + hex::encode(post_link_input.dependencies[2].clone()), + lib_test_nested_json_lib_test_nested + ); + } + "LibTest.json:LibTest" => { + assert_eq!(post_link_input.dependencies.len(), 0); + } + "LibTestNested.json:LibTestNested" => { + assert_eq!(post_link_input.dependencies.len(), 1); + assert_eq!( + hex::encode(post_link_input.dependencies[0].clone()), + lib_test_json_lib_test + ); + } + "Main.json:Main" => { + assert_eq!(post_link_input.dependencies.len(), 3); + assert_eq!( + hex::encode(post_link_input.dependencies[0].clone()), + lib_test_json_lib_test + ); + assert_eq!( + hex::encode(post_link_input.dependencies[1].clone()), + lib_test_json_lib_test + ); + assert_eq!( + hex::encode(post_link_input.dependencies[2].clone()), + lib_test_nested_json_lib_test_nested + ); + } + _ => assert!(false), + } + Ok(()) + }, + ) + .unwrap(); + } #[test] fn test_resolve_addr() { diff --git a/utils/testdata/linking/LinkTest.sol b/utils/testdata/linking/LinkTest.sol new file mode 100644 index 000000000000..b6ae16ba928f --- /dev/null +++ b/utils/testdata/linking/LinkTest.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.11; + +//import "./LibTest.sol"; + +// a library that needs to be linked with another library +library LibTestNested { + enum TestEnum2 { + A, + B, + C + } + + function foobar(TestEnum2 test) public view returns (uint256) { + return LibTest.foobar(101); + } +} + +// a library +library LibTest { + function foobar(uint256 a) public view returns (uint256) { + return a * 100; + } +} + + +// a contract that uses 2 linked libraries +contract Main { + function foo() public returns (uint256) { + return LibTest.foobar(1); + } + + function bar() public returns (uint256) { + return LibTestNested.foobar(LibTestNested.TestEnum2(0)); + } +} + +contract DsTestMini { + bool public failed; + + function fail() private { + failed = true; + } + + function assertEq(uint a, uint b) internal { + if (a != b) { + fail(); + } + } +} + + +contract LibLinkingTest is DsTestMini { + Main main; + function setUp() public { + main = new Main(); + } + + function testCall() public { + assertEq(100, main.foo()); + } + + function testCall2() public { + assertEq(10100, main.bar()); + } +}