geth_tracer/
geth_tracer.rs

1use crate::{
2    config::TracingInspectorConfig,
3    fourbyte::FourByteInspector,
4    tracing_inspector::TracingInspector,
5    types::{LogCallOrder, TxExecContext},
6    utils::{to_alloy_address, to_alloy_h256, to_alloy_u256},
7};
8use alloy_primitives::{Address, Bytes, LogData};
9use alloy_rpc_types_trace::geth::{
10    CallConfig, GethDebugBuiltInTracerType, GethDebugBuiltInTracerType::*,
11    GethDebugTracerType, GethDebugTracingOptions, GethTrace, NoopFrame,
12    PreStateConfig,
13};
14use cfx_executor::{
15    machine::Machine,
16    observer::{
17        CallTracer, CheckpointTracer, DrainTrace, InternalTransferTracer,
18        OpcodeTracer, SetAuthTracer, StorageTracer,
19    },
20    stack::{FrameResult, FrameReturn},
21};
22use cfx_types::{Space, H160};
23use cfx_vm_types::{ActionParams, CallType, Error, InterpreterInfo};
24use revm::{database::InMemoryDB, state::EvmState as State};
25use revm_interpreter::{Gas, InstructionResult, InterpreterResult};
26
27use std::sync::Arc;
28
29pub struct GethTracer {
30    inner: TracingInspector,
31    //
32    fourbyte_inspector: FourByteInspector,
33    //
34    tx_gas_limit: u64, // tx level gas limit
35    //
36    gas_left: u64, // update in call_result/create_result
37    // call depth
38    depth: usize,
39    //
40    opts: GethDebugTracingOptions,
41    // gas stack, used to trace gas_spent in call_result/create_result
42    pub gas_stack: Vec<u64>,
43}
44
45impl GethTracer {
46    pub fn new(
47        tx_exec_context: TxExecContext, machine: Arc<Machine>,
48        opts: GethDebugTracingOptions,
49    ) -> Self {
50        let TxExecContext { tx_gas_limit, .. } = tx_exec_context;
51        let config = match opts.tracer {
52            Some(GethDebugTracerType::BuiltInTracer(builtin_tracer)) => {
53                match builtin_tracer {
54                    FourByteTracer | NoopTracer | MuxTracer => {
55                        TracingInspectorConfig::none()
56                    }
57                    CallTracer => {
58                        let c = opts
59                            .tracer_config
60                            .clone()
61                            .into_call_config()
62                            .expect("should success");
63                        TracingInspectorConfig::from_geth_call_config(&c)
64                    }
65                    FlatCallTracer => TracingInspectorConfig::none(),
66                    PreStateTracer => {
67                        let c = opts
68                            .tracer_config
69                            .clone()
70                            .into_pre_state_config()
71                            .expect("should success");
72                        TracingInspectorConfig::from_geth_prestate_config(&c)
73                    }
74                    Erc7562Tracer => TracingInspectorConfig::none(),
75                }
76            }
77            Some(GethDebugTracerType::JsTracer(_)) => {
78                TracingInspectorConfig::none()
79            }
80            None => TracingInspectorConfig::from_geth_config(&opts.config),
81        };
82
83        Self {
84            inner: TracingInspector::new(config, machine, tx_exec_context),
85            fourbyte_inspector: FourByteInspector::new(),
86            tx_gas_limit,
87            depth: 0,
88            gas_left: tx_gas_limit,
89            opts,
90            gas_stack: Vec::new(),
91        }
92    }
93
94    fn tracer_type(&self) -> Option<GethDebugBuiltInTracerType> {
95        match self.opts.tracer.clone() {
96            Some(t) => match t {
97                GethDebugTracerType::BuiltInTracer(builtin_tracer) => {
98                    Some(builtin_tracer)
99                }
100                GethDebugTracerType::JsTracer(_) => {
101                    // not supported
102                    Some(NoopTracer)
103                }
104            },
105            None => None,
106        }
107    }
108
109    fn call_config(&self) -> Option<CallConfig> {
110        self.opts.tracer_config.clone().into_call_config().ok()
111    }
112
113    fn prestate_config(&self) -> Option<PreStateConfig> {
114        self.opts.tracer_config.clone().into_pre_state_config().ok()
115    }
116
117    pub fn is_fourbyte_tracer(&self) -> bool {
118        self.tracer_type() == Some(FourByteTracer)
119    }
120
121    pub fn gas_used(&self) -> u64 { self.tx_gas_limit - self.gas_left }
122
123    pub fn drain(self) -> GethTrace {
124        let trace = match self.tracer_type() {
125            Some(t) => match t {
126                FourByteTracer => self.fourbyte_inspector.drain(),
127                CallTracer => {
128                    let gas_used = self.gas_used();
129                    let opts = self.call_config().expect("should have config");
130                    let frame = self
131                        .inner
132                        .into_geth_builder()
133                        .geth_call_traces(opts, gas_used);
134                    GethTrace::CallTracer(frame)
135                }
136                PreStateTracer => {
137                    // TODO replace the empty state and db with a real state
138                    let opts =
139                        self.prestate_config().expect("should have config");
140                    let state = State::default();
141                    let db = InMemoryDB::default();
142                    let frame = self
143                        .inner
144                        .into_geth_builder()
145                        .geth_prestate_traces(state, opts, db)
146                        .unwrap();
147                    GethTrace::PreStateTracer(frame)
148                }
149                NoopTracer | MuxTracer | FlatCallTracer | Erc7562Tracer => {
150                    GethTrace::NoopTracer(NoopFrame::default())
151                }
152            },
153            None => {
154                let gas_used = self.gas_used();
155                let return_value = self
156                    .inner
157                    .last_call_return_data
158                    .clone()
159                    .unwrap_or_default();
160                let opts = self.opts.config;
161                let frame = self.inner.into_geth_builder().geth_traces(
162                    gas_used,
163                    return_value,
164                    opts,
165                );
166                GethTrace::Default(frame)
167            }
168        };
169
170        trace
171    }
172}
173
174impl DrainTrace for GethTracer {
175    fn drain_trace(self, map: &mut typemap::ShareDebugMap) {
176        map.insert::<GethTraceKey>(self.drain());
177    }
178}
179
180pub struct GethTraceKey;
181
182impl typemap::Key for GethTraceKey {
183    type Value = GethTrace;
184}
185
186impl CheckpointTracer for GethTracer {}
187
188impl InternalTransferTracer for GethTracer {}
189
190impl StorageTracer for GethTracer {}
191
192impl SetAuthTracer for GethTracer {}
193
194impl CallTracer for GethTracer {
195    fn record_call(&mut self, params: &ActionParams) {
196        if self.is_fourbyte_tracer() {
197            self.fourbyte_inspector.record_call(params);
198            return;
199        }
200
201        let gas_limit = params.gas.as_u64();
202        self.gas_stack.push(gas_limit);
203
204        // determine correct `from` and `to` based on the call scheme
205        let (from, to) = match params.call_type {
206            CallType::DelegateCall | CallType::CallCode => {
207                (params.address, params.code_address)
208            }
209            _ => (params.sender, params.address),
210        };
211
212        let value = if matches!(params.call_type, CallType::DelegateCall)
213            && self.inner.active_trace().is_some()
214        {
215            // for delegate calls we need to use the value of the top trace
216            let parent = self.inner.active_trace().unwrap();
217            parent.trace.value
218        } else {
219            to_alloy_u256(params.value.value())
220        };
221
222        // if calls to precompiles should be excluded, check whether this is a
223        // call to a precompile
224        let maybe_precompile =
225            self.inner.config.exclude_precompile_calls.then(|| {
226                self.inner.is_precompile_call(&to, value, params.space)
227            });
228
229        let to = to_alloy_address(to);
230        let from = to_alloy_address(from);
231        self.inner.start_trace_on_call(
232            to,
233            params.data.clone().unwrap_or_default().into(),
234            value,
235            params.call_type.into(),
236            from,
237            params.gas.as_u64(),
238            maybe_precompile,
239            self.tx_gas_limit,
240            self.depth,
241        );
242
243        self.depth += 1;
244    }
245
246    fn record_call_result(&mut self, result: &FrameResult) {
247        if self.is_fourbyte_tracer() {
248            return;
249        }
250
251        self.depth -= 1;
252        let mut gas_spent = self.gas_stack.pop().expect("should have value");
253
254        if let Ok(r) = result {
255            gas_spent = gas_spent - r.gas_left.as_u64();
256            self.gas_left = r.gas_left.as_u64();
257        }
258
259        let instruction_result = to_instruction_result(result);
260
261        if instruction_result.is_error() {
262            self.inner.gas_inspector.set_gas_remainning(0);
263        }
264
265        let output = result
266            .as_ref()
267            .map(|f| Bytes::from(f.return_data.to_vec()))
268            .unwrap_or_default();
269
270        let outcome = InterpreterResult {
271            result: instruction_result,
272            output,
273            gas: Gas::default(),
274        };
275
276        self.inner.fill_trace_on_call_end(outcome, None, gas_spent);
277    }
278
279    fn record_create(&mut self, params: &ActionParams) {
280        if self.is_fourbyte_tracer() {
281            return;
282        }
283
284        let gas_limit = params.gas.as_u64();
285        self.gas_stack.push(gas_limit);
286
287        let value = if matches!(params.call_type, CallType::DelegateCall) {
288            // for delegate calls we need to use the value of the top trace
289            if let Some(parent) = self.inner.active_trace() {
290                parent.trace.value
291            } else {
292                to_alloy_u256(params.value.value())
293            }
294        } else {
295            to_alloy_u256(params.value.value())
296        };
297
298        self.inner.start_trace_on_call(
299            Address::default(), // call_result will set this address
300            params.data.clone().unwrap_or_default().into(),
301            value,
302            params.call_type.into(),
303            to_alloy_address(params.sender),
304            params.gas.as_u64(),
305            Some(false),
306            params.gas.as_u64(),
307            self.depth,
308        );
309
310        self.depth += 1;
311    }
312
313    fn record_create_result(&mut self, result: &FrameResult) {
314        if self.is_fourbyte_tracer() {
315            return;
316        }
317
318        self.depth -= 1;
319        let mut gas_spent = self.gas_stack.pop().expect("should have value");
320
321        if let Ok(r) = result {
322            gas_spent = gas_spent - r.gas_left.as_u64();
323            self.gas_left = r.gas_left.as_u64();
324        }
325
326        let instruction_result = to_instruction_result(result);
327
328        if instruction_result.is_error() {
329            self.inner.gas_inspector.set_gas_remainning(0);
330        }
331
332        let output = result
333            .as_ref()
334            .map(|f| Bytes::from(f.return_data.to_vec()))
335            .unwrap_or_default();
336
337        let outcome = InterpreterResult {
338            result: instruction_result,
339            output,
340            gas: Gas::default(),
341        };
342
343        let create_address =
344            if let Ok(FrameReturn { create_address, .. }) = result {
345                create_address.as_ref().map(|h| to_alloy_address(*h))
346            } else {
347                None
348            };
349
350        self.inner
351            .fill_trace_on_call_end(outcome, create_address, gas_spent);
352    }
353}
354
355impl OpcodeTracer for GethTracer {
356    fn do_trace_opcode(&self, enabled: &mut bool) {
357        if self.inner.config.record_steps {
358            *enabled |= true;
359        }
360    }
361
362    fn initialize_interp(&mut self, gas_limit: cfx_types::U256) {
363        self.inner
364            .gas_inspector
365            .set_gas_remainning(gas_limit.as_u64());
366    }
367
368    fn step(&mut self, interp: &dyn InterpreterInfo) {
369        self.inner
370            .gas_inspector
371            .set_gas_remainning(interp.gas_remainning().as_u64());
372
373        if self.inner.config.record_steps {
374            self.inner.start_step(interp, self.depth as u64);
375        }
376    }
377
378    fn step_end(&mut self, interp: &dyn InterpreterInfo) {
379        let remainning = interp.gas_remainning().as_u64();
380        let last_gas_cost = self
381            .inner
382            .gas_inspector
383            .gas_remaining()
384            .saturating_sub(remainning);
385        self.inner.gas_inspector.set_gas_remainning(remainning);
386        self.inner.gas_inspector.set_last_gas_cost(last_gas_cost);
387
388        // trace
389        if self.inner.config.record_steps {
390            self.inner.fill_step_on_step_end(interp);
391        }
392    }
393
394    fn log(
395        &mut self, _address: &cfx_types::Address,
396        topics: &Vec<cfx_types::H256>, data: &[u8],
397    ) {
398        if self.inner.config.record_logs {
399            let trace_idx = self.inner.last_trace_idx();
400            let trace = &mut self.inner.traces.arena[trace_idx];
401            trace.ordering.push(LogCallOrder::Log(trace.logs.len()));
402            trace.logs.push(LogData::new_unchecked(
403                topics.iter().map(|f| to_alloy_h256(*f)).collect(),
404                Bytes::from(data.to_vec()),
405            ));
406        }
407    }
408
409    fn selfdestruct(
410        &mut self, space: Space, _contract: &cfx_types::Address,
411        target: &cfx_types::Address, _value: cfx_types::U256,
412    ) {
413        if self.is_fourbyte_tracer() {
414            return;
415        }
416
417        let trace_idx = self.inner.last_trace_idx();
418        let trace = &mut self.inner.traces.arena[trace_idx].trace;
419        trace.selfdestruct_refund_target =
420            Some(to_alloy_address(*target as H160))
421    }
422}
423
424pub fn to_instruction_result(frame_result: &FrameResult) -> InstructionResult {
425    let result = match frame_result {
426        Ok(r) => match r.apply_state {
427            true => InstructionResult::Return,
428            false => InstructionResult::Revert,
429        },
430        Err(err) => match err {
431            Error::OutOfGas => InstructionResult::OutOfGas,
432            Error::BadJumpDestination { .. } => InstructionResult::InvalidJump,
433            Error::BadInstruction { .. } => InstructionResult::OpcodeNotFound,
434            Error::StackUnderflow { .. } => InstructionResult::StackUnderflow,
435            Error::OutOfStack { .. } => InstructionResult::StackOverflow,
436            Error::SubStackUnderflow { .. } => {
437                InstructionResult::StackUnderflow
438            }
439            Error::OutOfSubStack { .. } => InstructionResult::StackOverflow,
440            Error::InvalidSubEntry => InstructionResult::NotActivated, //
441            Error::NotEnoughBalanceForStorage { .. } => {
442                InstructionResult::OutOfFunds
443            }
444            Error::ExceedStorageLimit => InstructionResult::OutOfGas, /* treat storage as gas */
445            Error::BuiltIn(_) => InstructionResult::PrecompileError,
446            Error::InternalContract(_) => InstructionResult::PrecompileError, /* treat internalContract as builtIn */
447            Error::MutableCallInStaticContext => {
448                InstructionResult::StateChangeDuringStaticCall
449            }
450            Error::StateDbError(_) => InstructionResult::FatalExternalError,
451            Error::Wasm(_) => InstructionResult::NotActivated,
452            Error::OutOfBounds => InstructionResult::OutOfOffset,
453            Error::Reverted => InstructionResult::Revert,
454            Error::InvalidAddress(_) => InstructionResult::Revert, /* when selfdestruct refund */
455            // address is invalid will emit this error
456            Error::ConflictAddress(_) => InstructionResult::CreateCollision,
457            Error::CreateContractStartingWithEF => {
458                InstructionResult::CreateContractStartingWithEF
459            }
460            Error::CreateInitCodeSizeLimit => {
461                InstructionResult::CreateInitCodeSizeLimit
462            }
463            Error::NonceOverflow(_) => InstructionResult::NonceOverflow,
464        },
465    };
466    result
467}