From 7efeaecb8f5dac2276a678e83d068d24eee1fa3c Mon Sep 17 00:00:00 2001 From: pawurb Date: Fri, 4 Oct 2024 09:41:33 +0200 Subject: [PATCH 01/22] feat(cast) add creation-code method [#8973] --- crates/cast/bin/args.rs | 10 ++- crates/cast/bin/cmd/creation_code.rs | 98 ++++++++++++++++++++++++++++ crates/cast/bin/cmd/mod.rs | 1 + crates/cast/bin/main.rs | 1 + crates/cast/tests/cli/main.rs | 16 +++++ 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 crates/cast/bin/cmd/creation_code.rs diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index fdb6a113354a..f52145b7ef70 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -1,8 +1,8 @@ use crate::cmd::{ access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args, - estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, - mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, - wallet::WalletSubcommands, + creation_code::CreationCodeArgs, estimate::EstimateArgs, find_block::FindBlockArgs, + interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, + send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, }; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; @@ -898,6 +898,10 @@ pub enum CastSubcommand { command: WalletSubcommands, }, + /// Download a contract creation code from Etherscan and RPC. + #[command(visible_alias = "cc")] + CreationCode(CreationCodeArgs), + /// Generate a Solidity interface from a given ABI. /// /// Currently does not support ABI encoder v2. diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs new file mode 100644 index 000000000000..95d9fb663006 --- /dev/null +++ b/crates/cast/bin/cmd/creation_code.rs @@ -0,0 +1,98 @@ +use std::sync::Arc; + +use alloy_primitives::{Address, Bytes}; +use alloy_provider::{ext::TraceApi, Provider}; +use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; +use clap::{command, Parser}; +use evm_disassembler::{disassemble_bytes, format_operations}; +use eyre::Result; +use foundry_block_explorers::Client; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils, +}; +use foundry_common::provider::RetryProvider; +use foundry_config::Config; + +/// CLI arguments for `cast creation-code`. +#[derive(Parser)] +pub struct CreationCodeArgs { + /// An Ethereum address, for which the bytecode will be fetched. + contract: Address, + + /// Disassemble bytecodes into individual opcodes. + #[arg(long)] + disassemble: bool, + + #[command(flatten)] + etherscan: EtherscanOpts, + #[command(flatten)] + rpc: RpcOpts, +} + +impl CreationCodeArgs { + pub async fn run(self) -> Result<()> { + let Self { contract, etherscan, rpc, disassemble } = self; + let config = Config::from(ðerscan); + let chain = config.chain.unwrap_or_default(); + let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, api_key)?; + + let config = Config::from(&rpc); + let provider = utils::get_provider(&config)?; + + let bytecode = fetch_creation_code(contract, client, &Arc::new(provider)).await?; + + if disassemble { + print!("{}", format_operations(disassemble_bytes(bytecode.into())?)?); + } else { + print!("{bytecode}"); + } + + Ok(()) + } +} + +/// Fetches the creation code of a contract from Etherscan and RPC. +async fn fetch_creation_code( + contract: Address, + client: Client, + provider: &Arc, +) -> Result +where +{ + let creation_data = client.contract_creation_data(contract).await?; + let creation_tx_hash = creation_data.transaction_hash; + let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; + let tx_data = tx_data.ok_or_else(|| eyre::eyre!("Could not find creation tx data."))?; + + let bytecode = if tx_data.inner.to.is_none() { + // Contract was created using a standard transaction + tx_data.inner.input + } else { + // Contract was created using a factory pattern or create2 + // Extract creation code from tx traces + let mut creation_bytecode = None; + + let traces = provider.trace_transaction(creation_tx_hash).await?; + + for trace in traces { + if let Some(TraceOutput::Create(CreateOutput { address, code: _, gas_used: _ })) = + trace.trace.result + { + if address == contract { + creation_bytecode = match trace.trace.action { + Action::Create(CreateAction { init, value: _, from: _, gas: _ }) => { + Some(init) + } + _ => None, + }; + } + } + } + + creation_bytecode.ok_or_else(|| eyre::eyre!("Could not find contract creation trace."))? + }; + + Ok(bytecode) +} diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index 6c904417407c..8a499ac69664 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -9,6 +9,7 @@ pub mod access_list; pub mod bind; pub mod call; pub mod create2; +pub mod creation_code; pub mod estimate; pub mod find_block; pub mod interface; diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 63894d980b0a..627f2bd66a2e 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -186,6 +186,7 @@ async fn main_args(args: CastArgs) -> Result<()> { println!("{}", SimpleCast::calldata_encode(sig, &args)?); } CastSubcommand::Interface(cmd) => cmd.run().await?, + CastSubcommand::CreationCode(cmd) => cmd.run().await?, CastSubcommand::Bind(cmd) => cmd.run().await?, CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index df566d1144c0..7cd49752f98d 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1326,3 +1326,19 @@ casttest!(hash_message, |_prj, cmd| { "#]]); }); + +// tests that fetches a sample contract creation code +// +casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "creation-code", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![["0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033"]]); +}); From 5f86a8feff8a0f6c53e0381cf00b8798aa4473c0 Mon Sep 17 00:00:00 2001 From: pawurb Date: Fri, 4 Oct 2024 11:26:18 +0200 Subject: [PATCH 02/22] Fix typo --- crates/cast/bin/cmd/creation_code.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 95d9fb663006..d8c9b440474c 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -58,9 +58,7 @@ async fn fetch_creation_code( contract: Address, client: Client, provider: &Arc, -) -> Result -where -{ +) -> Result { let creation_data = client.contract_creation_data(contract).await?; let creation_tx_hash = creation_data.transaction_hash; let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; From 057495ede32d16ef5a081dce6cc336d62ec53638 Mon Sep 17 00:00:00 2001 From: pawurb Date: Fri, 4 Oct 2024 12:01:00 +0200 Subject: [PATCH 03/22] Fix CI --- crates/cast/Cargo.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 5fadb49675ce..4e6b001ecfaf 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -44,9 +44,14 @@ alloy-json-abi.workspace = true alloy-json-rpc.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true -alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } +alloy-provider = { workspace = true, features = [ + "reqwest", + "ws", + "ipc", + "trace-api", +] } alloy-rlp.workspace = true -alloy-rpc-types = { workspace = true, features = ["eth"] } +alloy-rpc-types = { workspace = true, features = ["eth", "trace"] } alloy-serde.workspace = true alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } alloy-signer.workspace = true From b2807ed78f6340227ffb14bc66fecfb04c9dd1ac Mon Sep 17 00:00:00 2001 From: pawurb Date: Fri, 4 Oct 2024 17:31:16 +0200 Subject: [PATCH 04/22] Code review fixes --- crates/cast/bin/cmd/creation_code.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index d8c9b440474c..179df2adf548 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use alloy_primitives::{Address, Bytes}; use alloy_provider::{ext::TraceApi, Provider}; use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; @@ -41,7 +39,7 @@ impl CreationCodeArgs { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; - let bytecode = fetch_creation_code(contract, client, &Arc::new(provider)).await?; + let bytecode = fetch_creation_code(contract, client, provider).await?; if disassemble { print!("{}", format_operations(disassemble_bytes(bytecode.into())?)?); @@ -54,10 +52,12 @@ impl CreationCodeArgs { } /// Fetches the creation code of a contract from Etherscan and RPC. +/// +/// If present, constructor arguments are appended to the end of the bytecode. async fn fetch_creation_code( contract: Address, client: Client, - provider: &Arc, + provider: RetryProvider, ) -> Result { let creation_data = client.contract_creation_data(contract).await?; let creation_tx_hash = creation_data.transaction_hash; @@ -72,7 +72,9 @@ async fn fetch_creation_code( // Extract creation code from tx traces let mut creation_bytecode = None; - let traces = provider.trace_transaction(creation_tx_hash).await?; + let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| { + eyre::eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e) + })?; for trace in traces { if let Some(TraceOutput::Create(CreateOutput { address, code: _, gas_used: _ })) = From 0c78d608c81336112237d5152ec3f4926fc4dcf2 Mon Sep 17 00:00:00 2001 From: pawurb Date: Thu, 10 Oct 2024 11:39:21 +0200 Subject: [PATCH 05/22] Add creation-code flags and creation-args --- crates/cast/bin/args.rs | 10 +++- crates/cast/bin/cmd/creation_args.rs | 82 ++++++++++++++++++++++++++++ crates/cast/bin/cmd/creation_code.rs | 70 ++++++++++++++++++++++-- crates/cast/bin/cmd/interface.rs | 2 +- crates/cast/bin/cmd/mod.rs | 1 + crates/cast/bin/main.rs | 1 + crates/cast/tests/cli/main.rs | 37 +++++++++++++ 7 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 crates/cast/bin/cmd/creation_args.rs diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index f52145b7ef70..fcce6a607c71 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -1,8 +1,8 @@ use crate::cmd::{ access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args, - creation_code::CreationCodeArgs, estimate::EstimateArgs, find_block::FindBlockArgs, - interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, - send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, + creation_args::CreationArgsArgs, creation_code::CreationCodeArgs, estimate::EstimateArgs, + find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, + rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, }; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; @@ -902,6 +902,10 @@ pub enum CastSubcommand { #[command(visible_alias = "cc")] CreationCode(CreationCodeArgs), + /// Display args used for the contract initialization + #[command(visible_alias = "cra")] + CreationArgs(CreationArgsArgs), + /// Generate a Solidity interface from a given ABI. /// /// Currently does not support ABI encoder v2. diff --git a/crates/cast/bin/cmd/creation_args.rs b/crates/cast/bin/cmd/creation_args.rs new file mode 100644 index 000000000000..b2134d658aaf --- /dev/null +++ b/crates/cast/bin/cmd/creation_args.rs @@ -0,0 +1,82 @@ +use alloy_primitives::{Address, Bytes}; +use clap::{command, Parser}; +use eyre::Result; +use foundry_block_explorers::Client; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils, +}; +use foundry_config::Config; + +use super::{creation_code::fetch_creation_code, interface::fetch_abi_from_etherscan}; + +/// CLI arguments for `cast creation-code`. +#[derive(Parser)] +pub struct CreationArgsArgs { + /// An Ethereum address, for which the bytecode will be fetched. + contract: Address, + + #[command(flatten)] + etherscan: EtherscanOpts, + #[command(flatten)] + rpc: RpcOpts, +} + +impl CreationArgsArgs { + pub async fn run(self) -> Result<()> { + let Self { contract, etherscan, rpc } = self; + + let config = Config::from(ðerscan); + let chain = config.chain.unwrap_or_default(); + let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, api_key)?; + + let config = Config::from(&rpc); + let provider = utils::get_provider(&config)?; + + let bytecode = fetch_creation_code(contract, client, provider).await?; + + let args_arr = parse_creation_args(bytecode, contract, ðerscan).await?; + + for arg in args_arr { + println!("{arg}"); + } + + Ok(()) + } +} + +async fn parse_creation_args( + bytecode: Bytes, + contract: Address, + etherscan: &EtherscanOpts, +) -> Result> { + let abi = fetch_abi_from_etherscan(contract, etherscan).await?; + let abi = abi.into_iter().next().ok_or_else(|| eyre::eyre!("No ABI found."))?; + let (abi, _) = abi; + + if abi.constructor.is_none() { + return Err(eyre::eyre!("No constructor found.")); + } + + let constructor = abi.constructor.unwrap(); + + if constructor.inputs.is_empty() { + return Err(eyre::eyre!("No constructor arguments found.")); + } + + let args_size = constructor.inputs.len() * 32; + + let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()); + + let display_args: Vec = args_bytes + .chunks(32) + .enumerate() + .map(|(i, arg)| { + let arg = arg.to_vec(); + format!("{} {}", constructor.inputs[i].ty, Bytes::from(arg)) + }) + .collect(); + + Ok(display_args) +} diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 179df2adf548..07eecca30151 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -1,8 +1,8 @@ use alloy_primitives::{Address, Bytes}; use alloy_provider::{ext::TraceApi, Provider}; use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; +use cast::SimpleCast; use clap::{command, Parser}; -use evm_disassembler::{disassemble_bytes, format_operations}; use eyre::Result; use foundry_block_explorers::Client; use foundry_cli::{ @@ -12,6 +12,8 @@ use foundry_cli::{ use foundry_common::provider::RetryProvider; use foundry_config::Config; +use super::interface::fetch_abi_from_etherscan; + /// CLI arguments for `cast creation-code`. #[derive(Parser)] pub struct CreationCodeArgs { @@ -22,6 +24,14 @@ pub struct CreationCodeArgs { #[arg(long)] disassemble: bool, + /// Return creation bytecode without constructor arguments appended. + #[arg(long)] + without_args: bool, + + /// Return only constructor arguments. + #[arg(long)] + only_args: bool, + #[command(flatten)] etherscan: EtherscanOpts, #[command(flatten)] @@ -30,7 +40,12 @@ pub struct CreationCodeArgs { impl CreationCodeArgs { pub async fn run(self) -> Result<()> { - let Self { contract, etherscan, rpc, disassemble } = self; + let Self { contract, etherscan, rpc, disassemble, without_args, only_args } = self; + + if without_args && only_args { + return Err(eyre::eyre!("--without-args and --only-args are mutually exclusive.")); + } + let config = Config::from(ðerscan); let chain = config.chain.unwrap_or_default(); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); @@ -41,8 +56,11 @@ impl CreationCodeArgs { let bytecode = fetch_creation_code(contract, client, provider).await?; + let bytecode = + parse_code_output(bytecode, contract, ðerscan, without_args, only_args).await?; + if disassemble { - print!("{}", format_operations(disassemble_bytes(bytecode.into())?)?); + println!("{}", SimpleCast::disassemble(&bytecode)?); } else { print!("{bytecode}"); } @@ -51,10 +69,54 @@ impl CreationCodeArgs { } } +async fn parse_code_output( + bytecode: Bytes, + contract: Address, + etherscan: &EtherscanOpts, + without_args: bool, + only_args: bool, +) -> Result { + if !without_args && !only_args { + return Ok(bytecode); + } + + let abi = fetch_abi_from_etherscan(contract, etherscan).await?; + let abi = abi.into_iter().next().ok_or_else(|| eyre::eyre!("No ABI found."))?; + let (abi, _) = abi; + + if abi.constructor.is_none() { + if only_args { + return Err(eyre::eyre!("No constructor found.")); + } + return Ok(bytecode); + } + + let constructor = abi.constructor.unwrap(); + + if constructor.inputs.is_empty() { + if only_args { + return Err(eyre::eyre!("No constructor arguments found.")); + } + return Ok(bytecode); + } + + let args_size = constructor.inputs.len() * 32; + + let bytecode = if without_args { + Bytes::from(bytecode[..bytecode.len() - args_size].to_vec()) + } else if only_args { + Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()) + } else { + panic!("Unreachable.") + }; + + Ok(bytecode) +} + /// Fetches the creation code of a contract from Etherscan and RPC. /// /// If present, constructor arguments are appended to the end of the bytecode. -async fn fetch_creation_code( +pub async fn fetch_creation_code( contract: Address, client: Client, provider: RetryProvider, diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index b8de8d84deab..52aa66a1258b 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -139,7 +139,7 @@ fn load_abi_from_artifact(path_or_contract: &str) -> Result Result> { diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index 8a499ac69664..5bd1b17f9be2 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -9,6 +9,7 @@ pub mod access_list; pub mod bind; pub mod call; pub mod create2; +pub mod creation_args; pub mod creation_code; pub mod estimate; pub mod find_block; diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 627f2bd66a2e..9b1dc084559f 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -187,6 +187,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } CastSubcommand::Interface(cmd) => cmd.run().await?, CastSubcommand::CreationCode(cmd) => cmd.run().await?, + CastSubcommand::CreationArgs(cmd) => cmd.run().await?, CastSubcommand::Bind(cmd) => cmd.run().await?, CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 7cd49752f98d..d2f13ead6ff9 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1342,3 +1342,40 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { .assert_success() .stdout_eq(str![["0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033"]]); }); + +// tests that fetches a sample contract creation args bytes +// +casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "creation-code", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x6982508145454ce325ddbe47a25d4ec3d2311933", + "--rpc-url", + eth_rpc_url.as_str(), + "--only-args", + ]) + .assert_success() + .stdout_eq(str![["0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000"]]); +}); + +// tests that displays a sample contract creation args +// +casttest!(fetch_creation_args_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "creation-args", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x6982508145454ce325ddbe47a25d4ec3d2311933", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[ + "uint256 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 + +" + ]]); +}); From 6185a74b153005608cf568ad7d4848812513b4df Mon Sep 17 00:00:00 2001 From: pawurb Date: Thu, 10 Oct 2024 11:53:54 +0200 Subject: [PATCH 06/22] Update comments --- crates/cast/bin/cmd/creation_args.rs | 5 ++--- crates/cast/bin/cmd/creation_code.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/cast/bin/cmd/creation_args.rs b/crates/cast/bin/cmd/creation_args.rs index b2134d658aaf..76973d1d063f 100644 --- a/crates/cast/bin/cmd/creation_args.rs +++ b/crates/cast/bin/cmd/creation_args.rs @@ -10,7 +10,7 @@ use foundry_config::Config; use super::{creation_code::fetch_creation_code, interface::fetch_abi_from_etherscan}; -/// CLI arguments for `cast creation-code`. +/// CLI arguments for `cast creation-args`. #[derive(Parser)] pub struct CreationArgsArgs { /// An Ethereum address, for which the bytecode will be fetched. @@ -46,6 +46,7 @@ impl CreationArgsArgs { } } +/// Fetches the constructor arguments values and types from the creation bytecode and ABI. async fn parse_creation_args( bytecode: Bytes, contract: Address, @@ -60,13 +61,11 @@ async fn parse_creation_args( } let constructor = abi.constructor.unwrap(); - if constructor.inputs.is_empty() { return Err(eyre::eyre!("No constructor arguments found.")); } let args_size = constructor.inputs.len() * 32; - let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()); let display_args: Vec = args_bytes diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 07eecca30151..8a0c0deef1fa 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -69,6 +69,8 @@ impl CreationCodeArgs { } } +/// Parses the creation bytecode to return either the bytecode, or bytecoe without constructor +/// arguments or only the constructor arguments. async fn parse_code_output( bytecode: Bytes, contract: Address, @@ -92,7 +94,6 @@ async fn parse_code_output( } let constructor = abi.constructor.unwrap(); - if constructor.inputs.is_empty() { if only_args { return Err(eyre::eyre!("No constructor arguments found.")); @@ -114,8 +115,6 @@ async fn parse_code_output( } /// Fetches the creation code of a contract from Etherscan and RPC. -/// -/// If present, constructor arguments are appended to the end of the bytecode. pub async fn fetch_creation_code( contract: Address, client: Client, From e4e711007bf132ca0c1efa8bfe440ee9d80f3c25 Mon Sep 17 00:00:00 2001 From: pawurb Date: Thu, 10 Oct 2024 14:42:50 +0200 Subject: [PATCH 07/22] eyre style fixes --- crates/cast/bin/cmd/creation_args.rs | 8 ++++---- crates/cast/bin/cmd/creation_code.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/cast/bin/cmd/creation_args.rs b/crates/cast/bin/cmd/creation_args.rs index 76973d1d063f..2274234fa178 100644 --- a/crates/cast/bin/cmd/creation_args.rs +++ b/crates/cast/bin/cmd/creation_args.rs @@ -1,6 +1,6 @@ use alloy_primitives::{Address, Bytes}; use clap::{command, Parser}; -use eyre::Result; +use eyre::{eyre, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, @@ -53,16 +53,16 @@ async fn parse_creation_args( etherscan: &EtherscanOpts, ) -> Result> { let abi = fetch_abi_from_etherscan(contract, etherscan).await?; - let abi = abi.into_iter().next().ok_or_else(|| eyre::eyre!("No ABI found."))?; + let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; if abi.constructor.is_none() { - return Err(eyre::eyre!("No constructor found.")); + return Err(eyre!("No constructor found.")); } let constructor = abi.constructor.unwrap(); if constructor.inputs.is_empty() { - return Err(eyre::eyre!("No constructor arguments found.")); + return Err(eyre!("No constructor arguments found.")); } let args_size = constructor.inputs.len() * 32; diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 8a0c0deef1fa..7da25bac6764 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -3,7 +3,7 @@ use alloy_provider::{ext::TraceApi, Provider}; use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; use cast::SimpleCast; use clap::{command, Parser}; -use eyre::Result; +use eyre::{eyre, OptionExt, Result}; use foundry_block_explorers::Client; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, @@ -43,7 +43,7 @@ impl CreationCodeArgs { let Self { contract, etherscan, rpc, disassemble, without_args, only_args } = self; if without_args && only_args { - return Err(eyre::eyre!("--without-args and --only-args are mutually exclusive.")); + return Err(eyre!("--without-args and --only-args are mutually exclusive.")); } let config = Config::from(ðerscan); @@ -83,12 +83,12 @@ async fn parse_code_output( } let abi = fetch_abi_from_etherscan(contract, etherscan).await?; - let abi = abi.into_iter().next().ok_or_else(|| eyre::eyre!("No ABI found."))?; + let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; if abi.constructor.is_none() { if only_args { - return Err(eyre::eyre!("No constructor found.")); + return Err(eyre!("No constructor found.")); } return Ok(bytecode); } @@ -96,7 +96,7 @@ async fn parse_code_output( let constructor = abi.constructor.unwrap(); if constructor.inputs.is_empty() { if only_args { - return Err(eyre::eyre!("No constructor arguments found.")); + return Err(eyre!("No constructor arguments found.")); } return Ok(bytecode); } @@ -123,7 +123,7 @@ pub async fn fetch_creation_code( let creation_data = client.contract_creation_data(contract).await?; let creation_tx_hash = creation_data.transaction_hash; let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; - let tx_data = tx_data.ok_or_else(|| eyre::eyre!("Could not find creation tx data."))?; + let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?; let bytecode = if tx_data.inner.to.is_none() { // Contract was created using a standard transaction @@ -134,7 +134,7 @@ pub async fn fetch_creation_code( let mut creation_bytecode = None; let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| { - eyre::eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e) + eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e) })?; for trace in traces { @@ -152,7 +152,7 @@ pub async fn fetch_creation_code( } } - creation_bytecode.ok_or_else(|| eyre::eyre!("Could not find contract creation trace."))? + creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))? }; Ok(bytecode) From 92732e80e8241295a227e484a3c95787c133ffda Mon Sep 17 00:00:00 2001 From: pawurb Date: Fri, 18 Oct 2024 12:19:17 +0200 Subject: [PATCH 08/22] typo --- crates/cast/bin/cmd/creation_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 7da25bac6764..1fce123e0429 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -69,7 +69,7 @@ impl CreationCodeArgs { } } -/// Parses the creation bytecode to return either the bytecode, or bytecoe without constructor +/// Parses the creation bytecode to return either the bytecode, or bytecode without constructor /// arguments or only the constructor arguments. async fn parse_code_output( bytecode: Bytes, From be07698b87964cfec61b8a07831f395b9557f8b4 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 23 Oct 2024 11:28:15 +0200 Subject: [PATCH 09/22] use r#".."# for snapbox --- crates/cast/bin/cmd/creation_code.rs | 8 ++------ crates/cast/tests/cli/main.rs | 20 +++++++++----------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 1fce123e0429..d315cf44f247 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -138,14 +138,10 @@ pub async fn fetch_creation_code( })?; for trace in traces { - if let Some(TraceOutput::Create(CreateOutput { address, code: _, gas_used: _ })) = - trace.trace.result - { + if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result { if address == contract { creation_bytecode = match trace.trace.action { - Action::Create(CreateAction { init, value: _, from: _, gas: _ }) => { - Some(init) - } + Action::Create(CreateAction { init, .. }) => Some(init), _ => None, }; } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index d2f13ead6ff9..b48dd578651a 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1316,11 +1316,6 @@ casttest!(send_eip7702, async |_prj, cmd| { }); casttest!(hash_message, |_prj, cmd| { - cmd.args(["hash-message", "hello"]).assert_success().stdout_eq(str![[r#" -0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 - -"#]]); - cmd.cast_fuse().args(["hash-message", "0x68656c6c6f"]).assert_success().stdout_eq(str![[r#" 0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 @@ -1340,7 +1335,9 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { eth_rpc_url.as_str(), ]) .assert_success() - .stdout_eq(str![["0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033"]]); + .stdout_eq(str![[r#" +0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033 +"#]]); }); // tests that fetches a sample contract creation args bytes @@ -1357,7 +1354,9 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { "--only-args", ]) .assert_success() - .stdout_eq(str![["0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000"]]); + .stdout_eq(str![[r#" +0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 +"#]]); }); // tests that displays a sample contract creation args @@ -1373,9 +1372,8 @@ casttest!(fetch_creation_args_from_etherscan, |_prj, cmd| { eth_rpc_url.as_str(), ]) .assert_success() - .stdout_eq(str![[ - "uint256 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 + .stdout_eq(str![[r#" +uint256 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 -" - ]]); +"#]]); }); From 6e934da2d527bf6cce952548d6ac847584f67547 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:30:16 +0200 Subject: [PATCH 10/22] Apply suggestions from code review --- crates/cast/bin/cmd/creation_args.rs | 1 + crates/cast/bin/cmd/creation_code.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/creation_args.rs b/crates/cast/bin/cmd/creation_args.rs index 2274234fa178..ee532d36895a 100644 --- a/crates/cast/bin/cmd/creation_args.rs +++ b/crates/cast/bin/cmd/creation_args.rs @@ -18,6 +18,7 @@ pub struct CreationArgsArgs { #[command(flatten)] etherscan: EtherscanOpts, + #[command(flatten)] rpc: RpcOpts, } diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 1fce123e0429..0d15224811ed 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -138,12 +138,12 @@ pub async fn fetch_creation_code( })?; for trace in traces { - if let Some(TraceOutput::Create(CreateOutput { address, code: _, gas_used: _ })) = + if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result { if address == contract { creation_bytecode = match trace.trace.action { - Action::Create(CreateAction { init, value: _, from: _, gas: _ }) => { + Action::Create(CreateAction { init, .. }) => { Some(init) } _ => None, From b6ff62869b1dbd684677fae8192fb717327a4a6c Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 23 Oct 2024 11:34:16 +0200 Subject: [PATCH 11/22] fix test regression --- crates/cast/tests/cli/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index b48dd578651a..bb4c935249af 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1316,6 +1316,11 @@ casttest!(send_eip7702, async |_prj, cmd| { }); casttest!(hash_message, |_prj, cmd| { + cmd.args(["hash-message", "hello"]).assert_success().stdout_eq(str![[r#" +0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 + +"#]]); + cmd.cast_fuse().args(["hash-message", "0x68656c6c6f"]).assert_success().stdout_eq(str![[r#" 0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 From 0c930af25e4bf130c49f27f27197b5a5b4f90a07 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 23 Oct 2024 11:43:51 +0200 Subject: [PATCH 12/22] tag arguments as mutually exclusive --- crates/cast/bin/cmd/creation_code.rs | 9 +++------ crates/cast/tests/cli/main.rs | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index d315cf44f247..3b33cb947ba1 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -25,7 +25,7 @@ pub struct CreationCodeArgs { disassemble: bool, /// Return creation bytecode without constructor arguments appended. - #[arg(long)] + #[arg(long, conflicts_with = "only_args")] without_args: bool, /// Return only constructor arguments. @@ -34,6 +34,7 @@ pub struct CreationCodeArgs { #[command(flatten)] etherscan: EtherscanOpts, + #[command(flatten)] rpc: RpcOpts, } @@ -42,10 +43,6 @@ impl CreationCodeArgs { pub async fn run(self) -> Result<()> { let Self { contract, etherscan, rpc, disassemble, without_args, only_args } = self; - if without_args && only_args { - return Err(eyre!("--without-args and --only-args are mutually exclusive.")); - } - let config = Config::from(ðerscan); let chain = config.chain.unwrap_or_default(); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); @@ -62,7 +59,7 @@ impl CreationCodeArgs { if disassemble { println!("{}", SimpleCast::disassemble(&bytecode)?); } else { - print!("{bytecode}"); + println!("{bytecode}"); } Ok(()) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index bb4c935249af..1c7722b7375a 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1342,6 +1342,7 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { .assert_success() .stdout_eq(str![[r#" 0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033 + "#]]); }); @@ -1361,6 +1362,7 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { .assert_success() .stdout_eq(str![[r#" 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 + "#]]); }); From f5d1ae2433b7433373757df9b28618079df9acfa Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 23 Oct 2024 11:47:48 +0200 Subject: [PATCH 13/22] use unreachable! --- crates/cast/bin/cmd/creation_args.rs | 1 - crates/cast/bin/cmd/creation_code.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/cast/bin/cmd/creation_args.rs b/crates/cast/bin/cmd/creation_args.rs index ee532d36895a..5a1ed64d249a 100644 --- a/crates/cast/bin/cmd/creation_args.rs +++ b/crates/cast/bin/cmd/creation_args.rs @@ -38,7 +38,6 @@ impl CreationArgsArgs { let bytecode = fetch_creation_code(contract, client, provider).await?; let args_arr = parse_creation_args(bytecode, contract, ðerscan).await?; - for arg in args_arr { println!("{arg}"); } diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 3b33cb947ba1..29bbca55edf8 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -52,7 +52,6 @@ impl CreationCodeArgs { let provider = utils::get_provider(&config)?; let bytecode = fetch_creation_code(contract, client, provider).await?; - let bytecode = parse_code_output(bytecode, contract, ðerscan, without_args, only_args).await?; @@ -105,7 +104,7 @@ async fn parse_code_output( } else if only_args { Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()) } else { - panic!("Unreachable.") + unreachable!(); }; Ok(bytecode) From 261d82f79a8c43c69071e3aa8159888af039bacc Mon Sep 17 00:00:00 2001 From: pawurb Date: Wed, 23 Oct 2024 15:54:02 +0200 Subject: [PATCH 14/22] Rename and add abi_path param --- crates/cast/bin/args.rs | 11 ++++---- .../{creation_args.rs => constructor_args.rs} | 28 ++++++++++++++----- crates/cast/bin/cmd/creation_code.rs | 22 ++++++++++++--- crates/cast/bin/cmd/interface.rs | 2 +- crates/cast/bin/cmd/mod.rs | 2 +- crates/cast/bin/main.rs | 2 +- 6 files changed, 48 insertions(+), 19 deletions(-) rename crates/cast/bin/cmd/{creation_args.rs => constructor_args.rs} (72%) diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index fcce6a607c71..0ef66eb6a78d 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -1,8 +1,9 @@ use crate::cmd::{ - access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args, - creation_args::CreationArgsArgs, creation_code::CreationCodeArgs, estimate::EstimateArgs, - find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, - rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, + access_list::AccessListArgs, bind::BindArgs, call::CallArgs, + constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, + estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, + mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, + wallet::WalletSubcommands, }; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; @@ -904,7 +905,7 @@ pub enum CastSubcommand { /// Display args used for the contract initialization #[command(visible_alias = "cra")] - CreationArgs(CreationArgsArgs), + ConstructorArgs(ConstructorArgsArgs), /// Generate a Solidity interface from a given ABI. /// diff --git a/crates/cast/bin/cmd/creation_args.rs b/crates/cast/bin/cmd/constructor_args.rs similarity index 72% rename from crates/cast/bin/cmd/creation_args.rs rename to crates/cast/bin/cmd/constructor_args.rs index 5a1ed64d249a..6250a505dea2 100644 --- a/crates/cast/bin/cmd/creation_args.rs +++ b/crates/cast/bin/cmd/constructor_args.rs @@ -8,14 +8,22 @@ use foundry_cli::{ }; use foundry_config::Config; -use super::{creation_code::fetch_creation_code, interface::fetch_abi_from_etherscan}; +use super::{ + creation_code::fetch_creation_code, + interface::{fetch_abi_from_etherscan, load_abi_from_file}, +}; /// CLI arguments for `cast creation-args`. #[derive(Parser)] -pub struct CreationArgsArgs { +pub struct ConstructorArgsArgs { /// An Ethereum address, for which the bytecode will be fetched. contract: Address, + /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is + /// not verified on Etherscan + #[arg(long)] + abi_path: Option, + #[command(flatten)] etherscan: EtherscanOpts, @@ -23,9 +31,9 @@ pub struct CreationArgsArgs { rpc: RpcOpts, } -impl CreationArgsArgs { +impl ConstructorArgsArgs { pub async fn run(self) -> Result<()> { - let Self { contract, etherscan, rpc } = self; + let Self { contract, etherscan, rpc, abi_path } = self; let config = Config::from(ðerscan); let chain = config.chain.unwrap_or_default(); @@ -37,7 +45,7 @@ impl CreationArgsArgs { let bytecode = fetch_creation_code(contract, client, provider).await?; - let args_arr = parse_creation_args(bytecode, contract, ðerscan).await?; + let args_arr = parse_constructor_args(bytecode, contract, ðerscan, abi_path).await?; for arg in args_arr { println!("{arg}"); } @@ -47,12 +55,18 @@ impl CreationArgsArgs { } /// Fetches the constructor arguments values and types from the creation bytecode and ABI. -async fn parse_creation_args( +async fn parse_constructor_args( bytecode: Bytes, contract: Address, etherscan: &EtherscanOpts, + abi_path: Option, ) -> Result> { - let abi = fetch_abi_from_etherscan(contract, etherscan).await?; + let abi = if let Some(abi_path) = abi_path { + load_abi_from_file(&abi_path, None)? + } else { + fetch_abi_from_etherscan(contract, etherscan).await? + }; + let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 29bbca55edf8..7a2db4496d06 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -12,7 +12,7 @@ use foundry_cli::{ use foundry_common::provider::RetryProvider; use foundry_config::Config; -use super::interface::fetch_abi_from_etherscan; +use super::interface::{fetch_abi_from_etherscan, load_abi_from_file}; /// CLI arguments for `cast creation-code`. #[derive(Parser)] @@ -20,6 +20,11 @@ pub struct CreationCodeArgs { /// An Ethereum address, for which the bytecode will be fetched. contract: Address, + /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is + /// not verified on Etherscan + #[arg(long)] + abi_path: Option, + /// Disassemble bytecodes into individual opcodes. #[arg(long)] disassemble: bool, @@ -41,7 +46,8 @@ pub struct CreationCodeArgs { impl CreationCodeArgs { pub async fn run(self) -> Result<()> { - let Self { contract, etherscan, rpc, disassemble, without_args, only_args } = self; + let Self { contract, etherscan, rpc, disassemble, without_args, only_args, abi_path } = + self; let config = Config::from(ðerscan); let chain = config.chain.unwrap_or_default(); @@ -52,8 +58,10 @@ impl CreationCodeArgs { let provider = utils::get_provider(&config)?; let bytecode = fetch_creation_code(contract, client, provider).await?; + let bytecode = - parse_code_output(bytecode, contract, ðerscan, without_args, only_args).await?; + parse_code_output(bytecode, contract, ðerscan, abi_path, without_args, only_args) + .await?; if disassemble { println!("{}", SimpleCast::disassemble(&bytecode)?); @@ -71,6 +79,7 @@ async fn parse_code_output( bytecode: Bytes, contract: Address, etherscan: &EtherscanOpts, + abi_path: Option, without_args: bool, only_args: bool, ) -> Result { @@ -78,7 +87,12 @@ async fn parse_code_output( return Ok(bytecode); } - let abi = fetch_abi_from_etherscan(contract, etherscan).await?; + let abi = if let Some(abi_path) = abi_path { + load_abi_from_file(&abi_path, None)? + } else { + fetch_abi_from_etherscan(contract, etherscan).await? + }; + let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index 52aa66a1258b..fee126ba536f 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -108,7 +108,7 @@ struct InterfaceSource { } /// Load the ABI from a file. -fn load_abi_from_file(path: &str, name: Option) -> Result> { +pub fn load_abi_from_file(path: &str, name: Option) -> Result> { let file = std::fs::read_to_string(path).wrap_err("unable to read abi file")?; let obj: ContractObject = serde_json::from_str(&file)?; let abi = obj.abi.ok_or_else(|| eyre::eyre!("could not find ABI in file {path}"))?; diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index 5bd1b17f9be2..49e3ed2efe96 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -8,8 +8,8 @@ pub mod access_list; pub mod bind; pub mod call; +pub mod constructor_args; pub mod create2; -pub mod creation_args; pub mod creation_code; pub mod estimate; pub mod find_block; diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 9b1dc084559f..6fd44cbcf5d2 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -187,7 +187,7 @@ async fn main_args(args: CastArgs) -> Result<()> { } CastSubcommand::Interface(cmd) => cmd.run().await?, CastSubcommand::CreationCode(cmd) => cmd.run().await?, - CastSubcommand::CreationArgs(cmd) => cmd.run().await?, + CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, CastSubcommand::Bind(cmd) => cmd.run().await?, CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; From 6bde5732719c935db79efea7101a58997e27cff5 Mon Sep 17 00:00:00 2001 From: pawurb Date: Wed, 23 Oct 2024 16:47:11 +0200 Subject: [PATCH 15/22] Decode constructor args --- crates/cast/bin/cmd/constructor_args.rs | 11 ++++++++++- crates/cast/tests/cli/main.rs | 6 +++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/cast/bin/cmd/constructor_args.rs b/crates/cast/bin/cmd/constructor_args.rs index 6250a505dea2..c9eb789b41ad 100644 --- a/crates/cast/bin/cmd/constructor_args.rs +++ b/crates/cast/bin/cmd/constructor_args.rs @@ -1,3 +1,4 @@ +use alloy_dyn_abi::DynSolType; use alloy_primitives::{Address, Bytes}; use clap::{command, Parser}; use eyre::{eyre, OptionExt, Result}; @@ -87,9 +88,17 @@ async fn parse_constructor_args( .enumerate() .map(|(i, arg)| { let arg = arg.to_vec(); - format!("{} {}", constructor.inputs[i].ty, Bytes::from(arg)) + format_arg(&constructor.inputs[i].ty, arg).expect("Failed to format argument.") }) .collect(); Ok(display_args) } + +fn format_arg(ty: &str, arg: Vec) -> Result { + let arg_type: DynSolType = ty.parse().expect("Invalid ABI type."); + let bytes = Bytes::from(arg.clone()); + let decoded = arg_type.abi_decode(&arg)?; + + Ok(format!("{bytes} -> {decoded:?}")) +} diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 1c7722b7375a..a11fa3b78690 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1368,10 +1368,10 @@ casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { // tests that displays a sample contract creation args // -casttest!(fetch_creation_args_from_etherscan, |_prj, cmd| { +casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); cmd.args([ - "creation-args", + "constructor-args", "--etherscan-api-key", &next_mainnet_etherscan_api_key(), "0x6982508145454ce325ddbe47a25d4ec3d2311933", @@ -1380,7 +1380,7 @@ casttest!(fetch_creation_args_from_etherscan, |_prj, cmd| { ]) .assert_success() .stdout_eq(str![[r#" -uint256 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 +0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 -> Uint(420690000000000000000000000000000, 256) "#]]); }); From daf60139a3f8b6363fbc203d28fb26eec7822870 Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:24:55 +0100 Subject: [PATCH 16/22] Update crates/cast/bin/cmd/constructor_args.rs --- crates/cast/bin/cmd/constructor_args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/bin/cmd/constructor_args.rs b/crates/cast/bin/cmd/constructor_args.rs index c9eb789b41ad..62a63006d0aa 100644 --- a/crates/cast/bin/cmd/constructor_args.rs +++ b/crates/cast/bin/cmd/constructor_args.rs @@ -100,5 +100,5 @@ fn format_arg(ty: &str, arg: Vec) -> Result { let bytes = Bytes::from(arg.clone()); let decoded = arg_type.abi_decode(&arg)?; - Ok(format!("{bytes} -> {decoded:?}")) + Ok(format!("{bytes} → {decoded:?}")) } From c210f970d2e1b22ae782387e4a2216e0afdc5bca Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 28 Oct 2024 17:26:53 +0100 Subject: [PATCH 17/22] fix test --- crates/cast/tests/cli/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index a11fa3b78690..940b2997e0fd 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1380,7 +1380,7 @@ casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { ]) .assert_success() .stdout_eq(str![[r#" -0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 -> Uint(420690000000000000000000000000000, 256) +0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 → Uint(420690000000000000000000000000000, 256) "#]]); }); From dd8bada81ccf6720c8feb0724c5ffede798ec638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Urbanek?= Date: Mon, 28 Oct 2024 21:15:18 +0100 Subject: [PATCH 18/22] Update crates/cast/bin/args.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/cast/bin/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index 85c4d3c0a881..7552c3bd5f03 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -942,7 +942,7 @@ pub enum CastSubcommand { #[command(visible_alias = "cc")] CreationCode(CreationCodeArgs), - /// Display args used for the contract initialization + /// Display constructor arguments used for the contract initialization. #[command(visible_alias = "cra")] ConstructorArgs(ConstructorArgsArgs), From c70088bca74d1c4d96ffb2a01da34ca937067d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Urbanek?= Date: Mon, 28 Oct 2024 21:15:30 +0100 Subject: [PATCH 19/22] Update crates/cast/bin/cmd/creation_code.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/cast/bin/cmd/creation_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 7a2db4496d06..2d75fe868769 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -21,7 +21,7 @@ pub struct CreationCodeArgs { contract: Address, /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is - /// not verified on Etherscan + /// not verified on Etherscan. #[arg(long)] abi_path: Option, From 0ab37fcb91cff0d5005bb5348bbe5c357745987b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Urbanek?= Date: Mon, 28 Oct 2024 21:15:39 +0100 Subject: [PATCH 20/22] Update crates/cast/bin/cmd/creation_code.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/cast/bin/cmd/creation_code.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 2d75fe868769..f74ae86105bc 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -73,8 +73,10 @@ impl CreationCodeArgs { } } -/// Parses the creation bytecode to return either the bytecode, or bytecode without constructor -/// arguments or only the constructor arguments. +/// Parses the creation bytecode and returns one of the following: +/// - The complete bytecode +/// - The bytecode without constructor arguments +/// - Only the constructor arguments async fn parse_code_output( bytecode: Bytes, contract: Address, From a4ac98444d466378d9f015a1d769ff09020be5c2 Mon Sep 17 00:00:00 2001 From: pawurb Date: Tue, 29 Oct 2024 10:36:06 +0100 Subject: [PATCH 21/22] Fix formatting --- crates/cast/bin/cmd/creation_code.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index f74ae86105bc..6997bcb35eb5 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -74,8 +74,8 @@ impl CreationCodeArgs { } /// Parses the creation bytecode and returns one of the following: -/// - The complete bytecode -/// - The bytecode without constructor arguments +/// - The complete bytecode +/// - The bytecode without constructor arguments /// - Only the constructor arguments async fn parse_code_output( bytecode: Bytes, From 268636aa86fcf71180b68db8c5edc377af336e56 Mon Sep 17 00:00:00 2001 From: pawurb Date: Tue, 29 Oct 2024 12:26:56 +0100 Subject: [PATCH 22/22] Code review fixes --- crates/cast/bin/cmd/constructor_args.rs | 14 +++++--------- crates/cast/bin/cmd/creation_code.rs | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/cast/bin/cmd/constructor_args.rs b/crates/cast/bin/cmd/constructor_args.rs index 62a63006d0aa..b0a08704fb3c 100644 --- a/crates/cast/bin/cmd/constructor_args.rs +++ b/crates/cast/bin/cmd/constructor_args.rs @@ -48,7 +48,7 @@ impl ConstructorArgsArgs { let args_arr = parse_constructor_args(bytecode, contract, ðerscan, abi_path).await?; for arg in args_arr { - println!("{arg}"); + let _ = sh_println!("{arg}"); } Ok(()) @@ -71,11 +71,8 @@ async fn parse_constructor_args( let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; let (abi, _) = abi; - if abi.constructor.is_none() { - return Err(eyre!("No constructor found.")); - } + let constructor = abi.constructor.ok_or_else(|| eyre!("No constructor found."))?; - let constructor = abi.constructor.unwrap(); if constructor.inputs.is_empty() { return Err(eyre!("No constructor arguments found.")); } @@ -87,7 +84,6 @@ async fn parse_constructor_args( .chunks(32) .enumerate() .map(|(i, arg)| { - let arg = arg.to_vec(); format_arg(&constructor.inputs[i].ty, arg).expect("Failed to format argument.") }) .collect(); @@ -95,10 +91,10 @@ async fn parse_constructor_args( Ok(display_args) } -fn format_arg(ty: &str, arg: Vec) -> Result { +fn format_arg(ty: &str, arg: &[u8]) -> Result { let arg_type: DynSolType = ty.parse().expect("Invalid ABI type."); - let bytes = Bytes::from(arg.clone()); - let decoded = arg_type.abi_decode(&arg)?; + let decoded = arg_type.abi_decode(arg)?; + let bytes = Bytes::from(arg.to_vec()); Ok(format!("{bytes} → {decoded:?}")) } diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs index 6997bcb35eb5..bcafeac9488a 100644 --- a/crates/cast/bin/cmd/creation_code.rs +++ b/crates/cast/bin/cmd/creation_code.rs @@ -64,9 +64,9 @@ impl CreationCodeArgs { .await?; if disassemble { - println!("{}", SimpleCast::disassemble(&bytecode)?); + let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?); } else { - println!("{bytecode}"); + let _ = sh_println!("{bytecode}"); } Ok(())