1use alloy_consensus::Transaction;
2use alloy_network::{AnyNetwork, TransactionResponse};
3use alloy_primitives::{
4 Address, Bytes, U256,
5 map::{HashMap, HashSet},
6};
7use alloy_provider::{Provider, RootProvider};
8use alloy_rpc_types::BlockTransactions;
9use clap::Parser;
10use eyre::{Result, WrapErr};
11use foundry_cli::{
12 opts::{EtherscanOpts, RpcOpts},
13 utils::{TraceResult, handle_traces, init_progress},
14};
15use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_impersonated_tx, is_known_system_sender, shell};
16use foundry_compilers::artifacts::EvmVersion;
17use foundry_config::{
18 Config,
19 figment::{
20 self, Metadata, Profile,
21 value::{Dict, Map},
22 },
23};
24use foundry_evm::{
25 Env,
26 executors::{EvmError, TracingExecutor},
27 opts::EvmOpts,
28 traces::{InternalTraceMode, TraceMode, Traces},
29 utils::configure_tx_env,
30};
31use foundry_evm_core::env::AsEnvMut;
32
33use crate::utils::apply_chain_and_block_specific_env_changes;
34
35#[derive(Clone, Debug, Parser)]
37pub struct RunArgs {
38 tx_hash: String,
40
41 #[arg(long, short)]
43 debug: bool,
44
45 #[arg(long)]
47 decode_internal: bool,
48
49 #[arg(long, short)]
51 trace_printer: bool,
52
53 #[arg(long)]
57 quick: bool,
58
59 #[arg(long, default_value_t = false)]
61 disable_labels: bool,
62
63 #[arg(long, short)]
67 label: Vec<String>,
68
69 #[command(flatten)]
70 etherscan: EtherscanOpts,
71
72 #[command(flatten)]
73 rpc: RpcOpts,
74
75 #[arg(long)]
79 evm_version: Option<EvmVersion>,
80
81 #[arg(long, alias = "cups", value_name = "CUPS")]
87 pub compute_units_per_second: Option<u64>,
88
89 #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")]
95 pub no_rate_limit: bool,
96
97 #[arg(long, alias = "alphanet")]
99 pub odyssey: bool,
100
101 #[arg(long, visible_alias = "la")]
103 pub with_local_artifacts: bool,
104
105 #[arg(long)]
107 pub disable_block_gas_limit: bool,
108}
109
110impl RunArgs {
111 pub async fn run(self) -> Result<()> {
117 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
118 let evm_opts = figment.extract::<EvmOpts>()?;
119 let mut config = Config::from_provider(figment)?.sanitized();
120
121 let label = self.label;
122 let with_local_artifacts = self.with_local_artifacts;
123 let debug = self.debug;
124 let decode_internal = self.decode_internal;
125 let disable_labels = self.disable_labels;
126 let compute_units_per_second =
127 if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second };
128
129 let provider = foundry_cli::utils::get_provider_builder(&config)?
130 .compute_units_per_second_opt(compute_units_per_second)
131 .build()?;
132
133 let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?;
134 let tx = provider
135 .get_transaction_by_hash(tx_hash)
136 .await
137 .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))?
138 .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?;
139
140 if is_known_system_sender(tx.from())
142 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
143 {
144 return Err(eyre::eyre!(
145 "{:?} is a system transaction.\nReplaying system transactions is currently not supported.",
146 tx.tx_hash()
147 ));
148 }
149
150 let tx_block_number =
151 tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?;
152
153 let block = provider.get_block(tx_block_number.into()).full().await?;
155
156 config.fork_block_number = Some(tx_block_number - 1);
158
159 let create2_deployer = evm_opts.create2_deployer;
160 let (mut env, fork, chain, odyssey) =
161 TracingExecutor::get_fork_material(&config, evm_opts).await?;
162 let mut evm_version = self.evm_version;
163
164 env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit;
165 env.evm_env.cfg_env.limit_contract_code_size = None;
166 env.evm_env.block_env.number = U256::from(tx_block_number);
167
168 if let Some(block) = &block {
169 env.evm_env.block_env.timestamp = U256::from(block.header.timestamp);
170 env.evm_env.block_env.beneficiary = block.header.beneficiary;
171 env.evm_env.block_env.difficulty = block.header.difficulty;
172 env.evm_env.block_env.prevrandao = Some(block.header.mix_hash.unwrap_or_default());
173 env.evm_env.block_env.basefee = block.header.base_fee_per_gas.unwrap_or_default();
174 env.evm_env.block_env.gas_limit = block.header.gas_limit;
175
176 if evm_version.is_none() {
179 if block.header.excess_blob_gas.is_some() {
181 evm_version = Some(EvmVersion::Prague);
182 }
183 }
184 apply_chain_and_block_specific_env_changes::<AnyNetwork>(env.as_env_mut(), block);
185 }
186
187 let trace_mode = TraceMode::Call
188 .with_debug(self.debug)
189 .with_decode_internal(if self.decode_internal {
190 InternalTraceMode::Full
191 } else {
192 InternalTraceMode::None
193 })
194 .with_state_changes(shell::verbosity() > 4);
195 let mut executor = TracingExecutor::new(
196 env.clone(),
197 fork,
198 evm_version,
199 trace_mode,
200 odyssey,
201 create2_deployer,
202 None,
203 )?;
204 let mut env = Env::new_with_spec_id(
205 env.evm_env.cfg_env.clone(),
206 env.evm_env.block_env.clone(),
207 env.tx.clone(),
208 executor.spec_id(),
209 );
210
211 if !self.quick {
213 if !shell::is_json() {
214 sh_println!("Executing previous transactions from the block.")?;
215 }
216
217 if let Some(block) = block {
218 let pb = init_progress(block.transactions.len() as u64, "tx");
219 pb.set_position(0);
220
221 let BlockTransactions::Full(ref txs) = block.transactions else {
222 return Err(eyre::eyre!("Could not get block txs"));
223 };
224
225 for (index, tx) in txs.iter().enumerate() {
226 if is_known_system_sender(tx.from())
230 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
231 {
232 pb.set_position((index + 1) as u64);
233 continue;
234 }
235 if tx.tx_hash() == tx_hash {
236 break;
237 }
238
239 configure_tx_env(&mut env.as_env_mut(), &tx.inner);
240
241 env.evm_env.cfg_env.disable_balance_check = true;
242
243 if let Some(to) = Transaction::to(tx) {
244 trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction");
245 executor.transact_with_env(env.clone()).wrap_err_with(|| {
246 format!(
247 "Failed to execute transaction: {:?} in block {}",
248 tx.tx_hash(),
249 env.evm_env.block_env.number
250 )
251 })?;
252 } else {
253 trace!(tx=?tx.tx_hash(), "executing previous create transaction");
254 if let Err(error) = executor.deploy_with_env(env.clone(), None) {
255 match error {
256 EvmError::Execution(_) => (),
258 error => {
259 return Err(error).wrap_err_with(|| {
260 format!(
261 "Failed to deploy transaction: {:?} in block {}",
262 tx.tx_hash(),
263 env.evm_env.block_env.number
264 )
265 });
266 }
267 }
268 }
269 }
270
271 pb.set_position((index + 1) as u64);
272 }
273 }
274 }
275
276 let result = {
278 executor.set_trace_printer(self.trace_printer);
279
280 configure_tx_env(&mut env.as_env_mut(), &tx.inner);
281 if is_impersonated_tx(tx.inner.inner.inner()) {
282 env.evm_env.cfg_env.disable_balance_check = true;
283 }
284
285 if let Some(to) = Transaction::to(&tx) {
286 trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction");
287 TraceResult::try_from(executor.transact_with_env(env))?
288 } else {
289 trace!(tx=?tx.tx_hash(), "executing create transaction");
290 TraceResult::try_from(executor.deploy_with_env(env, None))?
291 }
292 };
293
294 let contracts_bytecode = fetch_contracts_bytecode_from_trace(&provider, &result).await?;
295 handle_traces(
296 result,
297 &config,
298 chain,
299 &contracts_bytecode,
300 label,
301 with_local_artifacts,
302 debug,
303 decode_internal,
304 disable_labels,
305 )
306 .await?;
307
308 Ok(())
309 }
310}
311
312pub async fn fetch_contracts_bytecode_from_trace(
313 provider: &RootProvider<AnyNetwork>,
314 result: &TraceResult,
315) -> Result<HashMap<Address, Bytes>> {
316 let mut contracts_bytecode = HashMap::default();
317 if let Some(ref traces) = result.traces {
318 let addresses = gather_trace_addresses(traces);
319 let results = futures::future::join_all(addresses.into_iter().map(async |a| {
320 (
321 a,
322 provider.get_code_at(a).await.unwrap_or_else(|e| {
323 sh_warn!("Failed to fetch code for {a:?}: {e:?}").ok();
324 Bytes::new()
325 }),
326 )
327 }))
328 .await;
329 for (address, code) in results {
330 if !code.is_empty() {
331 contracts_bytecode.insert(address, code);
332 }
333 }
334 }
335 Ok(contracts_bytecode)
336}
337
338fn gather_trace_addresses(traces: &Traces) -> HashSet<Address> {
339 let mut addresses = HashSet::default();
340 for (_, trace) in traces {
341 for node in trace.arena.nodes() {
342 if !node.trace.address.is_zero() {
343 addresses.insert(node.trace.address);
344 }
345 if !node.trace.caller.is_zero() {
346 addresses.insert(node.trace.caller);
347 }
348 }
349 }
350 addresses
351}
352
353impl figment::Provider for RunArgs {
354 fn metadata(&self) -> Metadata {
355 Metadata::named("RunArgs")
356 }
357
358 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
359 let mut map = Map::new();
360
361 if self.odyssey {
362 map.insert("odyssey".into(), self.odyssey.into());
363 }
364
365 if let Some(api_key) = &self.etherscan.key {
366 map.insert("etherscan_api_key".into(), api_key.as_str().into());
367 }
368
369 if let Some(api_version) = &self.etherscan.api_version {
370 map.insert("etherscan_api_version".into(), api_version.to_string().into());
371 }
372
373 if let Some(evm_version) = self.evm_version {
374 map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
375 }
376
377 Ok(Map::from([(Config::selected_profile(), map)]))
378 }
379}