cast/cmd/
creation_code.rs1use super::interface::load_abi_from_file;
2use crate::SimpleCast;
3use alloy_consensus::Transaction;
4use alloy_primitives::{Address, Bytes};
5use alloy_provider::{Provider, ext::TraceApi};
6use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput};
7use clap::{Parser, command};
8use eyre::{OptionExt, Result, eyre};
9use foundry_block_explorers::Client;
10use foundry_cli::{
11 opts::{EtherscanOpts, RpcOpts},
12 utils::{self, LoadConfig},
13};
14use foundry_common::{abi::fetch_abi_from_etherscan, provider::RetryProvider};
15use foundry_config::Config;
16
17foundry_config::impl_figment_convert!(CreationCodeArgs, etherscan, rpc);
18
19#[derive(Parser)]
21pub struct CreationCodeArgs {
22 contract: Address,
24
25 #[arg(long)]
28 abi_path: Option<String>,
29
30 #[arg(long)]
32 disassemble: bool,
33
34 #[arg(long, conflicts_with = "only_args")]
36 without_args: bool,
37
38 #[arg(long)]
40 only_args: bool,
41
42 #[command(flatten)]
43 etherscan: EtherscanOpts,
44
45 #[command(flatten)]
46 rpc: RpcOpts,
47}
48
49impl CreationCodeArgs {
50 pub async fn run(self) -> Result<()> {
51 let mut config = self.load_config()?;
52
53 let Self { contract, disassemble, without_args, only_args, abi_path, etherscan: _, rpc: _ } =
54 self;
55
56 let provider = utils::get_provider(&config)?;
57 let chain = provider.get_chain_id().await?;
58 config.chain = Some(chain.into());
59
60 let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?;
61
62 let bytecode = parse_code_output(
63 bytecode,
64 contract,
65 &config,
66 abi_path.as_deref(),
67 without_args,
68 only_args,
69 )
70 .await?;
71
72 if disassemble {
73 let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
74 } else {
75 let _ = sh_println!("{bytecode}");
76 }
77
78 Ok(())
79 }
80}
81
82pub async fn parse_code_output(
87 bytecode: Bytes,
88 contract: Address,
89 config: &Config,
90 abi_path: Option<&str>,
91 without_args: bool,
92 only_args: bool,
93) -> Result<Bytes> {
94 if !without_args && !only_args {
95 return Ok(bytecode);
96 }
97
98 let abi = if let Some(abi_path) = abi_path {
99 load_abi_from_file(abi_path, None)?
100 } else {
101 fetch_abi_from_etherscan(contract, config).await?
102 };
103
104 let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?;
105 let (abi, _) = abi;
106
107 if abi.constructor.is_none() {
108 if only_args {
109 return Err(eyre!("No constructor found."));
110 }
111 return Ok(bytecode);
112 }
113
114 let constructor = abi.constructor.unwrap();
115 if constructor.inputs.is_empty() {
116 if only_args {
117 return Err(eyre!("No constructor arguments found."));
118 }
119 return Ok(bytecode);
120 }
121
122 let args_size = constructor.inputs.len() * 32;
123
124 let bytecode = if without_args {
125 Bytes::from(bytecode[..bytecode.len() - args_size].to_vec())
126 } else if only_args {
127 Bytes::from(bytecode[bytecode.len() - args_size..].to_vec())
128 } else {
129 unreachable!();
130 };
131
132 Ok(bytecode)
133}
134
135pub async fn fetch_creation_code_from_etherscan(
137 contract: Address,
138 config: &Config,
139 provider: RetryProvider,
140) -> Result<Bytes> {
141 let chain = config.chain.unwrap_or_default();
142 let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
143 let client = Client::new(chain, api_key)?;
144 let creation_data = client.contract_creation_data(contract).await?;
145 let creation_tx_hash = creation_data.transaction_hash;
146 let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?;
147 let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?;
148
149 let bytecode = if tx_data.to().is_none() {
150 tx_data.input().clone()
152 } else {
153 let mut creation_bytecode = None;
156
157 let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| {
158 eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e)
159 })?;
160
161 for trace in traces {
162 if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result
163 && address == contract
164 {
165 creation_bytecode = match trace.trace.action {
166 Action::Create(CreateAction { init, .. }) => Some(init),
167 _ => None,
168 };
169 }
170 }
171
172 creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))?
173 };
174
175 Ok(bytecode)
176}