Skip to main content

zksync_vm2/
state.rs

1use primitive_types::{H160, U256};
2use zksync_vm2_interface::{HeapId, Tracer};
3
4use crate::{
5    addressing_modes::Addressable,
6    callframe::{Callframe, CallframeSnapshot},
7    fat_pointer::FatPointer,
8    heap::Heaps,
9    page_ids::{first_dynamic_base_page, next_page_group},
10    predication::Flags,
11    program::Program,
12    stack::Stack,
13    world_diff::Snapshot,
14    World,
15};
16
17/// State of a [`VirtualMachine`](crate::VirtualMachine).
18#[derive(Debug)]
19pub(crate) struct State<T, W> {
20    pub(crate) registers: [U256; 16],
21    pub(crate) register_pointer_flags: u16,
22    pub(crate) flags: Flags,
23    pub(crate) current_frame: Callframe<T, W>,
24    /// Contains indices to the far call instructions currently being executed.
25    /// They are needed to continue execution from the correct spot upon return.
26    pub(crate) previous_frames: Vec<Callframe<T, W>>,
27    pub(crate) heaps: Heaps,
28    pub(crate) transaction_number: u16,
29    pub(crate) context_u128: u128,
30    pub(crate) next_base_page: u32,
31    /// Whether the current instruction has written its second output (`dst1`) register.
32    /// Set by every `dst1` write; if still `false` after execution, `dst1` is cleared to zero,
33    /// matching `zk_evm`. Transient per-instruction bookkeeping; excluded from equality and snapshots.
34    pub(crate) dst1_was_updated: bool,
35}
36
37impl<T, W> State<T, W> {
38    pub(crate) fn new(
39        address: H160,
40        caller: H160,
41        calldata: &[u8],
42        gas: u32,
43        program: Program<T, W>,
44        world_before_this_frame: Snapshot,
45        stack: Box<Stack>,
46    ) -> Self {
47        let mut registers: [U256; 16] = Default::default();
48        registers[1] = FatPointer {
49            memory_page: HeapId::FIRST_CALLDATA,
50            offset: 0,
51            start: 0,
52            length: u32::try_from(calldata.len()).expect("calldata length overflow"),
53        }
54        .into_u256();
55
56        Self {
57            registers,
58            register_pointer_flags: 1 << 1, // calldata is a pointer
59            flags: Flags::new(false, false, false),
60            current_frame: Callframe::new(
61                address,
62                address,
63                caller,
64                program,
65                stack,
66                HeapId::FIRST,
67                HeapId::FIRST_AUX,
68                HeapId::FIRST_CALLDATA,
69                gas,
70                0,
71                0,
72                false,
73                false,
74                world_before_this_frame,
75            ),
76            previous_frames: vec![],
77
78            heaps: Heaps::new(calldata),
79
80            transaction_number: 0,
81            context_u128: 0,
82            next_base_page: first_dynamic_base_page(),
83            dst1_was_updated: false,
84        }
85    }
86
87    #[inline(always)]
88    pub(crate) fn use_gas(&mut self, amount: u32) -> Result<(), ()> {
89        if self.current_frame.gas >= amount {
90            self.current_frame.gas -= amount;
91            Ok(())
92        } else {
93            self.current_frame.gas = 0;
94            Err(())
95        }
96    }
97
98    pub(crate) fn set_context_u128(&mut self, value: u128) {
99        self.context_u128 = value;
100    }
101
102    pub(crate) fn get_context_u128(&self) -> u128 {
103        self.current_frame.context_u128
104    }
105
106    pub(crate) const fn next_base_page(&self) -> u32 {
107        self.next_base_page
108    }
109
110    /// Reserves the next far-call page group and returns its base page.
111    ///
112    /// The returned base page is used to derive the pages owned by a new frame,
113    /// such as its heap and aux heap.
114    pub(crate) fn allocate_base_page(&mut self) -> u32 {
115        let base_page = self.next_base_page;
116        self.next_base_page = next_page_group(self.next_base_page);
117        base_page
118    }
119}
120
121impl<T: Tracer, W: World<T>> State<T, W> {
122    /// Returns the total unspent gas in the VM, including stipends.
123    pub(crate) fn total_unspent_gas(&self) -> u32 {
124        self.current_frame.gas
125            + self
126                .previous_frames
127                .iter()
128                .map(Callframe::contained_gas)
129                .sum::<u32>()
130    }
131
132    pub(crate) fn snapshot(&self) -> StateSnapshot {
133        StateSnapshot {
134            registers: self.registers,
135            register_pointer_flags: self.register_pointer_flags,
136            flags: self.flags.clone(),
137            bootloader_frame: self.current_frame.snapshot(),
138            bootloader_heap_snapshot: self.heaps.snapshot(),
139            dynamic_heap_groups: self.heaps.dynamic_len(),
140            transaction_number: self.transaction_number,
141            context_u128: self.context_u128,
142            next_base_page: self.next_base_page,
143        }
144    }
145
146    pub(crate) fn rollback(
147        &mut self,
148        snapshot: StateSnapshot,
149        mut is_heap_pinned: impl FnMut(HeapId) -> bool,
150    ) {
151        let StateSnapshot {
152            registers,
153            register_pointer_flags,
154            flags,
155            bootloader_frame,
156            bootloader_heap_snapshot,
157            dynamic_heap_groups,
158            transaction_number,
159            context_u128,
160            next_base_page,
161        } = snapshot;
162
163        for heap in self.current_frame.rollback(bootloader_frame) {
164            if !is_heap_pinned(heap) {
165                self.heaps.deallocate(heap);
166            }
167        }
168        self.heaps.rollback(bootloader_heap_snapshot);
169
170        // Pages created after the host snapshot may no longer be reachable from any frame-level
171        // keep-alive list. Decommit pages are the important case: rollback first removes the
172        // global pin, so frame bookkeeping alone can miss a dynamic page that was kept alive only
173        // by that pin.
174        self.heaps.truncate_dynamic_to(dynamic_heap_groups);
175
176        self.registers = registers;
177        self.register_pointer_flags = register_pointer_flags;
178        self.flags = flags;
179        self.transaction_number = transaction_number;
180        self.context_u128 = context_u128;
181        self.next_base_page = next_base_page;
182
183        // Transient per-instruction bookkeeping, not part of the snapshot. It is always reset at the
184        // start of the next instruction before being read, so this is a functional no-op; we clear
185        // it here to keep the invariant "never survives an instruction boundary" self-evident.
186        self.dst1_was_updated = false;
187    }
188
189    pub(crate) fn delete_history(&mut self) {
190        self.heaps.delete_history();
191    }
192}
193
194impl<T, W> Clone for State<T, W> {
195    fn clone(&self) -> Self {
196        Self {
197            registers: self.registers,
198            register_pointer_flags: self.register_pointer_flags,
199            flags: self.flags.clone(),
200            current_frame: self.current_frame.clone(),
201            previous_frames: self.previous_frames.clone(),
202            heaps: self.heaps.clone(),
203            transaction_number: self.transaction_number,
204            context_u128: self.context_u128,
205            next_base_page: self.next_base_page,
206            dst1_was_updated: self.dst1_was_updated,
207        }
208    }
209}
210
211impl<T, W> PartialEq for State<T, W> {
212    fn eq(&self, other: &Self) -> bool {
213        // does not compare cycle counts to work with tests that
214        // expect no change after a rollback
215        self.registers == other.registers
216            && self.register_pointer_flags == other.register_pointer_flags
217            && self.flags == other.flags
218            && self.transaction_number == other.transaction_number
219            && self.context_u128 == other.context_u128
220            && self.next_base_page == other.next_base_page
221            && self.current_frame == other.current_frame
222            && self.previous_frames == other.previous_frames
223            && self.heaps == other.heaps
224    }
225}
226
227impl<T, W> Addressable for State<T, W> {
228    fn registers(&mut self) -> &mut [U256; 16] {
229        &mut self.registers
230    }
231
232    fn register_pointer_flags(&mut self) -> &mut u16 {
233        &mut self.register_pointer_flags
234    }
235
236    fn read_stack(&mut self, slot: u16) -> U256 {
237        self.current_frame.stack.get(slot)
238    }
239
240    fn write_stack(&mut self, slot: u16, value: U256) {
241        self.current_frame.stack.set(slot, value);
242    }
243
244    fn stack_pointer(&mut self) -> &mut u16 {
245        &mut self.current_frame.sp
246    }
247
248    fn read_stack_pointer_flag(&mut self, slot: u16) -> bool {
249        self.current_frame.stack.get_pointer_flag(slot)
250    }
251
252    fn set_stack_pointer_flag(&mut self, slot: u16) {
253        self.current_frame.stack.set_pointer_flag(slot);
254    }
255
256    fn clear_stack_pointer_flag(&mut self, slot: u16) {
257        self.current_frame.stack.clear_pointer_flag(slot);
258    }
259
260    fn mark_dst1_written(&mut self) {
261        self.dst1_was_updated = true;
262    }
263
264    fn code_page(&self) -> &[U256] {
265        self.current_frame.program.code_page()
266    }
267
268    fn in_kernel_mode(&self) -> bool {
269        self.current_frame.is_kernel
270    }
271}
272
273#[derive(Debug)]
274pub(crate) struct StateSnapshot {
275    registers: [U256; 16],
276    register_pointer_flags: u16,
277    flags: Flags,
278    bootloader_frame: CallframeSnapshot,
279    bootloader_heap_snapshot: (usize, usize),
280    dynamic_heap_groups: usize,
281    transaction_number: u16,
282    context_u128: u128,
283    next_base_page: u32,
284}