1use alloy_primitives::{Bytes, map::AddressHashMap};
2use foundry_cli::utils::{TraceResult, print_traces};
3use foundry_common::{
4 ContractsByArtifactBuilder, compile::ProjectCompiler, namespaced_storage, shell,
5};
6use foundry_compilers::artifacts::output_selection::ContractOutputSelection;
7use foundry_config::{Config, FoundryHardfork, TracingConfig};
8use foundry_debugger::DebuggerBuilder;
9use foundry_evm::{
10 opts::ForkEndpointIdentity,
11 traces::{
12 CallTraceDecoderBuilder, DebugTraceIdentifier, TraceContext,
13 debug::ContractSources,
14 erc8021::Attribution,
15 identifier::{SignaturesIdentifier, TraceIdentifiers},
16 },
17};
18use foundry_evm_networks::NetworkVariant;
19use itertools::Itertools;
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 resolve_remote_trace_hardfork(
36 configured: Option<FoundryHardfork>,
37 endpoint: &ForkEndpointIdentity,
38 block_timestamp: Option<u64>,
39) -> Option<FoundryHardfork> {
40 select_remote_trace_hardfork(configured, endpoint.hardfork, endpoint.network).or_else(|| {
41 block_timestamp.and_then(|timestamp| {
42 FoundryHardfork::from_chain_and_timestamp(endpoint.source_chain_id, timestamp)
43 })
44 })
45}
46
47pub(crate) fn ensure_remote_trace_context_unchanged(
48 before: &ForkEndpointIdentity,
49 after: &ForkEndpointIdentity,
50) -> eyre::Result<()> {
51 if before != after {
52 eyre::bail!(
53 "the RPC endpoint changed execution context while the remote trace was being \
54 collected; retry the command"
55 );
56 }
57 Ok(())
58}
59
60pub(crate) async fn handle_traces(
62 mut result: TraceResult,
63 config: &Config,
64 context: TraceContext,
65 contracts_bytecode: &AddressHashMap<Bytes>,
66 tracing: &TracingConfig,
67 with_local_artifacts: bool,
68 debug: bool,
69) -> eyre::Result<()> {
70 let (known_contracts, mut sources) = if with_local_artifacts {
71 let _ = sh_status!("Compiling project to generate artifacts");
73 let mut config = config.clone();
74 if debug {
75 if !config.extra_output.contains(&ContractOutputSelection::StorageLayout) {
76 config.extra_output.push(ContractOutputSelection::StorageLayout);
77 }
78 if let Err(error) = config.enable_debug_build_info() {
81 let _ = sh_warn!(
82 "the debugger may show source files that differ from the compiled ones: {error:#}"
83 );
84 }
85 }
86 let project = config.project()?;
87 let compiler = ProjectCompiler::new();
88 let mut output = compiler.compile(&project)?;
89 let mut sources = ContractSources::from_project_output(&output, project.root(), None)?;
90 if debug {
91 sources.insert_compiled_sources(&project.paths.build_infos);
92 }
93 let storage_layouts =
94 namespaced_storage::project_storage_layouts(&mut output, project.root());
95 (
96 Some(
97 ContractsByArtifactBuilder::new(
98 output.artifact_ids().map(|(id, artifact)| (id, artifact.into())),
99 )
100 .with_storage_layouts(storage_layouts)
101 .build(),
102 ),
103 sources,
104 )
105 } else {
106 (None, ContractSources::default())
107 };
108
109 let mut builder = CallTraceDecoderBuilder::new()
110 .with_tracing_config(tracing)
111 .with_signature_identifier(SignaturesIdentifier::from_config(config)?)
112 .with_networks(context.networks())
113 .with_chain_id(Some(context.chain().id()))
114 .with_hardfork(context.hardfork());
115 let mut identifier = TraceIdentifiers::new().with_external(config, Some(context.chain()))?;
116 if let Some(contracts) = &known_contracts {
117 builder = builder.with_known_contracts(contracts);
118 identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode);
119 }
120
121 let mut decoder = builder.build();
122
123 for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() {
124 decoder.identify(trace, &mut identifier);
125 }
126
127 if tracing.decode_internal || debug {
128 if let Some(ref etherscan_identifier) = identifier.external {
129 sources.merge(etherscan_identifier.get_compiled_contracts().await?);
130 }
131
132 if debug {
133 let mut builder = DebuggerBuilder::new()
134 .traces(result.traces.expect("missing traces"))
135 .decoder(&decoder)
136 .sources(sources);
137 if let Some(known_contracts) = &known_contracts {
138 builder = builder.known_contracts(known_contracts);
139 }
140 return builder.run(None);
141 }
142
143 decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources));
144 }
145
146 let attributions = result
149 .traces
150 .iter()
151 .flatten()
152 .flat_map(|(_, arena)| arena.nodes())
153 .filter_map(|node| Attribution::decode(&node.trace.data))
154 .unique()
155 .collect::<Vec<_>>();
156
157 print_traces(
158 &mut result,
159 &decoder,
160 tracing.verbosity > 0,
161 tracing.verbosity > 4,
162 tracing.trace_depth,
163 )
164 .await?;
165
166 if !shell::is_json() {
167 for attribution in attributions {
168 sh_println!("ERC-8021 attribution: {attribution}")?;
169 }
170 }
171
172 Ok(())
173}
174
175#[cfg(all(test, feature = "monad"))]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn remote_trace_hardfork_ignores_cross_network_override() {
181 let ethereum = FoundryHardfork::Ethereum(foundry_evm::hardforks::EthereumHardfork::Cancun);
182 let monad_eight = FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadEight);
183 let monad_nine = FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadNine);
184
185 assert_eq!(
186 select_remote_trace_hardfork(Some(ethereum), Some(monad_nine), NetworkVariant::Monad),
187 Some(monad_nine)
188 );
189 assert_eq!(
190 select_remote_trace_hardfork(
191 Some(monad_eight),
192 Some(monad_nine),
193 NetworkVariant::Monad
194 ),
195 Some(monad_eight)
196 );
197 }
198}