cast/cmd/
creation_code.rs

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