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