diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index fdb6a113354a2..f52145b7ef70f 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 0000000000000..42997ff7def38 --- /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 bytecode`. +#[derive(Parser)] +pub struct CreationCodeArgs { + /// An Ethereum address, for which the bytecode will be fetche. + 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 { + println!("{}", format_operations(disassemble_bytes(bytecode.into())?)?); + } else { + println!("{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 bytecode 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.clone()) + } + _ => 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 6c904417407c2..8a499ac696640 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 0b861f90d02a5..6d1c4be8df35b 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)?;