Skip to main content

mega_evme/common/
trace.rs

1//! Trace configuration for mega-evme
2
3use 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/// Tracer type for execution analysis
28#[derive(Debug, Clone, Copy, ValueEnum, Default)]
29#[non_exhaustive]
30pub enum TracerType {
31    /// Enable execution tracing (opcode-level trace in Geth format)
32    #[default]
33    Opcode,
34    /// Enable call tracing (tracks call frames in nested tree structure)
35    Call,
36    /// Enable pre-state tracing (retrieves account state before execution)
37    #[value(alias = "prestate")]
38    PreState,
39}
40
41/// Trace configuration arguments
42#[derive(Parser, Debug, Clone)]
43#[command(next_help_heading = "Trace Options")]
44pub struct TraceArgs {
45    /// Enable tracing
46    #[arg(long = "trace")]
47    pub trace: bool,
48
49    /// Output file for trace data (if not specified, prints to console)
50    #[arg(long = "trace.output")]
51    pub trace_output_file: Option<PathBuf>,
52
53    /// Tracer type to use (defaults to struct logger if not specified)
54    #[arg(long = "tracer", value_enum, default_value_t = TracerType::Opcode)]
55    pub tracer: TracerType,
56
57    /// Disable memory capture in traces (opcode tracer only)
58    #[arg(long = "trace.opcode.disable-memory")]
59    pub trace_opcode_disable_memory: bool,
60
61    /// Disable stack capture in traces (opcode tracer only)
62    #[arg(long = "trace.opcode.disable-stack")]
63    pub trace_opcode_disable_stack: bool,
64
65    /// Disable storage capture in traces (opcode tracer only)
66    #[arg(long = "trace.opcode.disable-storage")]
67    pub trace_opcode_disable_storage: bool,
68
69    /// Enable return data capture in traces (opcode tracer only)
70    #[arg(long = "trace.opcode.enable-return-data")]
71    pub trace_opcode_enable_return_data: bool,
72
73    /// Only trace top-level call (call tracer only)
74    #[arg(long = "trace.call.only-top-call")]
75    pub trace_call_only_top_call: bool,
76
77    /// Include logs in call trace (call tracer only)
78    #[arg(long = "trace.call.with-log")]
79    pub trace_call_with_log: bool,
80
81    /// Show state diff instead of prestate (pre-state tracer only)
82    #[arg(long = "trace.prestate.diff-mode", visible_aliases = ["trace.pre-state.diff-mode"])]
83    pub trace_prestate_diff_mode: bool,
84
85    /// Disable code in prestate output (pre-state tracer only)
86    #[arg(long = "trace.prestate.disable-code", visible_aliases = ["trace.pre-state.disable-code"])]
87    pub trace_prestate_disable_code: bool,
88
89    /// Disable storage in prestate output (pre-state tracer only)
90    #[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    /// Returns true if tracing is enabled
96    pub fn is_tracing_enabled(&self) -> bool {
97        self.trace
98    }
99
100    /// Creates a [`TracingInspector`] configured for full tracing
101    pub fn create_inspector(&self) -> TracingInspector {
102        let config = TracingInspectorConfig::all();
103        TracingInspector::new(config)
104    }
105
106    /// Creates [`GethDefaultTracingOptions`] from CLI arguments
107    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    /// Creates [`CallConfig`] from CLI arguments
118    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    /// Creates [`PreStateConfig`] from CLI arguments
126    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    /// Generates a JSON trace string for the default tracer
135    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        // Get output for trace generation
145        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        // Generate the geth trace
152        let geth_trace =
153            geth_builder.geth_traces(exec_result.gas_used(), Bytes::from(output), opts);
154
155        // Format as JSON
156        serde_json::to_string_pretty(&geth_trace)
157            .unwrap_or_else(|e| format!("Error serializing trace: {}", e))
158    }
159
160    /// Generates a JSON trace string for the call tracer
161    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        // Generate the call trace
171        let call_frame: CallFrame = geth_builder.geth_call_traces(config, exec_result.gas_used());
172
173        // Format as JSON
174        serde_json::to_string_pretty(&call_frame)
175            .unwrap_or_else(|e| format!("Error serializing call trace: {}", e))
176    }
177
178    /// Generates a JSON trace string for the prestate tracer.
179    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        // Generate the prestate trace using the database
190        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    /// Generates a JSON trace string from inspector and execution result based on tracer type.
198    /// Note: For `PreState` tracer, use `generate_prestate_trace` directly with database access.
199    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    /// Execute transaction with optional tracing
216    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            // Execute with tracing inspector
228            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            // Generate trace string based on tracer type
237            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            // Execute without tracing
244            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}