Skip to main content

cast/cmd/
creation_code.rs

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