Skip to main content

ethrex_levm/
tracing.rs

1pub use crate::opcode_tracer::{LevmOpcodeTracer, OpcodeTracerConfig};
2use crate::{
3    errors::{ContextResult, InternalError, TxResult, VMError},
4    vm::VM,
5};
6use bytes::Bytes;
7use ethrex_common::{
8    Address, U256,
9    tracing::{CallLog, CallTraceFrame, CallType},
10    types::Log,
11};
12
13/// Geth's callTracer (https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers)
14/// Use `LevmCallTracer::disabled()` when tracing is not wanted.
15#[derive(Debug, Default)]
16pub struct LevmCallTracer {
17    /// Stack for tracer callframes, at the end of execution there will be only one element.
18    pub callframes: Vec<CallTraceFrame>,
19    /// If true, trace only the top call (a.k.a. the external transaction)
20    pub only_top_call: bool,
21    /// If true, trace logs
22    pub with_log: bool,
23    /// If active is set to false it won't trace.
24    pub active: bool,
25}
26
27impl LevmCallTracer {
28    pub fn new(only_top_call: bool, with_log: bool) -> Self {
29        LevmCallTracer {
30            callframes: vec![],
31            only_top_call,
32            with_log,
33            active: true,
34        }
35    }
36
37    /// This is to keep LEVM's code clean, like `self.tracer.enter(...)`,
38    /// instead of something more complex or uglier when we don't want to trace.
39    /// (For now that we only implement one tracer it may be the most convenient solution)
40    pub fn disabled() -> Self {
41        LevmCallTracer {
42            active: false,
43            ..Default::default()
44        }
45    }
46
47    /// Starts trace call.
48    pub fn enter(
49        &mut self,
50        call_type: CallType,
51        from: Address,
52        to: Address,
53        value: U256,
54        gas: u64,
55        input: &Bytes, // For avoiding cloning when calling (cleaner code)
56    ) {
57        if !self.active {
58            return;
59        }
60        if self.only_top_call && !self.callframes.is_empty() {
61            // Only create callframe if it's the first one to be created.
62            return;
63        }
64
65        let callframe = CallTraceFrame {
66            call_type,
67            from,
68            to,
69            value,
70            gas,
71            input: input.clone(),
72            ..Default::default()
73        };
74
75        self.callframes.push(callframe);
76    }
77
78    /// Exits trace call.
79    /// Has no validations because it's a private method.
80    fn exit(
81        &mut self,
82        gas_used: u64,
83        output: Bytes,
84        error: Option<String>,
85        revert_reason: Option<String>,
86    ) -> Result<(), InternalError> {
87        let mut callframe = self.callframes.pop().ok_or(InternalError::CallFrame)?;
88
89        process_output(&mut callframe, gas_used, output, error, revert_reason);
90
91        // Append executed callframe to parent callframe if appropriate.
92        if let Some(parent_callframe) = self.callframes.last_mut() {
93            parent_callframe.calls.push(callframe);
94        } else {
95            self.callframes.push(callframe);
96        };
97        Ok(())
98    }
99
100    /// Exits trace call using the ContextResult.
101    pub fn exit_context(
102        &mut self,
103        ctx_result: &ContextResult,
104        is_top_call: bool,
105    ) -> Result<(), InternalError> {
106        if !self.active {
107            return Ok(());
108        }
109        if self.only_top_call && !is_top_call {
110            // We just want to register top call
111            return Ok(());
112        }
113        if is_top_call {
114            // After finishing transaction execution clear all logs of callframes that reverted.
115            clear_reverted_logs(self.current_callframe_mut()?);
116        }
117        let (gas_used, output) = (ctx_result.gas_used, ctx_result.output.clone());
118
119        let (error, revert_reason) = match ctx_result.result {
120            TxResult::Revert(ref err) => {
121                let reason = String::from_utf8(ctx_result.output.to_vec()).ok();
122                (Some(err.to_string()), reason)
123            }
124            _ => (None, None),
125        };
126
127        self.exit(gas_used, output, error, revert_reason)
128    }
129
130    /// Exits trace call when CALL or CREATE opcodes return early or in case SELFDESTRUCT is called.
131    pub fn exit_early(
132        &mut self,
133        gas_used: u64,
134        error: Option<String>,
135    ) -> Result<(), InternalError> {
136        if !self.active || self.only_top_call {
137            return Ok(());
138        }
139        self.exit(gas_used, Bytes::new(), error, None)
140    }
141
142    /// Registers log when opcode log is executed.
143    /// Note: Logs of callframes that reverted will be removed at end of execution.
144    pub fn log(&mut self, log: &Log) -> Result<(), InternalError> {
145        if !self.active || !self.with_log {
146            return Ok(());
147        }
148        if self.only_top_call && self.callframes.len() > 1 {
149            // Register logs for top call only.
150            return Ok(());
151        }
152        let callframe = self.current_callframe_mut()?;
153
154        let log = CallLog {
155            address: log.address,
156            topics: log.topics.clone(),
157            data: log.data.clone(),
158            position: match callframe.calls.len().try_into() {
159                Ok(pos) => pos,
160                Err(_) => return Err(InternalError::TypeConversion),
161            },
162        };
163
164        callframe.logs.push(log);
165        Ok(())
166    }
167
168    fn current_callframe_mut(&mut self) -> Result<&mut CallTraceFrame, InternalError> {
169        self.callframes.last_mut().ok_or(InternalError::CallFrame)
170    }
171}
172
173fn process_output(
174    callframe: &mut CallTraceFrame,
175    gas_used: u64,
176    output: Bytes,
177    error: Option<String>,
178    revert_reason: Option<String>,
179) {
180    callframe.gas_used = gas_used;
181    callframe.output = output;
182    callframe.error = error;
183    callframe.revert_reason = revert_reason;
184}
185
186/// Clear logs of callframe if it reverted and repeat the same with its subcalls.
187fn clear_reverted_logs(callframe: &mut CallTraceFrame) {
188    if callframe.error.is_some() {
189        callframe.logs.clear();
190    }
191    for subcall in &mut callframe.calls {
192        clear_reverted_logs(subcall);
193    }
194}
195
196impl<'a> VM<'a> {
197    /// This method is intended to be accessed after transaction execution
198    pub fn get_trace_result(&mut self) -> Result<CallTraceFrame, VMError> {
199        self.tracer
200            .callframes
201            .pop()
202            .ok_or(InternalError::CallFrame.into())
203    }
204}