1use std::fmt;
2
3use primitive_types::{H160, U256};
4use zksync_vm2_interface::{opcodes::TypeLevelCallingMode, CallingMode, HeapId, Tracer};
5
6use crate::{
7 callframe::{Callframe, FrameRemnant},
8 decommit::{materialize_decommit_page, u256_into_address},
9 instruction::ExecutionStatus,
10 page_ids::{aux_heap_page_from_base, code_page_from_base, heap_page_from_base},
11 stack::StackPool,
12 state::{State, StateSnapshot},
13 world_diff::{ExternalSnapshot, Snapshot, WorldDiff},
14 ExecutionEnd, Program, World,
15};
16
17#[derive(Debug, Clone)]
19pub struct Settings {
20 pub default_aa_code_hash: [u8; 32],
22 pub evm_interpreter_code_hash: [u8; 32],
24 pub hook_address: u32,
26}
27
28#[derive(Debug)]
30pub struct VirtualMachine<T, W> {
31 pub(crate) world_diff: WorldDiff,
32 pub(crate) state: State<T, W>,
33 pub(crate) settings: Settings,
34 pub(crate) stack_pool: StackPool,
35 pub(crate) snapshot: Option<VmSnapshot>,
36}
37
38impl<T: Tracer, W: World<T>> VirtualMachine<T, W> {
39 pub fn new(
41 address: H160,
42 program: Program<T, W>,
43 caller: H160,
44 calldata: &[u8],
45 gas: u32,
46 settings: Settings,
47 ) -> Self {
48 let world_diff = WorldDiff::default();
49 let world_before_this_frame = world_diff.snapshot();
50 let mut stack_pool = StackPool::default();
51
52 Self {
53 world_diff,
54 state: State::new(
55 address,
56 caller,
57 calldata,
58 gas,
59 program,
60 world_before_this_frame,
61 stack_pool.get(),
62 ),
63 settings,
64 stack_pool,
65 snapshot: None,
66 }
67 }
68
69 pub fn reserve_dynamic_heap_capacity(&mut self, n: usize) {
73 self.state.heaps.reserve_dynamic_groups(n);
74 }
75
76 pub fn world_diff(&self) -> &WorldDiff {
78 &self.world_diff
79 }
80
81 #[doc(hidden)]
85 pub fn world_diff_mut(&mut self) -> &mut WorldDiff {
86 &mut self.world_diff
87 }
88
89 #[doc(hidden)]
101 pub fn manually_decommit(&mut self, world: &mut W, tracer: &mut T, code_hash: U256) -> bool {
102 let (code, is_fresh) = self.world_diff.decommit_opcode(world, tracer, code_hash);
103 if is_fresh {
104 let base_page = self.state.allocate_base_page();
108 materialize_decommit_page(self, code_hash, &code, code_page_from_base(base_page));
109 }
110 is_fresh
111 }
112
113 pub fn run(&mut self, world: &mut W, tracer: &mut T) -> ExecutionEnd {
115 unsafe {
116 loop {
117 if let ExecutionStatus::Stopped(end) =
118 ((*self.state.current_frame.pc).handler)(self, world, tracer)
119 {
120 return end;
121 }
122 }
123 }
124 }
125
126 pub fn resume_with_additional_gas_limit(
133 &mut self,
134 world: &mut W,
135 tracer: &mut T,
136 gas_limit: u32,
137 ) -> Option<(u32, ExecutionEnd)> {
138 let minimum_gas = self.state.total_unspent_gas().saturating_sub(gas_limit);
139
140 let end = unsafe {
141 loop {
142 if let ExecutionStatus::Stopped(end) =
143 ((*self.state.current_frame.pc).handler)(self, world, tracer)
144 {
145 break end;
146 }
147
148 if self.state.total_unspent_gas() < minimum_gas {
149 return None;
150 }
151 }
152 };
153
154 self.state
155 .total_unspent_gas()
156 .checked_sub(minimum_gas)
157 .map(|left| (left, end))
158 }
159
160 pub fn make_snapshot(&mut self) {
167 assert!(self.snapshot.is_none(), "VM already has a snapshot");
168 assert!(
169 self.state.previous_frames.is_empty(),
170 "Snapshotting is only allowed in the bootloader"
171 );
172
173 self.snapshot = Some(VmSnapshot {
174 world_snapshot: self.world_diff.external_snapshot(),
175 state_snapshot: self.state.snapshot(),
176 });
177 }
178
179 pub fn rollback(&mut self) {
186 assert!(
187 self.state.previous_frames.is_empty(),
188 "Rolling back is only allowed in the bootloader"
189 );
190
191 let snapshot = self
192 .snapshot
193 .take()
194 .expect("`rollback()` called without a snapshot");
195 self.world_diff.external_rollback(snapshot.world_snapshot);
196 self.state.rollback(snapshot.state_snapshot, |heap| {
197 self.world_diff.is_decommit_page_pinned(heap)
198 });
199 self.delete_history();
200 }
201
202 pub fn pop_snapshot(&mut self) {
209 assert!(
210 self.state.previous_frames.is_empty(),
211 "Popping a snapshot is only allowed in the bootloader"
212 );
213 self.snapshot = None;
214 self.delete_history();
215 self.reclaim_bootloader_returndata_heaps();
216 }
217
218 fn reclaim_bootloader_returndata_heaps(&mut self) {
237 let mut kept = std::mem::take(&mut self.state.current_frame.heaps_i_am_keeping_alive);
244 kept.retain(|&heap| {
245 let pinned = self.world_diff.is_decommit_page_pinned(heap);
246 if !pinned {
247 self.state.heaps.deallocate(heap);
248 }
249 pinned
250 });
251 self.state.current_frame.heaps_i_am_keeping_alive = kept;
252 }
253
254 fn delete_history(&mut self) {
259 self.world_diff.delete_history();
260 self.state.delete_history();
261 }
262}
263
264impl<T: Tracer, W> VirtualMachine<T, W> {
265 #[allow(clippy::too_many_arguments)]
266 pub(crate) fn push_frame<M: TypeLevelCallingMode>(
267 &mut self,
268 code_address: H160,
269 program: Program<T, W>,
270 gas: u32,
271 exception_handler: u16,
272 is_static: bool,
273 is_evm_blob_format: bool,
274 calldata_heap: HeapId,
275 world_before_this_frame: Snapshot,
276 ) {
277 let base_page = self.state.allocate_base_page();
278 let heap_page = heap_page_from_base(base_page);
279 let aux_heap_page = aux_heap_page_from_base(base_page);
280 self.state.heaps.allocate_at(heap_page);
281 self.state.heaps.allocate_at(aux_heap_page);
282
283 let mut new_frame = Callframe::new(
284 if M::VALUE == CallingMode::Delegate {
285 self.state.current_frame.address
286 } else {
287 code_address
288 },
289 code_address,
290 match M::VALUE {
291 CallingMode::Normal => self.state.current_frame.address,
292 CallingMode::Delegate => self.state.current_frame.caller,
293 CallingMode::Mimic => u256_into_address(self.state.registers[15]),
294 },
295 program,
296 self.stack_pool.get(),
297 heap_page,
298 aux_heap_page,
299 calldata_heap,
300 gas,
301 exception_handler,
302 if M::VALUE == CallingMode::Delegate {
303 self.state.current_frame.context_u128
304 } else {
305 self.state.context_u128
306 },
307 is_static,
308 is_evm_blob_format,
309 world_before_this_frame,
310 );
311 self.state.context_u128 = 0;
312
313 std::mem::swap(&mut new_frame, &mut self.state.current_frame);
314 self.state.previous_frames.push(new_frame);
315 }
316
317 pub(crate) fn pop_frame(&mut self, heap_to_keep: Option<HeapId>) -> Option<FrameRemnant> {
318 let mut frame = self.state.previous_frames.pop()?;
319
320 for &heap in [
321 self.state.current_frame.heap,
322 self.state.current_frame.aux_heap,
323 ]
324 .iter()
325 .chain(&self.state.current_frame.heaps_i_am_keeping_alive)
326 {
327 if Some(heap) != heap_to_keep && !self.world_diff.is_decommit_page_pinned(heap) {
328 self.state.heaps.deallocate(heap);
329 }
330 }
331
332 std::mem::swap(&mut self.state.current_frame, &mut frame);
333 let Callframe {
334 exception_handler,
335 world_before_this_frame,
336 stack,
337 ..
338 } = frame;
339
340 self.stack_pool.recycle(stack);
341
342 self.state
343 .current_frame
344 .heaps_i_am_keeping_alive
345 .extend(heap_to_keep);
346
347 Some(FrameRemnant {
348 exception_handler,
349 snapshot: world_before_this_frame,
350 })
351 }
352
353 pub(crate) fn start_new_tx(&mut self) {
354 self.state.transaction_number = self.state.transaction_number.wrapping_add(1);
355 self.world_diff.clear_transient_storage();
356 }
357}
358
359impl<T: fmt::Debug, W: fmt::Debug> VirtualMachine<T, W> {
360 #[doc(hidden)] pub fn dump_state(&self) -> impl PartialEq + fmt::Debug {
363 self.state.clone()
364 }
365}
366
367#[derive(Debug)]
369pub(crate) struct VmSnapshot {
370 world_snapshot: ExternalSnapshot,
371 state_snapshot: StateSnapshot,
372}