Skip to main content

zksync_vm2_interface/
tracer_interface.rs

1use crate::GlobalStateInterface;
2
3macro_rules! forall_simple_opcodes {
4    ($m:ident) => {
5        $m!(Nop);
6        $m!(Add);
7        $m!(Sub);
8        $m!(And);
9        $m!(Or);
10        $m!(Xor);
11        $m!(ShiftLeft);
12        $m!(ShiftRight);
13        $m!(RotateLeft);
14        $m!(RotateRight);
15        $m!(Mul);
16        $m!(Div);
17        $m!(NearCall);
18        $m!(Jump);
19        $m!(Event);
20        $m!(L2ToL1Message);
21        $m!(Decommit);
22        $m!(This);
23        $m!(Caller);
24        $m!(CodeAddress);
25        $m!(ErgsLeft);
26        $m!(SP);
27        $m!(ContextMeta);
28        $m!(ContextU128);
29        $m!(SetContextU128);
30        $m!(IncrementTxNumber);
31        $m!(AuxMutating0);
32        $m!(PrecompileCall);
33        $m!(HeapRead);
34        $m!(HeapWrite);
35        $m!(AuxHeapRead);
36        $m!(AuxHeapWrite);
37        $m!(StaticMemoryRead);
38        $m!(StaticMemoryWrite);
39        $m!(PointerRead);
40        $m!(PointerAdd);
41        $m!(PointerSub);
42        $m!(PointerPack);
43        $m!(PointerShrink);
44        $m!(StorageRead);
45        $m!(StorageWrite);
46        $m!(TransientStorageRead);
47        $m!(TransientStorageWrite);
48    };
49}
50
51macro_rules! pub_struct {
52    ($x:ident) => {
53        #[doc = concat!("`", stringify!($x), "` opcode.")]
54        #[derive(Debug)]
55        pub struct $x;
56    };
57}
58
59/// EraVM opcodes.
60pub mod opcodes {
61    use std::marker::PhantomData;
62
63    use super::{CallingMode, ReturnType};
64
65    forall_simple_opcodes!(pub_struct);
66
67    /// `FarCall` group of opcodes distinguished by the calling mode (normal, delegate, or mimic).
68    #[derive(Debug)]
69    pub struct FarCall<M: TypeLevelCallingMode>(PhantomData<M>);
70
71    /// `Ret` group of opcodes distinguished by the return type (normal, panic, or revert).
72    #[derive(Debug)]
73    pub struct Ret<T: TypeLevelReturnType>(PhantomData<T>);
74
75    /// Normal [`Ret`]urn mode / [`FarCall`] mode.
76    #[derive(Debug)]
77    pub struct Normal;
78
79    /// Delegate [`FarCall`] mode.
80    #[derive(Debug)]
81    pub struct Delegate;
82
83    /// Mimic [`FarCall`] mode.
84    #[derive(Debug)]
85    pub struct Mimic;
86
87    /// Revert [`Ret`]urn mode.
88    #[derive(Debug)]
89    pub struct Revert;
90
91    /// Panic [`Ret`]urn mode.
92    #[derive(Debug)]
93    pub struct Panic;
94
95    /// Calling mode for the [`FarCall`] opcodes.
96    pub trait TypeLevelCallingMode {
97        /// Constant corresponding to this mode allowing to easily `match` it.
98        const VALUE: CallingMode;
99    }
100
101    impl TypeLevelCallingMode for Normal {
102        const VALUE: CallingMode = CallingMode::Normal;
103    }
104
105    impl TypeLevelCallingMode for Delegate {
106        const VALUE: CallingMode = CallingMode::Delegate;
107    }
108
109    impl TypeLevelCallingMode for Mimic {
110        const VALUE: CallingMode = CallingMode::Mimic;
111    }
112
113    /// Return type for the [`Ret`] opcodes.
114    pub trait TypeLevelReturnType {
115        /// Constant corresponding to this return type allowing to easily `match` it.
116        const VALUE: ReturnType;
117    }
118
119    impl TypeLevelReturnType for Normal {
120        const VALUE: ReturnType = ReturnType::Normal;
121    }
122
123    impl TypeLevelReturnType for Revert {
124        const VALUE: ReturnType = ReturnType::Revert;
125    }
126
127    impl TypeLevelReturnType for Panic {
128        const VALUE: ReturnType = ReturnType::Panic;
129    }
130}
131
132/// All supported EraVM opcodes in a single enumeration.
133#[allow(missing_docs)]
134#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
135pub enum Opcode {
136    Nop,
137    Add,
138    Sub,
139    And,
140    Or,
141    Xor,
142    ShiftLeft,
143    ShiftRight,
144    RotateLeft,
145    RotateRight,
146    Mul,
147    Div,
148    NearCall,
149    FarCall(CallingMode),
150    Ret(ReturnType),
151    Jump,
152    Event,
153    L2ToL1Message,
154    Decommit,
155    This,
156    Caller,
157    CodeAddress,
158    ErgsLeft,
159    SP,
160    ContextMeta,
161    ContextU128,
162    SetContextU128,
163    IncrementTxNumber,
164    AuxMutating0,
165    PrecompileCall,
166    HeapRead,
167    HeapWrite,
168    AuxHeapRead,
169    AuxHeapWrite,
170    StaticMemoryRead,
171    StaticMemoryWrite,
172    PointerRead,
173    PointerAdd,
174    PointerSub,
175    PointerPack,
176    PointerShrink,
177    StorageRead,
178    StorageWrite,
179    TransientStorageRead,
180    TransientStorageWrite,
181}
182
183/// All supported calling modes for [`FarCall`](opcodes::FarCall) opcode.
184#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
185pub enum CallingMode {
186    /// Normal calling mode.
187    Normal,
188    /// Delegate calling mode (similar to `delegatecall` in EVM).
189    Delegate,
190    /// Mimic calling mode (can only be used by system contracts; allows to emulate `eth_call` semantics while retaining the bootloader).
191    Mimic,
192}
193
194/// All supported return types for the [`Ret`](opcodes::Ret) opcode.
195#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
196pub enum ReturnType {
197    /// Normal return.
198    Normal,
199    /// Revert (e.g., a result of a Solidity `revert`).
200    Revert,
201    /// Panic, i.e. a non-revert abnormal control flow termination (e.g., out of gas).
202    Panic,
203}
204
205impl ReturnType {
206    /// Checks if this return type is [normal](Self::Normal).
207    pub fn is_failure(&self) -> bool {
208        *self != ReturnType::Normal
209    }
210}
211
212/// Trait mapping opcodes as types to the corresponding variants of the [`Opcode`] enum.
213pub trait OpcodeType {
214    /// `Opcode` variant corresponding to this opcode type.
215    const VALUE: Opcode;
216}
217
218macro_rules! impl_opcode {
219    ($x:ident) => {
220        impl OpcodeType for opcodes::$x {
221            const VALUE: Opcode = Opcode::$x;
222        }
223    };
224}
225
226forall_simple_opcodes!(impl_opcode);
227
228impl<M: opcodes::TypeLevelCallingMode> OpcodeType for opcodes::FarCall<M> {
229    const VALUE: Opcode = Opcode::FarCall(M::VALUE);
230}
231
232impl<T: opcodes::TypeLevelReturnType> OpcodeType for opcodes::Ret<T> {
233    const VALUE: Opcode = Opcode::Ret(T::VALUE);
234}
235
236/// EraVM instruction tracer.
237///
238/// [`Self::before_instruction()`] is called just before the actual instruction is executed.
239/// If the instruction is skipped, `before_instruction` will be called with [`Nop`](opcodes::Nop).
240/// [`Self::after_instruction()`] is called once the instruction is executed and the program
241/// counter has advanced.
242///
243/// # Examples
244///
245/// Here `FarCallCounter` counts the number of far calls.
246///
247/// ```
248/// # use zksync_vm2_interface::{Tracer, GlobalStateInterface, OpcodeType, Opcode};
249/// struct FarCallCounter(usize);
250///
251/// impl Tracer for FarCallCounter {
252///     fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) {
253///         match OP::VALUE {
254///             Opcode::FarCall(_) => self.0 += 1,
255///             _ => {}
256///         }
257///     }
258/// }
259/// ```
260pub trait Tracer {
261    /// This method is executed before an instruction handler.
262    ///
263    /// The default implementation does nothing.
264    fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) {
265        let _ = state;
266    }
267    /// This method is executed after an instruction handler.
268    ///
269    /// The return value indicates whether the VM should continue or stop execution.
270    /// The tracer's return value takes precedence over the VM but only if it is at least as severe.
271    /// For example, if the VM wants to stop and the tracer wants to suspend, the VM will still stop.
272    ///
273    /// The default implementation does nothing.
274    #[must_use]
275    fn after_instruction<OP: OpcodeType, S: GlobalStateInterface>(
276        &mut self,
277        state: &mut S,
278    ) -> ShouldStop {
279        let _ = state;
280        ShouldStop::Continue
281    }
282
283    /// Provides cycle statistics for "complex" instructions from the prover perspective (mostly precompile calls).
284    ///
285    /// The default implementation does nothing.
286    fn on_extra_prover_cycles(&mut self, _stats: CycleStats) {}
287}
288
289/// Returned from [`Tracer::after_instruction`] to indicate if the VM should stop.
290#[derive(Debug)]
291pub enum ShouldStop {
292    /// The VM should stop.
293    Stop,
294    /// The VM should continue.
295    Continue,
296}
297
298impl ShouldStop {
299    #[must_use]
300    #[inline(always)]
301    fn merge(self, other: ShouldStop) -> ShouldStop {
302        match (self, other) {
303            (ShouldStop::Continue, ShouldStop::Continue) => ShouldStop::Continue,
304            _ => ShouldStop::Stop,
305        }
306    }
307}
308
309/// Cycle statistics emitted by the VM and supplied to [`Tracer::on_extra_prover_cycles()`].
310#[derive(Debug, Clone, Copy, PartialEq)]
311pub enum CycleStats {
312    /// Call to the `keccak256` precompile with the specified number of hash cycles.
313    Keccak256(u32),
314    /// Call to the `sha256` precompile with the specified number of hash cycles.
315    Sha256(u32),
316    /// Call to the `ecrecover` precompile with the specified number of hash cycles.
317    EcRecover(u32),
318    /// Call to the `secp256r1_verify` precompile with the specified number of hash cycles.
319    Secp256r1Verify(u32),
320    /// Call to the `modexp` precompile took the specified number of cycles.
321    ModExp(u32),
322    /// Call to the `ecadd` precompile took the specified number of cycles.
323    EcAdd(u32),
324    /// Call to the `ecmul` precompile took the specified number of cycles.
325    EcMul(u32),
326    /// Call to the `ecpairing` precompile took the specified number of cycles.
327    EcPairing(u32),
328    /// Decommitting an opcode.
329    Decommit(u32),
330    /// Reading a slot from the VM storage.
331    StorageRead,
332    /// Writing a slot to the VM storage.
333    StorageWrite,
334}
335
336/// No-op tracer implementation.
337impl Tracer for () {}
338
339// Multiple tracers can be combined by building a linked list out of tuples.
340impl<A: Tracer, B: Tracer> Tracer for (A, B) {
341    fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) {
342        self.0.before_instruction::<OP, S>(state);
343        self.1.before_instruction::<OP, S>(state);
344    }
345
346    fn after_instruction<OP: OpcodeType, S: GlobalStateInterface>(
347        &mut self,
348        state: &mut S,
349    ) -> ShouldStop {
350        self.0
351            .after_instruction::<OP, S>(state)
352            .merge(self.1.after_instruction::<OP, S>(state))
353    }
354
355    fn on_extra_prover_cycles(&mut self, stats: CycleStats) {
356        self.0.on_extra_prover_cycles(stats);
357        self.1.on_extra_prover_cycles(stats);
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::{CallingMode, OpcodeType};
364    use crate::{opcodes, testonly::DummyState, GlobalStateInterface, Tracer};
365
366    struct FarCallCounter(usize);
367
368    impl Tracer for FarCallCounter {
369        fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, _: &mut S) {
370            if let super::Opcode::FarCall(CallingMode::Normal) = OP::VALUE {
371                self.0 += 1;
372            }
373        }
374    }
375
376    #[test]
377    fn test_tracer() {
378        let mut tracer = FarCallCounter(0);
379
380        tracer.before_instruction::<opcodes::Nop, _>(&mut DummyState);
381        assert_eq!(tracer.0, 0);
382
383        tracer.before_instruction::<opcodes::FarCall<opcodes::Normal>, _>(&mut DummyState);
384        assert_eq!(tracer.0, 1);
385
386        tracer.before_instruction::<opcodes::FarCall<opcodes::Mimic>, _>(&mut DummyState);
387        assert_eq!(tracer.0, 1);
388    }
389
390    #[test]
391    fn test_aggregate_tracer() {
392        let mut tracer = (FarCallCounter(0), (FarCallCounter(0), FarCallCounter(0)));
393
394        tracer.before_instruction::<opcodes::Nop, _>(&mut DummyState);
395        assert_eq!(tracer.0 .0, 0);
396        assert_eq!(tracer.1 .0 .0, 0);
397        assert_eq!(tracer.1 .1 .0, 0);
398
399        tracer.before_instruction::<opcodes::FarCall<opcodes::Normal>, _>(&mut DummyState);
400        assert_eq!(tracer.0 .0, 1);
401        assert_eq!(tracer.1 .0 .0, 1);
402        assert_eq!(tracer.1 .1 .0, 1);
403    }
404}