Skip to main content

cast/cmd/
creation_code.rs

1use super::interface::load_abi_from_file;
2use alloy_consensus::Transaction;
3use alloy_json_abi::{Constructor, JsonAbi};
4use alloy_primitives::{Address, Bytes};
5use alloy_provider::{Provider, ext::TraceApi};
6use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput};
7use clap::Parser;
8use eyre::{OptionExt, Result, eyre};
9use foundry_cli::{
10    opts::{EtherscanOpts, RpcOpts},
11    utils::{self, LoadConfig, fetch_abi_from_etherscan},
12};
13use foundry_config::Config;
14
15foundry_config::impl_figment_convert!(CreationCodeArgs, etherscan, rpc);
16
17/// CLI arguments for `cast creation-code`.
18#[derive(Parser)]
19pub struct CreationCodeArgs {
20    /// An Ethereum address, for which the bytecode will be fetched.
21    contract: Address,
22
23    /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is
24    /// not verified on Etherscan.
25    #[arg(long)]
26    abi_path: Option<String>,
27
28    /// Disassemble bytecodes into individual opcodes.
29    #[arg(long)]
30    disassemble: bool,
31
32    /// Return creation bytecode without constructor arguments appended.
33    #[arg(long, conflicts_with = "only_args")]
34    without_args: bool,
35
36    /// Return only constructor arguments.
37    #[arg(long)]
38    only_args: bool,
39
40    #[command(flatten)]
41    etherscan: EtherscanOpts,
42
43    #[command(flatten)]
44    rpc: RpcOpts,
45}
46
47impl CreationCodeArgs {
48    pub async fn run(self) -> Result<()> {
49        let mut config = self.load_config()?;
50        let Self { contract, disassemble, without_args, only_args, abi_path, .. } = self;
51
52        let bytecode = fetch_creation_code(&mut config, contract).await?;
53        let bytecode = parse_code_output(
54            bytecode,
55            contract,
56            &config,
57            abi_path.as_deref(),
58            without_args,
59            only_args,
60        )
61        .await?;
62
63        if disassemble {
64            sh_println!("{}", super::disassemble(&bytecode)?)?;
65        } else {
66            sh_println!("{bytecode}")?;
67        }
68        Ok(())
69    }
70}
71
72/// Parses the creation bytecode and returns one of the following:
73/// - The complete bytecode
74/// - The bytecode without constructor arguments
75/// - Only the constructor arguments
76pub(crate) async fn parse_code_output(
77    bytecode: Bytes,
78    contract: Address,
79    config: &Config,
80    abi_path: Option<&str>,
81    without_args: bool,
82    only_args: bool,
83) -> Result<Bytes> {
84    if !without_args && !only_args {
85        return Ok(bytecode);
86    }
87
88    let abi = load_abi(contract, config, abi_path).await?;
89    let constructor = match constructor_with_args(&abi) {
90        Ok(constructor) => constructor,
91        Err(e) if only_args => return Err(e),
92        Err(_) => return Ok(bytecode),
93    };
94    let split = constructor_args_offset(constructor, &bytecode)?;
95    Ok(if without_args { bytecode.slice(..split) } else { bytecode.slice(split..) })
96}
97
98/// Loads the ABI of `contract` from `abi_path`, or from Etherscan when no path is given.
99pub(crate) async fn load_abi(
100    contract: Address,
101    config: &Config,
102    abi_path: Option<&str>,
103) -> Result<JsonAbi> {
104    if let Some(path) = abi_path {
105        return load_abi_from_file(path);
106    }
107    let abis = fetch_abi_from_etherscan(contract, config).await?;
108    abis.into_iter().next().map(|(abi, _)| abi).ok_or_eyre("No ABI found.")
109}
110
111/// Returns the constructor of `abi`, failing if there is none or it takes no arguments.
112pub(crate) fn constructor_with_args(abi: &JsonAbi) -> Result<&Constructor> {
113    let constructor = abi.constructor().ok_or_else(|| eyre!("No constructor found."))?;
114    if constructor.inputs.is_empty() {
115        eyre::bail!("No constructor arguments found.");
116    }
117    Ok(constructor)
118}
119
120/// Returns the offset in `bytecode` at which the ABI-encoded constructor arguments start.
121pub(crate) fn constructor_args_offset(constructor: &Constructor, bytecode: &[u8]) -> Result<usize> {
122    let args_size = constructor.inputs.len() * 32;
123    bytecode.len().checked_sub(args_size).ok_or_else(|| {
124        eyre!(
125            "Invalid creation bytecode length: have {} bytes, need at least {} for {} constructor inputs",
126            bytecode.len(),
127            args_size,
128            constructor.inputs.len()
129        )
130    })
131}
132
133/// Connects to the configured RPC, pins `config.chain` to it, and fetches the creation code of
134/// `contract` using its Etherscan creation transaction.
135pub(crate) async fn fetch_creation_code(config: &mut Config, contract: Address) -> Result<Bytes> {
136    let provider = utils::get_provider(config)?;
137    let chain = provider.get_chain_id().await?.into();
138    config.chain = Some(chain);
139
140    let client = config
141        .get_etherscan_config_with_chain(Some(chain))?
142        .ok_or_else(|| eyre!("No Etherscan API key configured for chain {chain}"))?
143        .into_client_with_no_proxy(config.eth_rpc_no_proxy)?;
144    let creation_tx_hash = client.contract_creation_data(contract).await?.transaction_hash;
145    let tx_data = provider
146        .get_transaction_by_hash(creation_tx_hash)
147        .await?
148        .ok_or_eyre("Could not find creation tx data.")?;
149
150    if tx_data.to().is_none() {
151        // Contract was created using a standard transaction.
152        return Ok(tx_data.input().clone());
153    }
154
155    // Contract was created using a factory pattern or create2: extract the init code from the
156    // creation trace.
157    let traces = provider
158        .trace_transaction(creation_tx_hash)
159        .await
160        .map_err(|e| eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e))?;
161    traces
162        .into_iter()
163        .filter(|trace| {
164            matches!(&trace.trace.result, Some(TraceOutput::Create(CreateOutput { address, .. })) if *address == contract)
165        })
166        .filter_map(|trace| match trace.trace.action {
167            Action::Create(CreateAction { init, .. }) => Some(init),
168            _ => None,
169        })
170        .last()
171        .ok_or_else(|| eyre!("Could not find contract creation trace."))
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use std::io::Write;
178
179    #[tokio::test]
180    async fn rejects_creation_code_shorter_than_constructor_head() {
181        let mut abi = tempfile::NamedTempFile::new().unwrap();
182        write!(
183            abi,
184            r#"{{"abi":[{{"type":"constructor","inputs":[{{"name":"value","type":"uint256"}}]}}]}}"#
185        )
186        .unwrap();
187
188        for (without_args, only_args) in [(true, false), (false, true)] {
189            let err = parse_code_output(
190                Bytes::from(vec![0; 31]),
191                Address::ZERO,
192                &Config::default(),
193                abi.path().to_str(),
194                without_args,
195                only_args,
196            )
197            .await
198            .unwrap_err();
199
200            assert_eq!(
201                err.to_string(),
202                "Invalid creation bytecode length: have 31 bytes, need at least 32 for 1 constructor inputs"
203            );
204        }
205    }
206}