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#[derive(Parser)]
18pub struct CreationCodeArgs {
19 contract: Address,
21
22 #[arg(long)]
25 abi_path: Option<String>,
26
27 #[arg(long)]
29 disassemble: bool,
30
31 #[arg(long, conflicts_with = "only_args")]
33 without_args: bool,
34
35 #[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 chain = provider.get_chain_id().await?;
54 etherscan.chain = Some(chain.into());
55
56 let bytecode = fetch_creation_code_from_etherscan(contract, ðerscan, provider).await?;
57
58 let bytecode = parse_code_output(
59 bytecode,
60 contract,
61 ðerscan,
62 abi_path.as_deref(),
63 without_args,
64 only_args,
65 )
66 .await?;
67
68 if disassemble {
69 let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
70 } else {
71 let _ = sh_println!("{bytecode}");
72 }
73
74 Ok(())
75 }
76}
77
78pub async fn parse_code_output(
83 bytecode: Bytes,
84 contract: Address,
85 etherscan: &EtherscanOpts,
86 abi_path: Option<&str>,
87 without_args: bool,
88 only_args: bool,
89) -> Result<Bytes> {
90 if !without_args && !only_args {
91 return Ok(bytecode);
92 }
93
94 let abi = if let Some(abi_path) = abi_path {
95 load_abi_from_file(abi_path, None)?
96 } else {
97 fetch_abi_from_etherscan(contract, etherscan).await?
98 };
99
100 let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?;
101 let (abi, _) = abi;
102
103 if abi.constructor.is_none() {
104 if only_args {
105 return Err(eyre!("No constructor found."));
106 }
107 return Ok(bytecode);
108 }
109
110 let constructor = abi.constructor.unwrap();
111 if constructor.inputs.is_empty() {
112 if only_args {
113 return Err(eyre!("No constructor arguments found."));
114 }
115 return Ok(bytecode);
116 }
117
118 let args_size = constructor.inputs.len() * 32;
119
120 let bytecode = if without_args {
121 Bytes::from(bytecode[..bytecode.len() - args_size].to_vec())
122 } else if only_args {
123 Bytes::from(bytecode[bytecode.len() - args_size..].to_vec())
124 } else {
125 unreachable!();
126 };
127
128 Ok(bytecode)
129}
130
131pub async fn fetch_creation_code_from_etherscan(
133 contract: Address,
134 etherscan: &EtherscanOpts,
135 provider: RetryProvider,
136) -> Result<Bytes> {
137 let config = etherscan.load_config()?;
138 let chain = config.chain.unwrap_or_default();
139 let api_version = config.get_etherscan_api_version(Some(chain));
140 let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
141 let client = Client::new_with_api_version(chain, api_key, api_version)?;
142 let creation_data = client.contract_creation_data(contract).await?;
143 let creation_tx_hash = creation_data.transaction_hash;
144 let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?;
145 let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?;
146
147 let bytecode = if tx_data.to().is_none() {
148 tx_data.input().clone()
150 } else {
151 let mut creation_bytecode = None;
154
155 let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| {
156 eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e)
157 })?;
158
159 for trace in traces {
160 if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result {
161 if address == contract {
162 creation_bytecode = match trace.trace.action {
163 Action::Create(CreateAction { init, .. }) => Some(init),
164 _ => None,
165 };
166 }
167 }
168 }
169
170 creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))?
171 };
172
173 Ok(bytecode)
174}