1use std::fmt;
2
3use primitive_types::H160;
4use zksync_vm2_interface::{opcodes::TypeLevelCallingMode, CallingMode, HeapId, Tracer};
5
6use crate::{
7 callframe::{Callframe, FrameRemnant},
8 decommit::u256_into_address,
9 instruction::ExecutionStatus,
10 page_ids::{aux_heap_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 world_diff(&self) -> &WorldDiff {
71 &self.world_diff
72 }
73
74 #[doc(hidden)]
78 pub fn world_diff_mut(&mut self) -> &mut WorldDiff {
79 &mut self.world_diff
80 }
81
82 pub fn run(&mut self, world: &mut W, tracer: &mut T) -> ExecutionEnd {
84 unsafe {
85 loop {
86 if let ExecutionStatus::Stopped(end) =
87 ((*self.state.current_frame.pc).handler)(self, world, tracer)
88 {
89 return end;
90 }
91 }
92 }
93 }
94
95 pub fn resume_with_additional_gas_limit(
102 &mut self,
103 world: &mut W,
104 tracer: &mut T,
105 gas_limit: u32,
106 ) -> Option<(u32, ExecutionEnd)> {
107 let minimum_gas = self.state.total_unspent_gas().saturating_sub(gas_limit);
108
109 let end = unsafe {
110 loop {
111 if let ExecutionStatus::Stopped(end) =
112 ((*self.state.current_frame.pc).handler)(self, world, tracer)
113 {
114 break end;
115 }
116
117 if self.state.total_unspent_gas() < minimum_gas {
118 return None;
119 }
120 }
121 };
122
123 self.state
124 .total_unspent_gas()
125 .checked_sub(minimum_gas)
126 .map(|left| (left, end))
127 }
128
129 pub fn make_snapshot(&mut self) {
136 assert!(self.snapshot.is_none(), "VM already has a snapshot");
137 assert!(
138 self.state.previous_frames.is_empty(),
139 "Snapshotting is only allowed in the bootloader"
140 );
141
142 self.snapshot = Some(VmSnapshot {
143 world_snapshot: self.world_diff.external_snapshot(),
144 state_snapshot: self.state.snapshot(),
145 });
146 }
147
148 pub fn rollback(&mut self) {
155 assert!(
156 self.state.previous_frames.is_empty(),
157 "Rolling back is only allowed in the bootloader"
158 );
159
160 let snapshot = self
161 .snapshot
162 .take()
163 .expect("`rollback()` called without a snapshot");
164 self.world_diff.external_rollback(snapshot.world_snapshot);
165 self.state.rollback(snapshot.state_snapshot, |heap| {
166 self.world_diff.is_decommit_page_pinned(heap)
167 });
168 self.delete_history();
169 }
170
171 pub fn pop_snapshot(&mut self) {
178 assert!(
179 self.state.previous_frames.is_empty(),
180 "Popping a snapshot is only allowed in the bootloader"
181 );
182 self.snapshot = None;
183 self.delete_history();
184 }
185
186 fn delete_history(&mut self) {
191 self.world_diff.delete_history();
192 self.state.delete_history();
193 }
194}
195
196impl<T: Tracer, W> VirtualMachine<T, W> {
197 #[allow(clippy::too_many_arguments)]
198 pub(crate) fn push_frame<M: TypeLevelCallingMode>(
199 &mut self,
200 code_address: H160,
201 program: Program<T, W>,
202 gas: u32,
203 exception_handler: u16,
204 is_static: bool,
205 is_evm_blob_format: bool,
206 calldata_heap: HeapId,
207 world_before_this_frame: Snapshot,
208 ) {
209 let base_page = self.state.allocate_base_page();
210 let heap_page = heap_page_from_base(base_page);
211 let aux_heap_page = aux_heap_page_from_base(base_page);
212 self.state.heaps.allocate_at(heap_page);
213 self.state.heaps.allocate_at(aux_heap_page);
214
215 let mut new_frame = Callframe::new(
216 if M::VALUE == CallingMode::Delegate {
217 self.state.current_frame.address
218 } else {
219 code_address
220 },
221 code_address,
222 match M::VALUE {
223 CallingMode::Normal => self.state.current_frame.address,
224 CallingMode::Delegate => self.state.current_frame.caller,
225 CallingMode::Mimic => u256_into_address(self.state.registers[15]),
226 },
227 program,
228 self.stack_pool.get(),
229 heap_page,
230 aux_heap_page,
231 calldata_heap,
232 gas,
233 exception_handler,
234 if M::VALUE == CallingMode::Delegate {
235 self.state.current_frame.context_u128
236 } else {
237 self.state.context_u128
238 },
239 is_static,
240 is_evm_blob_format,
241 world_before_this_frame,
242 );
243 self.state.context_u128 = 0;
244
245 std::mem::swap(&mut new_frame, &mut self.state.current_frame);
246 self.state.previous_frames.push(new_frame);
247 }
248
249 pub(crate) fn pop_frame(&mut self, heap_to_keep: Option<HeapId>) -> Option<FrameRemnant> {
250 let mut frame = self.state.previous_frames.pop()?;
251
252 for &heap in [
253 self.state.current_frame.heap,
254 self.state.current_frame.aux_heap,
255 ]
256 .iter()
257 .chain(&self.state.current_frame.heaps_i_am_keeping_alive)
258 {
259 if Some(heap) != heap_to_keep && !self.world_diff.is_decommit_page_pinned(heap) {
260 self.state.heaps.deallocate(heap);
261 }
262 }
263
264 std::mem::swap(&mut self.state.current_frame, &mut frame);
265 let Callframe {
266 exception_handler,
267 world_before_this_frame,
268 stack,
269 ..
270 } = frame;
271
272 self.stack_pool.recycle(stack);
273
274 self.state
275 .current_frame
276 .heaps_i_am_keeping_alive
277 .extend(heap_to_keep);
278
279 Some(FrameRemnant {
280 exception_handler,
281 snapshot: world_before_this_frame,
282 })
283 }
284
285 pub(crate) fn start_new_tx(&mut self) {
286 self.state.transaction_number = self.state.transaction_number.wrapping_add(1);
287 self.world_diff.clear_transient_storage();
288 }
289}
290
291impl<T: fmt::Debug, W: fmt::Debug> VirtualMachine<T, W> {
292 #[doc(hidden)] pub fn dump_state(&self) -> impl PartialEq + fmt::Debug {
295 self.state.clone()
296 }
297}
298
299#[derive(Debug)]
301pub(crate) struct VmSnapshot {
302 world_snapshot: ExternalSnapshot,
303 state_snapshot: StateSnapshot,
304}