geth_tracer/
types.rs

1// Copyright 2023-2024 Paradigm.xyz
2// This file is part of reth.
3// Reth is a modular, contributor-friendly and blazing-fast implementation of
4// the Ethereum protocol
5
6// Permission is hereby granted, free of charge, to any
7// person obtaining a copy of this software and associated
8// documentation files (the "Software"), to deal in the
9// Software without restriction, including without
10// limitation the rights to use, copy, modify, merge,
11// publish, distribute, sublicense, and/or sell copies of
12// the Software, and to permit persons to whom the Software
13// is furnished to do so, subject to the following
14// conditions:
15
16// The above copyright notice and this permission notice
17// shall be included in all copies or substantial portions
18// of the Software.
19
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
21// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
22// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
23// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
24// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
27// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28// DEALINGS IN THE SOFTWARE.
29
30//! Types for representing call trace items.
31use crate::{config::TraceStyle, utils, utils::convert_memory};
32use alloy_primitives::{Address, Bytes, LogData, U256};
33use alloy_rpc_types_trace::geth::{
34    CallFrame, CallLogFrame, GethDefaultTracingOptions, GethTrace, StructLog,
35};
36use cfx_types::{Space, H256};
37use cfx_vm_types::CallType as CfxCallType;
38use primitives::{block::BlockHeight, BlockNumber};
39
40use revm_bytecode::{opcode, OpCode};
41use revm_interpreter::{CallScheme, CreateScheme, InstructionResult};
42use std::collections::VecDeque;
43
44/// A trace of a call.
45#[derive(Clone, Debug, Default, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct CallTrace {
48    /// The depth of the call
49    pub depth: usize,
50    /// Whether the call was successful
51    pub success: bool,
52    /// caller of this call
53    pub caller: Address,
54    /// The destination address of the call or the address from the created
55    /// contract.
56    ///
57    /// In other words, this is the callee if the [CallKind::Call] or the
58    /// address of the created contract if [CallKind::Create].
59    pub address: Address,
60    /// Whether this is a call to a precompile
61    ///
62    /// Note: This is an Option because not all tracers make use of this
63    pub maybe_precompile: Option<bool>,
64    /// Holds the target for the __selfdestruct__ refund target
65    ///
66    /// This is only set if a selfdestruct was executed.
67    ///
68    /// Note: This not necessarily guarantees that the status is
69    /// [InstructionResult::SelfDestruct] There's an edge case where a new
70    /// created contract is immediately selfdestructed.
71    pub selfdestruct_refund_target: Option<Address>,
72    /// The kind of call this is
73    pub kind: CallKind,
74    /// The value transferred in the call
75    pub value: U256,
76    /// The calldata for the call, or the init code for contract creations
77    pub data: Bytes,
78    /// The return data of the call if this was not a contract creation,
79    /// otherwise it is the runtime bytecode of the created contract
80    pub output: Bytes,
81    /// The gas cost of the call
82    pub gas_used: u64,
83    /// The gas limit of the call
84    pub gas_limit: u64,
85    /// The status of the trace's call
86    pub status: Option<InstructionResult>,
87    /// Opcode-level execution steps
88    pub steps: Vec<CallTraceStep>,
89}
90
91impl CallTrace {
92    /// Returns true if the status code is an error or revert, See
93    /// [InstructionResult::Revert]
94    #[inline]
95    pub const fn is_error(&self) -> bool {
96        let Some(status) = self.status else {
97            return false;
98        };
99        !status.is_ok()
100    }
101
102    /// Returns true if the status code is a revert.
103    #[inline]
104    pub fn is_revert(&self) -> bool {
105        self.status
106            .is_some_and(|status| status == InstructionResult::Revert)
107    }
108
109    /// Returns the error message if it is an erroneous result.
110    pub(crate) fn as_error_msg(&self, kind: TraceStyle) -> Option<String> {
111        self.status
112            .and_then(|status| utils::fmt_error_msg(status, kind))
113    }
114}
115
116/// A node in the arena
117#[derive(Clone, Debug, Default, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119pub struct CallTraceNode {
120    /// Parent node index in the arena
121    pub parent: Option<usize>,
122    /// Children node indexes in the arena
123    pub children: Vec<usize>,
124    /// This node's index in the arena
125    pub idx: usize,
126    /// The call trace
127    pub trace: CallTrace,
128    /// Recorded logs, if enabled
129    pub logs: Vec<LogData>,
130    /// Ordering of child calls and logs
131    pub ordering: Vec<LogCallOrder>,
132}
133
134impl CallTraceNode {
135    /// Returns the call context's execution address
136    ///
137    /// See `Inspector::call` impl of
138    /// [TracingInspector](crate::tracing::TracingInspector)
139    pub const fn execution_address(&self) -> Address {
140        if self.trace.kind.is_delegate() {
141            self.trace.caller
142        } else {
143            self.trace.address
144        }
145    }
146
147    /// Pushes all steps onto the stack in reverse order
148    /// so that the first step is on top of the stack
149    pub(crate) fn push_steps_on_stack<'a>(
150        &'a self, stack: &mut VecDeque<CallTraceStepStackItem<'a>>,
151    ) {
152        stack.extend(self.call_step_stack().into_iter().rev());
153    }
154
155    /// Returns a list of all steps in this trace in the order they were
156    /// executed
157    ///
158    /// If the step is a call, the id of the child trace is set.
159    pub(crate) fn call_step_stack(&self) -> Vec<CallTraceStepStackItem<'_>> {
160        let mut stack = Vec::with_capacity(self.trace.steps.len());
161        let mut child_id = 0;
162        for step in self.trace.steps.iter() {
163            let mut item = CallTraceStepStackItem {
164                trace_node: self,
165                step,
166                call_child_id: None,
167            };
168
169            // If the opcode is a call, put the child trace on the stack
170            if step.is_calllike_op() {
171                // The opcode of this step is a call but it's possible that this
172                // step resulted in a revert or out of gas error in which case there's no actual child call executed and recorded: <https://github.com/paradigmxyz/reth/issues/3915>
173                if let Some(call_id) = self.children.get(child_id).copied() {
174                    item.call_child_id = Some(call_id);
175                    child_id += 1;
176                }
177            }
178            stack.push(item);
179        }
180        stack
181    }
182
183    /// Returns true if this is a call to a precompile
184    #[inline]
185    pub fn is_precompile(&self) -> bool {
186        self.trace.maybe_precompile.unwrap_or(false)
187    }
188
189    /// Returns the kind of call the trace belongs to
190    #[inline]
191    pub const fn kind(&self) -> CallKind { self.trace.kind }
192
193    /// Returns the status of the call
194    #[inline]
195    pub const fn status(&self) -> Option<InstructionResult> {
196        self.trace.status
197    }
198
199    /// Returns true if the call was a selfdestruct
200    ///
201    /// A selfdestruct is marked by the refund target being set.
202    ///
203    /// See also `TracingInspector::selfdestruct`
204    ///
205    /// Note: We can't rely in the [Self::status] being
206    /// [InstructionResult::SelfDestruct] because there's an edge case where
207    /// a new created contract (CREATE) is immediately selfdestructed.
208    #[inline]
209    pub const fn is_selfdestruct(&self) -> bool {
210        self.trace.selfdestruct_refund_target.is_some()
211    }
212
213    /// If the trace is a selfdestruct, returns the `CallFrame` for a geth call
214    /// trace
215    pub fn geth_selfdestruct_call_trace(&self) -> Option<CallFrame> {
216        if self.is_selfdestruct() {
217            Some(CallFrame {
218                typ: "SELFDESTRUCT".to_string(),
219                from: self.trace.caller,
220                to: self.trace.selfdestruct_refund_target,
221                value: Some(self.trace.value),
222                ..Default::default()
223            })
224        } else {
225            None
226        }
227    }
228
229    /// Converts this call trace into an _empty_ geth [CallFrame]
230    pub fn geth_empty_call_frame(&self, include_logs: bool) -> CallFrame {
231        let mut call_frame = CallFrame {
232            typ: self.trace.kind.to_string(),
233            from: self.trace.caller,
234            to: Some(self.trace.address),
235            value: Some(self.trace.value),
236            gas: U256::from(self.trace.gas_limit),
237            gas_used: U256::from(self.trace.gas_used),
238            input: self.trace.data.clone(),
239            output: (!self.trace.output.is_empty())
240                .then(|| self.trace.output.clone()),
241            error: None,
242            revert_reason: None,
243            calls: Default::default(),
244            logs: Default::default(),
245        };
246
247        if self.trace.kind.is_static_call() {
248            // STATICCALL frames don't have a value
249            call_frame.value = None;
250        }
251
252        // we need to populate error and revert reason
253        if !self.trace.success {
254            call_frame.revert_reason =
255                utils::maybe_revert_reason(self.trace.output.as_ref());
256
257            // Note: the call tracer mimics parity's trace transaction and geth maps errors to parity style error messages, <https://github.com/ethereum/go-ethereum/blob/34d507215951fb3f4a5983b65e127577989a6db8/eth/tracers/native/call_flat.go#L39-L55>
258            call_frame.error = self.trace.as_error_msg(TraceStyle::Parity);
259        }
260
261        if include_logs && !self.logs.is_empty() {
262            call_frame.logs = self
263                .logs
264                .iter()
265                .map(|log| CallLogFrame {
266                    address: Some(self.execution_address()),
267                    topics: Some(log.topics().to_vec()),
268                    data: Some(log.data.clone()),
269                    index: None,
270                    position: None,
271                })
272                .collect();
273        }
274
275        call_frame
276    }
277}
278
279/// A unified representation of a call.
280#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
281#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
282#[cfg_attr(feature = "serde", serde(rename_all = "UPPERCASE"))]
283pub enum CallKind {
284    /// Represents a regular call.
285    #[default]
286    Call,
287    /// Represents a static call.
288    StaticCall,
289    /// Represents a call code operation.
290    CallCode,
291    /// Represents a delegate call.
292    DelegateCall,
293    /// Represents a contract creation operation.
294    Create,
295    /// Represents a contract creation operation using the CREATE2 opcode.
296    Create2,
297}
298
299impl CallKind {
300    /// Returns true if the call is a create
301    #[inline]
302    pub const fn is_any_create(&self) -> bool {
303        matches!(self, Self::Create | Self::Create2)
304    }
305
306    /// Returns true if the call is a delegate of some sorts
307    #[inline]
308    pub const fn is_delegate(&self) -> bool {
309        matches!(self, Self::DelegateCall | Self::CallCode)
310    }
311
312    /// Returns true if the call is [CallKind::StaticCall].
313    #[inline]
314    pub const fn is_static_call(&self) -> bool {
315        matches!(self, Self::StaticCall)
316    }
317}
318
319impl std::fmt::Display for CallKind {
320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321        match self {
322            Self::Call => {
323                write!(f, "CALL")
324            }
325            Self::StaticCall => {
326                write!(f, "STATICCALL")
327            }
328            Self::CallCode => {
329                write!(f, "CALLCODE")
330            }
331            Self::DelegateCall => {
332                write!(f, "DELEGATECALL")
333            }
334            Self::Create => {
335                write!(f, "CREATE")
336            }
337            Self::Create2 => {
338                write!(f, "CREATE2")
339            }
340        }
341    }
342}
343
344impl From<CallScheme> for CallKind {
345    fn from(scheme: CallScheme) -> Self {
346        match scheme {
347            CallScheme::Call => Self::Call,
348            CallScheme::StaticCall => Self::StaticCall,
349            CallScheme::CallCode => Self::CallCode,
350            CallScheme::DelegateCall => Self::DelegateCall,
351        }
352    }
353}
354
355impl From<CreateScheme> for CallKind {
356    fn from(create: CreateScheme) -> Self {
357        match create {
358            CreateScheme::Create => Self::Create,
359            CreateScheme::Create2 { .. } => Self::Create2,
360            CreateScheme::Custom { .. } => unreachable!(), /* conflux do not support custom create scheme */
361        }
362    }
363}
364
365impl From<CfxCallType> for CallKind {
366    fn from(ct: CfxCallType) -> Self {
367        match ct {
368            CfxCallType::None => Self::Create,
369            CfxCallType::Call => Self::Call,
370            CfxCallType::CallCode => Self::CallCode,
371            CfxCallType::DelegateCall => Self::DelegateCall,
372            CfxCallType::StaticCall => Self::StaticCall,
373        }
374    }
375}
376
377pub(crate) struct CallTraceStepStackItem<'a> {
378    /// The trace node that contains this step
379    pub(crate) trace_node: &'a CallTraceNode,
380    /// The step that this stack item represents
381    pub(crate) step: &'a CallTraceStep,
382    /// The index of the child call in the CallArena if this step's opcode is a
383    /// call
384    pub(crate) call_child_id: Option<usize>,
385}
386
387/// Ordering enum for calls and logs
388#[derive(Clone, Copy, Debug, PartialEq, Eq)]
389#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
390pub enum LogCallOrder {
391    /// Contains the index of the corresponding log
392    Log(usize),
393    /// Contains the index of the corresponding trace node
394    Call(usize),
395}
396
397/// Represents a tracked call step during execution
398#[derive(Clone, Debug, PartialEq, Eq)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
400pub struct CallTraceStep {
401    // Fields filled in `step`
402    /// Call depth
403    pub depth: u64,
404    /// Program counter before step execution
405    pub pc: usize,
406    /// Opcode to be executed
407    #[cfg_attr(feature = "serde", serde(with = "opcode_serde"))]
408    pub op: OpCode,
409    /// Current contract address
410    pub contract: Address,
411    /// Stack before step execution
412    pub stack: Option<Vec<U256>>,
413    /// The new stack items placed by this step if any
414    pub push_stack: Option<Vec<U256>>,
415    /// All allocated memory in a step
416    ///
417    /// This will be empty if memory capture is disabled
418    pub memory: RecordedMemory,
419    /// Size of memory at the beginning of the step
420    pub memory_size: usize,
421    /// Remaining gas before step execution
422    pub gas_remaining: u64,
423    /// Gas refund counter before step execution
424    pub gas_refund_counter: u64,
425    // Fields filled in `step_end`
426    /// Gas cost of step execution
427    pub gas_cost: u64,
428    /// Change of the contract state after step execution (effect of the
429    /// SLOAD/SSTORE instructions)
430    pub storage_change: Option<StorageChange>,
431    /// Final status of the step
432    ///
433    /// This is set after the step was executed.
434    pub status: Option<InstructionResult>,
435}
436
437// === impl CallTraceStep ===
438
439impl CallTraceStep {
440    /// Converts this step into a geth [StructLog]
441    ///
442    /// This sets memory and stack capture based on the `opts` parameter.
443    pub(crate) fn convert_to_geth_struct_log(
444        &self, opts: &GethDefaultTracingOptions,
445    ) -> StructLog {
446        let mut log = StructLog {
447            depth: self.depth,
448            error: self.as_error(),
449            gas: self.gas_remaining,
450            gas_cost: self.gas_cost,
451            op: self.op.as_str().into(),
452            pc: self.pc as u64,
453            refund_counter: (self.gas_refund_counter > 0)
454                .then_some(self.gas_refund_counter),
455            // Filled, if not disabled manually
456            stack: None,
457            // Filled in `CallTraceArena::geth_trace` as a result of compounding
458            // all slot changes
459            return_data: None,
460            // Filled via trace object
461            storage: None,
462            // Only enabled if `opts.enable_memory` is true
463            memory: None,
464            // This is None in the rpc response
465            memory_size: None,
466        };
467
468        if opts.is_stack_enabled() {
469            log.stack.clone_from(&self.stack);
470        }
471
472        if opts.is_memory_enabled() {
473            log.memory = Some(self.memory.memory_chunks());
474        }
475
476        log
477    }
478
479    /// Returns true if the step is a STOP opcode
480    #[inline]
481    pub(crate) const fn is_stop(&self) -> bool {
482        matches!(self.op.get(), opcode::STOP)
483    }
484
485    /// Returns true if the step is a call operation, any of
486    /// CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE, CREATE2
487    #[inline]
488    pub(crate) const fn is_calllike_op(&self) -> bool {
489        matches!(
490            self.op.get(),
491            opcode::CALL
492                | opcode::DELEGATECALL
493                | opcode::STATICCALL
494                | opcode::CREATE
495                | opcode::CALLCODE
496                | opcode::CREATE2
497        )
498    }
499
500    // Returns true if the status code is an error or revert, See
501    // [InstructionResult::Revert]
502    #[inline]
503    pub(crate) const fn is_error(&self) -> bool {
504        let Some(status) = self.status else {
505            return false;
506        };
507        status.is_error()
508    }
509
510    /// Returns the error message if it is an erroneous result.
511    #[inline]
512    pub(crate) fn as_error(&self) -> Option<String> {
513        self.is_error().then(|| format!("{:?}", self.status))
514    }
515}
516
517/// Represents the source of a storage change - e.g., whether it came
518/// from an SSTORE or SLOAD instruction.
519#[allow(clippy::upper_case_acronyms)]
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
521#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
522pub enum StorageChangeReason {
523    /// SLOAD opcode
524    SLOAD,
525    /// SSTORE opcode
526    SSTORE,
527}
528
529/// Represents a storage change during execution.
530///
531/// This maps to evm internals:
532/// [JournalEntry::StorageChange](revm::JournalEntry::StorageChange)
533///
534/// It is used to track both storage change and warm load of a storage slot. For
535/// warm load in regard to EIP-2929 AccessList had_value will be None.
536#[derive(Clone, Copy, Debug, PartialEq, Eq)]
537#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
538pub struct StorageChange {
539    /// key of the storage slot
540    pub key: U256,
541    /// Current value of the storage slot
542    pub value: U256,
543    /// The previous value of the storage slot, if any
544    pub had_value: Option<U256>,
545    /// How this storage was accessed
546    pub reason: StorageChangeReason,
547}
548
549/// Represents the memory captured during execution
550///
551/// This is a wrapper around the [SharedMemory](revm::interpreter::SharedMemory)
552/// context memory.
553#[derive(Clone, Debug, Default, PartialEq, Eq)]
554#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
555pub struct RecordedMemory(pub(crate) Vec<u8>);
556
557impl RecordedMemory {
558    #[inline]
559    pub(crate) fn new(mem: Vec<u8>) -> Self { Self(mem) }
560
561    /// Returns the memory as a byte slice
562    #[inline]
563    pub fn as_bytes(&self) -> &[u8] { &self.0 }
564
565    #[inline]
566    pub(crate) fn resize(&mut self, size: usize) { self.0.resize(size, 0); }
567
568    /// Returns the size of the memory
569    #[inline]
570    pub fn len(&self) -> usize { self.0.len() }
571
572    /// Returns whether the memory is empty
573    #[inline]
574    pub fn is_empty(&self) -> bool { self.0.is_empty() }
575
576    /// Converts the memory into 32byte hex chunks
577    #[inline]
578    pub fn memory_chunks(&self) -> Vec<String> {
579        convert_memory(self.as_bytes())
580    }
581}
582
583impl AsRef<[u8]> for RecordedMemory {
584    fn as_ref(&self) -> &[u8] { self.as_bytes() }
585}
586
587pub struct GethTraceWithHash {
588    pub trace: GethTrace,
589    pub tx_hash: H256,
590    pub space: Space,
591}
592
593#[derive(Clone)]
594pub struct TxExecContext {
595    pub tx_gas_limit: u64,
596    pub block_number: BlockNumber,
597    pub block_height: BlockHeight,
598}
599
600#[cfg(feature = "serde")]
601mod opcode_serde {
602    use super::OpCode;
603    use serde::{Deserialize, Deserializer, Serializer};
604
605    pub(super) fn serialize<S>(
606        op: &OpCode, serializer: S,
607    ) -> Result<S::Ok, S::Error>
608    where S: Serializer {
609        serializer.serialize_u8(op.get())
610    }
611
612    pub(super) fn deserialize<'de, D>(
613        deserializer: D,
614    ) -> Result<OpCode, D::Error>
615    where D: Deserializer<'de> {
616        let op = u8::deserialize(deserializer)?;
617        Ok(OpCode::new(op).unwrap_or_else(|| {
618            OpCode::new(revm_interpreter::opcode::INVALID).unwrap()
619        }))
620    }
621}