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