diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 11c7b507e355..7389d1f35e4d 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -5,7 +5,7 @@ use cast::revm::primitives::EnvWithHandlerCfg; use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ - opts::RpcOpts, + opts::{EtherscanOpts, RpcOpts}, utils::{handle_traces, init_progress, TraceResult}, }; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; @@ -58,13 +58,16 @@ pub struct RunArgs { #[arg(long, short)] label: Vec, + #[command(flatten)] + etherscan: EtherscanOpts, + #[command(flatten)] rpc: RpcOpts, /// The EVM version to use. /// /// Overrides the version specified in the config. - #[arg(long, short)] + #[arg(long)] evm_version: Option, /// Sets the number of assumed available compute units per second for this provider @@ -269,6 +272,10 @@ impl figment::Provider for RunArgs { map.insert("alphanet".into(), self.alphanet.into()); } + if let Some(api_key) = &self.etherscan.key { + map.insert("etherscan_api_key".into(), api_key.as_str().into()); + } + if let Some(evm_version) = self.evm_version { map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 6fb3ddb73c58..29527f7dc3bb 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -6,8 +6,8 @@ use anvil::{EthereumHardfork, NodeConfig}; use foundry_test_utils::{ casttest, file, rpc::{ - next_http_rpc_endpoint, next_mainnet_etherscan_api_key, next_rpc_endpoint, - next_ws_rpc_endpoint, + next_etherscan_api_key, next_http_rpc_endpoint, next_mainnet_etherscan_api_key, + next_rpc_endpoint, next_ws_rpc_endpoint, }, str, util::OutputExt, @@ -1503,3 +1503,28 @@ casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { "#]]); }); + +// +casttest!(test_non_mainnet_traces, |prj, cmd| { + prj.clear(); + cmd.args([ + "run", + "0xa003e419e2d7502269eb5eda56947b580120e00abfd5b5460d08f8af44a0c24f", + "--rpc-url", + next_rpc_endpoint(NamedChain::Optimism).as_str(), + "--etherscan-api-key", + next_etherscan_api_key(NamedChain::Optimism).as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [33841] FiatTokenProxy::fallback(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) + ├─ [26673] FiatTokenV2_2::approve(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) [delegatecall] + │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, value: 164054805 [1.64e8]) + │ └─ ← [Return] true + └─ ← [Return] true +... + +"#]]); +}); diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 7a41d075641b..17ecbcca170e 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -192,10 +192,6 @@ impl EvmOpts { /// Returns the chain ID from the RPC, if any. pub async fn get_remote_chain_id(&self) -> Option { if let Some(ref url) = self.fork_url { - if url.contains("mainnet") { - trace!(?url, "auto detected mainnet chain"); - return Some(Chain::mainnet()); - } trace!(?url, "retrieving chain via eth_chainId"); let provider = ProviderBuilder::new(url.as_str()) .compute_units_per_second(self.get_compute_units_per_second()) @@ -206,6 +202,14 @@ impl EvmOpts { if let Ok(id) = provider.get_chain_id().await { return Some(Chain::from(id)); } + + // Provider URLs could be of the format `{CHAIN_IDENTIFIER}-mainnet` + // (e.g. Alchemy `opt-mainnet`, `arb-mainnet`), fallback to this method only + // if we're not able to retrieve chain id from `RetryProvider`. + if url.contains("mainnet") { + trace!(?url, "auto detected mainnet chain"); + return Some(Chain::mainnet()); + } } None diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 7f0742143e4c..a974e395406d 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -1,6 +1,6 @@ //! RPC API keys utilities. -use foundry_config::NamedChain; +use foundry_config::{NamedChain, NamedChain::Optimism}; use rand::seq::SliceRandom; use std::sync::{ atomic::{AtomicUsize, Ordering}, @@ -75,6 +75,10 @@ static ETHERSCAN_MAINNET_KEYS: LazyLock> = LazyLock::new(|| { keys }); +// List of etherscan keys for Optimism. +static ETHERSCAN_OPTIMISM_KEYS: LazyLock> = + LazyLock::new(|| vec!["JQNGFHINKS1W7Y5FRXU4SPBYF43J3NYK46"]); + /// Returns the next index to use. fn next() -> usize { static NEXT_INDEX: AtomicUsize = AtomicUsize::new(0); @@ -127,6 +131,16 @@ pub fn next_mainnet_etherscan_api_key() -> String { ETHERSCAN_MAINNET_KEYS[idx].to_string() } +/// Returns the next etherscan api key for given chain. +pub fn next_etherscan_api_key(chain: NamedChain) -> String { + let keys = match chain { + Optimism => ÐERSCAN_OPTIMISM_KEYS, + _ => ÐERSCAN_MAINNET_KEYS, + }; + let idx = next() % keys.len(); + keys[idx].to_string() +} + fn next_url(is_ws: bool, chain: NamedChain) -> String { use NamedChain::*;