Skip to content

Commit

Permalink
feat(cast) add creation-code method [foundry-rs#8973]
Browse files Browse the repository at this point in the history
  • Loading branch information
pawurb committed Oct 4, 2024
1 parent a970b36 commit 08c4a70
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 3 deletions.
10 changes: 7 additions & 3 deletions crates/cast/bin/args.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
Expand Down
98 changes: 98 additions & 0 deletions crates/cast/bin/cmd/creation_code.rs
Original file line number Diff line number Diff line change
@@ -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(&etherscan);
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<RetryProvider>,
) -> Result<Bytes>
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)
}
1 change: 1 addition & 0 deletions crates/cast/bin/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down

0 comments on commit 08c4a70

Please sign in to comment.