Skip to main content

cast/
debug.rs

1use alloy_primitives::{Bytes, map::AddressHashMap};
2use foundry_cli::utils::{TraceResult, print_traces};
3use foundry_common::{ContractsByArtifactBuilder, compile::ProjectCompiler};
4use foundry_compilers::artifacts::output_selection::ContractOutputSelection;
5use foundry_config::{Config, FoundryHardfork, TracingConfig};
6use foundry_debugger::Debugger;
7use foundry_evm::{
8    opts::ForkEndpointIdentity,
9    traces::{
10        CallTraceDecoderBuilder, DebugTraceIdentifier, TraceContext,
11        debug::ContractSources,
12        identifier::{SignaturesIdentifier, TraceIdentifiers},
13    },
14};
15use foundry_evm_networks::NetworkVariant;
16
17pub(crate) fn select_remote_trace_hardfork(
18    configured: Option<FoundryHardfork>,
19    endpoint: Option<FoundryHardfork>,
20    network: NetworkVariant,
21) -> Option<FoundryHardfork> {
22    let namespace = network.hardfork_namespace();
23    configured
24        .filter(|hardfork| hardfork.namespace() == namespace)
25        .or_else(|| endpoint.filter(|hardfork| hardfork.namespace() == namespace))
26}
27
28/// Resolves the hardfork used to decode a trace executed by the remote node. A configured
29/// hardfork is an explicit override; otherwise an Anvil endpoint's exact execution hardfork is
30/// honored before consulting the source chain's schedule at `block_timestamp`.
31pub(crate) fn resolve_remote_trace_hardfork(
32    configured: Option<FoundryHardfork>,
33    endpoint: &ForkEndpointIdentity,
34    block_timestamp: Option<u64>,
35) -> Option<FoundryHardfork> {
36    select_remote_trace_hardfork(configured, endpoint.hardfork, endpoint.network).or_else(|| {
37        block_timestamp.and_then(|timestamp| {
38            FoundryHardfork::from_chain_and_timestamp(endpoint.source_chain_id, timestamp)
39        })
40    })
41}
42
43pub(crate) fn ensure_remote_trace_context_unchanged(
44    before: &ForkEndpointIdentity,
45    after: &ForkEndpointIdentity,
46) -> eyre::Result<()> {
47    if before != after {
48        eyre::bail!(
49            "the RPC endpoint changed execution context while the remote trace was being \
50             collected; retry the command"
51        );
52    }
53    Ok(())
54}
55
56/// labels the traces, conditionally prints them or opens the debugger
57pub(crate) async fn handle_traces(
58    mut result: TraceResult,
59    config: &Config,
60    context: TraceContext,
61    contracts_bytecode: &AddressHashMap<Bytes>,
62    tracing: &TracingConfig,
63    with_local_artifacts: bool,
64    debug: bool,
65) -> eyre::Result<()> {
66    let (known_contracts, mut sources) = if with_local_artifacts {
67        // Status prose goes to stderr so `--json` output on stdout stays machine-readable.
68        let _ = sh_status!("Compiling project to generate artifacts");
69        let mut config = config.clone();
70        if debug && !config.extra_output.contains(&ContractOutputSelection::StorageLayout) {
71            config.extra_output.push(ContractOutputSelection::StorageLayout);
72        }
73        let project = config.project()?;
74        let compiler = ProjectCompiler::new();
75        let output = compiler.compile(&project)?;
76        (
77            Some(
78                ContractsByArtifactBuilder::new(
79                    output.artifact_ids().map(|(id, artifact)| (id, artifact.into())),
80                )
81                .with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
82                    artifact.storage_layout.as_ref().map(|layout| (id, layout.clone()))
83                }))
84                .build(),
85            ),
86            ContractSources::from_project_output(&output, project.root(), None)?,
87        )
88    } else {
89        (None, ContractSources::default())
90    };
91
92    let mut builder = CallTraceDecoderBuilder::new()
93        .with_tracing_config(tracing)
94        .with_signature_identifier(SignaturesIdentifier::from_config(config)?)
95        .with_networks(context.networks())
96        .with_chain_id(Some(context.chain().id()))
97        .with_hardfork(context.hardfork());
98    let mut identifier = TraceIdentifiers::new().with_external(config, Some(context.chain()))?;
99    if let Some(contracts) = &known_contracts {
100        builder = builder.with_known_contracts(contracts);
101        identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode);
102    }
103
104    let mut decoder = builder.build();
105
106    for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() {
107        decoder.identify(trace, &mut identifier);
108    }
109
110    if tracing.decode_internal || debug {
111        if let Some(ref etherscan_identifier) = identifier.external {
112            sources.merge(etherscan_identifier.get_compiled_contracts().await?);
113        }
114
115        if debug {
116            let mut builder = Debugger::builder()
117                .traces(result.traces.expect("missing traces"))
118                .decoder(&decoder)
119                .sources(sources);
120            if let Some(known_contracts) = &known_contracts {
121                builder = builder.known_contracts(known_contracts);
122            }
123            let mut debugger = builder.build();
124            debugger.try_run_tui()?;
125            return Ok(());
126        }
127
128        decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources));
129    }
130
131    print_traces(
132        &mut result,
133        &decoder,
134        tracing.verbosity > 0,
135        tracing.verbosity > 4,
136        tracing.trace_depth,
137    )
138    .await?;
139
140    Ok(())
141}
142
143#[cfg(all(test, feature = "monad"))]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn remote_trace_hardfork_ignores_cross_network_override() {
149        let ethereum = FoundryHardfork::Ethereum(foundry_evm::hardforks::EthereumHardfork::Cancun);
150        let monad_eight = FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadEight);
151        let monad_nine = FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadNine);
152
153        assert_eq!(
154            select_remote_trace_hardfork(Some(ethereum), Some(monad_nine), NetworkVariant::Monad),
155            Some(monad_nine)
156        );
157        assert_eq!(
158            select_remote_trace_hardfork(
159                Some(monad_eight),
160                Some(monad_nine),
161                NetworkVariant::Monad
162            ),
163            Some(monad_eight)
164        );
165    }
166}