cast/cmd/
creation_code.rs1use super::interface::load_abi_from_file;
2use crate::SimpleCast;
3use alloy_consensus::Transaction;
4use alloy_network::AnyNetwork;
5use alloy_primitives::{Address, Bytes};
6use alloy_provider::{Provider, RootProvider, 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
52 let Self { contract, disassemble, without_args, only_args, abi_path, etherscan: _, rpc: _ } =
53 self;
54
55 let provider = utils::get_provider(&config)?;
56 let chain = provider.get_chain_id().await?;
57 config.chain = Some(chain.into());
58
59 let bytecode = fetch_creation_code_from_etherscan(contract, &config, provider).await?;
60
61 let bytecode = parse_code_output(
62 bytecode,
63 contract,
64 &config,
65 abi_path.as_deref(),
66 without_args,
67 only_args,
68 )
69 .await?;
70
71 if disassemble {
72 let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?);
73 } else {
74 let _ = sh_println!("{bytecode}");
75 }
76
77 Ok(())
78 }
79}
80
81pub async fn parse_code_output(
86 bytecode: Bytes,
87 contract: Address,
88 config: &Config,
89 abi_path: Option<&str>,
90 without_args: bool,
91 only_args: bool,
92) -> Result<Bytes> {
93 if !without_args && !only_args {
94 return Ok(bytecode);
95 }
96
97 let abi = if let Some(abi_path) = abi_path {
98 load_abi_from_file(abi_path, None)?
99 } else {
100 fetch_abi_from_etherscan(contract, config).await?
101 };
102
103 let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?;
104 let (abi, _) = abi;
105
106 if abi.constructor.is_none() {
107 if only_args {
108 return Err(eyre!("No constructor found."));
109 }
110 return Ok(bytecode);
111 }
112
113 let constructor = abi.constructor.unwrap();
114 if constructor.inputs.is_empty() {
115 if only_args {
116 return Err(eyre!("No constructor arguments found."));
117 }
118 return Ok(bytecode);
119 }
120
121 let args_size = constructor.inputs.len() * 32;
122
123 let bytecode = if without_args {
124 Bytes::from(bytecode[..bytecode.len() - args_size].to_vec())
125 } else if only_args {
126 Bytes::from(bytecode[bytecode.len() - args_size..].to_vec())
127 } else {
128 unreachable!();
129 };
130
131 Ok(bytecode)
132}
133
134pub async fn fetch_creation_code_from_etherscan(
136 contract: Address,
137 config: &Config,
138 provider: RootProvider<AnyNetwork>,
139) -> Result<Bytes> {
140 let chain = config.chain.unwrap_or_default();
141 let client = config
142 .get_etherscan_config_with_chain(Some(chain))?
143 .ok_or_else(|| eyre!("No Etherscan API key configured for chain {chain}"))?
144 .into_client_with_no_proxy(config.eth_rpc_no_proxy)?;
145 let creation_data = client.contract_creation_data(contract).await?;
146 let creation_tx_hash = creation_data.transaction_hash;
147 let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?;
148 let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?;
149
150 let bytecode = if tx_data.to().is_none() {
151 tx_data.input().clone()
153 } else {
154 let mut creation_bytecode = None;
157
158 let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| {
159 eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e)
160 })?;
161
162 for trace in traces {
163 if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result
164 && address == contract
165 {
166 creation_bytecode = match trace.trace.action {
167 Action::Create(CreateAction { init, .. }) => Some(init),
168 _ => None,
169 };
170 }
171 }
172
173 creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))?
174 };
175
176 Ok(bytecode)
177}