From 386c6427a467ab837004f118798f130b715d5fa9 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 08:54:32 +0000 Subject: [PATCH 01/26] create separate args --- cast/src/lib.rs | 29 +++++++++++++++++++++++++++ cli/src/cast.rs | 8 +------- cli/src/cmd/cast/mod.rs | 1 + cli/src/cmd/cast/storage.rs | 40 +++++++++++++++++++++++++++++++++++++ cli/src/opts/cast.rs | 22 +++----------------- 5 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 cli/src/cmd/cast/storage.rs diff --git a/cast/src/lib.rs b/cast/src/lib.rs index 5bd2715f862eb..53dc477e6ee69 100644 --- a/cast/src/lib.rs +++ b/cast/src/lib.rs @@ -681,6 +681,35 @@ where let res = self.provider.provider().request::(method, params).await?; Ok(serde_json::to_string(&res)?) } + + /// Returns the slot + /// + /// # Example + /// + /// ```no_run + /// use cast::Cast; + /// use ethers_providers::{Provider, Http}; + /// use ethers_core::types::{Address, H256}; + /// use std::{str::FromStr, convert::TryFrom}; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let cast = Cast::new(provider); + /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; + /// let slot = H256::zero(); + /// let storage = cast.storage(addr, slot, None).await?; + /// println!("{}", storage); + /// # Ok(()) + /// # } + /// ``` + pub async fn storage( + &self, + address: Address, + slot: H256, + block: Option, + ) -> Result { + Ok(format!("{}", self.provider.get_storage_at(from, location, block).await?)) + } } pub struct InterfaceSource { diff --git a/cli/src/cast.rs b/cli/src/cast.rs index f5476ceb1a3c1..e7a666c49ae12 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -289,13 +289,7 @@ async fn main() -> eyre::Result<()> { println!("{}", serde_json::to_string(&value)?); } Subcommands::Rpc(cmd) => cmd.run()?.await?, - Subcommands::Storage { address, slot, rpc_url, block } => { - let rpc_url = consume_config_rpc_url(rpc_url); - - let provider = try_get_http_provider(rpc_url)?; - let value = provider.get_storage_at(address, slot, block).await?; - println!("{:?}", value); - } + Subcommands::Storage(cmd) => cmd.run()?.await?, // Calls & transactions Subcommands::Call(cmd) => cmd.run().await?, diff --git a/cli/src/cmd/cast/mod.rs b/cli/src/cmd/cast/mod.rs index 619cf007a8c3e..66bbf01a4835a 100644 --- a/cli/src/cmd/cast/mod.rs +++ b/cli/src/cmd/cast/mod.rs @@ -12,4 +12,5 @@ pub mod interface; pub mod rpc; pub mod run; pub mod send; +pub mod storage; pub mod wallet; diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs new file mode 100644 index 0000000000000..26ddf8d6a34fc --- /dev/null +++ b/cli/src/cmd/cast/storage.rs @@ -0,0 +1,40 @@ +use crate::{cast::parse_slot, cmd::Cmd, utils::consume_config_rpc_url}; +use cast::Cast; +use clap::Parser; +use ethers::types::BlockId; +use eyre::Result; +use foundry_common::{parse_block_id, try_get_http_provider}; + +#[derive(Debug, Clone, Parser)] +pub struct StorageArgs { + #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] + address: NameOrAddress, + #[clap(help = "The storage slot number (hex or decimal)", parse(try_from_str = parse_slot), value_name = "SLOT")] + slot: Option, + #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] + rpc_url: Option, + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id), + value_name = "BLOCK" + )] + block: Option, +} + +impl StorageArgs { + async fn run(&self) -> Result<()> { + let rpc_url = consume_config_rpc_url(rpc_url); + let provider = try_get_http_provider(rpc_url)?; + let cast = Cast::new(provider); + + if let Some(slot) = self.slot { + println!("{}", cast.storage(self.address, slot, self.block).await?); + return Ok(()) + } + + Ok(()) + } +} diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 737c766aea3a9..a9b96cb379dda 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -3,7 +3,7 @@ use crate::{ cmd::cast::{ call::CallArgs, estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, - wallet::WalletSubcommands, + storage::StorageArgs, wallet::WalletSubcommands, }, utils::parse_u256, }; @@ -620,23 +620,7 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline visible_alias = "st", about = "Get the raw value of a contract's storage slot." )] - Storage { - #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] - address: NameOrAddress, - #[clap(help = "The storage slot number (hex or decimal)", parse(try_from_str = parse_slot), value_name = "SLOT")] - slot: H256, - #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] - rpc_url: Option, - #[clap( - long, - short = 'B', - help = "The block height you want to query at.", - long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), - value_name = "BLOCK" - )] - block: Option, - }, + Storage(StorageArgs), #[clap( name = "proof", visible_alias = "pr", @@ -765,7 +749,7 @@ pub fn parse_block_id(s: &str) -> eyre::Result { }) } -fn parse_slot(s: &str) -> eyre::Result { +pub fn parse_slot(s: &str) -> eyre::Result { Numeric::from_str(s) .map_err(|e| eyre::eyre!("Could not parse slot number: {e}")) .map(|n| H256::from_uint(&n.into())) From f50e3e14cb786acf7e8eae80ee6569ae8ef26224 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:33:29 +0000 Subject: [PATCH 02/26] feat: storage, initial --- Cargo.lock | 1 + cast/src/lib.rs | 6 +- cli/Cargo.toml | 22 ++-- cli/src/cast.rs | 2 +- cli/src/cmd/cast/storage.rs | 200 ++++++++++++++++++++++++++++++++--- cli/src/cmd/forge/inspect.rs | 64 ++++++----- 6 files changed, 232 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3e4865fd068d..9545ddf12b4ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2162,6 +2162,7 @@ dependencies = [ "strsim", "strum", "svm-rs", + "tempfile", "thiserror", "tokio", "toml", diff --git a/cast/src/lib.rs b/cast/src/lib.rs index 53dc477e6ee69..8dcc992a4792f 100644 --- a/cast/src/lib.rs +++ b/cast/src/lib.rs @@ -702,13 +702,13 @@ where /// # Ok(()) /// # } /// ``` - pub async fn storage( + pub async fn storage + Send + Sync>( &self, - address: Address, + from: T, slot: H256, block: Option, ) -> Result { - Ok(format!("{}", self.provider.get_storage_at(from, location, block).await?)) + Ok(format!("{:?}", self.provider.get_storage_at(from, slot, block).await?)) } } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 65b20c08626ca..cfbd551cd9c62 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,11 +8,7 @@ repository = "https://github.com/foundry-rs/foundry" keywords = ["ethereum", "web3"] [build-dependencies] -vergen = { version = "7.0", default-features = false, features = [ - "build", - "rustc", - "git", -] } +vergen = { version = "7.0", default-features = false, features = ["build", "rustc", "git"] } [dependencies] # foundry internal @@ -29,12 +25,7 @@ ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = fals solang-parser = "0.1.11" # cli -clap = { version = "3.0.10", features = [ - "derive", - "env", - "unicode", - "wrap_help", -] } +clap = { version = "3.0.10", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "3.0.4" clap_complete_fig = "3.2.4" yansi = "0.5.1" @@ -45,7 +36,11 @@ console = "0.15.0" watchexec = "2.0.0" atty = "0.2.14" comfy-table = "6.0.0" -reqwest = { version = "0.11.8", default-features = false, features = ["json", "rustls", "rustls-native-certs"] } +reqwest = { version = "0.11.8", default-features = false, features = [ + "json", + "rustls", + "rustls-native-certs" +] } dotenv = "0.15.0" dialoguer = { version = "0.10.2", default-features = false } @@ -61,6 +56,7 @@ dunce = "1.0.2" glob = "0.3.0" globset = "0.4.8" path-slash = "0.2.0" +tempfile = "3.3.0" # misc eyre = "0.6" @@ -70,7 +66,7 @@ serde_json = "1.0.67" regex = { version = "1.5.4", default-features = false } rpassword = "7.0.0" hex = "0.4.3" -serde = { version="1.0.133", features = ["derive"] } +serde = { version = "1.0.133", features = ["derive"] } itertools = "0.10.3" proptest = "1.0.0" semver = "1.0.5" diff --git a/cli/src/cast.rs b/cli/src/cast.rs index e7a666c49ae12..d3adc0ecbfa26 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -289,7 +289,7 @@ async fn main() -> eyre::Result<()> { println!("{}", serde_json::to_string(&value)?); } Subcommands::Rpc(cmd) => cmd.run()?.await?, - Subcommands::Storage(cmd) => cmd.run()?.await?, + Subcommands::Storage(cmd) => cmd.run().await?, // Calls & transactions Subcommands::Call(cmd) => cmd.run().await?, diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 26ddf8d6a34fc..2b97df84daf4c 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -1,40 +1,206 @@ -use crate::{cast::parse_slot, cmd::Cmd, utils::consume_config_rpc_url}; +use crate::{ + cmd::forge::{build, inspect::print_storage_layout}, + opts::cast::{parse_block_id, parse_name_or_address, parse_slot}, + utils::consume_config_rpc_url, +}; use cast::Cast; use clap::Parser; -use ethers::types::BlockId; -use eyre::Result; -use foundry_common::{parse_block_id, try_get_http_provider}; +use ethers::{ + prelude::*, + solc::artifacts::{output_selection::ContractOutputSelection, Optimizer, Settings}, +}; +use eyre::{ContextCompat, Result}; +use foundry_common::{compile::compile, try_get_http_provider}; +use foundry_config::Config; #[derive(Debug, Clone, Parser)] pub struct StorageArgs { + // Storage #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] address: NameOrAddress, #[clap(help = "The storage slot number (hex or decimal)", parse(try_from_str = parse_slot), value_name = "SLOT")] slot: Option, - #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] + #[clap(long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, #[clap( - long, - short = 'B', - help = "The block height you want to query at.", - long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), - value_name = "BLOCK" - )] + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id), + value_name = "BLOCK" + )] block: Option, + + // Etherscan + #[clap(long, short, env = "ETHERSCAN_API_KEY", help = "etherscan API key", value_name = "KEY")] + etherscan_api_key: Option, + #[clap( + long, + visible_alias = "chain-id", + env = "CHAIN", + help = "The chain ID the contract is deployed to.", + default_value = "mainnet", + value_name = "CHAIN" + )] + chain: Chain, + + // Forge + #[clap(flatten)] + build: build::CoreBuildArgs, } impl StorageArgs { - async fn run(&self) -> Result<()> { + pub async fn run(self) -> Result<()> { + let StorageArgs { address, block, build, rpc_url, slot, chain, etherscan_api_key } = self; + let rpc_url = consume_config_rpc_url(rpc_url); let provider = try_get_http_provider(rpc_url)?; - let cast = Cast::new(provider); - if let Some(slot) = self.slot { - println!("{}", cast.storage(self.address, slot, self.block).await?); + let address = match address { + NameOrAddress::Name(name) => provider.resolve_name(&name).await?, + NameOrAddress::Address(address) => address, + }; + + // Slot was provided, perform a simple RPC call + if let Some(slot) = slot { + let cast = Cast::new(provider); + println!("{}", cast.storage(address, slot, block).await?); return Ok(()) } - Ok(()) + // No slot was provided + + // Get deployed bytecode at given address + let address_code = provider.get_code(address, block).await?; + if address_code.is_empty() { + eyre::bail!("Provided address has no deployed code and thus no storage"); + } + + // Check if we're in a forge project + let project = build.project()?; + if project.paths.has_input_files() { + // Find in artifacts and pretty print + let project = with_storage_layout_output(project); + let out = compile(&project, false, false)?; + let artifact = out.artifacts().find(|(_, artifact)| match artifact.deployed_bytecode { + Some(ref deployed_code) => match deployed_code.bytecode { + Some(ref bytecode) => match bytecode.object.as_bytes() { + Some(bytes) => bytes == &address_code, + None => false, + }, + None => false, + }, + None => false, + }); + if let Some((_, artifact)) = artifact { + return print_storage_layout(&artifact.storage_layout, true) + } + } + + // Not a forge project or artifact not found + // Get code from Etherscan + let api_key = etherscan_api_key.or_else(|| { + let config = Config::load(); + config.get_etherscan_api_key(Some(chain)) + }).ok_or_else(|| eyre::eyre!("No Etherscan API Key is set. Consider using the ETHERSCAN_API_KEY env var, or setting the -e CLI argument or etherscan-api-key in foundry.toml"))?; + let client = ethers::etherscan::Client::new(chain, api_key)?; + println!("No artifacts found, fetching source code from etherscan..."); + let source = client.contract_source_code(address).await?; + let source_tree = source.source_tree()?; + + // Create a new temp project + // let root = tempfile::tempdir()?; + let root = std::path::PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/temp_build")); + println!("root: {}", root.display()); + std::fs::create_dir_all(&root)?; + source_tree.write_to(&root)?; + + // Configure Solc + let paths = ProjectPathsConfig::builder().sources(&root).build_with_root(root); + + let metadata = &source.items[0]; + let mut settings = Settings::default(); + + let mut optimizer = Optimizer::default(); + if parse_etherscan_bool(&metadata.optimization_used)? { + optimizer.enable(); + match metadata.runs.parse::() { + Ok(runs) => optimizer.runs(runs), + _ => {} + }; + } + settings.optimizer = optimizer; + if !metadata.source_code.contains("pragma solidity") { + eyre::bail!("Only Solidity verified contracts are allowed") + } + settings.evm_version = Some(metadata.evm_version.parse().unwrap_or_default()); + + let solc = match parse_etherscan_compiler_version(&metadata.compiler_version) { + Ok(v) => Solc::find_or_install_svm_version(v)?, + Err(_) => Solc::default(), + }; + let solc_config = SolcConfig::builder().settings(settings).build(); + + let project = Project::builder() + .solc(solc) + .solc_config(solc_config) + // .ephemeral() + // .no_artifacts() + .ignore_error_code(1878) // License warning + .ignore_error_code(5574) // Contract code size warning + .paths(paths) + .build()?; + let mut project = with_storage_layout_output(project); + + // Compile + let out = match compile(&project, false, false) { + Ok(out) => Ok(out), + // metadata does not contain many compiler settings... + Err(e) => { + if e.to_string().contains("--via-ir") { + project.solc_config.settings.via_ir = Some(true); + compile(&project, false, false) + } else { + Err(e) + } + } + }?; + let artifact = out.artifacts().find(|(name, _)| name == &metadata.contract_name); + let artifact = artifact.wrap_err("Compilation failed")?.1; + print_storage_layout(&artifact.storage_layout, true) } } + +fn with_storage_layout_output(mut project: Project) -> Project { + let mut outputs = ContractOutputSelection::basic(); + outputs.push(ContractOutputSelection::StorageLayout); + let settings = project.solc_config.settings.with_extra_output(outputs); + + project.solc_config.settings = settings; + project +} + +/// Usually 0 or 1 +fn parse_etherscan_bool(s: &str) -> Result { + let s = s.trim(); + match s.parse::() { + Ok(n) => match n { + 0 | 1 => Ok(n != 0), + _ => Err(eyre::eyre!("error parsing bool value from etherscan: number is not 0 or 1")), + }, + Err(e) => match s.parse::() { + Ok(b) => Ok(b), + Err(_) => Err(eyre::eyre!("error parsing bool value from etherscan: {}", e)), + }, + } +} + +fn parse_etherscan_compiler_version(s: &str) -> Result<&str> { + // "v0.6.8+commit.0bbfe453" + let mut version = s.split('+'); + // "v0.6.8" + let version = version.next().wrap_err("got empty compiler version from etherscan")?; + // "0.6.8" + Ok(version.strip_prefix('v').unwrap_or(version)) +} diff --git a/cli/src/cmd/forge/inspect.rs b/cli/src/cmd/forge/inspect.rs index 7cfc6ed32d17b..7a70495c10cd6 100644 --- a/cli/src/cmd/forge/inspect.rs +++ b/cli/src/cmd/forge/inspect.rs @@ -15,7 +15,7 @@ use ethers::{ }, info::ContractInfo, }, - solc::utils::canonicalize, + solc::{artifacts::StorageLayout, utils::canonicalize}, }; use foundry_common::compile; use serde_json::{to_value, Value}; @@ -131,34 +131,7 @@ impl Cmd for InspectArgs { println!("{}", serde_json::to_string_pretty(&to_value(&artifact.gas_estimates)?)?); } ContractArtifactFields::StorageLayout => { - if pretty { - if let Some(storage_layout) = &artifact.storage_layout { - let mut table = Table::new(); - table.set_header(vec![ - "Name", "Type", "Slot", "Offset", "Bytes", "Contract", - ]); - - for slot in &storage_layout.storage { - let storage_type = storage_layout.types.get(&slot.storage_type); - table.add_row(vec![ - slot.label.clone(), - storage_type.as_ref().map_or("?".to_string(), |t| t.label.clone()), - slot.slot.clone(), - slot.offset.to_string(), - storage_type - .as_ref() - .map_or("?".to_string(), |t| t.number_of_bytes.clone()), - slot.contract.clone(), - ]); - } - println!("{table}"); - } - } else { - println!( - "{}", - serde_json::to_string_pretty(&to_value(&artifact.storage_layout)?)? - ); - } + print_storage_layout(&artifact.storage_layout, pretty)?; } ContractArtifactFields::DevDoc => { println!("{}", serde_json::to_string_pretty(&to_value(&artifact.devdoc)?)?); @@ -199,6 +172,39 @@ impl Cmd for InspectArgs { } } +pub fn print_storage_layout( + storage_layout: &Option, + pretty: bool, +) -> eyre::Result<()> { + if storage_layout.is_none() { + eyre::bail!("Could not get storage layout") + } + if !pretty { + println!("{}", serde_json::to_string_pretty(&to_value(storage_layout)?)?); + return Ok(()) + } + + let storage_layout = storage_layout.as_ref().unwrap(); + let mut table = Table::new(); + table.set_header(vec!["Name", "Type", "Slot", "Offset", "Bytes", "Contract"]); + + for slot in &storage_layout.storage { + let storage_type = storage_layout.types.get(&slot.storage_type); + table.add_row(vec![ + slot.label.clone(), + storage_type.as_ref().map_or("?".to_string(), |t| t.label.clone()), + slot.slot.clone(), + slot.offset.to_string(), + storage_type.as_ref().map_or("?".to_string(), |t| t.number_of_bytes.clone()), + slot.contract.clone(), + ]); + } + + println!("{}", table); + + Ok(()) +} + /// Contract level output selection #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum ContractArtifactFields { From a3026da0f712647706c4392f125a919da1d2cb6a Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 15:27:45 +0000 Subject: [PATCH 03/26] use tmp dir --- cli/src/cmd/cast/storage.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 2b97df84daf4c..f86f2a1fac33e 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -110,14 +110,14 @@ impl StorageArgs { let source_tree = source.source_tree()?; // Create a new temp project - // let root = tempfile::tempdir()?; - let root = std::path::PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/temp_build")); - println!("root: {}", root.display()); - std::fs::create_dir_all(&root)?; - source_tree.write_to(&root)?; + let root = tempfile::tempdir()?; + let root_path = root.path(); + // let root = std::path::PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/temp_build")); + // let root_path = root.as_path(); + source_tree.write_to(root_path)?; // Configure Solc - let paths = ProjectPathsConfig::builder().sources(&root).build_with_root(root); + let paths = ProjectPathsConfig::builder().sources(root_path).build_with_root(root_path); let metadata = &source.items[0]; let mut settings = Settings::default(); @@ -139,14 +139,16 @@ impl StorageArgs { let solc = match parse_etherscan_compiler_version(&metadata.compiler_version) { Ok(v) => Solc::find_or_install_svm_version(v)?, Err(_) => Solc::default(), - }; + } + .with_base_path(root_path); let solc_config = SolcConfig::builder().settings(settings).build(); let project = Project::builder() .solc(solc) .solc_config(solc_config) - // .ephemeral() - // .no_artifacts() + .no_auto_detect() + .ephemeral() + .no_artifacts() .ignore_error_code(1878) // License warning .ignore_error_code(5574) // Contract code size warning .paths(paths) @@ -174,6 +176,7 @@ impl StorageArgs { fn with_storage_layout_output(mut project: Project) -> Project { let mut outputs = ContractOutputSelection::basic(); + outputs.push(ContractOutputSelection::Metadata); outputs.push(ContractOutputSelection::StorageLayout); let settings = project.solc_config.settings.with_extra_output(outputs); @@ -198,7 +201,7 @@ fn parse_etherscan_bool(s: &str) -> Result { fn parse_etherscan_compiler_version(s: &str) -> Result<&str> { // "v0.6.8+commit.0bbfe453" - let mut version = s.split('+'); + let mut version = s.trim().split('+'); // "v0.6.8" let version = version.next().wrap_err("got empty compiler version from etherscan")?; // "0.6.8" From 76afbd121552d74c23a94b8f5cce1a1f022dc44b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 15:51:45 +0000 Subject: [PATCH 04/26] refactor: parsing, errors, cleanup --- cli/src/cmd/cast/storage.rs | 70 ++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index f86f2a1fac33e..dbd2a7626a553 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -12,6 +12,7 @@ use ethers::{ use eyre::{ContextCompat, Result}; use foundry_common::{compile::compile, try_get_http_provider}; use foundry_config::Config; +use semver::Version; #[derive(Debug, Clone, Parser)] pub struct StorageArgs { @@ -83,16 +84,13 @@ impl StorageArgs { // Find in artifacts and pretty print let project = with_storage_layout_output(project); let out = compile(&project, false, false)?; - let artifact = out.artifacts().find(|(_, artifact)| match artifact.deployed_bytecode { - Some(ref deployed_code) => match deployed_code.bytecode { - Some(ref bytecode) => match bytecode.object.as_bytes() { - Some(bytes) => bytes == &address_code, - None => false, - }, - None => false, - }, - None => false, - }); + let match_code = |artifact: &ConfigurableContractArtifact| -> Option { + let bytes = + artifact.deployed_bytecode.as_ref()?.bytecode.as_ref()?.object.as_bytes()?; + Some(bytes == &address_code) + }; + let artifact = + out.artifacts().find(|(_, artifact)| match_code(artifact).unwrap_or_default()); if let Some((_, artifact)) = artifact { return print_storage_layout(&artifact.storage_layout, true) } @@ -107,6 +105,9 @@ impl StorageArgs { let client = ethers::etherscan::Client::new(chain, api_key)?; println!("No artifacts found, fetching source code from etherscan..."); let source = client.contract_source_code(address).await?; + if source.items.is_empty() { + eyre::bail!("Etherscan returned no data"); + } let source_tree = source.source_tree()?; // Create a new temp project @@ -119,11 +120,12 @@ impl StorageArgs { // Configure Solc let paths = ProjectPathsConfig::builder().sources(root_path).build_with_root(root_path); - let metadata = &source.items[0]; + // `items` has at least 1 item + let metadata = source.items.first().unwrap(); let mut settings = Settings::default(); let mut optimizer = Optimizer::default(); - if parse_etherscan_bool(&metadata.optimization_used)? { + if metadata.optimization_used.trim().parse::()? == 1 { optimizer.enable(); match metadata.runs.parse::() { Ok(runs) => optimizer.runs(runs), @@ -136,8 +138,11 @@ impl StorageArgs { } settings.evm_version = Some(metadata.evm_version.parse().unwrap_or_default()); - let solc = match parse_etherscan_compiler_version(&metadata.compiler_version) { - Ok(v) => Solc::find_or_install_svm_version(v)?, + let version = metadata.compiler_version.as_str().trim(); + let solc = match version.strip_prefix('v').unwrap_or(version).parse::() { + Ok(v) => { + Solc::find_or_install_svm_version(&format!("{}.{}.{}", v.major, v.minor, v.patch))? + } Err(_) => Solc::default(), } .with_base_path(root_path); @@ -161,6 +166,9 @@ impl StorageArgs { // metadata does not contain many compiler settings... Err(e) => { if e.to_string().contains("--via-ir") { + println!( + "Compilation failed due to \"stack too deep\", retrying with \"--via-ir\"..." + ); project.solc_config.settings.via_ir = Some(true); compile(&project, false, false) } else { @@ -169,8 +177,14 @@ impl StorageArgs { } }?; let artifact = out.artifacts().find(|(name, _)| name == &metadata.contract_name); - let artifact = artifact.wrap_err("Compilation failed")?.1; - print_storage_layout(&artifact.storage_layout, true) + let artifact = artifact.wrap_err("Artifact not found")?.1; + + print_storage_layout(&artifact.storage_layout, true)?; + + // Clear temp directory + root.close()?; + + Ok(()) } } @@ -183,27 +197,3 @@ fn with_storage_layout_output(mut project: Project) -> Project { project.solc_config.settings = settings; project } - -/// Usually 0 or 1 -fn parse_etherscan_bool(s: &str) -> Result { - let s = s.trim(); - match s.parse::() { - Ok(n) => match n { - 0 | 1 => Ok(n != 0), - _ => Err(eyre::eyre!("error parsing bool value from etherscan: number is not 0 or 1")), - }, - Err(e) => match s.parse::() { - Ok(b) => Ok(b), - Err(_) => Err(eyre::eyre!("error parsing bool value from etherscan: {}", e)), - }, - } -} - -fn parse_etherscan_compiler_version(s: &str) -> Result<&str> { - // "v0.6.8+commit.0bbfe453" - let mut version = s.trim().split('+'); - // "v0.6.8" - let version = version.next().wrap_err("got empty compiler version from etherscan")?; - // "0.6.8" - Ok(version.strip_prefix('v').unwrap_or(version)) -} From a91190ff6f000e8ab112ffd5fff510e514b726d4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 17:51:08 +0000 Subject: [PATCH 05/26] feat: resolve proxy implementations --- cli/src/cmd/cast/storage.rs | 68 +++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index dbd2a7626a553..6c38731f3375f 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -5,14 +5,20 @@ use crate::{ }; use cast::Cast; use clap::Parser; +use contract::ContractMetadata; +use errors::EtherscanError; use ethers::{ + etherscan::Client, prelude::*, - solc::artifacts::{output_selection::ContractOutputSelection, Optimizer, Settings}, + solc::artifacts::{ + output_selection::ContractOutputSelection, BytecodeHash, Optimizer, Settings, + }, }; use eyre::{ContextCompat, Result}; use foundry_common::{compile::compile, try_get_http_provider}; use foundry_config::Config; use semver::Version; +use std::{future::Future, pin::Pin}; #[derive(Debug, Clone, Parser)] pub struct StorageArgs { @@ -98,30 +104,29 @@ impl StorageArgs { // Not a forge project or artifact not found // Get code from Etherscan + println!("No artifacts found, fetching source code from Etherscan..."); let api_key = etherscan_api_key.or_else(|| { let config = Config::load(); config.get_etherscan_api_key(Some(chain)) }).ok_or_else(|| eyre::eyre!("No Etherscan API Key is set. Consider using the ETHERSCAN_API_KEY env var, or setting the -e CLI argument or etherscan-api-key in foundry.toml"))?; - let client = ethers::etherscan::Client::new(chain, api_key)?; - println!("No artifacts found, fetching source code from etherscan..."); - let source = client.contract_source_code(address).await?; - if source.items.is_empty() { - eyre::bail!("Etherscan returned no data"); - } + let client = Client::new(chain, api_key)?; + + let source = find_source(client, address).await?; + let metadata = source.items.first().unwrap(); + let source_tree = source.source_tree()?; // Create a new temp project let root = tempfile::tempdir()?; let root_path = root.path(); - // let root = std::path::PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/temp_build")); + // let root = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/temp_build")); // let root_path = root.as_path(); + let sources = root_path.join(&metadata.contract_name); source_tree.write_to(root_path)?; // Configure Solc - let paths = ProjectPathsConfig::builder().sources(root_path).build_with_root(root_path); + let paths = ProjectPathsConfig::builder().sources(sources).build_with_root(root_path); - // `items` has at least 1 item - let metadata = source.items.first().unwrap(); let mut settings = Settings::default(); let mut optimizer = Optimizer::default(); @@ -189,11 +194,44 @@ impl StorageArgs { } fn with_storage_layout_output(mut project: Project) -> Project { - let mut outputs = ContractOutputSelection::basic(); - outputs.push(ContractOutputSelection::Metadata); - outputs.push(ContractOutputSelection::StorageLayout); - let settings = project.solc_config.settings.with_extra_output(outputs); + project.solc_config.settings.metadata = Some(BytecodeHash::Ipfs.into()); + let settings = project.solc_config.settings.with_extra_output([ + ContractOutputSelection::Metadata, + ContractOutputSelection::StorageLayout, + ]); project.solc_config.settings = settings; project } + +/// If the code at `address` is a proxy, recurse until we find the implementation. +fn find_source( + client: Client, + address: Address, +) -> Pin>>> { + Box::pin(async move { + let source = client.contract_source_code(address).await?; + let metadata = source.items.first().wrap_err("Etherscan returned no data")?; + if metadata.proxy.parse::()? == 0 { + Ok(source) + } else { + let implementation = metadata.implementation.parse()?; + println!( + "Contract at {} is a proxy, trying to fetch source at {:?}...", + address, implementation + ); + match find_source(client, implementation).await { + impl_source @ Ok(_) => impl_source, + Err(e) => { + let err = EtherscanError::ContractCodeNotVerified(address).to_string(); + if e.to_string() == err { + println!("{}, using {}", err, address); + Ok(source) + } else { + Err(e) + } + } + } + } + }) +} From 676a53681f1f3ac61eb1e95e3ba1d6b4033fa951 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:03:09 +0000 Subject: [PATCH 06/26] chore: clippy --- cli/src/cmd/cast/storage.rs | 2 +- cli/src/cmd/forge/script/mod.rs | 4 ++-- forge/src/runner.rs | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 6c38731f3375f..20571edc29010 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -119,7 +119,7 @@ impl StorageArgs { // Create a new temp project let root = tempfile::tempdir()?; let root_path = root.path(); - // let root = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/temp_build")); + // let root = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/out")); // let root_path = root.as_path(); let sources = root_path.join(&metadata.contract_name); source_tree.write_to(root_path)?; diff --git a/cli/src/cmd/forge/script/mod.rs b/cli/src/cmd/forge/script/mod.rs index 573e738ce0fb5..8309ef634270d 100644 --- a/cli/src/cmd/forge/script/mod.rs +++ b/cli/src/cmd/forge/script/mod.rs @@ -696,7 +696,7 @@ mod tests { root.as_os_str().to_str().unwrap(), ]); - let err = args.clone().load_config_and_evm_opts().unwrap_err(); + let err = args.load_config_and_evm_opts().unwrap_err(); assert!(err.downcast::().is_ok()); @@ -736,7 +736,7 @@ mod tests { "--root", root.as_os_str().to_str().unwrap(), ]); - let err = args.clone().load_config_and_evm_opts().unwrap_err(); + let err = args.load_config_and_evm_opts().unwrap_err(); assert!(err.downcast::().is_ok()); diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 3b53ee4f7db31..3b032063de588 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -452,9 +452,7 @@ impl<'a> ContractRunner<'a> { if let Some(InvariantFuzzTestResult { invariants, cases, reverts }) = evm.invariant_fuzz(invariant_contract)? { - let results = invariants - .iter() - .map(|(_, test_error)| { + let results = invariants.values().map(|test_error| { let mut counterexample = None; let mut logs = logs.clone(); let mut traces = traces.clone(); From 56d2eddf372b0339d184de336faad299a7d1a435 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:15:43 +0000 Subject: [PATCH 07/26] chore: fmt --- forge/src/runner.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 1f479ad5d23a1..b3817084b7b3e 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -452,8 +452,9 @@ impl<'a> ContractRunner<'a> { if let Some(InvariantFuzzTestResult { invariants, cases, reverts }) = evm.invariant_fuzz(invariant_contract)? { - - let results = invariants.values().map(|test_error| { + let results = invariants + .values() + .map(|test_error| { let mut counterexample = None; let mut logs = logs.clone(); let mut traces = traces.clone(); From f3a6a602cdb01504a7a23d97365410e30fe114f2 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:19:19 +0000 Subject: [PATCH 08/26] chore: clippy --- cli/src/cmd/cast/storage.rs | 7 +++---- forge/tests/it/config.rs | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 20571edc29010..67e9e3749de44 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -132,10 +132,9 @@ impl StorageArgs { let mut optimizer = Optimizer::default(); if metadata.optimization_used.trim().parse::()? == 1 { optimizer.enable(); - match metadata.runs.parse::() { - Ok(runs) => optimizer.runs(runs), - _ => {} - }; + if let Ok(runs) = metadata.runs.parse::() { + optimizer.runs(runs); + } } settings.optimizer = optimizer; if !metadata.source_code.contains("pragma solidity") { diff --git a/forge/tests/it/config.rs b/forge/tests/it/config.rs index 9e479ac8509f0..6f1c948a90e95 100644 --- a/forge/tests/it/config.rs +++ b/forge/tests/it/config.rs @@ -188,6 +188,7 @@ pub fn rpc_endpoints() -> RpcEndpoints { /// A helper to assert the outcome of multiple tests with helpful assert messages #[track_caller] +#[allow(clippy::type_complexity)] pub fn assert_multiple( actuals: &BTreeMap, expecteds: BTreeMap< From 9b048e8a5a05c3e696ab09a23eeab736d1cc386d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 18:51:18 +0000 Subject: [PATCH 09/26] fix: output selection --- cli/src/cmd/cast/storage.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 67e9e3749de44..7bf81669873f7 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -10,9 +10,7 @@ use errors::EtherscanError; use ethers::{ etherscan::Client, prelude::*, - solc::artifacts::{ - output_selection::ContractOutputSelection, BytecodeHash, Optimizer, Settings, - }, + solc::artifacts::{Optimizer, Settings}, }; use eyre::{ContextCompat, Result}; use foundry_common::{compile::compile, try_get_http_provider}; @@ -119,7 +117,7 @@ impl StorageArgs { // Create a new temp project let root = tempfile::tempdir()?; let root_path = root.path(); - // let root = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/out")); + // let root = std::path::PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/out")); // let root_path = root.as_path(); let sources = root_path.join(&metadata.contract_name); source_tree.write_to(root_path)?; @@ -193,12 +191,9 @@ impl StorageArgs { } fn with_storage_layout_output(mut project: Project) -> Project { - project.solc_config.settings.metadata = Some(BytecodeHash::Ipfs.into()); - let settings = project.solc_config.settings.with_extra_output([ - ContractOutputSelection::Metadata, - ContractOutputSelection::StorageLayout, - ]); - + project.artifacts.additional_values.storage_layout = true; + let output_selection = project.artifacts.output_selection(); + let settings = project.solc_config.settings.with_extra_output(output_selection); project.solc_config.settings = settings; project } From 209d2e1bdf3f015af22f97cd2b031cf30e5dac82 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 24 Sep 2022 19:17:04 +0000 Subject: [PATCH 10/26] feat: add warnings --- cli/src/cmd/cast/storage.rs | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 7bf81669873f7..5d4eace19d5a1 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -13,11 +13,19 @@ use ethers::{ solc::artifacts::{Optimizer, Settings}, }; use eyre::{ContextCompat, Result}; -use foundry_common::{compile::compile, try_get_http_provider}; +use foundry_common::{ + compile::{compile, suppress_compile}, + try_get_http_provider, +}; use foundry_config::Config; use semver::Version; use std::{future::Future, pin::Pin}; +/// The minimum Solc version for outputting storage layouts. +/// +/// https://github.com/ethereum/solidity/blob/develop/Changelog.md#065-2020-04-06 +const MIN_SOLC: Version = Version::new(0, 6, 5); + #[derive(Debug, Clone, Parser)] pub struct StorageArgs { // Storage @@ -75,7 +83,6 @@ impl StorageArgs { } // No slot was provided - // Get deployed bytecode at given address let address_code = provider.get_code(address, block).await?; if address_code.is_empty() { @@ -141,11 +148,21 @@ impl StorageArgs { settings.evm_version = Some(metadata.evm_version.parse().unwrap_or_default()); let version = metadata.compiler_version.as_str().trim(); + let mut auto_detect = false; let solc = match version.strip_prefix('v').unwrap_or(version).parse::() { - Ok(v) => { - Solc::find_or_install_svm_version(&format!("{}.{}.{}", v.major, v.minor, v.patch))? + Ok(mut v) => { + if v < MIN_SOLC { + tracing::warn!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result it may be empty.", v, MIN_SOLC); + auto_detect = true; + v = MIN_SOLC + }; + let v = &format!("{}.{}.{}", v.major, v.minor, v.patch); + Solc::find_or_install_svm_version(v)? + } + Err(_) => { + auto_detect = true; + Solc::default() } - Err(_) => Solc::default(), } .with_base_path(root_path); let solc_config = SolcConfig::builder().settings(settings).build(); @@ -153,7 +170,7 @@ impl StorageArgs { let project = Project::builder() .solc(solc) .solc_config(solc_config) - .no_auto_detect() + .set_auto_detect(auto_detect) .ephemeral() .no_artifacts() .ignore_error_code(1878) // License warning @@ -163,16 +180,14 @@ impl StorageArgs { let mut project = with_storage_layout_output(project); // Compile - let out = match compile(&project, false, false) { + let out = match suppress_compile(&project) { Ok(out) => Ok(out), // metadata does not contain many compiler settings... Err(e) => { if e.to_string().contains("--via-ir") { - println!( - "Compilation failed due to \"stack too deep\", retrying with \"--via-ir\"..." - ); + tracing::warn!("Compilation failed due to \"stack too deep\", retrying with \"--via-ir\"..."); project.solc_config.settings.via_ir = Some(true); - compile(&project, false, false) + suppress_compile(&project) } else { Err(e) } From 31df1ef735bdc59f56f5dc69a1ef8b5cac39a918 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 28 Sep 2022 12:30:24 +0000 Subject: [PATCH 11/26] wip --- Cargo.lock | 53 ++++++++------- Cargo.toml | 21 +++--- cli/src/cmd/cast/storage.rs | 132 ++++++------------------------------ 3 files changed, 57 insertions(+), 149 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a8219afd8a56..a388bc42de773 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1625,9 +1625,8 @@ dependencies = [ [[package]] name = "ethers" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ - "ethers-addressbook", + "ethers-addressbook 0.17.0", "ethers-contract", "ethers-core", "ethers-etherscan", @@ -1640,7 +1639,17 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "ethers-addressbook" +version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "ethers-core", "once_cell", @@ -1651,7 +1660,6 @@ dependencies = [ [[package]] name = "ethers-contract" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -1669,7 +1677,6 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "Inflector", "cfg-if 1.0.0", @@ -1692,7 +1699,6 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -1706,7 +1712,6 @@ dependencies = [ [[package]] name = "ethers-core" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "arrayvec 0.7.2", "bytes", @@ -1737,9 +1742,9 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "ethers-core", + "ethers-solc", "getrandom 0.2.6", "reqwest", "semver", @@ -1753,7 +1758,6 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "async-trait", "auto_impl 0.5.0", @@ -1778,7 +1782,6 @@ dependencies = [ [[package]] name = "ethers-providers" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "async-trait", "auto_impl 1.0.1", @@ -1815,7 +1818,6 @@ dependencies = [ [[package]] name = "ethers-signers" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "async-trait", "coins-bip32", @@ -1838,7 +1840,6 @@ dependencies = [ [[package]] name = "ethers-solc" version = "0.17.0" -source = "git+https://github.com/gakonst/ethers-rs#afdab2a555c10d55a32c5991282b4353a4a81256" dependencies = [ "cfg-if 1.0.0", "dunce", @@ -2288,7 +2289,7 @@ name = "foundry-utils" version = "0.2.0" dependencies = [ "ethers", - "ethers-addressbook", + "ethers-addressbook 0.17.0 (git+https://github.com/gakonst/ethers-rs)", "ethers-contract", "ethers-core", "ethers-etherscan", @@ -3230,9 +3231,9 @@ checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" [[package]] name = "md-5" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b48670c893079d3c2ed79114e3644b7004df1c361a4e0ad52e2e6940d07c3d" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ "digest 0.10.5", ] @@ -3582,9 +3583,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "oorandom" @@ -4158,9 +4159,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" dependencies = [ "unicode-ident", ] @@ -4443,9 +4444,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64 0.13.0", "bytes", @@ -4460,10 +4461,10 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "rustls", @@ -5318,18 +5319,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 11f16f9925772..434c2f7267bbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ "fmt", "forge", "ui", - "utils", + "utils" ] [profile.dev] @@ -42,7 +42,6 @@ panic = 'unwind' incremental = true codegen-units = 16 - [profile.release] # Optimize for binary size, but keep loop vectorization opt-level = "s" @@ -55,15 +54,15 @@ panic = "abort" debug = 0 ## Patch ethers-rs with a local checkout then run `cargo update -p ethers` -#[patch."https://github.com/gakonst/ethers-rs"] -#ethers = { path = "../ethers-rs" } -#ethers-core = { path = "../ethers-rs/ethers-core" } -#ethers-contract = { path = "../ethers-rs/ethers-contract" } -#ethers-providers = { path = "../ethers-rs/ethers-providers" } -#ethers-signers = { path = "../ethers-rs/ethers-signers" } -#ethers-etherscan = { path = "../ethers-rs/ethers-etherscan" } -#ethers-solc = { path = "../ethers-rs/ethers-solc" } +[patch."https://github.com/gakonst/ethers-rs"] +ethers = { path = "../ethers-rs" } +ethers-core = { path = "../ethers-rs/ethers-core" } +ethers-contract = { path = "../ethers-rs/ethers-contract" } +ethers-providers = { path = "../ethers-rs/ethers-providers" } +ethers-signers = { path = "../ethers-rs/ethers-signers" } +ethers-etherscan = { path = "../ethers-rs/ethers-etherscan" } +ethers-solc = { path = "../ethers-rs/ethers-solc" } [patch.crates-io] #revm = { path = "../revm/crates/revm" } -revm = { git = "https://github.com/bluealloy/revm" } \ No newline at end of file +revm = { git = "https://github.com/bluealloy/revm" } diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 5d4eace19d5a1..ca779561ac956 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -5,21 +5,15 @@ use crate::{ }; use cast::Cast; use clap::Parser; -use contract::ContractMetadata; -use errors::EtherscanError; -use ethers::{ - etherscan::Client, - prelude::*, - solc::artifacts::{Optimizer, Settings}, -}; +use ethers::{etherscan::Client, prelude::*}; use eyre::{ContextCompat, Result}; use foundry_common::{ - compile::{compile, suppress_compile}, + compile::{compile, etherscan_project, suppress_compile}, try_get_http_provider, }; use foundry_config::Config; +use foundry_utils::find_source; use semver::Version; -use std::{future::Future, pin::Pin}; /// The minimum Solc version for outputting storage layouts. /// @@ -115,85 +109,31 @@ impl StorageArgs { config.get_etherscan_api_key(Some(chain)) }).ok_or_else(|| eyre::eyre!("No Etherscan API Key is set. Consider using the ETHERSCAN_API_KEY env var, or setting the -e CLI argument or etherscan-api-key in foundry.toml"))?; let client = Client::new(chain, api_key)?; - let source = find_source(client, address).await?; - let metadata = source.items.first().unwrap(); + let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?; + if !metadata.is_vyper() { + eyre::bail!("Contract at provided address is not a valid Solidity contract") + } - let source_tree = source.source_tree()?; + let version = metadata.compiler_version()?; + let auto_detect = version < MIN_SOLC; + if auto_detect { + println!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result it may be empty.", version, MIN_SOLC); + } - // Create a new temp project let root = tempfile::tempdir()?; let root_path = root.path(); - // let root = std::path::PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/out")); - // let root_path = root.as_path(); - let sources = root_path.join(&metadata.contract_name); - source_tree.write_to(root_path)?; - - // Configure Solc - let paths = ProjectPathsConfig::builder().sources(sources).build_with_root(root_path); - - let mut settings = Settings::default(); - - let mut optimizer = Optimizer::default(); - if metadata.optimization_used.trim().parse::()? == 1 { - optimizer.enable(); - if let Ok(runs) = metadata.runs.parse::() { - optimizer.runs(runs); - } - } - settings.optimizer = optimizer; - if !metadata.source_code.contains("pragma solidity") { - eyre::bail!("Only Solidity verified contracts are allowed") - } - settings.evm_version = Some(metadata.evm_version.parse().unwrap_or_default()); - - let version = metadata.compiler_version.as_str().trim(); - let mut auto_detect = false; - let solc = match version.strip_prefix('v').unwrap_or(version).parse::() { - Ok(mut v) => { - if v < MIN_SOLC { - tracing::warn!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result it may be empty.", v, MIN_SOLC); - auto_detect = true; - v = MIN_SOLC - }; - let v = &format!("{}.{}.{}", v.major, v.minor, v.patch); - Solc::find_or_install_svm_version(v)? - } - Err(_) => { - auto_detect = true; - Solc::default() - } - } - .with_base_path(root_path); - let solc_config = SolcConfig::builder().settings(settings).build(); - - let project = Project::builder() - .solc(solc) - .solc_config(solc_config) - .set_auto_detect(auto_detect) - .ephemeral() - .no_artifacts() - .ignore_error_code(1878) // License warning - .ignore_error_code(5574) // Contract code size warning - .paths(paths) - .build()?; + let project = etherscan_project(&metadata, root_path)?; let mut project = with_storage_layout_output(project); + project.auto_detect = auto_detect; // Compile - let out = match suppress_compile(&project) { - Ok(out) => Ok(out), - // metadata does not contain many compiler settings... - Err(e) => { - if e.to_string().contains("--via-ir") { - tracing::warn!("Compilation failed due to \"stack too deep\", retrying with \"--via-ir\"..."); - project.solc_config.settings.via_ir = Some(true); - suppress_compile(&project) - } else { - Err(e) - } - } - }?; - let artifact = out.artifacts().find(|(name, _)| name == &metadata.contract_name); + let out = suppress_compile(&project)?; + dbg!(out.artifacts().count()); + let artifact = out.artifacts().find(|(name, _)| { + println!("Artifact: {}", name); + name == &metadata.contract_name + }); let artifact = artifact.wrap_err("Artifact not found")?.1; print_storage_layout(&artifact.storage_layout, true)?; @@ -212,35 +152,3 @@ fn with_storage_layout_output(mut project: Project) -> Project { project.solc_config.settings = settings; project } - -/// If the code at `address` is a proxy, recurse until we find the implementation. -fn find_source( - client: Client, - address: Address, -) -> Pin>>> { - Box::pin(async move { - let source = client.contract_source_code(address).await?; - let metadata = source.items.first().wrap_err("Etherscan returned no data")?; - if metadata.proxy.parse::()? == 0 { - Ok(source) - } else { - let implementation = metadata.implementation.parse()?; - println!( - "Contract at {} is a proxy, trying to fetch source at {:?}...", - address, implementation - ); - match find_source(client, implementation).await { - impl_source @ Ok(_) => impl_source, - Err(e) => { - let err = EtherscanError::ContractCodeNotVerified(address).to_string(); - if e.to_string() == err { - println!("{}, using {}", err, address); - Ok(source) - } else { - Err(e) - } - } - } - } - }) -} From 0f1fddac362f2467346b2c77c6f44ded740a4bed Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 28 Sep 2022 12:43:07 +0000 Subject: [PATCH 12/26] update Cargo.lock --- Cargo.lock | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4c19d867eeb3..1d566d104a5b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1625,8 +1625,9 @@ dependencies = [ [[package]] name = "ethers" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ - "ethers-addressbook 0.17.0", + "ethers-addressbook", "ethers-contract", "ethers-core", "ethers-etherscan", @@ -1636,16 +1637,6 @@ dependencies = [ "ethers-solc", ] -[[package]] -name = "ethers-addressbook" -version = "0.17.0" -dependencies = [ - "ethers-core", - "once_cell", - "serde", - "serde_json", -] - [[package]] name = "ethers-addressbook" version = "0.17.0" @@ -1660,6 +1651,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -1677,6 +1669,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "Inflector", "cfg-if 1.0.0", @@ -1699,6 +1692,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -1712,6 +1706,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "arrayvec 0.7.2", "bytes", @@ -1742,9 +1737,9 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "ethers-core", - "ethers-solc", "getrandom 0.2.6", "reqwest", "semver", @@ -1758,6 +1753,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "async-trait", "auto_impl 0.5.0", @@ -1782,6 +1778,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "async-trait", "auto_impl 1.0.1", @@ -1818,6 +1815,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "async-trait", "coins-bip32", @@ -1840,6 +1838,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "0.17.0" +source = "git+https://github.com/gakonst/ethers-rs#d8791482d566e2203ab6a178524f1ed6705fe274" dependencies = [ "cfg-if 1.0.0", "dunce", @@ -2291,7 +2290,7 @@ name = "foundry-utils" version = "0.2.0" dependencies = [ "ethers", - "ethers-addressbook 0.17.0 (git+https://github.com/gakonst/ethers-rs)", + "ethers-addressbook", "ethers-contract", "ethers-core", "ethers-etherscan", From 2a02f8dfc630af567cd59493355e38af2f01fd4a Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 29 Sep 2022 17:17:53 +0000 Subject: [PATCH 13/26] fix --- cli/src/cmd/cast/storage.rs | 5 ++--- cli/src/opts/cast.rs | 18 +----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index ca779561ac956..ad763ec84e867 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -8,11 +8,11 @@ use clap::Parser; use ethers::{etherscan::Client, prelude::*}; use eyre::{ContextCompat, Result}; use foundry_common::{ + abi::find_source, compile::{compile, etherscan_project, suppress_compile}, try_get_http_provider, }; use foundry_config::Config; -use foundry_utils::find_source; use semver::Version; /// The minimum Solc version for outputting storage layouts. @@ -148,7 +148,6 @@ impl StorageArgs { fn with_storage_layout_output(mut project: Project) -> Project { project.artifacts.additional_values.storage_layout = true; let output_selection = project.artifacts.output_selection(); - let settings = project.solc_config.settings.with_extra_output(output_selection); - project.solc_config.settings = settings; + project.solc_config.settings = project.solc_config.settings.with_extra_output(output_selection); project } diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 8447ff6c6d206..7f6c032b6890c 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -620,23 +620,7 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline visible_alias = "st", about = "Get the raw value of a contract's storage slot." )] - Storage { - #[clap(help = "The contract address.", value_parser = parse_name_or_address, value_name = "ADDRESS")] - address: NameOrAddress, - #[clap(help = "The storage slot number (hex or decimal)", value_parser = parse_slot, value_name = "SLOT")] - slot: H256, - #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] - rpc_url: Option, - #[clap( - long, - short = 'B', - help = "The block height you want to query at.", - long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - value_parser = parse_block_id, - value_name = "BLOCK" - )] - block: Option, - }, + Storage(StorageArgs), #[clap( name = "proof", visible_alias = "pr", From f7800f2a379d31bd0d78951aaeff12c154d576f1 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 29 Sep 2022 18:03:23 +0000 Subject: [PATCH 14/26] chore: clippy --- cli/src/cmd/cast/storage.rs | 2 +- cli/tests/it/cmd.rs | 4 ++-- cli/tests/it/utils.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index ad763ec84e867..710a744269e06 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -123,7 +123,7 @@ impl StorageArgs { let root = tempfile::tempdir()?; let root_path = root.path(); - let project = etherscan_project(&metadata, root_path)?; + let project = etherscan_project(metadata, root_path)?; let mut project = with_storage_layout_output(project); project.auto_detect = auto_detect; diff --git a/cli/tests/it/cmd.rs b/cli/tests/it/cmd.rs index 377a56ee59796..02c216767997a 100644 --- a/cli/tests/it/cmd.rs +++ b/cli/tests/it/cmd.rs @@ -1374,7 +1374,7 @@ forgetest_init!(can_build_sizes_repeatedly, |_prj: TestProject, mut cmd: TestCom let table = out.split("Compiler run successful").nth(1).unwrap().trim(); let unchanged = cmd.stdout(); - assert!(unchanged.contains(&table), "{}", table); + assert!(unchanged.contains(table), "{}", table); }); // checks that build --names includes all contracts even if unchanged @@ -1388,5 +1388,5 @@ forgetest_init!(can_build_names_repeatedly, |_prj: TestProject, mut cmd: TestCom let list = out.split("Compiler run successful").nth(1).unwrap().trim(); let unchanged = cmd.stdout(); - assert!(unchanged.contains(&list), "{}", list); + assert!(unchanged.contains(list), "{}", list); }); diff --git a/cli/tests/it/utils.rs b/cli/tests/it/utils.rs index 86313da96e131..25e02b3930353 100644 --- a/cli/tests/it/utils.rs +++ b/cli/tests/it/utils.rs @@ -23,12 +23,12 @@ pub fn etherscan_key(chain: Chain) -> Option { } pub fn network_rpc_key(chain: &str) -> Option { - let key = format!("{}_RPC_URL", chain.to_uppercase().replace("-", "_")); + let key = format!("{}_RPC_URL", chain.to_uppercase().replace('-', "_")); std::env::var(&key).ok() } pub fn network_private_key(chain: &str) -> Option { - let key = format!("{}_PRIVATE_KEY", chain.to_uppercase().replace("-", "_")); + let key = format!("{}_PRIVATE_KEY", chain.to_uppercase().replace('-', "_")); std::env::var(&key).or_else(|_| std::env::var("TEST_PRIVATE_KEY")).ok() } From 6421482b4111522c5d1c5b1f8ac454bbaaaddb52 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:03:39 +0000 Subject: [PATCH 15/26] wip --- cli/src/cmd/cast/storage.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 710a744269e06..66d81af39a994 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -103,22 +103,22 @@ impl StorageArgs { // Not a forge project or artifact not found // Get code from Etherscan - println!("No artifacts found, fetching source code from Etherscan..."); + println!("No matching artifacts found, fetching source code from Etherscan..."); let api_key = etherscan_api_key.or_else(|| { let config = Config::load(); config.get_etherscan_api_key(Some(chain)) - }).ok_or_else(|| eyre::eyre!("No Etherscan API Key is set. Consider using the ETHERSCAN_API_KEY env var, or setting the -e CLI argument or etherscan-api-key in foundry.toml"))?; + }).wrap_err("No Etherscan API Key is set. Consider using the ETHERSCAN_API_KEY env var, or setting the -e CLI argument or etherscan-api-key in foundry.toml")?; let client = Client::new(chain, api_key)?; let source = find_source(client, address).await?; - let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?; - if !metadata.is_vyper() { + let metadata = source.items.first().unwrap(); + if metadata.is_vyper() { eyre::bail!("Contract at provided address is not a valid Solidity contract") } let version = metadata.compiler_version()?; let auto_detect = version < MIN_SOLC; if auto_detect { - println!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result it may be empty.", version, MIN_SOLC); + println!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result the output may be empty.", version, MIN_SOLC); } let root = tempfile::tempdir()?; @@ -134,7 +134,7 @@ impl StorageArgs { println!("Artifact: {}", name); name == &metadata.contract_name }); - let artifact = artifact.wrap_err("Artifact not found")?.1; + let (_, artifact) = artifact.wrap_err("Artifact not found")?; print_storage_layout(&artifact.storage_layout, true)?; From 8c7edbdb025535f3e0d857526a005d73be9cc2f4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 1 Oct 2022 19:30:14 +0000 Subject: [PATCH 16/26] chore: update arg parsing, clean up debugging --- cli/src/cmd/cast/storage.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 66d81af39a994..15a4728838889 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -23,9 +23,13 @@ const MIN_SOLC: Version = Version::new(0, 6, 5); #[derive(Debug, Clone, Parser)] pub struct StorageArgs { // Storage - #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] + #[clap(help = "The contract address.", value_parser = parse_name_or_address, value_name = "ADDRESS")] address: NameOrAddress, - #[clap(help = "The storage slot number (hex or decimal)", parse(try_from_str = parse_slot), value_name = "SLOT")] + #[clap( + help = "The storage slot number (hex or decimal)", + value_parser = parse_slot, + value_name = "SLOT" + )] slot: Option, #[clap(long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, @@ -34,7 +38,7 @@ pub struct StorageArgs { short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, @@ -129,11 +133,7 @@ impl StorageArgs { // Compile let out = suppress_compile(&project)?; - dbg!(out.artifacts().count()); - let artifact = out.artifacts().find(|(name, _)| { - println!("Artifact: {}", name); - name == &metadata.contract_name - }); + let artifact = out.artifacts().find(|(name, _)| name == &metadata.contract_name); let (_, artifact) = artifact.wrap_err("Artifact not found")?; print_storage_layout(&artifact.storage_layout, true)?; From d6aadf32d6c3017cca0c8856d4be6550edd970ea Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Oct 2022 11:51:59 +0200 Subject: [PATCH 17/26] fix: rpc url --- cli/src/cmd/cast/storage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 15a4728838889..dde7e1564913b 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -1,7 +1,7 @@ use crate::{ cmd::forge::{build, inspect::print_storage_layout}, opts::cast::{parse_block_id, parse_name_or_address, parse_slot}, - utils::consume_config_rpc_url, + utils::try_consume_config_rpc_url, }; use cast::Cast; use clap::Parser; @@ -65,7 +65,7 @@ impl StorageArgs { pub async fn run(self) -> Result<()> { let StorageArgs { address, block, build, rpc_url, slot, chain, etherscan_api_key } = self; - let rpc_url = consume_config_rpc_url(rpc_url); + let rpc_url = try_consume_config_rpc_url(rpc_url)?; let provider = try_get_http_provider(rpc_url)?; let address = match address { From b767bc559d9a780e7c147dfa95b0b6c852aa80ea Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:07:31 +0200 Subject: [PATCH 18/26] feat: try recompile with newer solc --- cli/src/cmd/cast/storage.rs | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index dde7e1564913b..1bd68420fa025 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -5,7 +5,7 @@ use crate::{ }; use cast::Cast; use clap::Parser; -use ethers::{etherscan::Client, prelude::*}; +use ethers::{etherscan::Client, prelude::*, solc::artifacts::StorageLayout}; use eyre::{ContextCompat, Result}; use foundry_common::{ abi::find_source, @@ -121,9 +121,6 @@ impl StorageArgs { let version = metadata.compiler_version()?; let auto_detect = version < MIN_SOLC; - if auto_detect { - println!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result the output may be empty.", version, MIN_SOLC); - } let root = tempfile::tempdir()?; let root_path = root.path(); @@ -132,11 +129,39 @@ impl StorageArgs { project.auto_detect = auto_detect; // Compile - let out = suppress_compile(&project)?; - let artifact = out.artifacts().find(|(name, _)| name == &metadata.contract_name); - let (_, artifact) = artifact.wrap_err("Artifact not found")?; + let mut out = suppress_compile(&project)?; + let artifact = { + let (_, artifact) = out + .artifacts() + .find(|(name, _)| name == &metadata.contract_name) + .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; + + if is_storage_layout_empty(&artifact.storage_layout) && auto_detect { + // try recompiling with the minimum version + println!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result the output may be empty.", version, MIN_SOLC); + let solc = Solc::find_or_install_svm_version(MIN_SOLC.to_string())?; + project.solc = solc; + project.auto_detect = false; + if let Ok(output) = suppress_compile(&project) { + out = output; + let (_, artifact) = out + .artifacts() + .find(|(name, _)| name == &metadata.contract_name) + .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; + artifact + } else { + artifact + } + } else { + artifact + } + }; - print_storage_layout(&artifact.storage_layout, true)?; + if is_storage_layout_empty(&artifact.storage_layout) { + println!("Storage layout is empty.") + } else { + print_storage_layout(&artifact.storage_layout, true)?; + } // Clear temp directory root.close()?; @@ -151,3 +176,11 @@ fn with_storage_layout_output(mut project: Project) -> Project { project.solc_config.settings = project.solc_config.settings.with_extra_output(output_selection); project } + +fn is_storage_layout_empty(storage_layout: &Option) -> bool { + if let Some(ref s) = storage_layout { + s.storage.is_empty() + } else { + true + } +} From a97aeeeadb01b7d570a4f219f6870bbeac964179 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Oct 2022 19:02:29 +0200 Subject: [PATCH 19/26] add test --- cli/tests/it/cast.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/cli/tests/it/cast.rs b/cli/tests/it/cast.rs index d2b7e12c4917e..e80e50e862b1e 100644 --- a/cli/tests/it/cast.rs +++ b/cli/tests/it/cast.rs @@ -239,3 +239,45 @@ casttest!(cast_run_succeeds, |_: TestProject, mut cmd: TestCommand| { assert!(output.contains("Transaction successfully executed")); assert!(!output.contains("Revert")); }); + +// tests that the `cast storage` command works correctly +casttest!(cast_storage_succeeds, |_: TestProject, mut cmd: TestCommand| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // WETH + // version < min, so empty storage layout + let address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; + cmd.cast_fuse().args(["storage", "--rpc-url", eth_rpc_url.as_str(), address]); + let output = cmd.stdout_lossy(); + assert!(output.contains("Storage layout is empty"), "{}", output); + // first slot is the name, always is "Wrapped Ether" + cmd.cast_fuse().args(["storage", "--rpc-url", eth_rpc_url.as_str(), address, "0"]); + let output = cmd.stdout_lossy(); + assert!( + output.contains("0x577261707065642045746865720000000000000000000000000000000000001a"), + "{}", + output + ); + + // Polygon bridge proxy + let address = "0xA0c68C638235ee32657e8f720a23ceC1bFc77C77"; + cmd.cast_fuse().args(["storage", "--rpc-url", eth_rpc_url.as_str(), address]); + let output = cmd.stdout_lossy(); + assert!( + output.contains("RootChainManager") && + output.contains("inited") && + output.contains("bool") && + output + .contains("0x0000000000000000000000000000000000000000000000000000000000000001"), + "{}", + output + ); + // first slot is `inited`, always is 1 + cmd.cast_fuse().args(["storage", "--rpc-url", eth_rpc_url.as_str(), address, "0"]); + let output = cmd.stdout_lossy(); + assert!( + output.contains("0x0000000000000000000000000000000000000000000000000000000000000001"), + "{}", + output + ); +}); From 65dc8cbf403cfa98028d13622558edd6c6984da7 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 14 Oct 2022 19:02:56 +0200 Subject: [PATCH 20/26] feat: add initial storage fetcher --- cli/src/cmd/cast/storage.rs | 105 ++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 17 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 1bd68420fa025..7f6c5ce851db0 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -1,18 +1,23 @@ use crate::{ - cmd::forge::{build, inspect::print_storage_layout}, + cmd::forge::build, opts::cast::{parse_block_id, parse_name_or_address, parse_slot}, utils::try_consume_config_rpc_url, }; use cast::Cast; use clap::Parser; -use ethers::{etherscan::Client, prelude::*, solc::artifacts::StorageLayout}; +use comfy_table::Table; +use ethers::{ + abi::ethabi::ethereum_types::BigEndianHash, etherscan::Client, prelude::*, + solc::artifacts::StorageLayout, +}; use eyre::{ContextCompat, Result}; use foundry_common::{ abi::find_source, compile::{compile, etherscan_project, suppress_compile}, - try_get_http_provider, + try_get_http_provider, RetryProvider, }; use foundry_config::Config; +use futures::future::join_all; use semver::Version; /// The minimum Solc version for outputting storage layouts. @@ -101,7 +106,7 @@ impl StorageArgs { let artifact = out.artifacts().find(|(_, artifact)| match_code(artifact).unwrap_or_default()); if let Some((_, artifact)) = artifact { - return print_storage_layout(&artifact.storage_layout, true) + return fetch_and_print_storage(provider, address, artifact, true).await } } @@ -122,6 +127,7 @@ impl StorageArgs { let version = metadata.compiler_version()?; let auto_detect = version < MIN_SOLC; + // Create a new temp project let root = tempfile::tempdir()?; let root_path = root.path(); let project = etherscan_project(metadata, root_path)?; @@ -131,7 +137,7 @@ impl StorageArgs { // Compile let mut out = suppress_compile(&project)?; let artifact = { - let (_, artifact) = out + let (_, mut artifact) = out .artifacts() .find(|(name, _)| name == &metadata.contract_name) .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; @@ -144,32 +150,97 @@ impl StorageArgs { project.auto_detect = false; if let Ok(output) = suppress_compile(&project) { out = output; - let (_, artifact) = out + let (_, new_artifact) = out .artifacts() .find(|(name, _)| name == &metadata.contract_name) .ok_or_else(|| eyre::eyre!("Could not find artifact"))?; - artifact - } else { - artifact + artifact = new_artifact; } - } else { - artifact } - }; - if is_storage_layout_empty(&artifact.storage_layout) { - println!("Storage layout is empty.") - } else { - print_storage_layout(&artifact.storage_layout, true)?; - } + artifact + }; // Clear temp directory root.close()?; + fetch_and_print_storage(provider, address, artifact, true).await + } +} + +async fn fetch_and_print_storage( + provider: RetryProvider, + address: Address, + artifact: &ConfigurableContractArtifact, + pretty: bool, +) -> Result<()> { + if is_storage_layout_empty(&artifact.storage_layout) { + println!("Storage layout is empty."); Ok(()) + } else { + let mut layout = artifact.storage_layout.as_ref().unwrap().clone(); + fetch_storage_values(provider, address, &mut layout).await?; + print_storage(layout, pretty) } } +/// Overrides the `value` field in [StorageLayout] with the slot's value to avoid creating new data +/// structures. +async fn fetch_storage_values( + provider: RetryProvider, + address: Address, + layout: &mut StorageLayout, +) -> Result<()> { + // TODO: Batch request? + // TODO: Array values + let futures: Vec<_> = layout + .storage + .iter() + .map(|slot| { + let slot_h256 = H256::from_uint(&slot.slot.parse::()?); + Ok(provider.get_storage_at(address, slot_h256, None)) + }) + .collect::>()?; + + for (value, slot) in join_all(futures).await.into_iter().zip(layout.storage.iter()) { + let value = value?.into_uint(); + let t = layout.types.get_mut(&slot.storage_type).expect("Bad storage"); + // TODO: Format value + t.value = Some(format!("{:?}", value)); + } + + Ok(()) +} + +fn print_storage(layout: StorageLayout, pretty: bool) -> Result<()> { + if !pretty { + println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?); + return Ok(()) + } + + let mut table = Table::new(); + table.set_header(vec!["Name", "Type", "Slot", "Offset", "Bytes", "Value", "Contract"]); + + for slot in layout.storage { + let storage_type = layout.types.get(&slot.storage_type); + table.add_row(vec![ + slot.label, + storage_type.as_ref().map_or("?".to_string(), |t| t.label.clone()), + slot.slot, + slot.offset.to_string(), + storage_type.as_ref().map_or("?".to_string(), |t| t.number_of_bytes.clone()), + storage_type + .as_ref() + .map_or("?".to_string(), |t| t.value.clone().unwrap_or_else(|| "0".to_string())), + slot.contract, + ]); + } + + println!("{}", table); + + Ok(()) +} + fn with_storage_layout_output(mut project: Project) -> Project { project.artifacts.additional_values.storage_layout = true; let output_selection = project.artifacts.output_selection(); From 40ac1e26b854bcae77f1c96d6d53ea125f5d5fda Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:34:35 +0100 Subject: [PATCH 21/26] fix test --- cli/tests/it/cast.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cli/tests/it/cast.rs b/cli/tests/it/cast.rs index e80e50e862b1e..bc0d45ac8ad8c 100644 --- a/cli/tests/it/cast.rs +++ b/cli/tests/it/cast.rs @@ -255,8 +255,7 @@ casttest!(cast_storage_succeeds, |_: TestProject, mut cmd: TestCommand| { let output = cmd.stdout_lossy(); assert!( output.contains("0x577261707065642045746865720000000000000000000000000000000000001a"), - "{}", - output + "{output}", ); // Polygon bridge proxy @@ -265,19 +264,15 @@ casttest!(cast_storage_succeeds, |_: TestProject, mut cmd: TestCommand| { let output = cmd.stdout_lossy(); assert!( output.contains("RootChainManager") && - output.contains("inited") && - output.contains("bool") && - output - .contains("0x0000000000000000000000000000000000000000000000000000000000000001"), - "{}", - output + output.contains("_roles") && + output.contains("mapping(bytes32 => struct AccessControl.RoleData)"), + "{output}", ); // first slot is `inited`, always is 1 cmd.cast_fuse().args(["storage", "--rpc-url", eth_rpc_url.as_str(), address, "0"]); let output = cmd.stdout_lossy(); assert!( output.contains("0x0000000000000000000000000000000000000000000000000000000000000001"), - "{}", - output + "{output}", ); }); From 3aa63e3cac681aafb5f01dda4ead704a9a946181 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:52:06 +0100 Subject: [PATCH 22/26] update TODOs --- cli/src/cmd/cast/storage.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 7f6c5ce851db0..91c7bc3b597ec 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -68,7 +68,7 @@ pub struct StorageArgs { impl StorageArgs { pub async fn run(self) -> Result<()> { - let StorageArgs { address, block, build, rpc_url, slot, chain, etherscan_api_key } = self; + let Self { address, block, build, rpc_url, slot, chain, etherscan_api_key } = self; let rpc_url = try_consume_config_rpc_url(rpc_url)?; let provider = try_get_http_provider(rpc_url)?; @@ -93,10 +93,10 @@ impl StorageArgs { } // Check if we're in a forge project - let project = build.project()?; + let mut project = build.project()?; if project.paths.has_input_files() { // Find in artifacts and pretty print - let project = with_storage_layout_output(project); + add_storage_layout_output(&mut project); let out = compile(&project, false, false)?; let match_code = |artifact: &ConfigurableContractArtifact| -> Option { let bytes = @@ -128,10 +128,11 @@ impl StorageArgs { let auto_detect = version < MIN_SOLC; // Create a new temp project + // TODO: Cache instead of using a temp directory: metadata from Etherscan won't change let root = tempfile::tempdir()?; let root_path = root.path(); - let project = etherscan_project(metadata, root_path)?; - let mut project = with_storage_layout_output(project); + let mut project = etherscan_project(metadata, root_path)?; + add_storage_layout_output(&mut project); project.auto_detect = auto_detect; // Compile @@ -191,8 +192,7 @@ async fn fetch_storage_values( address: Address, layout: &mut StorageLayout, ) -> Result<()> { - // TODO: Batch request? - // TODO: Array values + // TODO: Batch request; handle array values; let futures: Vec<_> = layout .storage .iter() @@ -205,7 +205,7 @@ async fn fetch_storage_values( for (value, slot) in join_all(futures).await.into_iter().zip(layout.storage.iter()) { let value = value?.into_uint(); let t = layout.types.get_mut(&slot.storage_type).expect("Bad storage"); - // TODO: Format value + // TODO: Better format values according to their Solidity type t.value = Some(format!("{:?}", value)); } @@ -241,11 +241,10 @@ fn print_storage(layout: StorageLayout, pretty: bool) -> Result<()> { Ok(()) } -fn with_storage_layout_output(mut project: Project) -> Project { +fn add_storage_layout_output(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; let output_selection = project.artifacts.output_selection(); - project.solc_config.settings = project.solc_config.settings.with_extra_output(output_selection); - project + project.solc_config.settings.push_all(output_selection); } fn is_storage_layout_empty(storage_layout: &Option) -> bool { From 6214e692fc591c9ab393a152f64dcc62b57216b5 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:55:34 +0100 Subject: [PATCH 23/26] other fixes --- cli/src/cmd/cast/storage.rs | 8 ++++---- cli/tests/it/utils.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/cmd/cast/storage.rs b/cli/src/cmd/cast/storage.rs index 91c7bc3b597ec..07de229adbc08 100644 --- a/cli/src/cmd/cast/storage.rs +++ b/cli/src/cmd/cast/storage.rs @@ -145,7 +145,7 @@ impl StorageArgs { if is_storage_layout_empty(&artifact.storage_layout) && auto_detect { // try recompiling with the minimum version - println!("The requested contract was compiled with {} while the minimum version for storage layouts is {} and as a result the output may be empty.", version, MIN_SOLC); + println!("The requested contract was compiled with {version} while the minimum version for storage layouts is {MIN_SOLC} and as a result the output may be empty."); let solc = Solc::find_or_install_svm_version(MIN_SOLC.to_string())?; project.solc = solc; project.auto_detect = false; @@ -200,13 +200,13 @@ async fn fetch_storage_values( let slot_h256 = H256::from_uint(&slot.slot.parse::()?); Ok(provider.get_storage_at(address, slot_h256, None)) }) - .collect::>()?; + .collect::>()?; for (value, slot) in join_all(futures).await.into_iter().zip(layout.storage.iter()) { let value = value?.into_uint(); let t = layout.types.get_mut(&slot.storage_type).expect("Bad storage"); // TODO: Better format values according to their Solidity type - t.value = Some(format!("{:?}", value)); + t.value = Some(format!("{value:?}")); } Ok(()) @@ -236,7 +236,7 @@ fn print_storage(layout: StorageLayout, pretty: bool) -> Result<()> { ]); } - println!("{}", table); + println!("{table}"); Ok(()) } diff --git a/cli/tests/it/utils.rs b/cli/tests/it/utils.rs index f403522ecd76e..36f11689581d4 100644 --- a/cli/tests/it/utils.rs +++ b/cli/tests/it/utils.rs @@ -24,12 +24,12 @@ pub fn etherscan_key(chain: Chain) -> Option { pub fn network_rpc_key(chain: &str) -> Option { let key = format!("{}_RPC_URL", chain.to_uppercase().replace('-', "_")); - std::env::var(&key).ok() + std::env::var(key).ok() } pub fn network_private_key(chain: &str) -> Option { let key = format!("{}_PRIVATE_KEY", chain.to_uppercase().replace('-', "_")); - std::env::var(&key).or_else(|_| std::env::var("TEST_PRIVATE_KEY")).ok() + std::env::var(key).or_else(|_| std::env::var("TEST_PRIVATE_KEY")).ok() } /// Represents external input required for executing verification requests From 3d16d55cedaf86ee550f77d33a9f3635241fa9d4 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sun, 18 Dec 2022 16:19:04 +0200 Subject: [PATCH 24/26] ci: use etherscan api key in all tests --- .github/workflows/cross-platform.yml | 1 + .github/workflows/test.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/cross-platform.yml b/.github/workflows/cross-platform.yml index b96d8c19dff77..185aa90e66d6c 100644 --- a/.github/workflows/cross-platform.yml +++ b/.github/workflows/cross-platform.yml @@ -5,6 +5,7 @@ on: name: cross-platform env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} CARGO_TERM_COLOR: always jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5f8d91eeab1f..59668d40b937d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,7 @@ name: test env: NEXTEST_EXPERIMENTAL_FILTER_EXPR: 1 CARGO_TERM_COLOR: always + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} jobs: build-tests: From d72ab6013d65ddfb3a249bb1628be8867c72c603 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sun, 18 Dec 2022 16:26:54 +0200 Subject: [PATCH 25/26] Revert "ci: use etherscan api key in all tests" This reverts commit 3d16d55cedaf86ee550f77d33a9f3635241fa9d4. This was not the right fix as it triggered all integration tests to run. --- .github/workflows/cross-platform.yml | 1 - .github/workflows/test.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/cross-platform.yml b/.github/workflows/cross-platform.yml index 185aa90e66d6c..b96d8c19dff77 100644 --- a/.github/workflows/cross-platform.yml +++ b/.github/workflows/cross-platform.yml @@ -5,7 +5,6 @@ on: name: cross-platform env: - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} CARGO_TERM_COLOR: always jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59668d40b937d..c5f8d91eeab1f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,6 @@ name: test env: NEXTEST_EXPERIMENTAL_FILTER_EXPR: 1 CARGO_TERM_COLOR: always - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} jobs: build-tests: From 607d829f57eb99ad358f12fe4dde0d5e520fb632 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sun, 18 Dec 2022 16:02:39 +0100 Subject: [PATCH 26/26] fix: add test to live ci --- .github/workflows/live-test.yml | 9 +++++---- cli/tests/it/cast.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/live-test.yml b/.github/workflows/live-test.yml index 6dbd397e1972c..062773715cd29 100644 --- a/.github/workflows/live-test.yml +++ b/.github/workflows/live-test.yml @@ -18,16 +18,17 @@ jobs: TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true + - name: Install nextest + uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v1 with: cache-on-failure: true - - - name: cargo test - run: cargo test --package foundry-cli --test it -- verify::test_live_can_deploy_and_verify --exact --nocapture + - name: cargo nextest + run: cargo nextest run -p foundry-cli -E "!test(~fork) & test(~live)" diff --git a/cli/tests/it/cast.rs b/cli/tests/it/cast.rs index 51a6a2746515e..bbe80b82bc5b7 100644 --- a/cli/tests/it/cast.rs +++ b/cli/tests/it/cast.rs @@ -241,7 +241,7 @@ casttest!(cast_run_succeeds, |_: TestProject, mut cmd: TestCommand| { }); // tests that the `cast storage` command works correctly -casttest!(cast_storage_succeeds, |_: TestProject, mut cmd: TestCommand| { +casttest!(test_live_cast_storage_succeeds, |_: TestProject, mut cmd: TestCommand| { let eth_rpc_url = next_http_rpc_endpoint(); // WETH