diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index f52145b7ef70f..fcce6a607c716 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 0000000000000..b2134d658aafb --- /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 179df2adf5489..07eecca301512 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 b8de8d84deabc..52aa66a1258b1 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 8a499ac696640..5bd1b17f9be2f 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 627f2bd66a2ef..9b1dc084559fc 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 7cd49752f98d7..d2f13ead6ff9c 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 + +" + ]]); +});