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 api_key = etherscan.key().unwrap_or_default();
54        let chain = provider.get_chain_id().await?;
55        etherscan.chain = Some(chain.into());
56        let client = Client::new(chain.into(), api_key)?;
57
58        let bytecode = fetch_creation_code(contract, client, provider).await?;
59
60        let bytecode = parse_code_output(
61            bytecode,
62            contract,
63            &etherscan,
64            abi_path.as_deref(),
65            without_args,
66            only_args,
67        )
68        .await?;
69
70        if disassemble {
71            let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
72        } else {
73            let _ = sh_println!("{bytecode}");
74        }
75
76        Ok(())
77    }
78}
79
80/// Parses the creation bytecode and returns one of the following:
81/// - The complete bytecode
82/// - The bytecode without constructor arguments
83/// - Only the constructor arguments
84pub async fn parse_code_output(
85    bytecode: Bytes,
86    contract: Address,
87    etherscan: &EtherscanOpts,
88    abi_path: Option<&str>,
89    without_args: bool,
90    only_args: bool,
91) -> Result<Bytes> {
92    if !without_args && !only_args {
93        return Ok(bytecode);
94    }
95
96    let abi = if let Some(abi_path) = abi_path {
97        load_abi_from_file(abi_path, None)?
98    } else {
99        fetch_abi_from_etherscan(contract, etherscan).await?
100    };
101
102    let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?;
103    let (abi, _) = abi;
104
105    if abi.constructor.is_none() {
106        if only_args {
107            return Err(eyre!("No constructor found."));
108        }
109        return Ok(bytecode);
110    }
111
112    let constructor = abi.constructor.unwrap();
113    if constructor.inputs.is_empty() {
114        if only_args {
115            return Err(eyre!("No constructor arguments found."));
116        }
117        return Ok(bytecode);
118    }
119
120    let args_size = constructor.inputs.len() * 32;
121
122    let bytecode = if without_args {
123        Bytes::from(bytecode[..bytecode.len() - args_size].to_vec())
124    } else if only_args {
125        Bytes::from(bytecode[bytecode.len() - args_size..].to_vec())
126    } else {
127        unreachable!();
128    };
129
130    Ok(bytecode)
131}
132
133/// Fetches the creation code of a contract from Etherscan and RPC.
134pub async fn fetch_creation_code(
135    contract: Address,
136    client: Client,
137    provider: RetryProvider,
138) -> Result<Bytes> {
139    let creation_data = client.contract_creation_data(contract).await?;
140    let creation_tx_hash = creation_data.transaction_hash;
141    let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?;
142    let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?;
143
144    let bytecode = if tx_data.to().is_none() {
145        // Contract was created using a standard transaction
146        tx_data.input().clone()
147    } else {
148        // Contract was created using a factory pattern or create2
149        // Extract creation code from tx traces
150        let mut creation_bytecode = None;
151
152        let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| {
153            eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e)
154        })?;
155
156        for trace in traces {
157            if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result {
158                if address == contract {
159                    creation_bytecode = match trace.trace.action {
160                        Action::Create(CreateAction { init, .. }) => Some(init),
161                        _ => None,
162                    };
163                }
164            }
165        }
166
167        creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))?
168    };
169
170    Ok(bytecode)
171}