zksync_vm2_interface/lib.rs
1//! # EraVM Stable Interface
2//!
3//! This crate defines an interface for tracers that will never change but may be extended.
4//! To be precise, a tracer using this interface will work in any VM written against that
5//! version or a newer one. Updating the tracer to depend on a newer interface version is
6//! not necessary. In fact, tracers should depend on the oldest version that has the required
7//! features.
8//!
9//! A struct implementing [`Tracer`] may read and mutate the VM's state via [`StateInterface`]
10//! when particular opcodes are executed.
11//!
12//! ## Why is extreme backwards compatibility required here?
13//!
14//! Suppose VM1 uses stable interface version 1 and VM2 uses stable interface version 2.
15//! With any sane design it would be trivial to take a tracer written for version 1 and
16//! update it to work with version 2. However, then it can no longer be used with VM1.
17//!
18//! This exact thing caused us a lot of trouble when we put many versions of `zk_evm` in `multivm`.
19//!
20//! ## How do I add a new feature to the interface?
21//!
22//! Do not change the existing traits. In fact, you should delete existing code in the new
23//! version that you publish and import it from the previous version instead.
24//!
25//! This is how you would add a new method to [`StateInterface`] and a new opcode.
26//!
27//! ```
28//! # use zksync_vm2_interface as zksync_vm2_interface_v1;
29//! use zksync_vm2_interface_v1::{
30//! StateInterface as StateInterfaceV1, GlobalStateInterface as GlobalStateInterfaceV1, Tracer as TracerV1, opcodes::NearCall,
31//! ShouldStop,
32//! };
33//!
34//! trait StateInterface: StateInterfaceV1 {
35//! fn get_some_new_field(&self) -> u32;
36//! }
37//!
38//! trait GlobalStateInterface: StateInterface + GlobalStateInterfaceV1 {}
39//!
40//! pub struct NewOpcode;
41//!
42//! #[derive(PartialEq, Eq)]
43//! enum Opcode {
44//! NewOpcode,
45//! NearCall,
46//! // ...
47//! }
48//!
49//! trait OpcodeType {
50//! const VALUE: Opcode;
51//! }
52//!
53//! impl OpcodeType for NewOpcode {
54//! const VALUE: Opcode = Opcode::NewOpcode;
55//! }
56//!
57//! // Do this for every old opcode
58//! impl OpcodeType for NearCall {
59//! const VALUE: Opcode = Opcode::NearCall;
60//! }
61//!
62//! trait Tracer {
63//! fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) {}
64//! fn after_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) -> ShouldStop {
65//! ShouldStop::Continue
66//! }
67//! }
68//!
69//! impl<T: TracerV1> Tracer for T {
70//! fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) {
71//! match OP::VALUE {
72//! Opcode::NewOpcode => {}
73//! // Do this for every old opcode
74//! Opcode::NearCall => {
75//! <Self as TracerV1>::before_instruction::<NearCall, _>(self, state)
76//! }
77//! }
78//! }
79//! fn after_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) -> ShouldStop {
80//! todo!()
81//! }
82//! }
83//!
84//! // Now you can use the new features by implementing TracerV2
85//! struct MyTracer;
86//! impl Tracer for MyTracer {
87//! fn before_instruction<OP: OpcodeType, S: GlobalStateInterface>(&mut self, state: &mut S) {
88//! if OP::VALUE == Opcode::NewOpcode {
89//! state.get_some_new_field();
90//! }
91//! }
92//! }
93//! ```
94
95pub use self::{state_interface::*, tracer_interface::*};
96
97mod state_interface;
98mod tracer_interface;