1use std::path::PathBuf;
4
5use alloy_primitives::Bytes;
6use alloy_rpc_types_trace::geth::{
7 CallConfig, CallFrame, GethDefaultTracingOptions, PreStateConfig,
8};
9use clap::{Parser, ValueEnum};
10use mega_evm::{
11 revm::{
12 context::{
13 result::{ExecutionResult, ResultAndState},
14 ContextTr,
15 },
16 database::DatabaseRef,
17 state::EvmState,
18 ExecuteEvm, InspectEvm,
19 },
20 MegaContext, MegaEvm, MegaHaltReason, MegaTransaction,
21};
22use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
23use tracing::{debug, info, trace};
24
25use super::{EvmeError, EvmeExternalEnvs, EvmeState};
26
27#[derive(Debug, Clone, Copy, ValueEnum, Default)]
29#[non_exhaustive]
30pub enum TracerType {
31 #[default]
33 Opcode,
34 Call,
36 #[value(alias = "prestate")]
38 PreState,
39}
40
41#[derive(Parser, Debug, Clone)]
43#[command(next_help_heading = "Trace Options")]
44pub struct TraceArgs {
45 #[arg(long = "trace")]
47 pub trace: bool,
48
49 #[arg(long = "trace.output")]
51 pub trace_output_file: Option<PathBuf>,
52
53 #[arg(long = "tracer", value_enum, default_value_t = TracerType::Opcode)]
55 pub tracer: TracerType,
56
57 #[arg(long = "trace.opcode.disable-memory")]
59 pub trace_opcode_disable_memory: bool,
60
61 #[arg(long = "trace.opcode.disable-stack")]
63 pub trace_opcode_disable_stack: bool,
64
65 #[arg(long = "trace.opcode.disable-storage")]
67 pub trace_opcode_disable_storage: bool,
68
69 #[arg(long = "trace.opcode.enable-return-data")]
71 pub trace_opcode_enable_return_data: bool,
72
73 #[arg(long = "trace.call.only-top-call")]
75 pub trace_call_only_top_call: bool,
76
77 #[arg(long = "trace.call.with-log")]
79 pub trace_call_with_log: bool,
80
81 #[arg(long = "trace.prestate.diff-mode", visible_aliases = ["trace.pre-state.diff-mode"])]
83 pub trace_prestate_diff_mode: bool,
84
85 #[arg(long = "trace.prestate.disable-code", visible_aliases = ["trace.pre-state.disable-code"])]
87 pub trace_prestate_disable_code: bool,
88
89 #[arg(long = "trace.prestate.disable-storage", visible_aliases = ["trace.pre-state.disable-storage"])]
91 pub trace_prestate_disable_storage: bool,
92}
93
94impl TraceArgs {
95 pub fn is_tracing_enabled(&self) -> bool {
97 self.trace
98 }
99
100 pub fn create_inspector(&self) -> TracingInspector {
102 let config = TracingInspectorConfig::all();
103 TracingInspector::new(config)
104 }
105
106 pub fn create_geth_options(&self) -> GethDefaultTracingOptions {
108 GethDefaultTracingOptions {
109 disable_storage: Some(self.trace_opcode_disable_storage),
110 disable_memory: Some(self.trace_opcode_disable_memory),
111 disable_stack: Some(self.trace_opcode_disable_stack),
112 enable_return_data: Some(self.trace_opcode_enable_return_data),
113 ..Default::default()
114 }
115 }
116
117 pub fn create_call_config(&self) -> CallConfig {
119 CallConfig {
120 only_top_call: Some(self.trace_call_only_top_call),
121 with_log: Some(self.trace_call_with_log),
122 }
123 }
124
125 pub fn create_prestate_config(&self) -> PreStateConfig {
127 PreStateConfig {
128 diff_mode: Some(self.trace_prestate_diff_mode),
129 disable_code: Some(self.trace_prestate_disable_code),
130 disable_storage: Some(self.trace_prestate_disable_storage),
131 }
132 }
133
134 fn generate_default_trace<HaltReason>(
136 &self,
137 inspector: &TracingInspector,
138 exec_result: &ExecutionResult<HaltReason>,
139 ) -> String {
140 let geth_builder = inspector.geth_builder();
141 let opts = self.create_geth_options();
142 debug!(opts = ?opts, "Generating default opcode trace");
143
144 let output = match exec_result {
146 ExecutionResult::Success { output, .. } => output.data().to_vec(),
147 ExecutionResult::Revert { output, .. } => output.to_vec(),
148 _ => Vec::new(),
149 };
150
151 let geth_trace =
153 geth_builder.geth_traces(exec_result.gas_used(), Bytes::from(output), opts);
154
155 serde_json::to_string_pretty(&geth_trace)
157 .unwrap_or_else(|e| format!("Error serializing trace: {}", e))
158 }
159
160 fn generate_call_trace<HaltReason>(
162 &self,
163 inspector: &TracingInspector,
164 exec_result: &ExecutionResult<HaltReason>,
165 ) -> String {
166 let geth_builder = inspector.geth_builder();
167 let config = self.create_call_config();
168 debug!(config = ?config, "Generating call trace");
169
170 let call_frame: CallFrame = geth_builder.geth_call_traces(config, exec_result.gas_used());
172
173 serde_json::to_string_pretty(&call_frame)
175 .unwrap_or_else(|e| format!("Error serializing call trace: {}", e))
176 }
177
178 fn generate_prestate_trace(
180 &self,
181 inspector: &TracingInspector,
182 result_and_state: &ResultAndState<MegaHaltReason>,
183 prestate: impl DatabaseRef,
184 ) -> String {
185 let geth_builder = inspector.geth_builder();
186 let config = self.create_prestate_config();
187 debug!(config = ?config, "Generating prestate trace");
188
189 match geth_builder.geth_prestate_traces(result_and_state, &config, prestate) {
191 Ok(prestate_frame) => serde_json::to_string_pretty(&prestate_frame)
192 .unwrap_or_else(|e| format!("Error serializing prestate trace: {}", e)),
193 Err(e) => format!("Error generating prestate trace: {:?}", e),
194 }
195 }
196
197 pub fn generate_trace(
200 &self,
201 inspector: &TracingInspector,
202 result_and_state: &ResultAndState<MegaHaltReason>,
203 prestate: impl DatabaseRef,
204 ) -> String {
205 info!(tracer = ?self.tracer, "Generating trace");
206 match self.tracer {
207 TracerType::Opcode => self.generate_default_trace(inspector, &result_and_state.result),
208 TracerType::Call => self.generate_call_trace(inspector, &result_and_state.result),
209 TracerType::PreState => {
210 self.generate_prestate_trace(inspector, result_and_state, prestate)
211 }
212 }
213 }
214
215 pub fn execute_transaction<N, P>(
217 &self,
218 evm_context: MegaContext<&mut EvmeState<N, P>, EvmeExternalEnvs>,
219 tx: MegaTransaction,
220 ) -> Result<(ExecutionResult<MegaHaltReason>, EvmState, Option<String>), EvmeError>
221 where
222 N: alloy_network::Network,
223 P: alloy_provider::Provider<N> + std::fmt::Debug,
224 {
225 if self.is_tracing_enabled() {
226 info!(tracer = ?self.tracer, "Evm executing with tracing");
227 let mut inspector = self.create_inspector();
229 let mut evm = MegaEvm::new(evm_context).with_inspector(&mut inspector);
230
231 let result_and_state = evm
232 .inspect_tx(tx)
233 .map_err(|e| EvmeError::ExecutionError(format!("EVM execution failed: {:?}", e)))?;
234 trace!(result_and_state = ?result_and_state, "Evm execution result and state");
235
236 let trace_str = self.generate_trace(evm.inspector, &result_and_state, evm.db_ref());
238 trace!(trace_str = ?trace_str, "Generated trace");
239
240 Ok((result_and_state.result, result_and_state.state, Some(trace_str)))
241 } else {
242 info!("Evm executing without tracing");
243 let mut evm = MegaEvm::new(evm_context);
245 let result_and_state = evm
246 .transact(tx)
247 .map_err(|e| EvmeError::ExecutionError(format!("EVM execution failed: {:?}", e)))?;
248 trace!(result_and_state = ?result_and_state, "Evm execution result and state");
249
250 Ok((result_and_state.result, result_and_state.state, None))
251 }
252 }
253}