cast/cmd/
creation_code.rs

1use super::interface::load_abi_from_file;
2use crate::SimpleCast;
3use alloy_consensus::Transaction;
4use alloy_primitives::{Address, Bytes};
5use alloy_provider::{Provider, ext::TraceApi};
6use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput};
7use clap::{Parser, command};
8use eyre::{OptionExt, Result, eyre};
9use foundry_block_explorers::Client;
10use foundry_cli::{
11    opts::{EtherscanOpts, RpcOpts},
12    utils::{self, LoadConfig},
13};
14use foundry_common::{abi::fetch_abi_from_etherscan, provider::RetryProvider};
15use foundry_config::Config;
16
17foundry_config::impl_figment_convert!(CreationCodeArgs, etherscan, rpc);
18
19/// CLI arguments for `cast creation-code`.
20#[derive(Parser)]
21pub struct CreationCodeArgs {
22    /// An Ethereum address, for which the bytecode will be fetched.
23    contract: Address,
24
25    /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is
26    /// not verified on Etherscan.
27    #[arg(long)]
28    abi_path: Option<String>,
29
30    /// Disassemble bytecodes into individual opcodes.
31    #[arg(long)]
32    disassemble: bool,
33
34    /// Return creation bytecode without constructor arguments appended.
35    #[arg(long, conflicts_with = "only_args")]
36    without_args: bool,
37
38    /// Return only constructor arguments.
39    #[arg(long)]
40    only_args: bool,
41
42    #[command(flatten)]
43    etherscan: EtherscanOpts,
44
45    #[command(flatten)]
46    rpc: RpcOpts,
47}
48
49impl CreationCodeArgs {
50    pub async fn run(self) -> Result<()> {
51        let mut config = self.load_config()?;
52
53        let Self { contract, disassemble, without_args, only_args, abi_path, etherscan: _, rpc: _ } =
54            self;
55
56        let provider = utils::get_provider(&config)?;
57        let chain = provider.get_chain_id().await?;
58        config.chain = Some(chain.into());
59
60        let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?;
61
62        let bytecode = parse_code_output(
63            bytecode,
64            contract,
65            &config,
66            abi_path.as_deref(),
67            without_args,
68            only_args,
69        )
70        .await?;
71
72        if disassemble {
73            let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
74        } else {
75            let _ = sh_println!("{bytecode}");
76        }
77
78        Ok(())
79    }
80}
81
82/// Parses the creation bytecode and returns one of the following:
83/// - The complete bytecode
84/// - The bytecode without constructor arguments
85/// - Only the constructor arguments
86pub async fn parse_code_output(
87    bytecode: Bytes,
88    contract: Address,
89    config: &Config,
90    abi_path: Option<&str>,
91    without_args: bool,
92    only_args: bool,
93) -> Result<Bytes> {
94    if !without_args && !only_args {
95        return Ok(bytecode);
96    }
97
98    let abi = if let Some(abi_path) = abi_path {
99        load_abi_from_file(abi_path, None)?
100    } else {
101        fetch_abi_from_etherscan(contract, config).await?
102    };
103
104    let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?;
105    let (abi, _) = abi;
106
107    if abi.constructor.is_none() {
108        if only_args {
109            return Err(eyre!("No constructor found."));
110        }
111        return Ok(bytecode);
112    }
113
114    let constructor = abi.constructor.unwrap();
115    if constructor.inputs.is_empty() {
116        if only_args {
117            return Err(eyre!("No constructor arguments found."));
118        }
119        return Ok(bytecode);
120    }
121
122    let args_size = constructor.inputs.len() * 32;
123
124    let bytecode = if without_args {
125        Bytes::from(bytecode[..bytecode.len() - args_size].to_vec())
126    } else if only_args {
127        Bytes::from(bytecode[bytecode.len() - args_size..].to_vec())
128    } else {
129        unreachable!();
130    };
131
132    Ok(bytecode)
133}
134
135/// Fetches the creation code of a contract from Etherscan and RPC.
136pub async fn fetch_creation_code_from_etherscan(
137    contract: Address,
138    config: &Config,
139    provider: RetryProvider,
140) -> Result<Bytes> {
141    let chain = config.chain.unwrap_or_default();
142    let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
143    let client = Client::new(chain, api_key)?;
144    let creation_data = client.contract_creation_data(contract).await?;
145    let creation_tx_hash = creation_data.transaction_hash;
146    let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?;
147    let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?;
148
149    let bytecode = if tx_data.to().is_none() {
150        // Contract was created using a standard transaction
151        tx_data.input().clone()
152    } else {
153        // Contract was created using a factory pattern or create2
154        // Extract creation code from tx traces
155        let mut creation_bytecode = None;
156
157        let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| {
158            eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e)
159        })?;
160
161        for trace in traces {
162            if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result
163                && address == contract
164            {
165                creation_bytecode = match trace.trace.action {
166                    Action::Create(CreateAction { init, .. }) => Some(init),
167                    _ => None,
168                };
169            }
170        }
171
172        creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))?
173    };
174
175    Ok(bytecode)
176}