cast/cmd/
creation_code.rs1use 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#[derive(Parser)]
19pub struct CreationCodeArgs {
20 contract: Address,
22
23 #[arg(long)]
26 abi_path: Option<String>,
27
28 #[arg(long)]
30 disassemble: bool,
31
32 #[arg(long, conflicts_with = "only_args")]
34 without_args: bool,
35
36 #[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
72pub(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
98pub(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
111pub(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
120pub(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
133pub(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 return Ok(tx_data.input().clone());
153 }
154
155 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}