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
28pub(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
56pub(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 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}