Skip to main content

cast/cmd/
run.rs

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/// CLI arguments for `cast run`.
77#[derive(Clone, Debug, Parser)]
78pub struct RunArgs {
79    /// The transaction hash.
80    tx_hash: String,
81
82    /// Opens the transaction in the debugger.
83    #[arg(long, short)]
84    debug: bool,
85
86    /// Print out opcode traces.
87    #[arg(long, short)]
88    trace_printer: bool,
89
90    /// Executes the transaction only with the state from the previous block.
91    ///
92    /// May result in different results than the live execution!
93    #[arg(long)]
94    quick: bool,
95
96    /// Do not use the block access list (BAL) of the transaction's block.
97    ///
98    /// If the node serves an EIP-7928 block access list, cast reads the transaction's prestate
99    /// from it instead of replaying the earlier transactions of the block. With this flag, the
100    /// block is always replayed.
101    #[arg(long)]
102    no_bal: bool,
103
104    /// Whether to replay system transactions.
105    #[arg(long, alias = "sys")]
106    replay_system_txes: bool,
107
108    /// Use debug_traceTransaction to fetch the prestate instead of replaying the block.
109    ///
110    /// This is significantly faster than replaying all previous transactions in the block, but
111    /// requires the node to expose the `debug_` namespace (most public RPCs don't). If the call
112    /// or response can't be used, cast silently falls back to the block access list, then to
113    /// replaying the block.
114    #[arg(long, default_value_t = false)]
115    prestate_tracer: bool,
116
117    /// Fetch the transaction's trace from the node via `debug_traceTransaction` (callTracer) and
118    /// render it, instead of re-executing the transaction locally.
119    ///
120    /// This skips the block replay entirely, so it is fast and reflects exactly what happened
121    /// on-chain, including chain-specific EVM behavior a local replay may not reproduce, but it
122    /// requires the node to expose the `debug_` namespace. The result is a call-tree view:
123    /// nested calls, value, gas, emitted logs and revert data. It does not provide the
124    /// opcode-level detail of a local run, so the local-execution-only flags (`--debug`,
125    /// `--decode-internal`, `--trace-printer`, `--quick`, `--no-bal`, `--prestate-tracer`,
126    /// `--evm-version`) do not apply.
127    #[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    /// Deprecated short alias for `--labels`.
138    #[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    /// The EVM version to use.
148    ///
149    /// Overrides the version specified in the config.
150    #[arg(long)]
151    evm_version: Option<EvmVersion>,
152
153    /// Use current project artifacts for trace decoding.
154    #[arg(long, visible_alias = "la")]
155    pub with_local_artifacts: bool,
156
157    /// Disable block gas limit check.
158    ///
159    /// Always implied: a mined transaction already passed its chain's own check.
160    #[arg(long)]
161    pub disable_block_gas_limit: bool,
162
163    /// Enable the tx gas limit checks as imposed by Osaka (EIP-7825).
164    #[arg(long)]
165    pub enable_tx_gas_limit: bool,
166}
167
168/// Target transaction resolved up front by [`RunArgs::fetch_target`], before any per-network
169/// preparation.
170struct TargetFetch {
171    tx: AnyRpcTransaction,
172    provider: RetryProvider,
173    compute_units_per_second: Option<u64>,
174}
175
176/// Fields only needed by the Monad-specific `execute_monad` path.
177#[cfg(feature = "monad")]
178struct MonadPrepared {
179    tx_block_number: u64,
180    compute_units_per_second: Option<u64>,
181}
182
183/// State assembled by [`RunArgs::prepare`] and consumed by the network-specific `execute_*`
184/// methods below.
185struct 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    /// The block access list of the target's block, when the node serves one.
196    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    /// Applies the network and tracing options to `config` and returns the resolved tracing
211    /// config.
212    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    /// Executes the transaction by replaying it
220    ///
221    /// This replays the entire block the transaction was mined in unless `quick` is set to true
222    /// or the node serves a block access list for the block.
223    ///
224    /// Note: This executes the transaction(s) as is: Cheatcodes are disabled
225    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        // Auto-detect network from fork chain ID when not explicitly configured.
239        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    /// `AnyNetwork` rather than `FEN::Network`: chains such as Arbitrum, Celo and the OP-stack
302    /// forks Foundry does not route to a dedicated network put transaction types the strict
303    /// Ethereum envelope cannot decode into every block, which would fail the full block fetch in
304    /// `prepare` for the whole chain. Execution still uses `FEN`.
305    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    /// Fetches the trace from the node via `debug_traceTransaction` (callTracer) instead of
324    /// re-executing the transaction locally. The node already holds the transaction's exact
325    /// pre-state and EVM rules, so this needs no block replay and no local executor; it also
326    /// handles system transactions.
327    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        // Local-artifact labeling matches deployed runtime bytecode against the project
387        // artifacts. There is no local executor on this path, so fetch the code over RPC for the
388        // addresses in the trace, at the transaction's block. Skip the extra round-trips unless
389        // local artifacts were requested.
390        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        // The remote node executed this trace, so its reported family is authoritative for
403        // decoding even when the caller selected a compatible local EVM implementation.
404        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        // we need to fork off the parent block
460        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            // fetch the block the transaction was mined in
465            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        // Mined transactions already passed the block gas limit check their chain applies, and
473        // some chains admit transactions whose gas limit exceeds it: BSC validator transactions
474        // carry a gas limit of `i64::MAX`. Re-applying the check can only reject a transaction
475        // the chain accepted.
476        fork.evm_env.cfg_env.disable_block_gas_limit = true;
477
478        // By default do not enforce transaction gas limits imposed by Osaka (EIP-7825).
479        // Users can opt-in to enable these limits by setting `enable_tx_gas_limit` to true.
480        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            // Unless explicitly configured, resolve the correct spec for the block using the same
493            // approach as reth: walk known chain activation conditions to find the latest active
494            // fork. Falls back to a blob-gas heuristic for unknown chains.
495            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                // TODO: add glamsterdam header field checks in the future
502                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        // Set the state to the moment right before the transaction.
537        //
538        // When `--prestate-tracer` is set, opportunistically try to fetch the prestate directly
539        // via `debug_traceTransaction` (much faster than replaying the block). This requires the
540        // `debug_` namespace, which most nodes don't expose, so it is opt-in and silently falls
541        // back to the block access list, then to replaying previous transactions in the block,
542        // if the call or parsing fails.
543        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        // A block access list (BAL) records every state write of the block by transaction index,
570        // so the target's prestate can be read from it instead of replaying the earlier
571        // transactions. Before Cancun, SELFDESTRUCT wipes storage the list does not enumerate.
572        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
600/// Fetches the block access list of `block`, if the node serves one that matches the header.
601async 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    /// Prepares the executor for the target transaction: enables tracing and, when the sender
628    /// was forged, disables the balance check.
629    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    /// Returns the index of the target transaction in its block.
654    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    /// Calls `f` for every transaction mined before the target, unless the block replay was
665    /// skipped (`--quick` or an applied prestate).
666    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        // Decode the target transaction before replaying the block: an envelope this build
702        // can't decode should fail fast.
703        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            // Index 0 holds the pre-block system writes and transaction `i` is index `i + 1`, so
710            // reads positioned at the target's own index see exactly the earlier writes.
711            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        // `BlockContext` is typed to `MonadEvmNetwork::Network` (`Ethereum`). Monad blocks only
762        // carry standard envelopes, so a typed provider can serve this path while the rest of the
763        // command stays on `AnyNetwork`.
764        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        // Decode the target transaction before replaying the block: an envelope this build
778        // can't decode should fail fast, not after paying for the entire prior-transaction
779        // replay.
780        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
822/// Returns the number and hash of a fetched block.
823pub(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
830/// Extracts the `callTracer` frame from a `debug_trace*` response.
831///
832/// Two RPC rejections deserve an actionable hint instead of the raw transport error, and they
833/// need different fixes: a disabled `debug` namespace, and missing historical state, hit
834/// whenever a full node has pruned the traced block.
835pub(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    // Chains that run a Cancun or later EVM without Ethereum's beacon chain, such as Polygon and
896    // Scroll, never populate this header field and never deploy the EIP-4788 contract, so there
897    // is no root to apply. Requiring one makes their blocks unreplayable.
898    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            // All relevant bytecodes should already be cached in the executor.
908            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
921/// Fetches bytecode for a mined transaction at its exact transaction index.
922///
923/// The prestate tracer provides the code that existed immediately before the transaction, which
924/// avoids reading end-of-block state for contracts changed or removed by later transactions. Any
925/// address absent from the prestate (for example, a contract created by this transaction) falls
926/// back to `eth_getCode` at the transaction's block.
927async 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
963/// Returns the distinct non-zero addresses and callers seen in the traces of `result`.
964pub(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        // Polygon and Scroll run a Cancun or later EVM without populating the header field.
1031        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}