Skip to main content

revive_utils/
lib.rs

1use alloy_primitives::{Address, B256, Bytes, Log, U256 as RU256};
2use foundry_evm_core::{Ecx, InspectorExt};
3use foundry_evm_traces::{
4    CallTraceArena, GethTraceBuilder, ParityTraceBuilder, TracingInspector, TracingInspectorConfig,
5};
6use polkadot_sdk::pallet_revive::evm::{CallTrace, CallType};
7use revm::{
8    Inspector,
9    context::{ContextTr, CreateScheme},
10    inspector::JournalExt,
11    interpreter::{
12        CallInputs, CallOutcome, CreateInputs, CreateOutcome, Gas, InstructionResult, Interpreter,
13        InterpreterResult,
14    },
15};
16
17/// A Wrapper around [TracingInspector] to allow adding zkEVM traces.
18#[derive(Clone, Debug, Default)]
19pub struct TraceCollector {
20    inner: TracingInspector,
21}
22
23impl TraceCollector {
24    /// Returns a new instance for the given config
25    pub fn new(config: TracingInspectorConfig) -> Self {
26        Self { inner: TracingInspector::new(config) }
27    }
28
29    /// Returns the inner [`TracingInspector`]
30    #[inline]
31    pub fn inner(&mut self) -> &mut TracingInspector {
32        &mut self.inner
33    }
34
35    /// Resets the inspector to its initial state of [Self::new].
36    /// This makes the inspector ready to be used again.
37    ///
38    /// Note that this method has no effect on the allocated capacity of the vector.
39    #[inline]
40    pub fn fuse(&mut self) {
41        self.inner.fuse()
42    }
43
44    /// Resets the inspector to it's initial state of [Self::new].
45    #[inline]
46    pub fn fused(self) -> Self {
47        Self { inner: self.inner.fused() }
48    }
49
50    /// Returns the config of the inspector.
51    pub const fn config(&self) -> &TracingInspectorConfig {
52        self.inner.config()
53    }
54
55    /// Returns a mutable reference to the config of the inspector.
56    pub fn config_mut(&mut self) -> &mut TracingInspectorConfig {
57        self.inner.config_mut()
58    }
59
60    /// Updates the config of the inspector.
61    pub fn update_config(
62        &mut self,
63        f: impl FnOnce(TracingInspectorConfig) -> TracingInspectorConfig,
64    ) {
65        self.inner.update_config(f);
66    }
67
68    /// Gets a reference to the recorded call traces.
69    pub const fn traces(&self) -> &CallTraceArena {
70        self.inner.traces()
71    }
72
73    /// Gets a mutable reference to the recorded call traces.
74    pub fn traces_mut(&mut self) -> &mut CallTraceArena {
75        self.inner.traces_mut()
76    }
77
78    /// Consumes the inspector and returns the recorded call traces.
79    pub fn into_traces(self) -> CallTraceArena {
80        self.inner.into_traces()
81    }
82
83    /// Manually the gas used of the root trace.
84    ///
85    /// This is useful if the root trace's gasUsed should mirror the actual gas used by the
86    /// transaction.
87    ///
88    /// This allows setting it manually by consuming the execution result's gas for example.
89    #[inline]
90    pub fn set_transaction_gas_used(&mut self, gas_used: u64) {
91        self.inner.set_transaction_gas_used(gas_used)
92    }
93
94    /// Convenience function for [ParityTraceBuilder::set_transaction_gas_used] that consumes the
95    /// type.
96    #[inline]
97    pub fn with_transaction_gas_used(self, gas_used: u64) -> Self {
98        Self { inner: self.inner.with_transaction_gas_used(gas_used) }
99    }
100
101    /// Consumes the Inspector and returns a [ParityTraceBuilder].
102    #[inline]
103    pub fn into_parity_builder(self) -> ParityTraceBuilder {
104        self.inner.into_parity_builder()
105    }
106
107    /// Consumes the Inspector and returns a [GethTraceBuilder].
108    #[inline]
109    pub fn into_geth_builder(self) -> GethTraceBuilder<'static> {
110        self.inner.into_geth_builder()
111    }
112}
113
114impl<CTX> Inspector<CTX> for TraceCollector
115where
116    CTX: ContextTr<Journal: JournalExt>,
117{
118    #[inline]
119    fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) {
120        self.inner.step(interp, context)
121    }
122
123    #[inline]
124    fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) {
125        self.inner.step_end(interp, context)
126    }
127
128    fn log(&mut self, interp: &mut Interpreter, context: &mut CTX, log: Log) {
129        self.inner.log(interp, context, log)
130    }
131
132    fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
133        self.inner.call(context, inputs)
134    }
135
136    fn call_end(&mut self, context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
137        self.inner.call_end(context, inputs, outcome)
138    }
139
140    fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
141        self.inner.create(context, inputs)
142    }
143
144    fn create_end(
145        &mut self,
146        context: &mut CTX,
147        inputs: &CreateInputs,
148        outcome: &mut CreateOutcome,
149    ) {
150        self.inner.create_end(context, inputs, outcome)
151    }
152
153    // EOF create hooks were removed in current revm version; only standard create is supported.
154
155    fn selfdestruct(&mut self, contract: Address, target: Address, value: RU256) {
156        <TracingInspector as Inspector<CTX>>::selfdestruct(&mut self.inner, contract, target, value)
157    }
158}
159
160impl InspectorExt for TraceCollector {
161    fn trace_revive(
162        &mut self,
163        context: Ecx<'_, '_, '_>,
164        call_traces: Box<dyn std::any::Any>,
165        record_top_call: bool,
166    ) {
167        let call_traces = *call_traces
168            .downcast::<CallTrace>()
169            .expect("TraceCollector::trace_revive expected call traces to be a CallTrace");
170        use revm::Inspector;
171        fn trace_call_recursive(
172            tracer: &mut TracingInspector,
173            context: Ecx<'_, '_, '_>,
174            call: CallTrace,
175            suppressed_top_call: bool,
176        ) -> u64 {
177            let inputs = &mut CallInputs {
178                input: revm::interpreter::CallInput::Bytes(call.input.0.clone().into()),
179                gas_limit: call.gas.try_into().unwrap_or(u64::MAX),
180                scheme: revm::interpreter::CallScheme::Call,
181                caller: call.from.0.into(),
182                value: revm::interpreter::CallValue::Transfer(RU256::from_be_bytes(
183                    call.value.unwrap_or_default().to_big_endian(),
184                )),
185                target_address: call.to.0.into(),
186                bytecode_address: call.to.0.into(),
187                is_static: false,
188                return_memory_offset: Default::default(),
189            };
190            let is_first_non_system_call = !suppressed_top_call;
191
192            // We ignore traces from system addresses, the default account abstraction calls on
193            // caller address, and the original call (identified when neither `to` or
194            // `from` are system addresses) since it is already included in EVM trace.
195            let record_trace =
196                !is_first_non_system_call && inputs.target_address != context.tx.caller;
197
198            let mut outcome = if let Some(reason) = &call.revert_reason {
199                CallOutcome {
200                    result: InterpreterResult {
201                        result: InstructionResult::Revert,
202                        output: reason.as_bytes().to_owned().into(),
203                        gas: Gas::new_spent(call.gas_used.as_u64()),
204                    },
205                    memory_offset: Default::default(),
206                }
207            } else {
208                CallOutcome {
209                    result: InterpreterResult {
210                        result: InstructionResult::Return,
211                        output: call.output.clone().0.into(),
212                        gas: Gas::new_spent(call.gas_used.as_u64()),
213                    },
214                    memory_offset: Default::default(),
215                }
216            };
217
218            let mut create_inputs =
219                if matches!(call.call_type, CallType::Create | CallType::Create2) {
220                    let scheme = match call.call_type {
221                        CallType::Create => CreateScheme::Create,
222                        CallType::Create2 => {
223                            // For Create2 traces from Polkadot Revive, the CallTrace doesn't
224                            // provide a separate salt field. We derive a salt by hashing the input
225                            // to ensure it fits in U256 and is deterministic.
226                            let salt =
227                                RU256::from_be_bytes(
228                                    B256::from_slice(
229                                        &alloy_primitives::keccak256::<&[u8]>(
230                                            call.input.0.as_ref(),
231                                        )[..],
232                                    )
233                                    .0,
234                                );
235                            CreateScheme::Create2 { salt }
236                        }
237                        _ => panic!("impossible"),
238                    };
239                    Some(CreateInputs {
240                        caller: inputs.caller,
241                        scheme,
242                        value: inputs.value.get(),
243                        init_code: inputs.input.bytes(context),
244                        gas_limit: inputs.gas_limit,
245                    })
246                } else {
247                    None
248                };
249
250            // start span
251            if record_trace {
252                if let Some(inputs) = &mut create_inputs {
253                    tracer.create(context, inputs);
254                } else {
255                    tracer.call(context, inputs);
256                }
257            }
258            for log in call.logs {
259                tracer.log(
260                    &mut Default::default(),
261                    context,
262                    Log::new_unchecked(
263                        log.address.0.into(),
264                        log.topics.iter().map(|x| B256::from_slice(x.as_bytes())).collect(),
265                        log.data.0.into(),
266                    ),
267                );
268            }
269
270            // We increment the depth for inner calls as normally traces are processed
271            // during execution, where the environment takes care of updating the context
272            let (new_depth, overflow) = context.journaled_state.depth.overflowing_add(1);
273            if !overflow && record_trace {
274                context.journaled_state.depth = new_depth;
275            }
276
277            // recurse into inner calls
278            // record extra gas from ignored traces, to add it at end
279            let mut extra_gas = if record_trace { 0u64 } else { call.gas_used.as_u64() };
280            for inner_call in call.calls {
281                let inner_extra_gas = trace_call_recursive(
282                    tracer,
283                    context,
284                    inner_call,
285                    suppressed_top_call || is_first_non_system_call,
286                );
287                extra_gas = extra_gas.saturating_add(inner_extra_gas);
288            }
289
290            // We then decrement the call depth so `call_end`/`create_end` has the correct context
291            if !overflow && record_trace {
292                context.journaled_state.depth = context.journaled_state.depth.saturating_sub(1);
293            }
294
295            // finish span
296            if record_trace {
297                if let Some(inputs) = &mut create_inputs {
298                    let mut outcome = if let Some(reason) = call.revert_reason {
299                        CreateOutcome {
300                            result: InterpreterResult {
301                                result: InstructionResult::Revert,
302                                output: reason.as_bytes().to_owned().into(),
303                                gas: Gas::new_spent(call.gas_used.as_u64() + extra_gas),
304                            },
305                            address: None,
306                        }
307                    } else {
308                        CreateOutcome {
309                            result: InterpreterResult {
310                                result: InstructionResult::Return,
311                                output: Bytes::from(call.output.clone().0),
312                                gas: Gas::new_spent(call.gas_used.as_u64() + extra_gas),
313                            },
314                            address: Some(call.to.0.into()),
315                        }
316                    };
317
318                    tracer.create_end(context, inputs, &mut outcome);
319                } else {
320                    if extra_gas != 0 {
321                        outcome.result.gas = Gas::new_spent(outcome.result.gas.spent() + extra_gas);
322                    }
323                    tracer.call_end(context, inputs, &mut outcome);
324                }
325            }
326
327            extra_gas
328        }
329
330        let (new_depth, overflow) = context.journaled_state.depth.overflowing_add(1);
331        // If we are going to record the top call then we don't want to change the call depth
332        if !overflow && !record_top_call {
333            context.journaled_state.depth = new_depth;
334        }
335
336        trace_call_recursive(&mut self.inner, context, call_traces, record_top_call);
337
338        if !overflow && !record_top_call {
339            context.journaled_state.depth = context.journaled_state.depth.saturating_sub(1);
340        }
341    }
342}