Skip to main content

revm_inspectors/tracing/
config.rs

1use alloy_primitives::{map::HashSet, U256};
2use alloy_rpc_types_trace::{
3    geth::{
4        erc7562::Erc7562Config, CallConfig, FlatCallConfig, GethDefaultTracingOptions,
5        PreStateConfig,
6    },
7    parity::TraceType,
8};
9use revm::bytecode::opcode::OpCode;
10
11/// 256 bits each marking whether an opcode should be included into steps trace or not.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13#[must_use]
14pub struct OpcodeFilter(U256);
15
16impl Default for OpcodeFilter {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22impl OpcodeFilter {
23    /// Returns a new [OpcodeFilter] that does not trace any opcodes.
24    #[inline]
25    pub const fn new() -> Self {
26        Self(U256::ZERO)
27    }
28
29    /// Returns whether steps with given [OpCode] should be traced.
30    #[inline]
31    pub fn is_enabled(&self, op: OpCode) -> bool {
32        self.0.bit(op.get() as usize)
33    }
34
35    /// Enables tracing of given [OpCode].
36    #[inline]
37    pub fn enable(&mut self, op: OpCode) -> &mut Self {
38        self.0.set_bit(op.get() as usize, true);
39        self
40    }
41
42    /// Enables tracing of given [OpCode].
43    #[inline]
44    pub const fn enabled(mut self, op: OpCode) -> Self {
45        let index = op.get() as usize;
46        let mut limbs = self.0.into_limbs();
47        let (limb, bit) = (index / 64, index % 64);
48        limbs[limb] |= 1 << bit;
49        self.0 = U256::from_limbs(limbs);
50        self
51    }
52}
53
54/// Gives guidance to the [TracingInspector](crate::tracing::TracingInspector).
55///
56/// Use [TracingInspectorConfig::default_parity] or [TracingInspectorConfig::default_geth] to get
57/// the default configs for specific styles of traces.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
59pub struct TracingInspectorConfig {
60    /// Whether to record every individual opcode level step.
61    pub record_steps: bool,
62    /// Whether to record individual memory snapshots.
63    pub record_memory_snapshots: bool,
64    /// Whether to record individual stack snapshots.
65    pub record_stack_snapshots: StackSnapshotType,
66    /// Whether to record state diffs.
67    pub record_state_diff: bool,
68    /// Whether to record returndata buffer snapshots.
69    pub record_returndata_snapshots: bool,
70    /// Optional filter for opcodes to record. If provided, only steps with opcode in this set will
71    /// be recorded.
72    pub record_opcodes_filter: Option<OpcodeFilter>,
73    /// Whether to ignore precompile calls.
74    pub exclude_precompile_calls: bool,
75    /// Whether to record logs
76    pub record_logs: bool,
77    /// Whether to record immediate bytes for opcodes.
78    pub record_immediate_bytes: bool,
79}
80
81impl TracingInspectorConfig {
82    /// Returns a config with everything enabled.
83    pub const fn all() -> Self {
84        Self {
85            record_steps: true,
86            record_memory_snapshots: true,
87            record_stack_snapshots: StackSnapshotType::All,
88            record_state_diff: true,
89            record_returndata_snapshots: true,
90            record_opcodes_filter: None,
91            exclude_precompile_calls: false,
92            record_logs: true,
93            record_immediate_bytes: true,
94        }
95    }
96
97    /// Returns a config with everything disabled.
98    pub const fn none() -> Self {
99        Self {
100            record_steps: false,
101            record_memory_snapshots: false,
102            record_stack_snapshots: StackSnapshotType::None,
103            record_state_diff: false,
104            record_returndata_snapshots: false,
105            exclude_precompile_calls: false,
106            record_logs: false,
107            record_opcodes_filter: None,
108            record_immediate_bytes: false,
109        }
110    }
111
112    /// Returns a config for parity style traces.
113    ///
114    /// This config does _not_ record opcode level traces and is suited for `trace_transaction`
115    pub const fn default_parity() -> Self {
116        Self {
117            record_steps: false,
118            record_memory_snapshots: false,
119            record_stack_snapshots: StackSnapshotType::None,
120            record_state_diff: false,
121            record_returndata_snapshots: false,
122            exclude_precompile_calls: true,
123            record_logs: false,
124            record_opcodes_filter: None,
125            record_immediate_bytes: false,
126        }
127    }
128
129    /// Returns the [`TracingInspectorConfig`] for [`TraceType::StateDiff`].
130    ///
131    /// This is the same as [`Self::default_parity`]
132    ///
133    /// Note: the parity statediffs can be populated entirely via the execution result, so we don't
134    /// need statediff recording
135    pub const fn parity_statediff() -> Self {
136        Self::default_parity()
137    }
138
139    /// Returns the [`TracingInspectorConfig`] for [`TraceType::VmTrace`].
140    pub const fn parity_vm_trace() -> Self {
141        Self::default_parity()
142            .set_steps(true)
143            .set_stack_snapshots(StackSnapshotType::Pushes)
144            .set_memory_snapshots(true)
145            // also need statediffs for recording altered storage in `VmExecutedOperation.store`
146            .set_state_diffs(true)
147    }
148
149    /// Returns a config for geth style traces.
150    ///
151    /// This config does _not_ record opcode level traces and is suited for `debug_traceTransaction`
152    ///
153    /// This will configure the default output of geth's default
154    /// [StructLogTracer](alloy_rpc_types_trace::geth::DefaultFrame).
155    pub const fn default_geth() -> Self {
156        Self {
157            record_steps: true,
158            record_memory_snapshots: false,
159            record_stack_snapshots: StackSnapshotType::Full,
160            record_state_diff: true,
161            record_returndata_snapshots: false,
162            exclude_precompile_calls: false,
163            record_logs: false,
164            record_opcodes_filter: None,
165            record_immediate_bytes: false,
166        }
167    }
168
169    /// Returns the [TracingInspectorConfig] depending on the enabled [TraceType]s
170    ///
171    /// Note: the parity statediffs can be populated entirely via the execution result, so we don't
172    /// need statediff recording
173    #[inline]
174    pub fn from_parity_config(trace_types: &HashSet<TraceType>) -> Self {
175        let needs_vm_trace = trace_types.contains(&TraceType::VmTrace);
176        let snap_type =
177            if needs_vm_trace { StackSnapshotType::Pushes } else { StackSnapshotType::None };
178        Self::default_parity()
179            .set_steps(needs_vm_trace)
180            .set_stack_snapshots(snap_type)
181            .set_memory_snapshots(needs_vm_trace)
182    }
183
184    /// Returns a config for geth style traces based on the given [GethDefaultTracingOptions].
185    ///
186    /// This will configure the output of geth's default
187    /// [StructLogTracer](alloy_rpc_types_trace::geth::DefaultFrame) according to the given config.
188    #[inline]
189    pub fn from_geth_config(config: &GethDefaultTracingOptions) -> Self {
190        Self {
191            record_memory_snapshots: config.enable_memory.unwrap_or_default(),
192            record_stack_snapshots: if config.disable_stack.unwrap_or_default() {
193                StackSnapshotType::None
194            } else {
195                StackSnapshotType::Full
196            },
197            record_state_diff: !config.disable_storage.unwrap_or_default(),
198            record_returndata_snapshots: config.is_return_data_enabled(),
199            ..Self::default_geth()
200        }
201    }
202
203    /// Returns a config for geth's [CallTracer](alloy_rpc_types_trace::geth::CallFrame).
204    ///
205    /// This returns [Self::none] and enables [TracingInspectorConfig::record_logs] if configured in
206    /// the given [CallConfig]
207    #[inline]
208    pub fn from_geth_call_config(config: &CallConfig) -> Self {
209        Self::none()
210            // call tracer is similar parity tracer with optional support for logs
211            .set_record_logs(config.with_log.unwrap_or_default())
212    }
213
214    /// Returns a config for geth's
215    /// [Erc7562Frame](alloy_rpc_types_trace::geth::erc7562::Erc7562Frame).
216    #[inline]
217    pub fn from_geth_erc7562_config(config: &Erc7562Config) -> Self {
218        Self::none()
219            // call tracer is similar parity tracer with optional support for logs
220            .set_record_logs(config.with_log.unwrap_or_default())
221            // need memory snapshots for keccak preimages
222            .set_memory_snapshots(true)
223            // need stack snapshots for keccak preimages
224            .set_stack_snapshots(StackSnapshotType::Full)
225            // need state diffs to capture SLOAD values in accessedSlots.reads
226            .set_state_diffs(true)
227            .steps()
228    }
229
230    /// Returns a config for geth's
231    /// [FlatCallTracer](alloy_rpc_types_trace::geth::call::FlatCallFrame).
232    ///
233    /// This returns [Self::default_parity] and sets
234    /// [TracingInspectorConfig::exclude_precompile_calls] if configured in the given
235    /// [FlatCallConfig]
236    #[inline]
237    pub fn from_flat_call_config(config: &FlatCallConfig) -> Self {
238        Self::default_parity()
239            // call tracer is similar parity tracer with optional support for logs
240            .set_exclude_precompile_calls(!config.include_precompiles.unwrap_or_default())
241    }
242
243    /// Returns a config for geth's [PrestateTracer](alloy_rpc_types_trace::geth::PreStateFrame).
244    ///
245    /// Note: This currently returns [Self::none] because the prestate tracer result currently
246    /// relies on the execution result entirely, see
247    /// [GethTraceBuilder::geth_prestate_traces](crate::tracing::geth::GethTraceBuilder::geth_prestate_traces)
248    #[inline]
249    pub const fn from_geth_prestate_config(_config: &PreStateConfig) -> Self {
250        Self::none()
251    }
252
253    /// Merge another config into this one.
254    #[inline]
255    pub fn merge(&mut self, other: Self) -> &mut Self {
256        self.record_steps |= other.record_steps;
257        self.record_memory_snapshots |= other.record_memory_snapshots;
258        self.record_stack_snapshots = other.record_stack_snapshots;
259        self.record_state_diff |= other.record_state_diff;
260        self.record_returndata_snapshots |= other.record_returndata_snapshots;
261        self.exclude_precompile_calls |= other.exclude_precompile_calls;
262        self.record_logs |= other.record_logs;
263        self.record_opcodes_filter = self.record_opcodes_filter.or(other.record_opcodes_filter);
264        self.record_immediate_bytes |= other.record_immediate_bytes;
265        self
266    }
267
268    /// Configure whether calls to precompiles should be ignored.
269    ///
270    /// If set to `true`, calls to precompiles without value transfers will be ignored.
271    pub const fn set_exclude_precompile_calls(mut self, exclude_precompile_calls: bool) -> Self {
272        self.exclude_precompile_calls = exclude_precompile_calls;
273        self
274    }
275
276    /// Disable recording of individual opcode level steps
277    pub const fn disable_steps(self) -> Self {
278        self.set_steps(false)
279    }
280
281    /// Enable recording of individual opcode level steps
282    pub const fn steps(self) -> Self {
283        self.set_steps(true)
284    }
285
286    /// Configure whether individual opcode level steps should be recorded
287    pub const fn set_steps(mut self, record_steps: bool) -> Self {
288        self.record_steps = record_steps;
289        self
290    }
291
292    /// Disable recording of individual memory snapshots
293    pub const fn disable_memory_snapshots(self) -> Self {
294        self.set_memory_snapshots(false)
295    }
296
297    /// Enable recording of individual memory snapshots
298    pub const fn memory_snapshots(self) -> Self {
299        self.set_memory_snapshots(true)
300    }
301
302    /// Configure whether the tracer should record memory snapshots
303    pub const fn set_memory_snapshots(mut self, record_memory_snapshots: bool) -> Self {
304        self.record_memory_snapshots = record_memory_snapshots;
305        self
306    }
307
308    /// Disable recording of individual stack snapshots
309    pub const fn disable_stack_snapshots(self) -> Self {
310        self.set_stack_snapshots(StackSnapshotType::None)
311    }
312
313    /// Enable recording of individual stack snapshots
314    pub const fn stack_snapshots(self) -> Self {
315        self.set_stack_snapshots(StackSnapshotType::Full)
316    }
317
318    /// Configure how the tracer should record stack snapshots
319    pub const fn set_stack_snapshots(mut self, record_stack_snapshots: StackSnapshotType) -> Self {
320        self.record_stack_snapshots = record_stack_snapshots;
321        self
322    }
323
324    /// Disable recording of state diffs
325    pub const fn disable_state_diffs(self) -> Self {
326        self.set_state_diffs(false)
327    }
328
329    /// Configure whether the tracer should record state diffs
330    pub const fn set_state_diffs(mut self, record_state_diff: bool) -> Self {
331        self.record_state_diff = record_state_diff;
332        self
333    }
334
335    /// Sets state diff recording to true.
336    ///
337    /// Also enables steps recording since state diff recording requires steps recording.
338    pub const fn with_state_diffs(self) -> Self {
339        self.set_steps_and_state_diffs(true)
340    }
341
342    /// Configure whether the tracer should record steps and state diffs.
343    ///
344    /// This is a convenience method for setting both [TracingInspectorConfig::set_steps] and
345    /// [TracingInspectorConfig::set_state_diffs] since tracking state diffs requires steps tracing.
346    pub const fn set_steps_and_state_diffs(mut self, steps_and_diffs: bool) -> Self {
347        self.record_steps = steps_and_diffs;
348        self.record_state_diff = steps_and_diffs;
349        self
350    }
351
352    /// Disable recording of individual logs
353    pub const fn disable_record_logs(self) -> Self {
354        self.set_record_logs(false)
355    }
356
357    /// Enable recording of individual logs
358    pub const fn record_logs(self) -> Self {
359        self.set_record_logs(true)
360    }
361
362    /// Configure whether the tracer should record logs
363    pub const fn set_record_logs(mut self, record_logs: bool) -> Self {
364        self.record_logs = record_logs;
365        self
366    }
367
368    /// Configure whether the tracer should record immediate bytes
369    pub const fn set_immediate_bytes(mut self, record_immediate_bytes: bool) -> Self {
370        self.record_immediate_bytes = record_immediate_bytes;
371        self
372    }
373
374    /// Enable recording of immediate bytes
375    pub const fn record_immediate_bytes(self) -> Self {
376        self.set_immediate_bytes(true)
377    }
378
379    /// If [OpcodeFilter] is configured, returns whether the given opcode should be recorded.
380    /// Otherwise, always returns true.
381    #[inline]
382    pub fn should_record_opcode(&self, op: OpCode) -> bool {
383        self.record_opcodes_filter.as_ref().is_none_or(|filter| filter.is_enabled(op))
384    }
385}
386
387/// How much of the stack to record. Nothing, just the items pushed, or the full stack
388#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
389pub enum StackSnapshotType {
390    /// Don't record stack snapshots
391    #[default]
392    None,
393    /// Record full, push stack
394    All,
395    /// Record only the items pushed to the stack
396    Pushes,
397    /// Record the full stack
398    Full,
399}
400
401impl StackSnapshotType {
402    /// Returns true if this is the [StackSnapshotType::All] variant
403    #[inline]
404    pub const fn is_all(self) -> bool {
405        matches!(self, Self::All)
406    }
407
408    /// Returns true if this is the [StackSnapshotType::Full] variant
409    #[inline]
410    pub const fn is_full(self) -> bool {
411        matches!(self, Self::Full)
412    }
413
414    /// Returns true if this is the [StackSnapshotType::Pushes] variant
415    #[inline]
416    pub const fn is_pushes(self) -> bool {
417        matches!(self, Self::Pushes)
418    }
419}
420
421/// What kind of tracing style this is.
422///
423/// This affects things like error messages.
424#[derive(Clone, Copy, Debug, PartialEq, Eq)]
425pub(crate) enum TraceStyle {
426    /// Parity style tracer
427    Parity,
428    /// Geth style tracer
429    #[allow(dead_code)]
430    Geth,
431}
432
433impl TraceStyle {
434    /// Returns true if this is a parity style tracer.
435    pub(crate) const fn is_parity(self) -> bool {
436        matches!(self, Self::Parity)
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_parity_config() {
446        let mut s = HashSet::default();
447        s.insert(TraceType::StateDiff);
448        let config = TracingInspectorConfig::from_parity_config(&s);
449        // not required
450        assert!(!config.record_steps);
451        assert!(!config.record_state_diff);
452
453        let mut s = HashSet::default();
454        s.insert(TraceType::VmTrace);
455        let config = TracingInspectorConfig::from_parity_config(&s);
456        assert!(config.record_steps);
457        assert!(!config.record_state_diff);
458
459        let mut s = HashSet::default();
460        s.insert(TraceType::VmTrace);
461        s.insert(TraceType::StateDiff);
462        let config = TracingInspectorConfig::from_parity_config(&s);
463        assert!(config.record_steps);
464        // not required for StateDiff
465        assert!(!config.record_state_diff);
466    }
467
468    #[test]
469    fn test_flat_call_config() {
470        let config = FlatCallConfig { include_precompiles: Some(true), ..Default::default() };
471        let config = TracingInspectorConfig::from_flat_call_config(&config);
472        assert!(!config.exclude_precompile_calls);
473
474        let config = FlatCallConfig { include_precompiles: Some(false), ..Default::default() };
475        let config = TracingInspectorConfig::from_flat_call_config(&config);
476        assert!(config.exclude_precompile_calls);
477    }
478
479    #[test]
480    fn test_all_records_all_stack_snapshots() {
481        // `all()` enables everything, so the stack snapshot type should be the most
482        // complete variant (`All` = full stack + pushes), not `Full` (full only).
483        let config = TracingInspectorConfig::all();
484        assert_eq!(config.record_stack_snapshots, StackSnapshotType::All);
485        assert!(config.record_stack_snapshots.is_all());
486    }
487}