Skip to main content

cast/
debug.rs

1use alloy_chains::Chain;
2#[cfg(test)]
3use alloy_primitives::B256;
4use alloy_primitives::{Bytes, map::AddressHashMap};
5use foundry_cli::utils::{TraceResult, print_traces};
6use foundry_common::{ContractsByArtifactBuilder, compile::ProjectCompiler};
7use foundry_compilers::artifacts::output_selection::ContractOutputSelection;
8use foundry_config::{Config, FoundryHardfork, TracingConfig};
9use foundry_debugger::Debugger;
10use foundry_evm::{
11    hardforks::{ExecutionSpec, TempoHardfork},
12    opts::ForkEndpointIdentity,
13    traces::{
14        CallTraceDecoderBuilder, DebugTraceIdentifier,
15        debug::ContractSources,
16        identifier::{SignaturesIdentifier, TraceIdentifiers},
17    },
18};
19use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
20
21pub(crate) fn select_remote_trace_hardfork(
22    configured: Option<FoundryHardfork>,
23    endpoint: Option<FoundryHardfork>,
24    network: NetworkVariant,
25) -> Option<FoundryHardfork> {
26    let namespace = network.hardfork_namespace();
27    configured
28        .filter(|hardfork| hardfork.namespace() == namespace)
29        .or_else(|| endpoint.filter(|hardfork| hardfork.namespace() == namespace))
30}
31
32pub(crate) fn ensure_remote_trace_context_unchanged(
33    before: &ForkEndpointIdentity,
34    after: &ForkEndpointIdentity,
35) -> eyre::Result<()> {
36    if before != after {
37        eyre::bail!(
38            "the RPC endpoint changed execution context while the remote trace was being \
39             collected; retry the command"
40        );
41    }
42    Ok(())
43}
44
45/// labels the traces, conditionally prints them or opens the debugger
46#[expect(clippy::too_many_arguments)]
47pub(crate) async fn handle_traces(
48    mut result: TraceResult,
49    config: &Config,
50    chain: Chain,
51    contracts_bytecode: &AddressHashMap<Bytes>,
52    tracing: &TracingConfig,
53    with_local_artifacts: bool,
54    debug: bool,
55    hardfork: Option<FoundryHardfork>,
56    networks: NetworkConfigs,
57) -> eyre::Result<()> {
58    let (known_contracts, mut sources) = if with_local_artifacts {
59        // Status prose goes to stderr so `--json` output on stdout stays machine-readable.
60        let _ = sh_status!("Compiling project to generate artifacts");
61        let mut config = config.clone();
62        if debug && !config.extra_output.contains(&ContractOutputSelection::StorageLayout) {
63            config.extra_output.push(ContractOutputSelection::StorageLayout);
64        }
65        let project = config.project()?;
66        let compiler = ProjectCompiler::new();
67        let output = compiler.compile(&project)?;
68        (
69            Some(
70                ContractsByArtifactBuilder::new(
71                    output.artifact_ids().map(|(id, artifact)| (id, artifact.into())),
72                )
73                .with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
74                    artifact.storage_layout.as_ref().map(|layout| (id, layout.clone()))
75                }))
76                .build(),
77            ),
78            ContractSources::from_project_output(&output, project.root(), None)?,
79        )
80    } else {
81        (None, ContractSources::default())
82    };
83
84    let resolved_hardfork = hardfork.or(config.hardfork);
85    let execution_network = networks.execution_network();
86    let tempo_hardfork = resolved_hardfork.and_then(TempoHardfork::from_foundry_hardfork);
87    let is_tempo = execution_network.is_tempo();
88    #[cfg(feature = "monad")]
89    let is_monad = execution_network.is_monad();
90    #[cfg(feature = "monad")]
91    let monad_hardfork =
92        resolved_hardfork.and_then(foundry_evm::hardforks::MonadHardfork::from_foundry_hardfork);
93    let mut builder = CallTraceDecoderBuilder::new()
94        .with_tracing_config(tracing)
95        .with_signature_identifier(SignaturesIdentifier::from_config(config)?)
96        .with_networks(networks)
97        .with_chain_id(Some(chain.id()))
98        .with_tempo_hardfork(
99            tempo_hardfork.or_else(|| is_tempo.then(|| config.evm_spec_id::<TempoHardfork>())),
100        );
101    #[cfg(feature = "monad")]
102    {
103        builder = builder.with_monad_hardfork(monad_hardfork.or_else(|| {
104            is_monad.then(|| config.evm_spec_id::<foundry_evm::hardforks::MonadHardfork>())
105        }));
106    }
107    let mut identifier = TraceIdentifiers::new().with_external(config, Some(chain))?;
108    if let Some(contracts) = &known_contracts {
109        builder = builder.with_known_contracts(contracts);
110        identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode);
111    }
112
113    let mut decoder = builder.build();
114
115    for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() {
116        decoder.identify(trace, &mut identifier);
117    }
118
119    if tracing.decode_internal || debug {
120        if let Some(ref etherscan_identifier) = identifier.external {
121            sources.merge(etherscan_identifier.get_compiled_contracts().await?);
122        }
123
124        if debug {
125            let mut builder = Debugger::builder()
126                .traces(result.traces.expect("missing traces"))
127                .decoder(&decoder)
128                .sources(sources);
129            if let Some(known_contracts) = &known_contracts {
130                builder = builder.known_contracts(known_contracts);
131            }
132            let mut debugger = builder.build();
133            debugger.try_run_tui()?;
134            return Ok(());
135        }
136
137        decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources));
138    }
139
140    print_traces(
141        &mut result,
142        &decoder,
143        tracing.verbosity > 0,
144        tracing.verbosity > 4,
145        tracing.trace_depth,
146    )
147    .await?;
148
149    Ok(())
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn remote_trace_context_rejects_same_url_reset() {
158        let before = ForkEndpointIdentity {
159            endpoint: "http://localhost:8545".to_string(),
160            execution_chain_id: 1,
161            source_chain_id: 1,
162            network: NetworkVariant::Ethereum,
163            network_profile: foundry_evm_networks::NetworkConfigs::default(),
164            reported_hardfork: None,
165            hardfork: None,
166            instance_id: Some(B256::with_last_byte(1)),
167            source_fork_block_number: None,
168            source_fork_block_hash: None,
169        };
170        assert!(ensure_remote_trace_context_unchanged(&before, &before).is_ok());
171
172        let mut after = before.clone();
173        after.instance_id = Some(B256::with_last_byte(2));
174        assert!(ensure_remote_trace_context_unchanged(&before, &after).is_err());
175
176        let mut before_unknown = before;
177        before_unknown.instance_id = None;
178        before_unknown.reported_hardfork = Some("FutureA".to_string());
179        let mut after_unknown = before_unknown.clone();
180        after_unknown.reported_hardfork = Some("FutureB".to_string());
181        assert!(ensure_remote_trace_context_unchanged(&before_unknown, &after_unknown).is_err());
182    }
183
184    #[test]
185    #[cfg(feature = "monad")]
186    fn remote_trace_hardfork_ignores_cross_network_override() {
187        let ethereum = FoundryHardfork::Ethereum(foundry_evm::hardforks::EthereumHardfork::Cancun);
188        let monad_eight = FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadEight);
189        let monad_nine = FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadNine);
190
191        assert_eq!(
192            select_remote_trace_hardfork(Some(ethereum), Some(monad_nine), NetworkVariant::Monad),
193            Some(monad_nine)
194        );
195        assert_eq!(
196            select_remote_trace_hardfork(
197                Some(monad_eight),
198                Some(monad_nine),
199                NetworkVariant::Monad
200            ),
201            Some(monad_eight)
202        );
203    }
204}