1use super::fetch_code_via_rpc;
2use crate::{
3 debug::{ensure_remote_trace_context_unchanged, handle_traces, resolve_remote_trace_hardfork},
4 rpc_trace::{call_frame_to_arena, is_method_not_found_error, is_missing_state_error},
5 traces::TraceKind,
6 utils::{
7 apply_chain_and_block_specific_env_changes_for_chain,
8 apply_chain_specific_tx_replay_env_changes_for_chain, block_env_from_header,
9 },
10};
11use alloy_consensus::{BlockHeader, Transaction, transaction::SignerRecoverable};
12use alloy_eips::{BlockNumHash, eip7928::compute_block_access_list_hash};
13use alloy_network::{
14 AnyNetwork, AnyRpcBlock, AnyRpcTransaction, AnyTxEnvelope, BlockResponse, Network,
15 ReceiptResponse, TransactionResponse, primitives::HeaderResponse,
16};
17use alloy_primitives::{
18 Address, B256, Bytes, U256,
19 map::{AddressHashMap, AddressSet},
20};
21use alloy_provider::{Provider, ext::DebugApi};
22use alloy_rpc_types::{
23 BlockId, BlockTransactions,
24 trace::geth::{CallConfig, CallFrame, GethDebugTracingOptions, GethTrace, PreStateConfig},
25};
26use alloy_transport::TransportError;
27use clap::Parser;
28use eyre::{Result, WrapErr};
29use foundry_cli::{
30 opts::{EtherscanOpts, RpcOpts, TracingArgs},
31 utils::{TraceResult, init_progress},
32};
33use foundry_common::{
34 SYSTEM_TRANSACTION_TYPE, is_known_system_sender,
35 provider::{ProviderBuilder, RetryProvider},
36 shell,
37};
38use foundry_compilers::artifacts::EvmVersion;
39use foundry_config::{
40 Config, TracingConfig,
41 figment::{
42 self, Metadata, Profile,
43 value::{Dict, Map},
44 },
45};
46use foundry_evm::{
47 core::{
48 FoundryBlock as _,
49 env::FromAnyRpcTransaction as _,
50 evm::{EthEvmNetwork, EvmEnvFor, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor},
51 },
52 executors::{Executor, ExecutorBuilder, TracingExecutor},
53 hardforks::FoundryHardfork,
54 opts::EvmOpts,
55 traces::{InternalTraceMode, SparsedTraceArena, TraceContext, TraceRequirements},
56};
57use foundry_evm_networks::NetworkConfigs;
58use futures::TryFutureExt;
59use revm::{
60 DatabaseRef,
61 context::Block,
62 primitives::hardfork::SpecId,
63 state::bal::{Bal, BlockAccessIndex},
64};
65use std::sync::Arc;
66
67#[cfg(feature = "base")]
68use foundry_evm::core::evm::BaseEvmNetwork;
69
70#[cfg(feature = "monad")]
71use foundry_evm::core::evm::{BlockContext, ChainFor, MonadEvmNetwork};
72
73#[cfg(feature = "optimism")]
74use foundry_evm::core::evm::OpEvmNetwork;
75
76#[derive(Clone, Debug, Parser)]
78pub struct RunArgs {
79 tx_hash: String,
81
82 #[arg(long, short)]
84 debug: bool,
85
86 #[arg(long, short)]
88 trace_printer: bool,
89
90 #[arg(long)]
94 quick: bool,
95
96 #[arg(long)]
102 no_bal: bool,
103
104 #[arg(long, alias = "sys")]
106 replay_system_txes: bool,
107
108 #[arg(long, default_value_t = false)]
115 prestate_tracer: bool,
116
117 #[arg(
128 long,
129 default_value_t = false,
130 conflicts_with_all = ["debug", "decode_internal", "trace_printer", "quick", "no_bal", "prestate_tracer", "evm_version"]
131 )]
132 debug_trace_transaction: bool,
133
134 #[command(flatten)]
135 tracing: TracingArgs,
136
137 #[arg(short = 'l', value_name = "ADDRESS:LABEL", hide = true)]
139 legacy_labels: Vec<String>,
140
141 #[command(flatten)]
142 etherscan: EtherscanOpts,
143
144 #[command(flatten)]
145 rpc: RpcOpts,
146
147 #[arg(long)]
151 evm_version: Option<EvmVersion>,
152
153 #[arg(long, visible_alias = "la")]
155 pub with_local_artifacts: bool,
156
157 #[arg(long)]
161 pub disable_block_gas_limit: bool,
162
163 #[arg(long)]
165 pub enable_tx_gas_limit: bool,
166}
167
168struct TargetFetch {
171 tx: AnyRpcTransaction,
172 provider: RetryProvider,
173 compute_units_per_second: Option<u64>,
174}
175
176#[cfg(feature = "monad")]
178struct MonadPrepared {
179 tx_block_number: u64,
180 compute_units_per_second: Option<u64>,
181}
182
183struct PreparedRun<FEN: FoundryEvmNetwork> {
186 args: RunArgs,
187 config: Box<Config>,
188 tracing: TracingConfig,
189 tx: AnyRpcTransaction,
190 block: Option<AnyRpcBlock>,
191 evm_env: EvmEnvFor<FEN>,
192 executor: TracingExecutor<FEN>,
193 trace_context: TraceContext,
194 prestate_applied: bool,
195 block_access_list: Option<Arc<Bal>>,
197 #[cfg(feature = "monad")]
198 monad: MonadPrepared,
199}
200
201impl RunArgs {
202 fn resolve_tracing(&self, config: &TracingConfig, verbosity: u8) -> TracingConfig {
203 if self.debug_trace_transaction {
204 self.tracing.resolve_call_tracer(config, verbosity)
205 } else {
206 self.tracing.resolve(config, verbosity)
207 }
208 }
209
210 fn configure_tracing(&mut self, config: &mut Config, evm_opts: &EvmOpts) -> TracingConfig {
213 config.networks = evm_opts.networks;
214 self.tracing.labels.append(&mut self.legacy_labels);
215 config.tracing = self.resolve_tracing(&config.tracing, shell::verbosity());
216 config.tracing.clone()
217 }
218
219 pub async fn run(self) -> Result<()> {
226 let figment = self.rpc.clone().into_figment(self.with_local_artifacts).merge(&self);
227 let (mut config, mut evm_opts) = super::load_cast_config_and_evm_opts(figment)?;
228 if config.eth_rpc_url.is_none()
229 && let Some(chain) = self.etherscan.chain
230 {
231 let alias = chain.to_string();
232 if config.rpc_endpoints.contains_key(&alias) {
233 config.eth_rpc_url = Some(alias);
234 }
235 }
236 evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned());
237
238 evm_opts.infer_network_from_fork().await?;
240
241 if self.debug_trace_transaction {
242 return self.remote_trace(config, evm_opts).await;
243 }
244
245 if evm_opts.networks.is_tempo() {
246 return self
247 .run_with_evm(config, evm_opts, ExecutorBuilder::<TempoEvmNetwork>::new())
248 .await;
249 }
250
251 #[cfg(feature = "base")]
252 if evm_opts.networks.is_base() {
253 return self
254 .run_with_evm(config, evm_opts, ExecutorBuilder::<BaseEvmNetwork>::new())
255 .await;
256 }
257
258 #[cfg(feature = "monad")]
259 if evm_opts.networks.is_monad() {
260 let target = self.fetch_target(&config).await?;
261 let mut run = self
262 .prepare::<MonadEvmNetwork>(
263 config,
264 evm_opts,
265 target,
266 ExecutorBuilder::<MonadEvmNetwork>::new(),
267 )
268 .await?;
269 let result = run.execute_monad().await?;
270 return run.finish(result).await;
271 }
272
273 #[cfg(feature = "optimism")]
274 if evm_opts.networks.is_optimism() {
275 return self
276 .run_with_evm(config, evm_opts, ExecutorBuilder::<OpEvmNetwork>::new())
277 .await;
278 }
279
280 self.run_with_evm(config, evm_opts, ExecutorBuilder::<EthEvmNetwork>::new()).await
281 }
282
283 async fn run_with_evm<FEN: FoundryEvmNetwork>(
284 self,
285 config: Box<Config>,
286 evm_opts: EvmOpts,
287 executor_builder: ExecutorBuilder<FEN>,
288 ) -> Result<()> {
289 let target = self.fetch_target(&config).await?;
290 if is_system_transaction(&target.tx) && !self.replay_system_txes {
291 eyre::bail!(
292 "{:?} is a system transaction.\nReplaying system transactions is currently not supported.",
293 target.tx.tx_hash()
294 );
295 }
296 let mut run = self.prepare::<FEN>(config, evm_opts, target, executor_builder).await?;
297 let result = run.execute_ordinary()?;
298 run.finish(result).await
299 }
300
301 async fn fetch_target(&self, config: &Config) -> Result<TargetFetch> {
306 let compute_units_per_second = if self.rpc.common.no_rpc_rate_limit {
307 Some(u64::MAX)
308 } else {
309 self.rpc.common.compute_units_per_second
310 };
311 let provider = ProviderBuilder::<AnyNetwork>::from_config(config)?
312 .compute_units_per_second_opt(compute_units_per_second)
313 .build()?;
314 let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?;
315 let tx = provider
316 .get_transaction_by_hash(tx_hash)
317 .await
318 .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))?
319 .ok_or_else(|| eyre::eyre!("tx not found: {tx_hash:?}"))?;
320 Ok(TargetFetch { tx, provider, compute_units_per_second })
321 }
322
323 async fn remote_trace(mut self, mut config: Box<Config>, evm_opts: EvmOpts) -> Result<()> {
328 let TargetFetch { tx, provider, .. } = self.fetch_target(&config).await?;
329 let tx_hash = tx.tx_hash();
330 let tracing = self.configure_tracing(&mut config, &evm_opts);
331 let with_local_artifacts = self.with_local_artifacts;
332
333 let endpoint_identity = evm_opts.discover_fork_endpoint().await?;
334 let tx_inclusion = tx
335 .block_hash_num()
336 .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?;
337
338 let frame = call_tracer_frame(
339 provider
340 .debug_trace_transaction(
341 tx_hash,
342 GethDebugTracingOptions::call_tracer(CallConfig::default().with_log()),
343 )
344 .await,
345 "debug_traceTransaction",
346 "drop `--debug-trace-transaction` to re-execute the transaction locally",
347 "the transaction's block; use an archive endpoint",
348 )?;
349
350 let receipt = provider
351 .get_transaction_receipt(tx_hash)
352 .await?
353 .ok_or_else(|| eyre::eyre!("tx receipt not found: {:?}", tx_hash))?;
354 ensure_remote_transaction_inclusion(
355 tx_hash,
356 tx_inclusion,
357 receipt.block_hash_num(),
358 "transaction receipt",
359 )?;
360
361 let transaction_block = provider.get_block_by_hash(tx_inclusion.hash).await?;
362 ensure_remote_transaction_inclusion(
363 tx_hash,
364 tx_inclusion,
365 transaction_block.as_ref().map(block_num_hash),
366 "block fetched by hash",
367 )?;
368 let Some(transaction_block) = transaction_block else {
369 unreachable!("ensure_remote_transaction_inclusion errors when the block is missing")
370 };
371
372 let root_create_address = Transaction::to(&tx)
373 .is_none()
374 .then(|| receipt.contract_address().unwrap_or_else(|| tx.from().create(tx.nonce())));
375 let arena = SparsedTraceArena {
376 arena: call_frame_to_arena(&frame, root_create_address),
377 ignored: Default::default(),
378 diagnostics: Default::default(),
379 };
380 let result = TraceResult {
381 success: receipt.status(),
382 traces: Some(vec![(TraceKind::Execution, arena)]),
383 gas_used: receipt.gas_used(),
384 };
385
386 let contracts_bytecode = if with_local_artifacts {
391 fetch_transaction_contracts_bytecode_via_rpc(
392 &provider,
393 &result,
394 tx_hash,
395 BlockId::hash(tx_inclusion.hash),
396 )
397 .await
398 } else {
399 Default::default()
400 };
401
402 let chain = alloy_chains::Chain::from_id(endpoint_identity.source_chain_id);
405 let resolved_hardfork = resolve_remote_trace_hardfork(
406 config.hardfork,
407 &endpoint_identity,
408 Some(transaction_block.header().timestamp()),
409 );
410 let final_endpoint_identity = evm_opts.discover_fork_endpoint().await?;
411 ensure_remote_trace_context_unchanged(&endpoint_identity, &final_endpoint_identity)?;
412
413 let current_tx = provider.get_transaction_by_hash(tx_hash).await?;
414 ensure_remote_transaction_inclusion(
415 tx_hash,
416 tx_inclusion,
417 current_tx.and_then(|tx| tx.block_hash_num()),
418 "transaction lookup",
419 )?;
420 let canonical_block = provider.get_block_by_number(tx_inclusion.number.into()).await?;
421 ensure_remote_transaction_inclusion(
422 tx_hash,
423 tx_inclusion,
424 canonical_block.as_ref().map(block_num_hash),
425 "canonical block lookup",
426 )?;
427 handle_traces(
428 result,
429 &config,
430 {
431 let context =
432 TraceContext::new(chain, endpoint_identity.network_profile, resolved_hardfork);
433 context.with_hardfork(context.decoding_hardfork(&config))
434 },
435 &contracts_bytecode,
436 &tracing,
437 with_local_artifacts,
438 false,
439 )
440 .await
441 }
442
443 async fn prepare<FEN: FoundryEvmNetwork>(
444 mut self,
445 mut config: Box<Config>,
446 evm_opts: EvmOpts,
447 target: TargetFetch,
448 executor_builder: ExecutorBuilder<FEN>,
449 ) -> Result<PreparedRun<FEN>> {
450 #[cfg_attr(not(feature = "monad"), allow(unused_variables))]
451 let TargetFetch { tx, provider, compute_units_per_second } = target;
452 let tx_hash = tx.tx_hash();
453 let tracing = self.configure_tracing(&mut config, &evm_opts);
454
455 let tx_block_number = tx
456 .block_number()
457 .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?;
458
459 config.fork_block_number = Some(tx_block_number - 1);
461
462 let create2_deployer = evm_opts.create2_deployer;
463 let (block, mut fork) = tokio::try_join!(
464 provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into),
466 TracingExecutor::<FEN>::get_fork(&mut config, evm_opts)
467 )?;
468 let chain = fork.context().chain();
469 let networks = fork.context().networks();
470
471 let mut evm_version = self.evm_version;
472 fork.evm_env.cfg_env.disable_block_gas_limit = true;
477
478 if !self.enable_tx_gas_limit {
481 fork.evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX);
482 }
483
484 fork.evm_env.cfg_env.limit_contract_code_size = None;
485 fork.evm_env.block_env.set_number(U256::from(tx_block_number));
486
487 let mut parent_beacon_block_root = None;
488 if let Some(block) = &block {
489 fork.evm_env.block_env = block_env_from_header(block.header());
490 parent_beacon_block_root = block.header().parent_beacon_block_root();
491
492 if evm_version.is_none()
496 && config.hardfork.is_none()
497 && FoundryHardfork::from_chain_and_timestamp(chain.id(), block.header().timestamp())
498 .is_none()
499 && block.header().excess_blob_gas().is_some()
500 {
501 evm_version = Some(EvmVersion::Cancun);
503 }
504 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
505 &mut fork.evm_env,
506 block,
507 chain.id(),
508 config.networks,
509 );
510 }
511 fork.resolve_spec(&config, evm_version);
512 fork.extend_precompile_labels(&mut config);
513
514 apply_chain_specific_tx_replay_env_changes_for_chain(&mut fork.evm_env, chain.id());
515
516 let trace_context = fork.context();
517 let mut evm_env = fork.evm_env.clone();
518
519 let mut executor = fork.into_executor(
520 executor_builder,
521 TraceRequirements::none(),
522 create2_deployer,
523 None,
524 )?;
525
526 evm_env.cfg_env.set_spec_and_mainnet_gas_params(executor.spec_id());
527
528 let spec_id = (*evm_env.cfg_env.spec()).into();
529
530 if let Some(parent_beacon_block_root) =
531 parent_beacon_block_root_for_network(networks, spec_id, parent_beacon_block_root)
532 {
533 executor.apply_beacon_root(parent_beacon_block_root)?;
534 }
535
536 let mut prestate_applied = false;
544 if !self.quick && self.prestate_tracer {
545 trace!(?tx_hash, "attempting to fetch prestate via debug_traceTransaction");
546 match provider
547 .debug_trace_transaction(
548 tx_hash,
549 GethDebugTracingOptions::prestate_tracer(PreStateConfig::default()),
550 )
551 .await
552 {
553 Ok(trace) => match trace.try_into_pre_state_frame() {
554 Ok(pre_state_frame) => {
555 executor.apply_prestate_trace(pre_state_frame.into_pre_state())?;
556 prestate_applied = true;
557 trace!("prestate trace applied successfully, skipping block replay");
558 }
559 Err(err) => {
560 trace!(%err, "failed to parse prestate trace response");
561 }
562 },
563 Err(err) => {
564 trace!(?err, "debug_traceTransaction failed, falling back to block replay");
565 }
566 }
567 }
568
569 let block_access_list = if !self.quick
573 && !self.no_bal
574 && !prestate_applied
575 && spec_id.is_enabled_in(SpecId::CANCUN)
576 && let Some(block) = &block
577 {
578 fetch_block_access_list(&provider, block).await?
579 } else {
580 None
581 };
582
583 Ok(PreparedRun {
584 args: self,
585 config,
586 tracing,
587 tx,
588 block,
589 evm_env,
590 executor,
591 trace_context,
592 prestate_applied,
593 block_access_list,
594 #[cfg(feature = "monad")]
595 monad: MonadPrepared { tx_block_number, compute_units_per_second },
596 })
597 }
598}
599
600async fn fetch_block_access_list(
602 provider: &RetryProvider,
603 block: &AnyRpcBlock,
604) -> Result<Option<Arc<Bal>>> {
605 let header = block.header();
606 let bal = match provider.get_block_access_list(BlockId::hash(header.hash)).await {
607 Ok(Some(bal)) => bal,
608 Ok(None) => return Ok(None),
609 Err(err) => {
610 trace!(%err, "block access list unavailable, falling back to block replay");
611 return Ok(None);
612 }
613 };
614 if let Some(hash) = header.block_access_list_hash()
615 && compute_block_access_list_hash(&bal) != hash
616 {
617 sh_warn!(
618 "block access list of block {} does not match its header, replaying the block instead",
619 header.number()
620 )?;
621 return Ok(None);
622 }
623 Ok(Some(Arc::new(Bal::try_from_alloy(bal).wrap_err("invalid block access list")?)))
624}
625
626impl<FEN: FoundryEvmNetwork> PreparedRun<FEN> {
627 fn prepare_target(&mut self) {
630 let requirements = TraceRequirements::none()
631 .with_calls(true)
632 .with_debug(self.args.debug)
633 .with_decode_internal(if self.tracing.decode_internal {
634 InternalTraceMode::Full
635 } else {
636 InternalTraceMode::None
637 })
638 .with_state_changes(self.tracing.verbosity > 4);
639 self.executor.set_trace_requirements(requirements);
640 self.executor.set_trace_printer(self.args.trace_printer);
641
642 let sender_is_forged = match &*self.tx.inner.inner {
643 AnyTxEnvelope::Ethereum(inner) => {
644 inner.recover_signer().is_ok_and(|signer| signer != self.tx.from())
645 }
646 AnyTxEnvelope::Unknown(_) => true,
647 };
648 if sender_is_forged {
649 self.evm_env.cfg_env.disable_balance_check = true;
650 }
651 }
652
653 fn target_index(&self) -> Result<usize> {
655 let Some(block) = &self.block else { return Ok(0) };
656 full_transactions(block)?
657 .iter()
658 .position(|candidate| candidate.tx_hash() == self.tx.tx_hash())
659 .ok_or_else(|| {
660 eyre::eyre!("transaction {:?} is missing from its block", self.tx.tx_hash())
661 })
662 }
663
664 fn for_each_prefix_transaction(
667 &self,
668 target_index: usize,
669 mut f: impl FnMut(usize, &AnyRpcTransaction) -> Result<()>,
670 ) -> Result<()> {
671 if self.args.quick || self.prestate_applied {
672 return Ok(());
673 }
674 sh_status!("Executing previous transactions from the block.")?;
675 let Some(block) = &self.block else { return Ok(()) };
676 let txs = full_transactions(block)?;
677 let pb = init_progress(txs.len() as u64, "tx");
678 for (index, tx) in txs.iter().take(target_index).enumerate() {
679 if let Some(to) = Transaction::to(tx) {
680 trace!(tx=?tx.tx_hash(), ?to, "preparing previous call transaction");
681 } else {
682 trace!(tx=?tx.tx_hash(), "preparing previous create transaction");
683 }
684 f(index, tx)?;
685 pb.set_position((index + 1) as u64);
686 }
687 Ok(())
688 }
689
690 fn trace_kind(&self) -> TraceKind {
691 if let Some(to) = Transaction::to(&self.tx) {
692 trace!(tx=?self.tx.tx_hash(), ?to, "executing call transaction");
693 TraceKind::Execution
694 } else {
695 trace!(tx=?self.tx.tx_hash(), "executing create transaction");
696 TraceKind::Deployment
697 }
698 }
699
700 fn execute_ordinary(&mut self) -> Result<TraceResult> {
701 let target_tx_env = TxEnvFor::<FEN>::from_any_rpc_transaction(&self.tx)?;
704 let target_index = self.target_index()?;
705 self.prepare_target();
706
707 let mut replay = Vec::new();
708 if let Some(bal) = self.block_access_list.take() {
709 trace!("reading prestate from block access list, skipping block replay");
712 self.executor
713 .backend_mut()
714 .set_bal(bal, BlockAccessIndex::new(target_index as u64 + 1));
715 } else {
716 let block_number = self.evm_env.block_env.number();
717 let replay_system_txes = self.args.replay_system_txes;
718 self.for_each_prefix_transaction(target_index, |_, tx| {
719 if !is_system_transaction(tx) || replay_system_txes {
720 let tx_env =
721 TxEnvFor::<FEN>::from_any_rpc_transaction(tx).wrap_err_with(|| {
722 format!(
723 "Failed to prepare transaction: {:?} in block {}",
724 tx.tx_hash(),
725 block_number
726 )
727 })?;
728 replay.push((tx.tx_hash(), tx_env));
729 }
730 Ok(())
731 })?;
732 }
733 let result = self.executor.transact_with_ordinary_block_replay(
734 self.evm_env.clone(),
735 target_tx_env,
736 replay,
737 )?;
738 let trace_kind = self.trace_kind();
739 trace!(tx_hash=?self.tx.tx_hash(), "completed execution");
740 Ok(TraceResult::from_raw(result, trace_kind))
741 }
742
743 async fn finish(self, result: TraceResult) -> Result<()> {
744 let contracts_bytecode = fetch_contracts_bytecode_from_trace(&self.executor, &result)?;
745 handle_traces(
746 result,
747 &self.config,
748 self.trace_context,
749 &contracts_bytecode,
750 &self.tracing,
751 self.args.with_local_artifacts,
752 self.args.debug,
753 )
754 .await
755 }
756}
757
758#[cfg(feature = "monad")]
759impl PreparedRun<MonadEvmNetwork> {
760 async fn execute_monad(&mut self) -> Result<TraceResult> {
761 let provider = ProviderBuilder::<alloy_network::Ethereum>::from_config(&self.config)?
765 .compute_units_per_second_opt(self.monad.compute_units_per_second)
766 .build()?;
767 let block =
768 provider.get_block(self.monad.tx_block_number.into()).full().await?.ok_or_else(
769 || {
770 eyre::eyre!(
771 "block {} is required to reconstruct transaction context",
772 self.monad.tx_block_number
773 )
774 },
775 )?;
776 let block_context = BlockContext::<MonadEvmNetwork>::fetch(&provider, &block).await?;
777 let target_tx_env = TxEnvFor::<MonadEvmNetwork>::from_any_rpc_transaction(&self.tx)?;
781 let target_index = self.target_index()?;
782 self.prepare_target();
783
784 let mut replay = Vec::new();
785 self.for_each_prefix_transaction(target_index, |index, tx| {
786 let tx_env = TxEnvFor::<MonadEvmNetwork>::from_any_rpc_transaction(tx)?;
787 let chain_context: ChainFor<MonadEvmNetwork> = block_context.transaction(index);
788 replay.push((tx.tx_hash(), tx_env, chain_context));
789 Ok(())
790 })?;
791 let result = self.executor.transact_with_monad_block_replay(
792 self.evm_env.clone(),
793 target_tx_env,
794 block_context.transaction(target_index),
795 replay,
796 self.args.replay_system_txes,
797 )?;
798 let Some((result, used_system_replay)) = result else {
799 eyre::bail!(
800 "{:?} is a system transaction.\nReplaying system transactions is currently not supported.",
801 self.tx.tx_hash()
802 );
803 };
804 if used_system_replay {
805 trace!(tx=?self.tx.tx_hash(), "executed canonical system transaction");
806 }
807 Ok(TraceResult::from_raw(result, self.trace_kind()))
808 }
809}
810
811fn is_system_transaction(tx: &AnyRpcTransaction) -> bool {
812 is_known_system_sender(tx.from()) || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
813}
814
815fn full_transactions(block: &AnyRpcBlock) -> Result<&[AnyRpcTransaction]> {
816 let BlockTransactions::Full(txs) = block.transactions() else {
817 eyre::bail!("Could not get block txs");
818 };
819 Ok(txs)
820}
821
822pub(super) fn block_num_hash<B: BlockResponse>(block: &B) -> BlockNumHash
824where
825 B::Header: HeaderResponse,
826{
827 BlockNumHash::new(block.header().number(), block.header().hash())
828}
829
830pub(super) fn call_tracer_frame(
836 response: Result<GethTrace, TransportError>,
837 method: &str,
838 local_hint: &str,
839 missing_state_hint: &str,
840) -> Result<CallFrame> {
841 let trace = response.map_err(|err| -> eyre::Report {
842 if is_method_not_found_error(&err) {
843 eyre::eyre!(
844 "the RPC endpoint does not support `{method}` (method not found); use a node with the `debug` namespace enabled (e.g. a local anvil/reth or an archive endpoint), or {local_hint}"
845 )
846 } else if is_missing_state_error(&err) {
847 eyre::eyre!(
848 "the RPC endpoint does not have the historical state for {missing_state_hint}"
849 )
850 } else {
851 err.into()
852 }
853 })?;
854 let GethTrace::CallTracer(frame) = trace else {
855 eyre::bail!(
856 "`{method}` did not return a callTracer frame; the RPC endpoint may not support the `callTracer`"
857 );
858 };
859 Ok(frame)
860}
861
862fn ensure_remote_transaction_inclusion(
863 tx_hash: B256,
864 expected: BlockNumHash,
865 actual: Option<BlockNumHash>,
866 source: &str,
867) -> Result<()> {
868 let Some(actual) = actual else {
869 eyre::bail!(
870 "transaction {tx_hash} changed inclusion while collecting its remote trace: {source} no longer reports it as mined; retry the command"
871 );
872 };
873 if actual != expected {
874 eyre::bail!(
875 "transaction {tx_hash} changed inclusion while collecting its remote trace: expected block {} at {}, but {source} reported block {} at {}; retry the command",
876 expected.hash,
877 expected.number,
878 actual.hash,
879 actual.number,
880 );
881 }
882
883 Ok(())
884}
885
886const fn parent_beacon_block_root_for_network(
887 networks: NetworkConfigs,
888 spec_id: SpecId,
889 parent_beacon_block_root: Option<B256>,
890) -> Option<B256> {
891 if networks.is_monad() || !spec_id.is_enabled_in(SpecId::CANCUN) {
892 return None;
893 }
894
895 parent_beacon_block_root
899}
900
901pub fn fetch_contracts_bytecode_from_trace<FEN: FoundryEvmNetwork>(
902 executor: &Executor<FEN>,
903 result: &TraceResult,
904) -> Result<AddressHashMap<Bytes>> {
905 let contracts_bytecode = trace_addresses(result)
906 .filter_map(|addr| {
907 let code = executor
909 .backend()
910 .basic_ref(addr)
911 .inspect_err(|e| _ = sh_warn!("Failed to fetch code for {addr}: {e}"))
912 .ok()??
913 .code?
914 .bytes();
915 (!code.is_empty()).then_some((addr, code))
916 })
917 .collect();
918 Ok(contracts_bytecode)
919}
920
921async fn fetch_transaction_contracts_bytecode_via_rpc<N: Network, P: Provider<N>>(
928 provider: &P,
929 result: &TraceResult,
930 tx_hash: B256,
931 block: BlockId,
932) -> AddressHashMap<Bytes> {
933 let mut contracts_bytecode = AddressHashMap::default();
934 let prestate_config = PreStateConfig { disable_storage: Some(true), ..Default::default() };
935 match provider
936 .debug_trace_transaction(tx_hash, GethDebugTracingOptions::prestate_tracer(prestate_config))
937 .await
938 {
939 Ok(trace) => match trace.try_into_pre_state_frame() {
940 Ok(prestate) => {
941 for (&address, account) in prestate.pre_state() {
942 if let Some(code) = account.code.clone().filter(|code| !code.is_empty()) {
943 contracts_bytecode.insert(address, code);
944 }
945 }
946 }
947 Err(err) => {
948 let _ = sh_warn!("Failed to parse transaction prestate for local artifacts: {err}");
949 }
950 },
951 Err(err) => {
952 let _ = sh_warn!("Failed to fetch transaction prestate for local artifacts: {err}");
953 }
954 }
955
956 let missing_addresses = trace_addresses(result)
957 .filter(|address| !contracts_bytecode.contains_key(address))
958 .collect::<Vec<_>>();
959 contracts_bytecode.extend(fetch_code_via_rpc(provider, missing_addresses, block).await);
960 contracts_bytecode
961}
962
963pub(super) fn trace_addresses(result: &TraceResult) -> impl Iterator<Item = Address> {
965 let mut addresses = AddressSet::default();
966 for (_, trace) in result.traces.iter().flatten() {
967 for node in trace.arena.nodes() {
968 addresses.extend(
969 [node.trace.address, node.trace.caller].into_iter().filter(|a| !a.is_zero()),
970 );
971 }
972 }
973 addresses.into_iter()
974}
975
976impl figment::Provider for RunArgs {
977 fn metadata(&self) -> Metadata {
978 Metadata::named("RunArgs")
979 }
980
981 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
982 let mut map = Map::new();
983
984 if let Some(api_key) = &self.etherscan.key {
985 map.insert("etherscan_api_key".into(), api_key.as_str().into());
986 }
987
988 if let Some(evm_version) = self.evm_version {
989 map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?);
990 }
991
992 Ok(Map::from([(Config::selected_profile(), map)]))
993 }
994}
995
996#[cfg(test)]
997mod tests {
998 use super::*;
999 use alloy_primitives::address;
1000
1001 #[test]
1002 fn parses_legacy_short_label_alias() {
1003 let address = address!("0x0000000000000000000000000000000000000001");
1004 let label = format!("{address}:alice");
1005 let args = RunArgs::parse_from(["cast run", "0x00", "-l", &label]);
1006
1007 assert_eq!(args.legacy_labels, vec![label]);
1008 }
1009
1010 #[test]
1011 fn debug_trace_transaction_accepts_label_and_render_flags() {
1012 let args = RunArgs::try_parse_from([
1013 "foundry-cli",
1014 "--debug-trace-transaction",
1015 "0x0000000000000000000000000000000000000000000000000000000000000000",
1016 "--label",
1017 "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth",
1018 "--disable-labels",
1019 "--trace-depth",
1020 "2",
1021 "--with-local-artifacts",
1022 ]);
1023 assert!(args.is_ok(), "--debug-trace-transaction must accept label/rendering flags");
1024 }
1025
1026 #[test]
1027 fn parent_beacon_block_root_is_applied_only_when_the_header_has_one() {
1028 let networks = NetworkConfigs::default();
1029 let root = Some(B256::repeat_byte(0x42));
1030 for (networks, spec_id, root, expected) in [
1032 (networks, SpecId::CANCUN, None, None),
1033 (networks, SpecId::CANCUN, root, root),
1034 (networks, SpecId::SHANGHAI, root, None),
1035 (networks, SpecId::SHANGHAI, None, None),
1036 #[cfg(feature = "monad")]
1037 (NetworkConfigs::with_monad(), SpecId::PRAGUE, root, None),
1038 #[cfg(feature = "monad")]
1039 (NetworkConfigs::with_monad(), SpecId::OSAKA, root, None),
1040 #[cfg(feature = "monad")]
1041 (NetworkConfigs::with_monad(), SpecId::PRAGUE, None, None),
1042 #[cfg(feature = "monad")]
1043 (NetworkConfigs::with_monad(), SpecId::OSAKA, None, None),
1044 ] {
1045 assert_eq!(parent_beacon_block_root_for_network(networks, spec_id, root), expected);
1046 }
1047 }
1048
1049 #[test]
1050 fn debug_trace_transaction_ignores_configured_internal_decoding() {
1051 let args = RunArgs::parse_from(["cast run", "0x00", "--debug-trace-transaction"]);
1052 let config = TracingConfig { decode_internal: true, ..Default::default() };
1053
1054 assert!(!args.resolve_tracing(&config, 0).decode_internal);
1055 }
1056}