Skip to main content

cast/
debug.rs

1use alloy_chains::Chain;
2use alloy_primitives::{Bytes, map::AddressHashMap};
3use foundry_cli::utils::{TraceResult, print_traces};
4use foundry_common::{ContractsByArtifact, compile::ProjectCompiler};
5use foundry_config::{Config, TracingConfig};
6use foundry_debugger::Debugger;
7use foundry_evm::{
8    hardforks::TempoHardfork,
9    traces::{
10        CallTraceDecoderBuilder, DebugTraceIdentifier,
11        debug::ContractSources,
12        identifier::{SignaturesIdentifier, TraceIdentifiers},
13    },
14};
15
16/// labels the traces, conditionally prints them or opens the debugger
17#[expect(clippy::too_many_arguments)]
18pub(crate) async fn handle_traces(
19    mut result: TraceResult,
20    config: &Config,
21    chain: Chain,
22    contracts_bytecode: &AddressHashMap<Bytes>,
23    tracing: &TracingConfig,
24    with_local_artifacts: bool,
25    debug: bool,
26    tempo_hardfork: Option<TempoHardfork>,
27) -> eyre::Result<()> {
28    let (known_contracts, mut sources) = if with_local_artifacts {
29        // Status prose goes to stderr so `--json` output on stdout stays machine-readable.
30        let _ = sh_status!("Compiling project to generate artifacts");
31        let project = config.project()?;
32        let compiler = ProjectCompiler::new();
33        let output = compiler.compile(&project)?;
34        (
35            Some(ContractsByArtifact::new(
36                output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())),
37            )),
38            ContractSources::from_project_output(&output, project.root(), None)?,
39        )
40    } else {
41        (None, ContractSources::default())
42    };
43
44    let is_tempo = tempo_hardfork.is_some() || chain.is_tempo();
45    let mut builder = CallTraceDecoderBuilder::new()
46        .with_tracing_config(tracing)
47        .with_signature_identifier(SignaturesIdentifier::from_config(config)?)
48        .with_chain_id((!is_tempo).then(|| chain.id()))
49        .with_tempo_hardfork(
50            tempo_hardfork
51                .or_else(|| chain.is_tempo().then(|| config.evm_spec_id::<TempoHardfork>())),
52        );
53    let mut identifier = TraceIdentifiers::new().with_external(config, Some(chain))?;
54    if let Some(contracts) = &known_contracts {
55        builder = builder.with_known_contracts(contracts);
56        identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode);
57    }
58
59    let mut decoder = builder.build();
60
61    for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() {
62        decoder.identify(trace, &mut identifier);
63    }
64
65    if tracing.decode_internal || debug {
66        if let Some(ref etherscan_identifier) = identifier.external {
67            sources.merge(etherscan_identifier.get_compiled_contracts().await?);
68        }
69
70        if debug {
71            let mut debugger = Debugger::builder()
72                .traces(result.traces.expect("missing traces"))
73                .decoder(&decoder)
74                .sources(sources)
75                .build();
76            debugger.try_run_tui()?;
77            return Ok(());
78        }
79
80        decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources));
81    }
82
83    print_traces(
84        &mut result,
85        &decoder,
86        tracing.verbosity > 0,
87        tracing.verbosity > 4,
88        tracing.trace_depth,
89    )
90    .await?;
91
92    Ok(())
93}