Skip to main content

cast/cmd/
creation_code.rs

1use super::interface::load_abi_from_file;
2use alloy_consensus::Transaction;
3use alloy_dyn_abi::{DynSolType, Specifier};
4use alloy_json_abi::{Constructor, JsonAbi};
5use alloy_primitives::{Address, Bytes};
6use alloy_provider::{Provider, 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        let Self { contract, disassemble, without_args, only_args, abi_path, .. } = self;
52
53        let bytecode = fetch_creation_code(&mut config, contract).await?;
54        let bytecode = parse_code_output(
55            bytecode,
56            contract,
57            &config,
58            abi_path.as_deref(),
59            without_args,
60            only_args,
61        )
62        .await?;
63
64        if disassemble {
65            sh_println!("{}", super::disassemble(&bytecode)?)?;
66        } else {
67            sh_println!("{bytecode}")?;
68        }
69        Ok(())
70    }
71}
72
73/// Parses the creation bytecode and returns one of the following:
74/// - The complete bytecode
75/// - The bytecode without constructor arguments
76/// - Only the constructor arguments
77pub(crate) async fn parse_code_output(
78    bytecode: Bytes,
79    contract: Address,
80    config: &Config,
81    abi_path: Option<&str>,
82    without_args: bool,
83    only_args: bool,
84) -> Result<Bytes> {
85    if !without_args && !only_args {
86        return Ok(bytecode);
87    }
88
89    let abi = load_abi(contract, config, abi_path).await?;
90    let constructor = match constructor_with_args(&abi) {
91        Ok(constructor) => constructor,
92        Err(e) if only_args => return Err(e),
93        Err(_) => return Ok(bytecode),
94    };
95    let split = constructor_args_offset(constructor, &bytecode)?;
96    Ok(if without_args { bytecode.slice(..split) } else { bytecode.slice(split..) })
97}
98
99/// Loads the ABI of `contract` from `abi_path`, or from Etherscan when no path is given.
100pub(crate) async fn load_abi(
101    contract: Address,
102    config: &Config,
103    abi_path: Option<&str>,
104) -> Result<JsonAbi> {
105    if let Some(path) = abi_path {
106        return load_abi_from_file(path);
107    }
108    let abis = fetch_abi_from_etherscan(contract, config, false).await?;
109    abis.into_iter().next().map(|(abi, _)| abi).ok_or_eyre("No ABI found.")
110}
111
112/// Returns the constructor of `abi`, failing if there is none or it takes no arguments.
113pub(crate) fn constructor_with_args(abi: &JsonAbi) -> Result<&Constructor> {
114    let constructor = abi.constructor().ok_or_else(|| eyre!("No constructor found."))?;
115    if constructor.inputs.is_empty() {
116        eyre::bail!("No constructor arguments found.");
117    }
118    Ok(constructor)
119}
120
121/// Returns the offset in `bytecode` at which the ABI-encoded constructor arguments start.
122///
123/// The arguments are appended to the init code, so their encoding is a word-aligned suffix of the
124/// creation bytecode. Static arguments occupy a fixed number of words, but dynamic ones carry
125/// their own lengths, so the suffix is searched for a unique candidate that decodes as the
126/// constructor inputs and encodes back to the same bytes. Nested ABI data can make this ambiguous.
127pub(crate) fn constructor_args_offset(constructor: &Constructor, bytecode: &[u8]) -> Result<usize> {
128    let types =
129        constructor.inputs.iter().map(|input| input.resolve()).collect::<Result<Vec<_>, _>>()?;
130    let min_words = types.iter().map(DynSolType::minimum_words).sum::<usize>();
131    let max_offset = bytecode.len().checked_sub(min_words * 32).ok_or_else(|| {
132        eyre!(
133            "Invalid creation bytecode length: have {} bytes, need at least {} for {} constructor inputs",
134            bytecode.len(),
135            min_words * 32,
136            constructor.inputs.len()
137        )
138    })?;
139    if !types.iter().any(DynSolType::is_dynamic) {
140        return Ok(max_offset);
141    }
142
143    let tuple = DynSolType::Tuple(types);
144    let mut candidates = (min_words..=bytecode.len() / 32)
145        .map(|words| bytecode.len() - words * 32)
146        .filter(|&offset| {
147            let args = &bytecode[offset..];
148            tuple.abi_decode_params(args).is_ok_and(|value| value.abi_encode_params() == args)
149        });
150    let offset = candidates.next().ok_or_else(|| {
151        eyre!("Could not find constructor arguments matching the ABI in the creation bytecode")
152    })?;
153    if candidates.next().is_some() {
154        eyre::bail!(
155            "Multiple constructor argument suffixes match the ABI in the creation bytecode"
156        );
157    }
158    Ok(offset)
159}
160
161/// Connects to the configured RPC, pins `config.chain` to it, and fetches the creation code of
162/// `contract` using its Etherscan creation transaction.
163pub(crate) async fn fetch_creation_code(config: &mut Config, contract: Address) -> Result<Bytes> {
164    let provider = utils::get_provider(config)?;
165    let chain = provider.get_chain_id().await?.into();
166    config.chain = Some(chain);
167
168    let client = config
169        .get_etherscan_config_with_chain(Some(chain))?
170        .ok_or_else(|| eyre!("No Etherscan API key configured for chain {chain}"))?
171        .into_client_with_no_proxy(config.eth_rpc_no_proxy)?;
172    let creation_tx_hash = client.contract_creation_data(contract).await?.transaction_hash;
173    let tx_data = provider
174        .get_transaction_by_hash(creation_tx_hash)
175        .await?
176        .ok_or_eyre("Could not find creation tx data.")?;
177
178    if tx_data.to().is_none() {
179        // Contract was created using a standard transaction.
180        return Ok(tx_data.input().clone());
181    }
182
183    // Contract was created using a factory pattern or create2: extract the init code from the
184    // creation trace.
185    let traces = provider
186        .trace_transaction(creation_tx_hash)
187        .await
188        .map_err(|e| eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e))?;
189    traces
190        .into_iter()
191        .filter(|trace| {
192            matches!(&trace.trace.result, Some(TraceOutput::Create(CreateOutput { address, .. })) if *address == contract)
193        })
194        .filter_map(|trace| match trace.trace.action {
195            Action::Create(CreateAction { init, .. }) => Some(init),
196            _ => None,
197        })
198        .last()
199        .ok_or_else(|| eyre!("Could not find contract creation trace."))
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use alloy_dyn_abi::DynSolValue;
206    use alloy_primitives::U256;
207    use std::io::Write;
208
209    fn constructor(inputs: &str) -> Constructor {
210        let abi: JsonAbi =
211            serde_json::from_str(&format!(r#"[{{"type":"constructor","inputs":[{inputs}]}}]"#))
212                .unwrap();
213        abi.constructor().unwrap().clone()
214    }
215
216    #[test]
217    fn splits_static_constructor_args() {
218        let constructor = constructor(
219            r#"{"name":"owner","type":"address"},{"name":"limits","type":"uint256[2]"}"#,
220        );
221        let args = DynSolValue::Tuple(vec![
222            DynSolValue::Address(Address::repeat_byte(0x11)),
223            DynSolValue::FixedArray(vec![
224                DynSolValue::Uint(U256::ONE, 256),
225                DynSolValue::Uint(U256::from(2), 256),
226            ]),
227        ])
228        .abi_encode_params();
229        let init_code = vec![0xfe; 77];
230        let bytecode = [init_code.as_slice(), args.as_slice()].concat();
231
232        assert_eq!(constructor_args_offset(&constructor, &bytecode).unwrap(), init_code.len());
233    }
234
235    #[test]
236    fn splits_dynamic_constructor_args() {
237        let constructor = constructor(
238            r#"{"name":"name","type":"string"},{"name":"supply","type":"uint256"},{"name":"admins","type":"address[]"}"#,
239        );
240        let args = DynSolValue::Tuple(vec![
241            DynSolValue::String("Creation code with a name longer than one word".into()),
242            DynSolValue::Uint(U256::from(42), 256),
243            DynSolValue::Array(vec![
244                DynSolValue::Address(Address::repeat_byte(0x11)),
245                DynSolValue::Address(Address::repeat_byte(0x22)),
246            ]),
247        ])
248        .abi_encode_params();
249        let init_code = vec![0xfe; 77];
250        let bytecode = [init_code.as_slice(), args.as_slice()].concat();
251
252        let offset = constructor_args_offset(&constructor, &bytecode).unwrap();
253        assert_eq!(offset, init_code.len());
254        assert_eq!(&bytecode[offset..], args.as_slice());
255    }
256
257    #[test]
258    fn rejects_ambiguous_dynamic_constructor_args() {
259        let constructor = constructor(r#"{"name":"data","type":"bytes"}"#);
260        let nested =
261            DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![0x42; 32])]).abi_encode_params();
262        let args = DynSolValue::Tuple(vec![DynSolValue::Bytes(nested)]).abi_encode_params();
263        for init_len in [64, 77] {
264            let bytecode = [vec![0xfe; init_len], args.clone()].concat();
265            assert_eq!(
266                constructor_args_offset(&constructor, &bytecode).unwrap_err().to_string(),
267                "Multiple constructor argument suffixes match the ABI in the creation bytecode"
268            );
269        }
270    }
271
272    #[test]
273    fn rejects_creation_code_without_matching_args() {
274        let constructor = constructor(r#"{"name":"name","type":"string"}"#);
275        let bytecode = vec![0xfe; 100];
276
277        let err = constructor_args_offset(&constructor, &bytecode).unwrap_err();
278        assert_eq!(
279            err.to_string(),
280            "Could not find constructor arguments matching the ABI in the creation bytecode"
281        );
282    }
283
284    #[tokio::test]
285    async fn rejects_creation_code_shorter_than_constructor_head() {
286        let mut abi = tempfile::NamedTempFile::new().unwrap();
287        write!(
288            abi,
289            r#"{{"abi":[{{"type":"constructor","inputs":[{{"name":"value","type":"uint256"}}]}}]}}"#
290        )
291        .unwrap();
292
293        for (without_args, only_args) in [(true, false), (false, true)] {
294            let err = parse_code_output(
295                Bytes::from(vec![0; 31]),
296                Address::ZERO,
297                &Config::default(),
298                abi.path().to_str(),
299                without_args,
300                only_args,
301            )
302            .await
303            .unwrap_err();
304
305            assert_eq!(
306                err.to_string(),
307                "Invalid creation bytecode length: have 31 bytes, need at least 32 for 1 constructor inputs"
308            );
309        }
310    }
311}