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