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#[derive(Parser)]
20pub struct CreationCodeArgs {
21 contract: Address,
23
24 #[arg(long)]
27 abi_path: Option<String>,
28
29 #[arg(long)]
31 disassemble: bool,
32
33 #[arg(long, conflicts_with = "only_args")]
35 without_args: bool,
36
37 #[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
73pub(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
99pub(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
112pub(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
121pub(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
161pub(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 return Ok(tx_data.input().clone());
181 }
182
183 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}