From d754ca437aca6a50db661e5b25694f768b8fff24 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 10:38:22 +0300 Subject: [PATCH 01/32] Place snapshot inside VM --- crates/vm2/src/vm.rs | 61 +++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 1a82e4d..6f62cfd 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -12,7 +12,7 @@ use crate::{ stack::StackPool, state::{State, StateSnapshot}, world_diff::{ExternalSnapshot, Snapshot, WorldDiff}, - ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, + ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, World, }; /// [`VirtualMachine`] settings. @@ -34,6 +34,7 @@ pub struct VirtualMachine { /// Instruction that is jumped to when things go wrong while executing another. /// Boxed, so the pointer isn't invalidated by moves. pub(crate) panic: Box>, + pub(crate) snapshot: Option, } impl VirtualMachine { @@ -66,22 +67,24 @@ impl VirtualMachine { None, Arguments::new(Predicate::Always, RETURN_COST, ModeRequirements::none()), )), + snapshot: None, } } - /// Provides a reference to the [`World`](crate::World) diff accumulated by VM execution so far. + /// Provides a reference to the [`World`] diff accumulated by VM execution so far. pub fn world_diff(&self) -> &WorldDiff { &self.world_diff } - /// Provides a mutable reference to the [`World`](crate::World) diff accumulated by VM execution so far. + /// Provides a mutable reference to the [`World`] diff accumulated by VM execution so far. /// - /// It is unsound to mutate `WorldDiff` in the middle of VM execution in the general case; thus, this method should only be used in tests. + /// It is unsound to mutate [`WorldDiff`] in the middle of VM execution in the general case; thus, this method should only be used in tests. #[doc(hidden)] pub fn world_diff_mut(&mut self) -> &mut WorldDiff { &mut self.world_diff } + /// Runs this VM with the specified [`World`] and [`Tracer`] until an end of execution due to a hook, or an error. pub fn run(&mut self, world: &mut W, tracer: &mut T) -> ExecutionEnd { unsafe { loop { @@ -128,44 +131,66 @@ impl VirtualMachine { .map(|left| (left, end)) } - /// Returns a compact representation of the VM's current state, - /// including pending side effects like storage changes and emitted events. - /// [`Self::rollback()`] can be used to return the VM to this state. + /// Creates a VM snapshot. The snapshot can then be rolled back to, or discarded. /// /// # Panics /// - /// Calling this function outside the initial callframe is not allowed. - pub fn snapshot(&self) -> VmSnapshot { + /// - Panics if called outside the initial (bootloader) callframe. + /// - Panics if this VM already has a snapshot. + pub fn make_snapshot(&mut self) { + assert!(self.snapshot.is_none(), "VM already has a snapshot"); assert!( self.state.previous_frames.is_empty(), - "Snapshotting is only allowed in the bootloader!" + "Snapshotting is only allowed in the bootloader" ); - VmSnapshot { + + self.snapshot = Some(VmSnapshot { world_snapshot: self.world_diff.external_snapshot(), state_snapshot: self.state.snapshot(), - } + }); } - /// Returns the VM to the state it was in when the snapshot was created. + /// Returns the VM to the state it was in when [`Self::make_snapshot()`] was called. If there is /// /// # Panics /// - /// - Rolling back snapshots in anything but LIFO order may panic. - /// - Rolling back outside the initial callframe will panic. - pub fn rollback(&mut self, snapshot: VmSnapshot) { + /// - Panics if this VM doesn't hold a snapshot. + /// - Panics if called outside the initial (bootloader) callframe. + pub fn rollback(&mut self) { assert!( self.state.previous_frames.is_empty(), - "Rolling back is only allowed in the bootloader!" + "Rolling back is only allowed in the bootloader" ); + + let snapshot = self + .snapshot + .take() + .expect("`rollback()` called without a snapshot"); self.world_diff.external_rollback(snapshot.world_snapshot); self.state.rollback(snapshot.state_snapshot); + self.delete_history(); + } + + /// Pops a [previously made](Self::make_snapshot()) snapshot without rolling back to it. This effectively commits + /// all changes made up to this point, so that they cannot be rolled back. + /// + /// # Panics + /// + /// - Panics if called outside the initial (bootloader) callframe. + pub fn pop_snapshot(&mut self) { + assert!( + self.state.previous_frames.is_empty(), + "Popping a snapshot is only allowed in the bootloader" + ); + self.snapshot = None; + self.delete_history(); } /// This must only be called when it is known that the VM cannot be rolled back, /// so there must not be any external snapshots and the callstack /// should ideally be empty, though in practice it sometimes contains /// a near call inside the bootloader. - pub fn delete_history(&mut self) { + fn delete_history(&mut self) { self.world_diff.delete_history(); self.state.delete_history(); } From f011adc48e6f5dcac33f7980eb8c3aafbd0a174a Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 10:38:41 +0300 Subject: [PATCH 02/32] Place `W: World<_>` bounds everywhere --- crates/vm2/src/decommit.rs | 2 +- crates/vm2/src/instruction_handlers/binop.rs | 21 ++++---- crates/vm2/src/instruction_handlers/common.rs | 29 ++++++++--- .../vm2/src/instruction_handlers/context.rs | 30 ++++++++--- crates/vm2/src/instruction_handlers/event.rs | 8 +-- .../vm2/src/instruction_handlers/far_call.rs | 2 +- .../src/instruction_handlers/heap_access.rs | 50 +++++++++++-------- crates/vm2/src/instruction_handlers/jump.rs | 19 ++++--- .../vm2/src/instruction_handlers/near_call.rs | 6 +-- crates/vm2/src/instruction_handlers/nop.rs | 6 +-- .../vm2/src/instruction_handlers/pointer.rs | 19 ++++--- .../src/instruction_handlers/precompiles.rs | 6 +-- crates/vm2/src/instruction_handlers/ret.rs | 32 ++++++++---- .../vm2/src/instruction_handlers/storage.rs | 4 +- crates/vm2/src/lib.rs | 50 +++++++++++++++---- .../src/single_instruction_test/callframe.rs | 2 +- .../single_instruction_test/into_zk_evm.rs | 7 ++- .../src/single_instruction_test/program.rs | 2 +- .../state_to_zk_evm.rs | 4 +- crates/vm2/src/single_instruction_test/vm.rs | 1 + crates/vm2/src/vm.rs | 3 +- crates/vm2/src/world_diff.rs | 4 +- 22 files changed, 196 insertions(+), 111 deletions(-) diff --git a/crates/vm2/src/decommit.rs b/crates/vm2/src/decommit.rs index b7aa379..fd364c7 100644 --- a/crates/vm2/src/decommit.rs +++ b/crates/vm2/src/decommit.rs @@ -142,7 +142,7 @@ pub(crate) struct UnpaidDecommit { /// Doesn't check for any errors. /// Doesn't cost anything but also doesn't make the code free in future decommits. #[doc(hidden)] // should be used only in low-level testing / benches -pub fn initial_decommit>(world: &mut W, address: H160) -> Program { +pub fn initial_decommit>(world: &mut W, address: H160) -> Program { let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); let code_info = world diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index 871e034..cbefe13 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -13,22 +13,21 @@ use crate::{ }, instruction::{ExecutionStatus, Instruction}, predication::Flags, - VirtualMachine, + VirtualMachine, World, }; -fn binop< +fn binop( + vm: &mut VirtualMachine, + world: &mut W, + tracer: &mut T, +) -> ExecutionStatus +where T: Tracer, - W, + W: World, Op: Binop, In1: Source, Out: Destination, - const SWAP: bool, - const SET_FLAGS: bool, ->( - vm: &mut VirtualMachine, - world: &mut W, - tracer: &mut T, -) -> ExecutionStatus { +{ boilerplate::(vm, world, tracer, |vm, args| { let a = In1::get(args, &mut vm.state); let b = Register2::get(args, &mut vm.state); @@ -236,7 +235,7 @@ macro_rules! from_binop { }; } -impl Instruction { +impl> Instruction { #[inline(always)] pub(crate) fn from_binop( src1: AnySource, diff --git a/crates/vm2/src/instruction_handlers/common.rs b/crates/vm2/src/instruction_handlers/common.rs index 4f86511..11f962e 100644 --- a/crates/vm2/src/instruction_handlers/common.rs +++ b/crates/vm2/src/instruction_handlers/common.rs @@ -1,15 +1,20 @@ use zksync_vm2_interface::{opcodes, OpcodeType, Tracer}; use super::ret::free_panic; -use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine}; +use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; #[inline(always)] -pub(crate) fn boilerplate( +pub(crate) fn boilerplate( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments), -) -> ExecutionStatus { +) -> ExecutionStatus +where + Opcode: OpcodeType, + T: Tracer, + W: World, +{ full_boilerplate::(vm, world, tracer, |vm, args, _, _| { business_logic(vm, args); ExecutionStatus::Running @@ -17,12 +22,17 @@ pub(crate) fn boilerplate( } #[inline(always)] -pub(crate) fn boilerplate_ext( +pub(crate) fn boilerplate_ext( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W, &mut T), -) -> ExecutionStatus { +) -> ExecutionStatus +where + Opcode: OpcodeType, + T: Tracer, + W: World, +{ full_boilerplate::(vm, world, tracer, |vm, args, world, tracer| { business_logic(vm, args, world, tracer); ExecutionStatus::Running @@ -30,7 +40,7 @@ pub(crate) fn boilerplate_ext( } #[inline(always)] -pub(crate) fn full_boilerplate( +pub(crate) fn full_boilerplate( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -40,7 +50,12 @@ pub(crate) fn full_boilerplate( &mut W, &mut T, ) -> ExecutionStatus, -) -> ExecutionStatus { +) -> ExecutionStatus +where + Opcode: OpcodeType, + T: Tracer, + W: World, +{ let args = unsafe { &(*vm.state.current_frame.pc).arguments }; if vm.state.use_gas(args.get_static_gas_cost()).is_err() diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index 679ef97..a30a401 100644 --- a/crates/vm2/src/instruction_handlers/context.rs +++ b/crates/vm2/src/instruction_handlers/context.rs @@ -11,14 +11,19 @@ use crate::{ decommit::address_into_u256, instruction::ExecutionStatus, state::State, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn context( +fn context( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + Op: ContextOp, +{ boilerplate::(vm, world, tracer, |vm, args| { let result = Op::get(&vm.state); Register1::set(args, &mut vm.state, result) @@ -65,7 +70,7 @@ impl ContextOp for SP { } } -fn context_meta( +fn context_meta>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -90,7 +95,7 @@ fn context_meta( }) } -fn set_context_u128( +fn set_context_u128>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -101,7 +106,7 @@ fn set_context_u128( }) } -fn increment_tx_number( +fn increment_tx_number>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -111,7 +116,7 @@ fn increment_tx_number( }) } -fn aux_mutating( +fn aux_mutating>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -121,7 +126,7 @@ fn aux_mutating( }) } -impl Instruction { +impl> Instruction { fn from_context(out: Register1, arguments: Arguments) -> Self { Self { handler: context::, @@ -132,39 +137,48 @@ impl Instruction { pub fn from_this(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + pub fn from_caller(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + pub fn from_code_address(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + pub fn from_ergs_left(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + pub fn from_context_u128(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + pub fn from_context_sp(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + pub fn from_context_meta(out: Register1, arguments: Arguments) -> Self { Self { handler: context_meta, arguments: arguments.write_destination(&out), } } + pub fn from_set_context_u128(src: Register1, arguments: Arguments) -> Self { Self { handler: set_context_u128, arguments: arguments.write_source(&src), } } + pub fn from_increment_tx_number(arguments: Arguments) -> Self { Self { handler: increment_tx_number, arguments, } } + pub fn from_aux_mutating(arguments: Arguments) -> Self { Self { handler: aux_mutating, diff --git a/crates/vm2/src/instruction_handlers/event.rs b/crates/vm2/src/instruction_handlers/event.rs index 71bc44a..46e001d 100644 --- a/crates/vm2/src/instruction_handlers/event.rs +++ b/crates/vm2/src/instruction_handlers/event.rs @@ -7,10 +7,10 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, instruction::ExecutionStatus, world_diff::{Event, L2ToL1Log}, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn event( +fn event>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -32,7 +32,7 @@ fn event( }) } -fn l2_to_l1( +fn l2_to_l1>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -52,7 +52,7 @@ fn l2_to_l1( }) } -impl Instruction { +impl> Instruction { pub fn from_event( key: Register1, value: Register2, diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 556c728..4d1152d 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -182,7 +182,7 @@ pub(crate) fn get_far_call_arguments(abi: U256) -> FarCallABI { /// /// This function needs to be called even if we already know we will panic because /// overflowing start + length makes the heap resize even when already panicking. -pub(crate) fn get_far_call_calldata( +pub(crate) fn get_far_call_calldata>( raw_abi: U256, is_pointer: bool, vm: &mut VirtualMachine, diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index 70aba1e..aa11528 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -1,7 +1,10 @@ use primitive_types::U256; use zksync_vm2_interface::{opcodes, OpcodeType, Tracer}; -use super::common::{boilerplate, full_boilerplate}; +use super::{ + common::{boilerplate, full_boilerplate}, + monomorphization::*, +}; use crate::{ addressing_modes::{ Arguments, Destination, DestinationWriter, Immediate1, Register1, Register2, @@ -10,7 +13,7 @@ use crate::{ fat_pointer::FatPointer, instruction::ExecutionStatus, state::State, - ExecutionEnd, HeapId, Instruction, VirtualMachine, + ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; pub(crate) trait HeapFromState { @@ -60,11 +63,17 @@ fn bigger_than_last_address(x: U256) -> bool { x.0[0] > LAST_ADDRESS.into() || x.0[1] != 0 || x.0[2] != 0 || x.0[3] != 0 } -fn load( +fn load( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + H: HeapFromState, + In: Source, +{ boilerplate::(vm, world, tracer, |vm, args| { // Pointers need not be masked here even though we do not care about them being pointers. // They will panic, though because they are larger than 2^32. @@ -96,18 +105,17 @@ fn load( }) } -fn store< - T: Tracer, - W, - H: HeapFromState, - In: Source, - const INCREMENT: bool, - const HOOKING_ENABLED: bool, ->( +fn store( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + H: HeapFromState, + In: Source, +{ full_boilerplate::(vm, world, tracer, |vm, args, _, _| { // Pointers need not be masked here even though we do not care about them being pointers. // They will panic, though because they are larger than 2^32. @@ -147,10 +155,12 @@ fn store< /// Pays for more heap space. Doesn't acually grow the heap. /// That distinction is necessary because the bootloader gets u32::MAX heap for free. -pub(crate) fn grow_heap( - state: &mut State, - new_bound: u32, -) -> Result<(), ()> { +pub(crate) fn grow_heap(state: &mut State, new_bound: u32) -> Result<(), ()> +where + T: Tracer, + W: World, + H: HeapFromState, +{ let already_paid = H::get_heap_size(state); if *already_paid < new_bound { let to_pay = new_bound - *already_paid; @@ -161,7 +171,7 @@ pub(crate) fn grow_heap( Ok(()) } -fn load_pointer( +fn load_pointer, const INCREMENT: bool>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -195,9 +205,7 @@ fn load_pointer( }) } -use super::monomorphization::*; - -impl Instruction { +impl> Instruction { #[inline(always)] pub fn from_heap_load( src: RegisterOrImmediate, diff --git a/crates/vm2/src/instruction_handlers/jump.rs b/crates/vm2/src/instruction_handlers/jump.rs index 3ca7a55..9a71157 100644 --- a/crates/vm2/src/instruction_handlers/jump.rs +++ b/crates/vm2/src/instruction_handlers/jump.rs @@ -1,20 +1,21 @@ use zksync_vm2_interface::{opcodes, Tracer}; -use super::common::boilerplate; +use super::{common::boilerplate, monomorphization::*}; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, Immediate1, Register1, RelativeStack, Source, }, instruction::{ExecutionStatus, Instruction}, - VirtualMachine, + VirtualMachine, World, }; -fn jump( - vm: &mut VirtualMachine, - world: &mut W, - tracer: &mut T, -) -> ExecutionStatus { +fn jump(vm: &mut VirtualMachine, world: &mut W, tracer: &mut T) -> ExecutionStatus +where + T: Tracer, + W: World, + In: Source, +{ boilerplate::(vm, world, tracer, |vm, args| { let target = In::get(args, &mut vm.state).low_u32() as u16; @@ -25,9 +26,7 @@ fn jump( }) } -use super::monomorphization::*; - -impl Instruction { +impl> Instruction { pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { handler: monomorphize!(jump [T W] match_source source), diff --git a/crates/vm2/src/instruction_handlers/near_call.rs b/crates/vm2/src/instruction_handlers/near_call.rs index 16c3a19..5519c1d 100644 --- a/crates/vm2/src/instruction_handlers/near_call.rs +++ b/crates/vm2/src/instruction_handlers/near_call.rs @@ -5,10 +5,10 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register1, Source}, instruction::ExecutionStatus, predication::Flags, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn near_call( +fn near_call>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -37,7 +37,7 @@ fn near_call( }) } -impl Instruction { +impl> Instruction { pub fn from_near_call( gas: Register1, destination: Immediate1, diff --git a/crates/vm2/src/instruction_handlers/nop.rs b/crates/vm2/src/instruction_handlers/nop.rs index 7349ae2..0794a00 100644 --- a/crates/vm2/src/instruction_handlers/nop.rs +++ b/crates/vm2/src/instruction_handlers/nop.rs @@ -4,10 +4,10 @@ use super::common::boilerplate; use crate::{ addressing_modes::{destination_stack_address, AdvanceStackPointer, Arguments, Source}, instruction::ExecutionStatus, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn nop( +fn nop>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -23,7 +23,7 @@ fn nop( }) } -impl Instruction { +impl> Instruction { pub fn from_nop( pop: AdvanceStackPointer, push: AdvanceStackPointer, diff --git a/crates/vm2/src/instruction_handlers/pointer.rs b/crates/vm2/src/instruction_handlers/pointer.rs index c78a92d..d6aa1a6 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -4,7 +4,7 @@ use zksync_vm2_interface::{ OpcodeType, Tracer, }; -use super::common::boilerplate; +use super::{common::boilerplate, monomorphization::*}; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, @@ -12,14 +12,21 @@ use crate::{ }, fat_pointer::FatPointer, instruction::ExecutionStatus, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn ptr( +fn ptr( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + Op: PtrOp, + In1: Source, + Out: Destination, +{ boilerplate::(vm, world, tracer, |vm, args| { let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { ( @@ -102,9 +109,7 @@ impl PtrOp for PointerShrink { } } -use super::monomorphization::*; - -impl Instruction { +impl> Instruction { #[inline(always)] pub fn from_ptr( src1: AnySource, diff --git a/crates/vm2/src/instruction_handlers/precompiles.rs b/crates/vm2/src/instruction_handlers/precompiles.rs index 9d88ed7..d1838b8 100644 --- a/crates/vm2/src/instruction_handlers/precompiles.rs +++ b/crates/vm2/src/instruction_handlers/precompiles.rs @@ -21,10 +21,10 @@ use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, instruction::ExecutionStatus, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn precompile_call( +fn precompile_call>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -130,7 +130,7 @@ impl Memory for Heaps { } } -impl Instruction { +impl> Instruction { pub fn from_precompile_call( abi: Register1, burn: Register2, diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index f423bf8..1ab82e8 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -1,3 +1,5 @@ +use std::convert::Infallible; + use primitive_types::U256; use zksync_vm2_interface::{ opcodes::{self, Normal, Panic, Revert, TypeLevelReturnType}, @@ -11,13 +13,18 @@ use crate::{ instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, predication::Flags, - Instruction, Predicate, VirtualMachine, + Instruction, Predicate, VirtualMachine, World, }; -fn naked_ret( +fn naked_ret( vm: &mut VirtualMachine, args: &Arguments, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + RT: TypeLevelReturnType, +{ let mut return_type = RT::VALUE; let near_call_leftover_gas = vm.state.current_frame.gas; @@ -115,11 +122,16 @@ fn naked_ret( ExecutionStatus::Running } -fn ret( +fn ret( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + RT: TypeLevelReturnType, +{ full_boilerplate::, _, _>(vm, world, tracer, |vm, args, _, _| { naked_ret::(vm, args) }) @@ -134,13 +146,13 @@ fn ret( /// - the far call stack overflows /// /// For all other panics, point the instruction pointer at [PANIC] instead. -pub(crate) fn free_panic( +pub(crate) fn free_panic>( vm: &mut VirtualMachine, tracer: &mut T, ) -> ExecutionStatus { tracer.before_instruction::, _>(vm); // args aren't used for panics unless TO_LABEL - let result = naked_ret::( + let result = naked_ret::( vm, &Arguments::new(Predicate::Always, 0, ModeRequirements::none()), ); @@ -173,16 +185,16 @@ pub(crate) fn panic_from_failed_far_call( } /// Panics, burning all available gas. -static INVALID_INSTRUCTION: Instruction<(), ()> = Instruction::from_invalid(); +static INVALID_INSTRUCTION: Instruction<(), Infallible> = Instruction::from_invalid(); pub fn invalid_instruction<'a, T, W>() -> &'a Instruction { // Safety: the handler of an invalid instruction is never read. - unsafe { &*(&INVALID_INSTRUCTION as *const Instruction<(), ()>).cast() } + unsafe { &*(&INVALID_INSTRUCTION as *const Instruction<_, _>).cast() } } pub(crate) const RETURN_COST: u32 = 5; -impl Instruction { +impl> Instruction { pub fn from_ret(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { diff --git a/crates/vm2/src/instruction_handlers/storage.rs b/crates/vm2/src/instruction_handlers/storage.rs index ff8fd20..9d5628b 100644 --- a/crates/vm2/src/instruction_handlers/storage.rs +++ b/crates/vm2/src/instruction_handlers/storage.rs @@ -27,7 +27,7 @@ fn sstore>( }) } -fn sstore_transient( +fn sstore_transient>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -60,7 +60,7 @@ fn sload>( }) } -fn sload_transient( +fn sload_transient>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index a8333bf..4532439 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -1,4 +1,7 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::{ + convert::Infallible, + hash::{DefaultHasher, Hash, Hasher}, +}; use primitive_types::{H160, U256}; pub use zksync_vm2_interface::{ @@ -46,16 +49,11 @@ mod tracing; mod vm; mod world_diff; -pub trait World: StorageInterface + Sized { - /// This will be called *every* time a contract is called. Caching and decoding is - /// the world implementor's job. - fn decommit(&mut self, hash: U256) -> Program; - - fn decommit_code(&mut self, hash: U256) -> Vec; -} - +/// VM storage access operations. pub trait StorageInterface { - /// There is no write_storage; [WorldDiff::get_storage_changes] gives a list of all storage changes. + /// Reads the specified slot from the storage. + /// + /// There is no write counterpart; [`WorldDiff::get_storage_changes()`] gives a list of all storage changes. fn read_storage(&mut self, contract: H160, key: U256) -> Option; /// Computes the cost of writing a storage slot. @@ -65,6 +63,38 @@ pub trait StorageInterface { fn is_free_storage_slot(&self, contract: &H160, key: &U256) -> bool; } +impl StorageInterface for Infallible { + fn read_storage(&mut self, _contract: H160, _key: U256) -> Option { + unreachable!("`Infallible` cannot be constructed") + } + + fn cost_of_writing_storage(&mut self, _initial_value: Option, _new_value: U256) -> u32 { + unreachable!("`Infallible` cannot be constructed") + } + + fn is_free_storage_slot(&self, _contract: &H160, _key: &U256) -> bool { + unreachable!("`Infallible` cannot be constructed") + } +} + +pub trait World: StorageInterface + Sized { + /// This will be called *every* time a contract is called. Caching and decoding is + /// the world implementor's job. + fn decommit(&mut self, hash: U256) -> Program; + + fn decommit_code(&mut self, hash: U256) -> Vec; +} + +impl World for Infallible { + fn decommit(&mut self, _hash: U256) -> Program { + unreachable!("`Infallible` cannot be constructed") + } + + fn decommit_code(&mut self, _hash: U256) -> Vec { + unreachable!("`Infallible` cannot be constructed") + } +} + /// Deterministic (across program runs and machines) hash that can be used for `Debug` implementations /// to concisely represent large amounts of data. #[cfg_attr(feature = "single_instruction_test", allow(dead_code))] // Currently used entirely in types overridden by `single_instruction_test` feature diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index c1f5b76..f392888 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -64,7 +64,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { } } -impl Callframe { +impl> Callframe { pub fn raw_first_instruction(&self) -> u64 { self.program.raw_first_instruction } diff --git a/crates/vm2/src/single_instruction_test/into_zk_evm.rs b/crates/vm2/src/single_instruction_test/into_zk_evm.rs index 0411815..1232160 100644 --- a/crates/vm2/src/single_instruction_test/into_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/into_zk_evm.rs @@ -15,7 +15,7 @@ use zkevm_opcode_defs::{decoding::EncodingModeProduction, TRANSIENT_STORAGE_AUX_ use zksync_vm2_interface::Tracer; use super::{stack::Stack, state_to_zk_evm::vm2_state_to_zk_evm_state, MockWorld}; -use crate::{StorageInterface, VirtualMachine}; +use crate::{StorageInterface, VirtualMachine, World}; type ZkEvmState = VmState< MockWorldWrapper, @@ -28,7 +28,10 @@ type ZkEvmState = VmState< EncodingModeProduction, >; -pub fn vm2_to_zk_evm(vm: &VirtualMachine, world: MockWorld) -> ZkEvmState { +pub fn vm2_to_zk_evm>( + vm: &VirtualMachine, + world: MockWorld, +) -> ZkEvmState { let mut event_sink = InMemoryEventSink::new(); event_sink.start_frame(zk_evm::aux_structures::Timestamp(0)); diff --git a/crates/vm2/src/single_instruction_test/program.rs b/crates/vm2/src/single_instruction_test/program.rs index d7679cb..87f8831 100644 --- a/crates/vm2/src/single_instruction_test/program.rs +++ b/crates/vm2/src/single_instruction_test/program.rs @@ -67,7 +67,7 @@ impl Program { } } -impl Program { +impl> Program { pub fn for_decommit() -> Self { Self { raw_first_instruction: 0, diff --git a/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs b/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs index c0b9409..a48485f 100644 --- a/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs @@ -11,10 +11,10 @@ use zksync_vm2_interface::Tracer; use crate::{ callframe::{Callframe, NearCallFrame}, state::State, - Instruction, + Instruction, World, }; -pub(crate) fn vm2_state_to_zk_evm_state( +pub(crate) fn vm2_state_to_zk_evm_state>( state: &State, panic: &Instruction, ) -> VmLocalState<8, EncodingModeProduction> { diff --git a/crates/vm2/src/single_instruction_test/vm.rs b/crates/vm2/src/single_instruction_test/vm.rs index b3435d4..3951c94 100644 --- a/crates/vm2/src/single_instruction_test/vm.rs +++ b/crates/vm2/src/single_instruction_test/vm.rs @@ -83,6 +83,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for VirtualMachine { None, Arguments::new(Predicate::Always, 5, ModeRequirements::none()), )), + snapshot: None, }) } } diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 6f62cfd..79fe749 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -37,7 +37,8 @@ pub struct VirtualMachine { pub(crate) snapshot: Option, } -impl VirtualMachine { +impl> VirtualMachine { + /// Creates a new VM instance. pub fn new( address: H160, program: Program, diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index 8973d47..af8a226 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -276,9 +276,7 @@ impl WorldDiff { self.decommitted_hashes.as_ref().keys().copied() } - /// Get a snapshot for selecting which logs [Self::events_after] & Co output. - /// The snapshot can't be used for rolling back the VM because the method for - /// that is private. Use [crate::VirtualMachine::snapshot] for that instead. + /// Get a snapshot for selecting which logs & co. to output using [`Self::events_after()`] and other methods. pub fn snapshot(&self) -> Snapshot { Snapshot { storage_changes: self.storage_changes.snapshot(), From 13689bbba03d23912021c64ec5c49f956cfe57b5 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 10:40:20 +0300 Subject: [PATCH 03/32] Make `Snapshot` private --- crates/vm2/src/lib.rs | 3 +-- crates/vm2/src/vm.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index 4532439..1164e51 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -18,11 +18,10 @@ pub use self::{ mode_requirements::ModeRequirements, predication::Predicate, program::Program, - vm::{Settings, VirtualMachine, VmSnapshot as Snapshot}, + vm::{Settings, VirtualMachine}, world_diff::{Event, L2ToL1Log, WorldDiff}, }; -// FIXME: revise visibility pub mod addressing_modes; #[cfg(not(feature = "single_instruction_test"))] mod bitset; diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 79fe749..ee9f3d2 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -294,7 +294,7 @@ impl VirtualMachine { /// Snapshot of a [`VirtualMachine`]. #[derive(Debug)] -pub struct VmSnapshot { +pub(crate) struct VmSnapshot { world_snapshot: ExternalSnapshot, state_snapshot: StateSnapshot, } From fceafc196cad3c5e0231fe63be3ec3a1f52e0b4c Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 11:02:08 +0300 Subject: [PATCH 04/32] Brush up `Program` construction --- crates/vm2/benches/nested_near_call.rs | 4 +- crates/vm2/src/callframe.rs | 6 +- crates/vm2/src/decode.rs | 33 +++----- crates/vm2/src/instruction.rs | 21 +---- .../vm2/src/instruction_handlers/context.rs | 14 ++-- crates/vm2/src/instruction_handlers/mod.rs | 5 -- crates/vm2/src/instruction_handlers/ret.rs | 6 +- crates/vm2/src/lib.rs | 2 +- crates/vm2/src/program.rs | 79 ++++++++++++++++++- .../print_mock_info.rs | 8 +- crates/vm2/src/state.rs | 7 +- crates/vm2/src/testworld.rs | 2 +- crates/vm2/src/tracing.rs | 8 +- crates/vm2/tests/bytecode_behaviour.rs | 17 +--- crates/vm2/tests/far_call_decommitment.rs | 4 +- crates/vm2/tests/panic.rs | 2 +- crates/vm2/tests/stipend.rs | 4 +- 17 files changed, 122 insertions(+), 100 deletions(-) diff --git a/crates/vm2/benches/nested_near_call.rs b/crates/vm2/benches/nested_near_call.rs index 8119108..e24d88e 100644 --- a/crates/vm2/benches/nested_near_call.rs +++ b/crates/vm2/benches/nested_near_call.rs @@ -11,7 +11,7 @@ use zksync_vm2::{ #[divan::bench] fn nested_near_call(bencher: Bencher) { - let program = Program::new( + let program = Program::from_raw( vec![Instruction::from_near_call( // zero means pass all gas Register1(Register::new(0)), @@ -46,7 +46,7 @@ fn nested_near_call(bencher: Bencher) { #[divan::bench] fn nested_near_call_with_storage_write(bencher: Bencher) { - let program = Program::new( + let program = Program::from_raw( vec![ Instruction::from_ergs_left( Register1(Register::new(1)), diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index e48e597..4be6538 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -1,6 +1,6 @@ use primitive_types::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; -use zksync_vm2_interface::HeapId; +use zksync_vm2_interface::{HeapId, Tracer}; use crate::{ decommit::is_kernel, @@ -8,7 +8,7 @@ use crate::{ program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, - Instruction, + Instruction, World, }; #[derive(Debug)] @@ -54,7 +54,7 @@ pub(crate) struct NearCallFrame { world_before_this_frame: Snapshot, } -impl Callframe { +impl> Callframe { #[allow(clippy::too_many_arguments)] pub(crate) fn new( address: H160, diff --git a/crates/vm2/src/decode.rs b/crates/vm2/src/decode.rs index 5360220..2da8f79 100644 --- a/crates/vm2/src/decode.rs +++ b/crates/vm2/src/decode.rs @@ -6,7 +6,13 @@ use zkevm_opcode_defs::{ RET_TO_LABEL_BIT_IDX, SET_FLAGS_FLAG_IDX, SWAP_OPERANDS_FLAG_IDX_FOR_ARITH_OPCODES, SWAP_OPERANDS_FLAG_IDX_FOR_PTR_OPCODE, UMA_INCREMENT_FLAG_IDX, }; -use zksync_vm2_interface::{opcodes, Tracer}; +use zksync_vm2_interface::{ + opcodes::{ + self, Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, + RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, + }, + Tracer, +}; use crate::{ addressing_modes::{ @@ -14,31 +20,12 @@ use crate::{ Immediate1, Immediate2, Register, Register1, Register2, RegisterAndImmediate, RelativeStack, Source, SourceWriter, }, - instruction::{jump_to_beginning, ExecutionEnd, ExecutionStatus}, - instruction_handlers::{ - Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, - RotateRight, ShiftLeft, ShiftRight, Sub, Xor, - }, + instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, Instruction, Predicate, VirtualMachine, World, }; -pub fn decode_program>( - raw: &[u64], - is_bootloader: bool, -) -> Vec> { - raw.iter() - .take(1 << 16) - .map(|i| decode(*i, is_bootloader)) - .chain(std::iter::once(if raw.len() >= 1 << 16 { - jump_to_beginning() - } else { - Instruction::from_invalid() - })) - .collect() -} - -fn unimplemented_instruction(variant: Opcode) -> Instruction { +fn unimplemented_instruction>(variant: Opcode) -> Instruction { let mut arguments = Arguments::new(Predicate::Always, 0, ModeRequirements::none()); let variant_as_number: u16 = unsafe { std::mem::transmute(variant) }; Immediate1(variant_as_number).write_source(&mut arguments); @@ -48,7 +35,7 @@ fn unimplemented_instruction(variant: Opcode) -> Instruction { } } -fn unimplemented_handler( +fn unimplemented_handler>( vm: &mut VirtualMachine, _: &mut W, _: &mut T, diff --git a/crates/vm2/src/instruction.rs b/crates/vm2/src/instruction.rs index 261b5e0..730ebfa 100644 --- a/crates/vm2/src/instruction.rs +++ b/crates/vm2/src/instruction.rs @@ -1,8 +1,6 @@ use std::fmt; -use crate::{ - addressing_modes::Arguments, mode_requirements::ModeRequirements, vm::VirtualMachine, Predicate, -}; +use crate::{addressing_modes::Arguments, vm::VirtualMachine}; #[doc(hidden)] // should only be used for low-level testing / benchmarking pub struct Instruction { @@ -32,23 +30,6 @@ pub enum ExecutionEnd { ProgramFinished(Vec), Reverted(Vec), Panicked, - /// Returned when the bootloader writes to the heap location [crate::Settings::hook_address] SuspendedOnHook(u32), } - -pub(crate) fn jump_to_beginning() -> Instruction { - Instruction { - handler: jump_to_beginning_handler, - arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()), - } -} -fn jump_to_beginning_handler( - vm: &mut VirtualMachine, - _: &mut W, - _: &mut T, -) -> ExecutionStatus { - let first_instruction = vm.state.current_frame.program.instruction(0).unwrap(); - vm.state.current_frame.pc = first_instruction; - ExecutionStatus::Running -} diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index a30a401..54ed87a 100644 --- a/crates/vm2/src/instruction_handlers/context.rs +++ b/crates/vm2/src/instruction_handlers/context.rs @@ -31,41 +31,41 @@ where } trait ContextOp: OpcodeType { - fn get(state: &State) -> U256; + fn get>(state: &State) -> U256; } impl ContextOp for This { - fn get(state: &State) -> U256 { + fn get>(state: &State) -> U256 { address_into_u256(state.current_frame.address) } } impl ContextOp for Caller { - fn get(state: &State) -> U256 { + fn get>(state: &State) -> U256 { address_into_u256(state.current_frame.caller) } } impl ContextOp for CodeAddress { - fn get(state: &State) -> U256 { + fn get>(state: &State) -> U256 { address_into_u256(state.current_frame.code_address) } } impl ContextOp for ErgsLeft { - fn get(state: &State) -> U256 { + fn get>(state: &State) -> U256 { U256([state.current_frame.gas as u64, 0, 0, 0]) } } impl ContextOp for ContextU128 { - fn get(state: &State) -> U256 { + fn get>(state: &State) -> U256 { state.get_context_u128().into() } } impl ContextOp for SP { - fn get(state: &State) -> U256 { + fn get>(state: &State) -> U256 { state.current_frame.sp.into() } } diff --git a/crates/vm2/src/instruction_handlers/mod.rs b/crates/vm2/src/instruction_handlers/mod.rs index 9b62fab..26b79c9 100644 --- a/crates/vm2/src/instruction_handlers/mod.rs +++ b/crates/vm2/src/instruction_handlers/mod.rs @@ -1,8 +1,3 @@ -pub use zksync_vm2_interface::opcodes::{ - Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, - RotateRight, ShiftLeft, ShiftRight, Sub, Xor, -}; - pub(crate) use self::{ heap_access::{AuxHeap, Heap}, ret::{invalid_instruction, RETURN_COST}, diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 1ab82e8..c0add22 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -162,7 +162,7 @@ pub(crate) fn free_panic>( /// Formally, a far call pushes a new frame and returns from it immediately if it panics. /// This function instead panics without popping a frame to save on allocation. -pub(crate) fn panic_from_failed_far_call( +pub(crate) fn panic_from_failed_far_call>( vm: &mut VirtualMachine, tracer: &mut T, exception_handler: u16, @@ -171,14 +171,10 @@ pub(crate) fn panic_from_failed_far_call( // Gas is already subtracted in the far call code. // No need to roll back, as no changes are made in this "frame". - vm.state.set_context_u128(0); - vm.state.registers = [U256::zero(); 16]; vm.state.register_pointer_flags = 2; - vm.state.flags = Flags::new(true, false, false); - vm.state.current_frame.set_pc_from_u16(exception_handler); tracer.after_instruction::, _>(vm); diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index 1164e51..678e4ff 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -26,7 +26,7 @@ pub mod addressing_modes; #[cfg(not(feature = "single_instruction_test"))] mod bitset; mod callframe; -pub mod decode; +mod decode; mod decommit; mod fat_pointer; #[cfg(not(feature = "single_instruction_test"))] diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index 93ebc8f..e579653 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -1,8 +1,12 @@ use std::{fmt, sync::Arc}; use primitive_types::U256; +use zksync_vm2_interface::Tracer; -use crate::{hash_for_debugging, Instruction}; +use crate::{ + addressing_modes::Arguments, decode::decode, hash_for_debugging, instruction::ExecutionStatus, + Instruction, ModeRequirements, Predicate, VirtualMachine, World, +}; /// Compiled EraVM bytecode. /// @@ -47,14 +51,50 @@ impl fmt::Debug for Program { } } -impl Program { - pub fn new(instructions: Vec>, code_page: Vec) -> Self { +impl> Program { + /// Creates a new program. + pub fn new(bytecode: Vec, enable_hooks: bool) -> Self { + let instructions = decode_program( + &bytecode + .chunks_exact(8) + .map(|chunk| u64::from_be_bytes(chunk.try_into().unwrap())) + .collect::>(), + enable_hooks, + ); + let code_page = bytecode + .chunks_exact(32) + .map(U256::from_big_endian) + .collect::>(); Self { + instructions: instructions.into(), code_page: code_page.into(), + } + } + + /// Creates a new program from `U256` words. + pub fn from_words(bytecode_words: Vec, enable_hooks: bool) -> Self { + let instructions = decode_program( + &bytecode_words + .iter() + .flat_map(|x| x.0.into_iter().rev()) + .collect::>(), + enable_hooks, + ); + Self { instructions: instructions.into(), + code_page: bytecode_words.into(), } } + #[doc(hidden)] // should only be used in low-level tests / benchmarks + pub fn from_raw(instructions: Vec>, code_page: Vec) -> Self { + Self { + instructions: instructions.into(), + code_page: code_page.into(), + } + } + + // FIXME: check if it's used pub fn instruction(&self, n: u16) -> Option<&Instruction> { self.instructions.get::(n.into()) } @@ -75,3 +115,36 @@ impl PartialEq for Program { && Arc::ptr_eq(&self.instructions, &other.instructions) } } + +/// "Jump to start" instruction placed at the end of programs exceeding `1 << 16` instructions. +fn jump_to_beginning>() -> Instruction { + Instruction { + handler: jump_to_beginning_handler, + arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()), + } +} + +fn jump_to_beginning_handler>( + vm: &mut VirtualMachine, + _: &mut W, + _: &mut T, +) -> ExecutionStatus { + let first_instruction = vm.state.current_frame.program.instruction(0).unwrap(); + vm.state.current_frame.pc = first_instruction; + ExecutionStatus::Running +} + +fn decode_program>( + raw: &[u64], + is_bootloader: bool, +) -> Vec> { + raw.iter() + .take(1 << 16) + .map(|i| decode(*i, is_bootloader)) + .chain(std::iter::once(if raw.len() >= 1 << 16 { + jump_to_beginning() + } else { + Instruction::from_invalid() + })) + .collect() +} diff --git a/crates/vm2/src/single_instruction_test/print_mock_info.rs b/crates/vm2/src/single_instruction_test/print_mock_info.rs index a2285d7..d2cac07 100644 --- a/crates/vm2/src/single_instruction_test/print_mock_info.rs +++ b/crates/vm2/src/single_instruction_test/print_mock_info.rs @@ -1,6 +1,8 @@ -use crate::{callframe::Callframe, state::State, VirtualMachine}; +use zksync_vm2_interface::Tracer; -impl VirtualMachine { +use crate::{callframe::Callframe, state::State, VirtualMachine, World}; + +impl> VirtualMachine { pub fn print_mock_info(&self) { self.state.print_mock_info(); println!("Events: {:?}", self.world_diff.events()); @@ -12,7 +14,7 @@ impl VirtualMachine { } } -impl State { +impl> State { pub fn print_mock_info(&self) { if let Some((heapid, heap)) = self.heaps.read.read_that_happened() { println!("Heap: {:?}", heapid); diff --git a/crates/vm2/src/state.rs b/crates/vm2/src/state.rs index 8d61067..3d58665 100644 --- a/crates/vm2/src/state.rs +++ b/crates/vm2/src/state.rs @@ -1,5 +1,5 @@ use primitive_types::{H160, U256}; -use zksync_vm2_interface::HeapId; +use zksync_vm2_interface::{HeapId, Tracer}; use crate::{ addressing_modes::Addressable, @@ -10,6 +10,7 @@ use crate::{ program::Program, stack::Stack, world_diff::Snapshot, + World, }; /// State of a [`VirtualMachine`](crate::VirtualMachine). @@ -27,7 +28,7 @@ pub(crate) struct State { pub(crate) context_u128: u128, } -impl State { +impl> State { pub(crate) fn new( address: H160, caller: H160, @@ -173,7 +174,7 @@ impl PartialEq for State { } } -impl Addressable for State { +impl> Addressable for State { fn registers(&mut self) -> &mut [U256; 16] { &mut self.registers } diff --git a/crates/vm2/src/testworld.rs b/crates/vm2/src/testworld.rs index 0bb0e3c..abc52eb 100644 --- a/crates/vm2/src/testworld.rs +++ b/crates/vm2/src/testworld.rs @@ -17,7 +17,7 @@ pub struct TestWorld { pub hash_to_contract: BTreeMap>, } -impl TestWorld { +impl TestWorld { pub fn new(contracts: &[(Address, Program)]) -> Self { let mut address_to_hash = BTreeMap::new(); let mut hash_to_contract = BTreeMap::new(); diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index ddf454f..e41e997 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -7,10 +7,10 @@ use crate::{ callframe::{Callframe, NearCallFrame}, decommit::is_kernel, predication::{self, Predicate}, - VirtualMachine, + VirtualMachine, World, }; -impl StateInterface for VirtualMachine { +impl> StateInterface for VirtualMachine { fn read_register(&self, register: u8) -> (U256, bool) { ( self.state.registers[register as usize], @@ -168,7 +168,7 @@ struct CallframeWrapper<'a, T, W> { near_call: Option, } -impl CallframeInterface for CallframeWrapper<'_, T, W> { +impl> CallframeInterface for CallframeWrapper<'_, T, W> { fn address(&self) -> H160 { self.frame.address } @@ -377,7 +377,7 @@ mod test { #[test] fn callframe_picking() { - let program = Program::new(vec![Instruction::from_invalid()], vec![]); + let program = Program::from_raw(vec![Instruction::from_invalid()], vec![]); let address = Address::from_low_u64_be(0x1234567890abcdef); let mut world = TestWorld::new(&[(address, program)]); diff --git a/crates/vm2/tests/bytecode_behaviour.rs b/crates/vm2/tests/bytecode_behaviour.rs index a924b83..9ea050f 100644 --- a/crates/vm2/tests/bytecode_behaviour.rs +++ b/crates/vm2/tests/bytecode_behaviour.rs @@ -1,27 +1,14 @@ #![cfg(not(feature = "single_instruction_test"))] -use primitive_types::U256; use zkevm_opcode_defs::ethereum_types::Address; use zksync_vm2::{ - decode::decode_program, initial_decommit, testworld::TestWorld, ExecutionEnd, Program, - Settings, VirtualMachine, World, + initial_decommit, testworld::TestWorld, ExecutionEnd, Program, Settings, VirtualMachine, World, }; use zksync_vm2_interface::{CallframeInterface, StateInterface, Tracer}; fn program_from_file>(filename: &str) -> Program { let blob = std::fs::read(filename).unwrap(); - Program::new( - decode_program( - &blob - .chunks_exact(8) - .map(|chunk| u64::from_be_bytes(chunk.try_into().unwrap())) - .collect::>(), - false, - ), - blob.chunks_exact(32) - .map(U256::from_big_endian) - .collect::>(), - ) + Program::new(blob, false) } #[test] diff --git a/crates/vm2/tests/far_call_decommitment.rs b/crates/vm2/tests/far_call_decommitment.rs index 4b749b9..0db0259 100644 --- a/crates/vm2/tests/far_call_decommitment.rs +++ b/crates/vm2/tests/far_call_decommitment.rs @@ -29,7 +29,7 @@ fn create_test_world() -> TestWorld<()> { let mut abi = U256::zero(); abi.0[3] = GAS_TO_PASS.into(); - let main_program = Program::new( + let main_program = Program::from_raw( vec![ // 0..=2: Prepare and execute far call Instruction::from_add( @@ -88,7 +88,7 @@ fn create_test_world() -> TestWorld<()> { vec![abi, CALLED_ADDRESS.to_low_u64_be().into()], ); - let called_program = Program::new( + let called_program = Program::from_raw( vec![ Instruction::from_add( Register1(r0).into(), diff --git a/crates/vm2/tests/panic.rs b/crates/vm2/tests/panic.rs index 272ac36..005dcbe 100644 --- a/crates/vm2/tests/panic.rs +++ b/crates/vm2/tests/panic.rs @@ -29,7 +29,7 @@ proptest! { )); } - let program = Program::new(instructions, vec![]); + let program = Program::from_raw(instructions, vec![]); let address = Address::from_low_u64_be(0x1234567890abcdef); let mut world = TestWorld::new(&[(address, program)]); diff --git a/crates/vm2/tests/stipend.rs b/crates/vm2/tests/stipend.rs index 3f9e133..c935602 100644 --- a/crates/vm2/tests/stipend.rs +++ b/crates/vm2/tests/stipend.rs @@ -24,7 +24,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { let mut abi = U256::zero(); abi.0[3] = gas_to_pass as u64; - let main_program = Program::new( + let main_program = Program::from_raw( vec![ Instruction::from_add( CodePage(RegisterAndImmediate { @@ -68,7 +68,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { vec![abi, ethereum_address.into()], ); - let interpreter = Program::new( + let interpreter = Program::from_raw( vec![ Instruction::from_add( Register1(r0).into(), From 690c72eb35df7a5a9d20dac06a23cf258d8f17a3 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 11:16:23 +0300 Subject: [PATCH 05/32] Reduce visibility of `Program` methods --- crates/vm2/src/program.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index e579653..86a2a2d 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -94,12 +94,12 @@ impl> Program { } } - // FIXME: check if it's used - pub fn instruction(&self, n: u16) -> Option<&Instruction> { + pub(crate) fn instruction(&self, n: u16) -> Option<&Instruction> { self.instructions.get::(n.into()) } - pub fn code_page(&self) -> &Arc<[U256]> { + /// Returns a reference to the code page of this program. + pub fn code_page(&self) -> &[U256] { &self.code_page } } From 9f72c5f95c0c751d56ca659c1bc8e66bdf381b39 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 11:36:51 +0300 Subject: [PATCH 06/32] Remove UB in invalid instruction handling --- crates/vm2/src/callframe.rs | 8 +++-- crates/vm2/src/instruction_handlers/mod.rs | 2 +- crates/vm2/src/instruction_handlers/ret.rs | 12 +------ crates/vm2/src/lib.rs | 29 +--------------- crates/vm2/src/program.rs | 34 ++++++++++++++----- .../src/single_instruction_test/program.rs | 6 +++- 6 files changed, 40 insertions(+), 51 deletions(-) diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 4be6538..70bb78c 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -4,7 +4,6 @@ use zksync_vm2_interface::{HeapId, Tracer}; use crate::{ decommit::is_kernel, - instruction_handlers::invalid_instruction, program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, @@ -133,6 +132,7 @@ impl> Callframe { }) } + // FIXME: can overflow on invalid instruction pub(crate) fn get_pc_as_u16(&self) -> u16 { unsafe { self.pc.offset_from(self.program.instruction(0).unwrap()) as u16 } } @@ -143,7 +143,11 @@ impl> Callframe { self.pc = self .program .instruction(index) - .unwrap_or(invalid_instruction()) + .unwrap_or_else(|| self.program.invalid_instruction()) + } + + pub fn set_invalid_pc(&mut self) { + self.pc = self.program.invalid_instruction(); } /// The total amount of gas in this frame, including gas currently inaccessible because of a near call. diff --git a/crates/vm2/src/instruction_handlers/mod.rs b/crates/vm2/src/instruction_handlers/mod.rs index 26b79c9..bb7c498 100644 --- a/crates/vm2/src/instruction_handlers/mod.rs +++ b/crates/vm2/src/instruction_handlers/mod.rs @@ -1,6 +1,6 @@ pub(crate) use self::{ heap_access::{AuxHeap, Heap}, - ret::{invalid_instruction, RETURN_COST}, + ret::RETURN_COST, }; mod binop; diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index c0add22..030cc03 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -1,5 +1,3 @@ -use std::convert::Infallible; - use primitive_types::U256; use zksync_vm2_interface::{ opcodes::{self, Normal, Panic, Revert, TypeLevelReturnType}, @@ -79,7 +77,7 @@ where // But to continue execution would be nonsensical and can cause UB because there // is no next instruction after a panic arising from some other instruction. - vm.state.current_frame.pc = invalid_instruction(); + vm.state.current_frame.set_invalid_pc(); return if let Some(return_value) = return_value_or_panic { let output = vm.state.heaps[return_value.memory_page] @@ -180,14 +178,6 @@ pub(crate) fn panic_from_failed_far_call>( tracer.after_instruction::, _>(vm); } -/// Panics, burning all available gas. -static INVALID_INSTRUCTION: Instruction<(), Infallible> = Instruction::from_invalid(); - -pub fn invalid_instruction<'a, T, W>() -> &'a Instruction { - // Safety: the handler of an invalid instruction is never read. - unsafe { &*(&INVALID_INSTRUCTION as *const Instruction<_, _>).cast() } -} - pub(crate) const RETURN_COST: u32 = 5; impl> Instruction { diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index 678e4ff..0b15299 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -1,7 +1,4 @@ -use std::{ - convert::Infallible, - hash::{DefaultHasher, Hash, Hasher}, -}; +use std::hash::{DefaultHasher, Hash, Hasher}; use primitive_types::{H160, U256}; pub use zksync_vm2_interface::{ @@ -62,20 +59,6 @@ pub trait StorageInterface { fn is_free_storage_slot(&self, contract: &H160, key: &U256) -> bool; } -impl StorageInterface for Infallible { - fn read_storage(&mut self, _contract: H160, _key: U256) -> Option { - unreachable!("`Infallible` cannot be constructed") - } - - fn cost_of_writing_storage(&mut self, _initial_value: Option, _new_value: U256) -> u32 { - unreachable!("`Infallible` cannot be constructed") - } - - fn is_free_storage_slot(&self, _contract: &H160, _key: &U256) -> bool { - unreachable!("`Infallible` cannot be constructed") - } -} - pub trait World: StorageInterface + Sized { /// This will be called *every* time a contract is called. Caching and decoding is /// the world implementor's job. @@ -84,16 +67,6 @@ pub trait World: StorageInterface + Sized { fn decommit_code(&mut self, hash: U256) -> Vec; } -impl World for Infallible { - fn decommit(&mut self, _hash: U256) -> Program { - unreachable!("`Infallible` cannot be constructed") - } - - fn decommit_code(&mut self, _hash: U256) -> Vec { - unreachable!("`Infallible` cannot be constructed") - } -} - /// Deterministic (across program runs and machines) hash that can be used for `Debug` implementations /// to concisely represent large amounts of data. #[cfg_attr(feature = "single_instruction_test", allow(dead_code))] // Currently used entirely in types overridden by `single_instruction_test` feature diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index 86a2a2d..cc35e55 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -4,7 +4,10 @@ use primitive_types::U256; use zksync_vm2_interface::Tracer; use crate::{ - addressing_modes::Arguments, decode::decode, hash_for_debugging, instruction::ExecutionStatus, + addressing_modes::{Arguments, INVALID_INSTRUCTION_COST}, + decode::decode, + hash_for_debugging, + instruction::ExecutionStatus, Instruction, ModeRequirements, Predicate, VirtualMachine, World, }; @@ -86,8 +89,17 @@ impl> Program { } } + /// Constructs a program from the provided instructions. + /// + /// This will insert a last invalid instruction if necessary to maintain `Program` invariants. #[doc(hidden)] // should only be used in low-level tests / benchmarks - pub fn from_raw(instructions: Vec>, code_page: Vec) -> Self { + pub fn from_raw(mut instructions: Vec>, code_page: Vec) -> Self { + if instructions.last().map_or(true, |instr| { + instr.arguments.get_static_gas_cost() != INVALID_INSTRUCTION_COST + }) { + instructions.push(Instruction::from_invalid()); + } + Self { instructions: instructions.into(), code_page: code_page.into(), @@ -102,13 +114,18 @@ impl> Program { pub fn code_page(&self) -> &[U256] { &self.code_page } + + pub(crate) fn invalid_instruction(&self) -> &Instruction { + // `unwrap()` is safe (unless the program is constructed manually); the last + self.instructions.last().unwrap() + } } // This implementation compares pointers instead of programs. // // That works well enough for the tests that this is written for. // I don't want to implement PartialEq for Instruction because -// comparing function pointers can work in suprising ways. +// comparing function pointers can work in surprising ways. impl PartialEq for Program { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.code_page, &other.code_page) @@ -138,13 +155,14 @@ fn decode_program>( raw: &[u64], is_bootloader: bool, ) -> Vec> { + let should_wrap = raw.len() >= 1 << 16; + + // Always insert invalid instruction at the end (unreachable in the case of long programs) so that + // it's possible to set `callframe.pc` without introducing UB. raw.iter() .take(1 << 16) .map(|i| decode(*i, is_bootloader)) - .chain(std::iter::once(if raw.len() >= 1 << 16 { - jump_to_beginning() - } else { - Instruction::from_invalid() - })) + .chain(should_wrap.then(jump_to_beginning)) + .chain([Instruction::from_invalid()]) .collect() } diff --git a/crates/vm2/src/single_instruction_test/program.rs b/crates/vm2/src/single_instruction_test/program.rs index 87f8831..cdc3855 100644 --- a/crates/vm2/src/single_instruction_test/program.rs +++ b/crates/vm2/src/single_instruction_test/program.rs @@ -50,7 +50,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Program { } impl Program { - pub fn instruction(&self, n: u16) -> Option<&Instruction> { + pub(crate) fn instruction(&self, n: u16) -> Option<&Instruction> { if n == 0 { Some(&self.first_instruction.get(n).as_ref()[0]) } else { @@ -62,6 +62,10 @@ impl Program { } } + pub(crate) fn invalid_instruction(&self) -> &Instruction { + &self.first_instruction.value_read[1] + } + pub fn code_page(&self) -> &Arc<[U256]> { &self.code_page } From ff157bc52c07ec9442f94467970d743da5763dd3 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 11:49:29 +0300 Subject: [PATCH 07/32] Move integration tests to library --- crates/vm2/benches/nested_near_call.rs | 2 +- crates/vm2/src/lib.rs | 4 +++- crates/vm2/src/{testworld.rs => testonly.rs} | 3 +++ .../vm2/{ => src}/tests/bytecode_behaviour.rs | 17 ++++++----------- crates/vm2/{ => src}/tests/bytecodes/call_far | Bin .../{ => src}/tests/far_call_decommitment.rs | 9 ++++----- crates/vm2/src/tests/mod.rs | 6 ++++++ .../{ => src}/tests/panic.proptest-regressions | 0 crates/vm2/{ => src}/tests/panic.rs | 7 +++---- crates/vm2/{ => src}/tests/stipend.rs | 9 ++++----- crates/vm2/src/tracing.rs | 2 +- 11 files changed, 31 insertions(+), 28 deletions(-) rename crates/vm2/src/{testworld.rs => testonly.rs} (97%) rename crates/vm2/{ => src}/tests/bytecode_behaviour.rs (62%) rename crates/vm2/{ => src}/tests/bytecodes/call_far (100%) rename crates/vm2/{ => src}/tests/far_call_decommitment.rs (98%) create mode 100644 crates/vm2/src/tests/mod.rs rename crates/vm2/{ => src}/tests/panic.proptest-regressions (100%) rename crates/vm2/{ => src}/tests/panic.rs (95%) rename crates/vm2/{ => src}/tests/stipend.rs (98%) diff --git a/crates/vm2/benches/nested_near_call.rs b/crates/vm2/benches/nested_near_call.rs index e24d88e..c770e0c 100644 --- a/crates/vm2/benches/nested_near_call.rs +++ b/crates/vm2/benches/nested_near_call.rs @@ -3,7 +3,7 @@ use zkevm_opcode_defs::ethereum_types::Address; use zksync_vm2::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register, Register1, Register2}, initial_decommit, - testworld::TestWorld, + testonly::TestWorld, Instruction, ModeRequirements, Predicate::Always, Program, Settings, VirtualMachine, diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index 0b15299..c13332b 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -40,7 +40,9 @@ pub mod single_instruction_test; #[cfg(not(feature = "single_instruction_test"))] mod stack; mod state; -pub mod testworld; +pub mod testonly; +#[cfg(all(test, not(feature = "single_instruction_test")))] +mod tests; mod tracing; mod vm; mod world_diff; diff --git a/crates/vm2/src/testworld.rs b/crates/vm2/src/testonly.rs similarity index 97% rename from crates/vm2/src/testworld.rs rename to crates/vm2/src/testonly.rs index abc52eb..8fbd642 100644 --- a/crates/vm2/src/testworld.rs +++ b/crates/vm2/src/testonly.rs @@ -1,3 +1,5 @@ +//! Test-only tools for EraVM. + use std::{ collections::{hash_map::DefaultHasher, BTreeMap}, hash::{Hash, Hasher}, @@ -11,6 +13,7 @@ use zksync_vm2_interface::Tracer; use crate::{address_into_u256, Program, StorageInterface, World}; +/// Test [`World`] implementation. #[derive(Debug)] pub struct TestWorld { pub address_to_hash: BTreeMap, diff --git a/crates/vm2/tests/bytecode_behaviour.rs b/crates/vm2/src/tests/bytecode_behaviour.rs similarity index 62% rename from crates/vm2/tests/bytecode_behaviour.rs rename to crates/vm2/src/tests/bytecode_behaviour.rs index 9ea050f..d935e7f 100644 --- a/crates/vm2/tests/bytecode_behaviour.rs +++ b/crates/vm2/src/tests/bytecode_behaviour.rs @@ -1,15 +1,9 @@ -#![cfg(not(feature = "single_instruction_test"))] - use zkevm_opcode_defs::ethereum_types::Address; -use zksync_vm2::{ - initial_decommit, testworld::TestWorld, ExecutionEnd, Program, Settings, VirtualMachine, World, -}; -use zksync_vm2_interface::{CallframeInterface, StateInterface, Tracer}; +use zksync_vm2_interface::{CallframeInterface, StateInterface}; -fn program_from_file>(filename: &str) -> Program { - let blob = std::fs::read(filename).unwrap(); - Program::new(blob, false) -} +use crate::{ + initial_decommit, testonly::TestWorld, ExecutionEnd, Program, Settings, VirtualMachine, +}; #[test] fn call_to_invalid_address() { @@ -18,7 +12,8 @@ fn call_to_invalid_address() { // result in an infinite loop. let address = Address::from_low_u64_be(0x1234567890abcdef); - let mut world = TestWorld::new(&[(address, program_from_file("tests/bytecodes/call_far"))]); + let bytecode = include_bytes!("bytecodes/call_far").to_vec(); + let mut world = TestWorld::new(&[(address, Program::new(bytecode, false))]); let program = initial_decommit(&mut world, address); let mut vm = VirtualMachine::new( diff --git a/crates/vm2/tests/bytecodes/call_far b/crates/vm2/src/tests/bytecodes/call_far similarity index 100% rename from crates/vm2/tests/bytecodes/call_far rename to crates/vm2/src/tests/bytecodes/call_far diff --git a/crates/vm2/tests/far_call_decommitment.rs b/crates/vm2/src/tests/far_call_decommitment.rs similarity index 98% rename from crates/vm2/tests/far_call_decommitment.rs rename to crates/vm2/src/tests/far_call_decommitment.rs index 0db0259..98d4248 100644 --- a/crates/vm2/tests/far_call_decommitment.rs +++ b/crates/vm2/src/tests/far_call_decommitment.rs @@ -1,18 +1,17 @@ -#![cfg(not(feature = "single_instruction_test"))] - use std::collections::HashSet; use primitive_types::{H160, U256}; use zkevm_opcode_defs::ethereum_types::Address; -use zksync_vm2::{ +use zksync_vm2_interface::{opcodes, CallframeInterface, StateInterface}; + +use crate::{ addressing_modes::{ Arguments, CodePage, Immediate1, Register, Register1, Register2, RegisterAndImmediate, }, initial_decommit, - testworld::TestWorld, + testonly::TestWorld, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, }; -use zksync_vm2_interface::{opcodes, CallframeInterface, StateInterface}; const GAS_TO_PASS: u32 = 10_000; const LARGE_BYTECODE_LEN: usize = 10_000; diff --git a/crates/vm2/src/tests/mod.rs b/crates/vm2/src/tests/mod.rs new file mode 100644 index 0000000..b11f289 --- /dev/null +++ b/crates/vm2/src/tests/mod.rs @@ -0,0 +1,6 @@ +//! Low-level VM tests. + +mod bytecode_behaviour; +mod far_call_decommitment; +mod panic; +mod stipend; diff --git a/crates/vm2/tests/panic.proptest-regressions b/crates/vm2/src/tests/panic.proptest-regressions similarity index 100% rename from crates/vm2/tests/panic.proptest-regressions rename to crates/vm2/src/tests/panic.proptest-regressions diff --git a/crates/vm2/tests/panic.rs b/crates/vm2/src/tests/panic.rs similarity index 95% rename from crates/vm2/tests/panic.rs rename to crates/vm2/src/tests/panic.rs index 005dcbe..5f79462 100644 --- a/crates/vm2/tests/panic.rs +++ b/crates/vm2/src/tests/panic.rs @@ -1,11 +1,10 @@ -#![cfg(not(feature = "single_instruction_test"))] - use proptest::prelude::*; use zkevm_opcode_defs::ethereum_types::Address; -use zksync_vm2::{ + +use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register, Register1}, initial_decommit, - testworld::TestWorld, + testonly::TestWorld, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, }; diff --git a/crates/vm2/tests/stipend.rs b/crates/vm2/src/tests/stipend.rs similarity index 98% rename from crates/vm2/tests/stipend.rs rename to crates/vm2/src/tests/stipend.rs index c935602..10d8f22 100644 --- a/crates/vm2/tests/stipend.rs +++ b/crates/vm2/src/tests/stipend.rs @@ -1,17 +1,16 @@ -#![cfg(not(feature = "single_instruction_test"))] - use primitive_types::U256; use zkevm_opcode_defs::ethereum_types::Address; -use zksync_vm2::{ +use zksync_vm2_interface::{opcodes, CallframeInterface, StateInterface}; + +use crate::{ address_into_u256, addressing_modes::{ Arguments, CodePage, Immediate1, Register, Register1, Register2, RegisterAndImmediate, }, initial_decommit, - testworld::TestWorld, + testonly::TestWorld, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, }; -use zksync_vm2_interface::{opcodes, CallframeInterface, StateInterface}; const INITIAL_GAS: u32 = 1000; diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index e41e997..d841308 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -373,7 +373,7 @@ mod test { use zksync_vm2_interface::HeapId; use super::*; - use crate::{initial_decommit, testworld::TestWorld, Instruction, Program, VirtualMachine}; + use crate::{initial_decommit, testonly::TestWorld, Instruction, Program, VirtualMachine}; #[test] fn callframe_picking() { From a3cf1d364e3cf31f5fda21d82e224f27a9eeea31 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 12:48:33 +0300 Subject: [PATCH 08/32] Document VM interface --- Cargo.toml | 6 ++ crates/vm2-interface/Cargo.toml | 3 + crates/vm2-interface/src/state_interface.rs | 97 ++++++++++++++++--- crates/vm2-interface/src/tracer_interface.rs | 64 +++++++++++- .../src/instruction_handlers/precompiles.rs | 2 +- 5 files changed, 157 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70428c3..6744f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,9 @@ zk_evm = { git = "https://github.com/matter-labs/era-zk_evm.git", rev = "b7caa02 # Dependencies within the workspace zksync_vm2_interface = { version = "0.1.0", path = "crates/vm2-interface" } zksync_vm2 = { version = "0.1.0", path = "crates/vm2" } + +[workspace.lints.rust] +missing_docs = "warn" +missing_debug_implementations = "warn" +unreachable_pub = "warn" +unused_qualifications = "warn" diff --git a/crates/vm2-interface/Cargo.toml b/crates/vm2-interface/Cargo.toml index 38a5bec..8f43907 100644 --- a/crates/vm2-interface/Cargo.toml +++ b/crates/vm2-interface/Cargo.toml @@ -13,3 +13,6 @@ categories.workspace = true [dependencies] primitive-types.workspace = true + +[lints] +workspace = true diff --git a/crates/vm2-interface/src/state_interface.rs b/crates/vm2-interface/src/state_interface.rs index 1b5d6b7..b7572dd 100644 --- a/crates/vm2-interface/src/state_interface.rs +++ b/crates/vm2-interface/src/state_interface.rs @@ -1,93 +1,148 @@ use primitive_types::{H160, U256}; +/// Public interface of the VM state. Encompasses both read and write methods. pub trait StateInterface { + /// Reads a register with the specified zero-based index. Returns a value together with a pointer flag. fn read_register(&self, register: u8) -> (U256, bool); + /// Sets a register with the specified zero-based index fn set_register(&mut self, register: u8, value: U256, is_pointer: bool); + /// Returns a mutable handle to the current call frame. fn current_frame(&mut self) -> impl CallframeInterface + '_; + /// Returns the total number of call frames. fn number_of_callframes(&self) -> usize; + /// Returns a mutable handle to a call frame with the specified index, where /// zero is the current frame, one is the frame before that etc. fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_; - fn read_heap_byte(&self, heap: HeapId, index: u32) -> u8; - /// Reads an entire `U256` word in the big-endian order from the specified heap page / `index` + /// Reads a single byte from the specified heap page at the specified 0-based offset. + fn read_heap_byte(&self, heap: HeapId, offset: u32) -> u8; + /// Reads an entire `U256` word in the big-endian order from the specified heap page / `offset` /// (which is the index of the most significant byte of the read value). - fn read_heap_u256(&self, heap: HeapId, index: u32) -> U256; - /// Writes an entire `U256` word in the big-endian order to the specified heap page at the specified `index` + fn read_heap_u256(&self, heap: HeapId, offset: u32) -> U256; + /// Writes an entire `U256` word in the big-endian order to the specified heap page at the specified `offset` /// (which is the index of the most significant byte of the written value). - fn write_heap_u256(&mut self, heap: HeapId, index: u32, value: U256); + fn write_heap_u256(&mut self, heap: HeapId, offset: u32, value: U256); + /// Returns current execution flags. fn flags(&self) -> Flags; + /// Sets current execution flags. fn set_flags(&mut self, flags: Flags); + /// Returns the currently set 0-based transaction number. fn transaction_number(&self) -> u16; + /// Sets the current transaction number. fn set_transaction_number(&mut self, value: u16); + /// Returns the value of the context register. fn context_u128_register(&self) -> u128; + /// Sets the value of the context register. fn set_context_u128_register(&mut self, value: u128); + /// Iterates over storage slots read or written during VM execution. fn get_storage_state(&self) -> impl Iterator; - + /// Iterates over all transient storage slots set during VM execution. fn get_transient_storage_state(&self) -> impl Iterator; + /// Gets value of the specified transient storage slot. fn get_transient_storage(&self, address: H160, slot: U256) -> U256; + /// Sets value of the specified transient storage slot. fn write_transient_storage(&mut self, address: H160, slot: U256, value: U256); + /// Iterates over events emitted during VM execution. fn events(&self) -> impl Iterator; + /// Iterates over L2-to-L1 logs emitted during VM execution. fn l2_to_l1_logs(&self) -> impl Iterator; + /// Gets the current amount of published pubdata. fn pubdata(&self) -> i32; + /// Sets the current amount of published pubdata. fn set_pubdata(&mut self, value: i32); } +/// VM execution flags. See the EraVM reference for more details. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Flags { + /// "Less than" flag. pub less_than: bool, + /// "Equal" flag. pub equal: bool, + /// "Greater than" flag. pub greater: bool, } +/// Public interface of an EraVM call frame. pub trait CallframeInterface { + /// Address of the storage context associated with this frame. For delegate calls, this address is inherited from the calling contract; + /// otherwise, it's the same as [`Self::code_address()`]. fn address(&self) -> H160; + /// Sets the address of the executing contract. fn set_address(&mut self, address: H160); + /// Address of the contract being executed. fn code_address(&self) -> H160; + /// Sets the address of the contract being executed. fn set_code_address(&mut self, address: H160); + /// Address of the calling contract. Respects delegate and mimic calls. fn caller(&self) -> H160; + /// Sets the address of the calling contract fn set_caller(&mut self, address: H160); - /// During panic and arbitrary code execution this returns None. + /// Returns the current program counter (i.e., 0-based index of the instruction being executed). + /// During panic and arbitrary code execution this returns `None`. fn program_counter(&self) -> Option; - + /// Sets the program counter. /// The VM will execute an invalid instruction if you jump out of the program. fn set_program_counter(&mut self, value: u16); + /// Returns a 0-based index of the starting exception handler instruction. fn exception_handler(&self) -> u16; + /// Sets the exception handler pointer. fn set_exception_handler(&mut self, value: u16); + /// Checks whether the call is static. fn is_static(&self) -> bool; + /// Checks whether the call is executed in the kernel mode. fn is_kernel(&self) -> bool; + /// Returns the remaining amount of gas. fn gas(&self) -> u32; + /// Sets the remaining amount of gas. fn set_gas(&mut self, new_gas: u32); + /// Additional fn stipend(&self) -> u32; + /// Returns the context register for this call. fn context_u128(&self) -> u128; + /// Sets the context register for this call. fn set_context_u128(&mut self, value: u128); + /// Checks whether this frame corresponds to a near call. fn is_near_call(&self) -> bool; + /// Reads the specified stack slot. Returns a value together with a pointer flag. fn read_stack(&self, index: u16) -> (U256, bool); + /// Sets the value and pointer flag for the specified stack slot. fn write_stack(&mut self, index: u16, value: U256, is_pointer: bool); + /// Returns the stack pointer. fn stack_pointer(&self) -> u16; + /// Sets the stack pointer. fn set_stack_pointer(&mut self, value: u16); + /// Returns ID of the main heap page used in this call. fn heap(&self) -> HeapId; + /// Returns the main heap page boundary (number of paid bytes). fn heap_bound(&self) -> u32; + /// Sets the main heap page boundary. fn set_heap_bound(&mut self, value: u32); + /// Returns ID of the auxiliary heap page used in this call. fn aux_heap(&self) -> HeapId; + /// Returns the auxiliary heap page boundary (number of paid bytes). fn aux_heap_bound(&self) -> u32; + /// Sets the auxiliary heap page boundary. fn set_aux_heap_bound(&mut self, value: u32); + /// Reads a word from the code page of the executing contract. fn read_code_page(&self, slot: u16) -> U256; } @@ -109,33 +164,49 @@ impl HeapId { Self(value) } + /// Converts this ID to an integer value. pub const fn as_u32(self) -> u32 { self.0 } } +/// Event emitted by EraVM. +/// /// There is no address field because nobody is interested in events that don't come -/// from the event writer, so we simply do not record events coming frome anywhere else. +/// from the event writer, so we simply do not record events coming from anywhere else. #[derive(Clone, PartialEq, Debug)] pub struct Event { + /// Event key. pub key: U256, + /// Event value. pub value: U256, + /// Is this event first in a chain of events? pub is_first: bool, + /// Shard identifier (currently, always set to 0). pub shard_id: u8, + /// 0-based index of a transaction that has emitted this event. pub tx_number: u16, } +/// L2-to-L1 log emitted by EraVM. #[derive(Debug)] pub struct L2ToL1Log { + /// Log key. pub key: U256, + /// Log value. pub value: U256, + /// Is this a service log? pub is_service: bool, + /// Address of the contract that has emitted this log. pub address: H160, + /// Shard identifier (currently, always set to 0). pub shard_id: u8, + /// 0-based index of a transaction that has emitted this event. pub tx_number: u16, } #[cfg(test)] +#[derive(Debug)] pub struct DummyState; #[cfg(test)] @@ -212,11 +283,11 @@ impl StateInterface for DummyState { unimplemented!() } - fn events(&self) -> impl Iterator { + fn events(&self) -> impl Iterator { std::iter::empty() } - fn l2_to_l1_logs(&self) -> impl Iterator { + fn l2_to_l1_logs(&self) -> impl Iterator { std::iter::empty() } @@ -319,7 +390,7 @@ impl CallframeInterface for DummyState { unimplemented!() } - fn heap(&self) -> crate::HeapId { + fn heap(&self) -> HeapId { unimplemented!() } @@ -331,7 +402,7 @@ impl CallframeInterface for DummyState { unimplemented!() } - fn aux_heap(&self) -> crate::HeapId { + fn aux_heap(&self) -> HeapId { unimplemented!() } diff --git a/crates/vm2-interface/src/tracer_interface.rs b/crates/vm2-interface/src/tracer_interface.rs index 3aa7530..7463c7f 100644 --- a/crates/vm2-interface/src/tracer_interface.rs +++ b/crates/vm2-interface/src/tracer_interface.rs @@ -48,54 +48,87 @@ macro_rules! forall_simple_opcodes { macro_rules! pub_struct { ($x:ident) => { + #[doc = concat!("`", stringify!($x), "` opcode.")] + #[derive(Debug)] pub struct $x; }; } +/// EraVM opcodes. pub mod opcodes { use std::marker::PhantomData; use super::{CallingMode, ReturnType}; forall_simple_opcodes!(pub_struct); + + /// `FarCall` group of opcodes distinguished by the calling mode (normal, delegate, or mimic). + #[derive(Debug)] pub struct FarCall(PhantomData); + + /// `Ret` group of opcodes distinguished by the return type (normal, panic, or revert). + #[derive(Debug)] pub struct Ret(PhantomData); + /// Normal [`Ret`]urn mode / [`FarCall`] mode. + #[derive(Debug)] pub struct Normal; + + /// Delegate [`FarCall`] mode. + #[derive(Debug)] pub struct Delegate; + + /// Mimic [`FarCall`] mode. + #[derive(Debug)] pub struct Mimic; + + /// Revert [`Ret`]urn mode. + #[derive(Debug)] pub struct Revert; + + /// Panic [`Ret`]urn mode. + #[derive(Debug)] pub struct Panic; + /// Calling mode for the [`FarCall`] opcodes. pub trait TypeLevelCallingMode { + /// Constant corresponding to this mode allowing to easily `match` it. const VALUE: CallingMode; } impl TypeLevelCallingMode for Normal { const VALUE: CallingMode = CallingMode::Normal; } + impl TypeLevelCallingMode for Delegate { const VALUE: CallingMode = CallingMode::Delegate; } + impl TypeLevelCallingMode for Mimic { const VALUE: CallingMode = CallingMode::Mimic; } + /// Return type for the [`Ret`] opcodes. pub trait TypeLevelReturnType { + /// Constant corresponding to this return type allowing to easily `match` it. const VALUE: ReturnType; } impl TypeLevelReturnType for Normal { const VALUE: ReturnType = ReturnType::Normal; } + impl TypeLevelReturnType for Revert { const VALUE: ReturnType = ReturnType::Revert; } + impl TypeLevelReturnType for Panic { const VALUE: ReturnType = ReturnType::Panic; } } +/// All supported EraVM opcodes in a single enumeration. +#[allow(missing_docs)] #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum Opcode { Nop, @@ -143,27 +176,38 @@ pub enum Opcode { TransientStorageWrite, } +/// All supported calling modes for [`FarCall`] opcode. #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum CallingMode { + /// Normal calling mode. Normal, + /// Delegate calling mode (similar to `delegatecall` in EVM). Delegate, + /// Mimic calling mode (can only be used by system contracts; allows to emulate `eth_call` semantics while retaining the bootloader). Mimic, } +/// All supported return types for the [`Ret`] opcode. #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum ReturnType { + /// Normal return. Normal, + /// Revert (e.g., a result of a Solidity `revert`). Revert, + /// Panic, i.e. a non-revert abnormal control flow termination (e.g., out of gas). Panic, } impl ReturnType { + /// Checks if this return type is [normal](Self::Normal). pub fn is_failure(&self) -> bool { *self != ReturnType::Normal } } +/// Trait mapping opcodes as types to the corresponding variants of the [`Opcode`] enum. pub trait OpcodeType { + /// `Opcode` variant corresponding to this opcode type. const VALUE: Opcode; } @@ -209,23 +253,41 @@ impl OpcodeType for opcodes::Ret { /// } /// ``` pub trait Tracer { + /// Executes logic before an instruction handler. + /// + /// The default implementation does nothing. fn before_instruction(&mut self, _state: &mut S) {} + /// Executes logic after an instruction handler. + /// + /// The default implementation does nothing. fn after_instruction(&mut self, _state: &mut S) {} + /// Provides cycle statistics for "complex" instructions from the prover perspective (mostly precompile calls). + /// + /// The default implementation does nothing. fn on_extra_prover_cycles(&mut self, _stats: CycleStats) {} } +/// Cycle statistics emitted by the VM and supplied to [`Tracer::on_extra_prover_cycles()`]. #[derive(Debug, Clone, Copy)] pub enum CycleStats { + /// Call to the `keccak256` precompile with the specified number of hash cycles. Keccak256(u32), + /// Call to the `sha256` precompile with the specified number of hash cycles. Sha256(u32), + /// Call to the `ecrecover` precompile with the specified number of hash cycles. EcRecover(u32), - Secp256k1Verify(u32), + /// Call to the `secp256r1_verify` precompile with the specified number of hash cycles. + Secp256r1Verify(u32), + /// Decommitting an opcode. Decommit(u32), + /// Reading a slot from the VM storage. StorageRead, + /// Writing a slot to the VM storage. StorageWrite, } +/// No-op tracer implementation. impl Tracer for () {} // Multiple tracers can be combined by building a linked list out of tuples. diff --git a/crates/vm2/src/instruction_handlers/precompiles.rs b/crates/vm2/src/instruction_handlers/precompiles.rs index d1838b8..6eae545 100644 --- a/crates/vm2/src/instruction_handlers/precompiles.rs +++ b/crates/vm2/src/instruction_handlers/precompiles.rs @@ -82,7 +82,7 @@ fn precompile_call>( )); } SECP256R1_VERIFY_PRECOMPILE_ADDRESS => { - tracer.on_extra_prover_cycles(CycleStats::Secp256k1Verify( + tracer.on_extra_prover_cycles(CycleStats::Secp256r1Verify( secp256r1_verify_function::<_, false>(0, query, heaps).0 as u32, )); } From 22fbfd69fbf74765636bdf3ced491293aee3c9f0 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 13:26:08 +0300 Subject: [PATCH 09/32] Document VM implementation --- crates/vm2-interface/src/state_interface.rs | 4 +- crates/vm2/Cargo.toml | 3 ++ crates/vm2/benches/nested_near_call.rs | 2 + crates/vm2/src/addressing_modes.rs | 21 ++++++++-- crates/vm2/src/bitset.rs | 6 +-- crates/vm2/src/callframe.rs | 4 +- crates/vm2/src/decode.rs | 2 +- crates/vm2/src/fat_pointer.rs | 5 +++ crates/vm2/src/heap.rs | 2 +- crates/vm2/src/instruction.rs | 6 ++- crates/vm2/src/instruction_handlers/event.rs | 3 +- .../vm2/src/instruction_handlers/far_call.rs | 29 +++++++------ crates/vm2/src/lib.rs | 16 ++++++-- crates/vm2/src/mode_requirements.rs | 5 ++- crates/vm2/src/predication.rs | 18 +++++--- crates/vm2/src/rollback.rs | 8 ++-- .../src/single_instruction_test/callframe.rs | 4 +- .../single_instruction_test/into_zk_evm.rs | 17 ++++---- .../src/single_instruction_test/mock_array.rs | 10 ++--- crates/vm2/src/single_instruction_test/mod.rs | 2 + .../print_mock_info.rs | 4 +- .../universal_state.rs | 2 +- crates/vm2/src/stack.rs | 4 +- crates/vm2/src/testonly.rs | 5 ++- crates/vm2/src/tracing.rs | 17 +------- crates/vm2/src/vm.rs | 2 + crates/vm2/src/world_diff.rs | 41 ++++++------------- 27 files changed, 134 insertions(+), 108 deletions(-) diff --git a/crates/vm2-interface/src/state_interface.rs b/crates/vm2-interface/src/state_interface.rs index b7572dd..57197c2 100644 --- a/crates/vm2-interface/src/state_interface.rs +++ b/crates/vm2-interface/src/state_interface.rs @@ -174,7 +174,7 @@ impl HeapId { /// /// There is no address field because nobody is interested in events that don't come /// from the event writer, so we simply do not record events coming from anywhere else. -#[derive(Clone, PartialEq, Debug)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Event { /// Event key. pub key: U256, @@ -189,7 +189,7 @@ pub struct Event { } /// L2-to-L1 log emitted by EraVM. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct L2ToL1Log { /// Log key. pub key: U256, diff --git a/crates/vm2/Cargo.toml b/crates/vm2/Cargo.toml index 4134bb3..5b0b329 100644 --- a/crates/vm2/Cargo.toml +++ b/crates/vm2/Cargo.toml @@ -27,6 +27,9 @@ anyhow = { workspace = true, optional = true } divan.workspace = true proptest.workspace = true +[lints] +workspace = true + [[bench]] name = "nested_near_call" harness = false diff --git a/crates/vm2/benches/nested_near_call.rs b/crates/vm2/benches/nested_near_call.rs index c770e0c..6dd28df 100644 --- a/crates/vm2/benches/nested_near_call.rs +++ b/crates/vm2/benches/nested_near_call.rs @@ -1,3 +1,5 @@ +//! Low-level benchmarks. + use divan::{black_box, Bencher}; use zkevm_opcode_defs::ethereum_types::Address; use zksync_vm2::{ diff --git a/crates/vm2/src/addressing_modes.rs b/crates/vm2/src/addressing_modes.rs index 0e257a5..182babf 100644 --- a/crates/vm2/src/addressing_modes.rs +++ b/crates/vm2/src/addressing_modes.rs @@ -87,7 +87,7 @@ impl DestinationWriter for Option { } } -/// Argument provided to an instruction in an EraVM bytecode. +/// Arguments provided to an instruction in an EraVM bytecode. // It is important for performance that this fits into 8 bytes. #[derive(Debug)] pub struct Arguments { @@ -105,6 +105,7 @@ pub(crate) const SLOAD_COST: u32 = 2008; pub(crate) const INVALID_INSTRUCTION_COST: u32 = 4294967295; impl Arguments { + /// Creates arguments from the provided info. pub const fn new( predicate: Predicate, gas_cost: u32, @@ -280,7 +281,9 @@ impl SourceWriter for Immediate2 { #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct RegisterAndImmediate { + /// Immediate value. pub immediate: u16, + /// Register spec. pub register: Register, } @@ -510,16 +513,22 @@ impl PackedRegisters { } } -/// All supported addressing modes for source arguments. +/// All supported addressing modes for the first source argument. #[enum_dispatch(SourceWriter)] #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum AnySource { + /// Register mode. Register1, + /// Immediate mode. Immediate1, + /// Absolute stack addressing. AbsoluteStack, + /// Relative stack addressing. RelativeStack, + /// Relative stack addressing that updates the stack pointer on access. AdvanceStackPointer, + /// Addressing into the code page of the executing contract. CodePage, } @@ -528,7 +537,9 @@ pub enum AnySource { #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum RegisterOrImmediate { + /// Register mode. Register1, + /// Immediate mode. Immediate1, } @@ -548,13 +559,17 @@ impl TryFrom for RegisterOrImmediate { } } -/// All supported addressing modes for destination arguments. +/// All supported addressing modes for the first destination argument. #[enum_dispatch(DestinationWriter)] #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum AnyDestination { + /// Register mode. Register1, + /// Absolute stack addressing. AbsoluteStack, + /// Relative stack addressing. RelativeStack, + /// Relative stack addressing that updates the stack pointer on access. AdvanceStackPointer, } diff --git a/crates/vm2/src/bitset.rs b/crates/vm2/src/bitset.rs index ad1b4f8..2b14c6d 100644 --- a/crates/vm2/src/bitset.rs +++ b/crates/vm2/src/bitset.rs @@ -4,19 +4,19 @@ pub(crate) struct Bitset([u64; 1 << 10]); impl Bitset { #[inline(always)] - pub fn get(&self, i: u16) -> bool { + pub(crate) fn get(&self, i: u16) -> bool { let (slot, bit) = slot_and_bit(i); self.0[slot] & bit != 0 } #[inline(always)] - pub fn set(&mut self, i: u16) { + pub(crate) fn set(&mut self, i: u16) { let (slot, bit) = slot_and_bit(i); self.0[slot] |= bit; } #[inline(always)] - pub fn clear(&mut self, i: u16) { + pub(crate) fn clear(&mut self, i: u16) { let (slot, bit) = slot_and_bit(i); self.0[slot] &= !bit; } diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 70bb78c..b32464a 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -139,14 +139,14 @@ impl> Callframe { /// Sets the next instruction to execute to the instruction at the given index. /// If the index is out of bounds, the invalid instruction is used. - pub fn set_pc_from_u16(&mut self, index: u16) { + pub(crate) fn set_pc_from_u16(&mut self, index: u16) { self.pc = self .program .instruction(index) .unwrap_or_else(|| self.program.invalid_instruction()) } - pub fn set_invalid_pc(&mut self) { + pub(crate) fn set_invalid_pc(&mut self) { self.pc = self.program.invalid_instruction(); } diff --git a/crates/vm2/src/decode.rs b/crates/vm2/src/decode.rs index 2da8f79..44a64bd 100644 --- a/crates/vm2/src/decode.rs +++ b/crates/vm2/src/decode.rs @@ -61,7 +61,7 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I zkevm_opcode_defs::Condition::Ge => crate::Predicate::IfGE, zkevm_opcode_defs::Condition::Le => crate::Predicate::IfLE, zkevm_opcode_defs::Condition::Ne => crate::Predicate::IfNotEQ, - zkevm_opcode_defs::Condition::GtOrLt => crate::Predicate::IfGtOrLT, + zkevm_opcode_defs::Condition::GtOrLt => crate::Predicate::IfGTOrLT, }; let arguments = Arguments::new( predicate, diff --git a/crates/vm2/src/fat_pointer.rs b/crates/vm2/src/fat_pointer.rs index 8e7b907..662bb7f 100644 --- a/crates/vm2/src/fat_pointer.rs +++ b/crates/vm2/src/fat_pointer.rs @@ -5,9 +5,13 @@ use zksync_vm2_interface::HeapId; #[derive(Debug)] #[repr(C)] pub struct FatPointer { + /// Additional pointer offset inside the `start..(start + length)` range. pub offset: u32, + /// ID of the heap page this points to. pub memory_page: HeapId, + /// 0-based index of the pointer start byte at the `memory` page. pub start: u32, + /// Length of the pointed slice in bytes. pub length: u32, } @@ -29,6 +33,7 @@ impl From for FatPointer { } impl FatPointer { + /// Converts this pointer into a `U256` word. #[cfg(target_endian = "little")] pub fn into_u256(self) -> U256 { U256::zero() + unsafe { std::mem::transmute::(self) } diff --git a/crates/vm2/src/heap.rs b/crates/vm2/src/heap.rs index 7d85285..fdcc948 100644 --- a/crates/vm2/src/heap.rs +++ b/crates/vm2/src/heap.rs @@ -230,7 +230,7 @@ impl Heaps { } } - pub fn write_u256(&mut self, heap: HeapId, start_address: u32, value: U256) { + pub(crate) fn write_u256(&mut self, heap: HeapId, start_address: u32, value: U256) { if heap == HeapId::FIRST { let prev_value = self[heap].read_u256(start_address); self.bootloader_heap_rollback_info diff --git a/crates/vm2/src/instruction.rs b/crates/vm2/src/instruction.rs index 730ebfa..05789ed 100644 --- a/crates/vm2/src/instruction.rs +++ b/crates/vm2/src/instruction.rs @@ -25,11 +25,15 @@ pub(crate) enum ExecutionStatus { Stopped(ExecutionEnd), } +/// End of a VM execution returned from [`VirtualMachine::run()`]. #[derive(Debug, PartialEq)] pub enum ExecutionEnd { + /// The executed program has finished and returned the specified data. ProgramFinished(Vec), + /// The executed program has reverted returning the specified data. Reverted(Vec), + /// The executed program has panicked. Panicked, - /// Returned when the bootloader writes to the heap location [crate::Settings::hook_address] + /// Returned when the bootloader writes to the heap location specified by [`hook_address`](crate::Settings.hook_address). SuspendedOnHook(u32), } diff --git a/crates/vm2/src/instruction_handlers/event.rs b/crates/vm2/src/instruction_handlers/event.rs index 46e001d..58bda54 100644 --- a/crates/vm2/src/instruction_handlers/event.rs +++ b/crates/vm2/src/instruction_handlers/event.rs @@ -1,12 +1,11 @@ use primitive_types::H160; use zkevm_opcode_defs::ADDRESS_EVENT_WRITER; -use zksync_vm2_interface::{opcodes, Tracer}; +use zksync_vm2_interface::{opcodes, Event, L2ToL1Log, Tracer}; use super::common::boilerplate_ext; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, instruction::ExecutionStatus, - world_diff::{Event, L2ToL1Log}, Instruction, VirtualMachine, World, }; diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 4d1152d..551ac98 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -158,11 +158,12 @@ fn far_call< }) } +#[derive(Debug)] pub(crate) struct FarCallABI { - pub gas_to_pass: u32, - pub shard_id: u8, - pub is_constructor_call: bool, - pub is_system_call: bool, + pub(crate) gas_to_pass: u32, + pub(crate) shard_id: u8, + pub(crate) is_constructor_call: bool, + pub(crate) is_system_call: bool, } pub(crate) fn get_far_call_arguments(abi: U256) -> FarCallABI { @@ -204,11 +205,11 @@ pub(crate) fn get_far_call_calldata>( return None; } match target { - ToHeap => { + FatPointerTarget::ToHeap => { grow_heap::<_, _, Heap>(&mut vm.state, bound).ok()?; pointer.memory_page = vm.state.current_frame.heap; } - ToAuxHeap => { + FatPointerTarget::ToAuxHeap => { grow_heap::<_, _, AuxHeap>(&mut vm.state, bound).ok()?; pointer.memory_page = vm.state.current_frame.aux_heap; } @@ -218,10 +219,10 @@ pub(crate) fn get_far_call_calldata>( // TODO PLA-974 revert to not growing the heap on failure as soon as zk_evm is fixed let bound = u32::MAX; match target { - ToHeap => { + FatPointerTarget::ToHeap => { grow_heap::<_, _, Heap>(&mut vm.state, bound).ok()?; } - ToAuxHeap => { + FatPointerTarget::ToAuxHeap => { grow_heap::<_, _, AuxHeap>(&mut vm.state, bound).ok()?; } } @@ -233,23 +234,25 @@ pub(crate) fn get_far_call_calldata>( Some(pointer) } +#[derive(Debug)] enum FatPointerSource { MakeNewPointer(FatPointerTarget), ForwardFatPointer, } + +#[derive(Debug)] enum FatPointerTarget { ToHeap, ToAuxHeap, } -use FatPointerTarget::*; impl FatPointerSource { - pub const fn from_abi(value: u8) -> Self { + pub(crate) const fn from_abi(value: u8) -> Self { match value { - 0 => Self::MakeNewPointer(ToHeap), + 0 => Self::MakeNewPointer(FatPointerTarget::ToHeap), 1 => Self::ForwardFatPointer, - 2 => Self::MakeNewPointer(ToAuxHeap), - _ => Self::MakeNewPointer(ToHeap), // default + 2 => Self::MakeNewPointer(FatPointerTarget::ToAuxHeap), + _ => Self::MakeNewPointer(FatPointerTarget::ToHeap), // default } } } diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index c13332b..f0e993b 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -1,8 +1,13 @@ +//! # High-Performance ZKsync Era VM +//! +//! This crate provides high-performance [`VirtualMachine`] for ZKsync Era. + use std::hash::{DefaultHasher, Hash, Hasher}; use primitive_types::{H160, U256}; pub use zksync_vm2_interface::{ - CallframeInterface, CycleStats, HeapId, Opcode, OpcodeType, ReturnType, StateInterface, Tracer, + CallframeInterface, CycleStats, Event, HeapId, L2ToL1Log, Opcode, OpcodeType, ReturnType, + StateInterface, Tracer, }; // Re-export missing modules if single instruction testing is enabled @@ -16,7 +21,7 @@ pub use self::{ predication::Predicate, program::Program, vm::{Settings, VirtualMachine}, - world_diff::{Event, L2ToL1Log, WorldDiff}, + world_diff::WorldDiff, }; pub mod addressing_modes; @@ -61,11 +66,16 @@ pub trait StorageInterface { fn is_free_storage_slot(&self, contract: &H160, key: &U256) -> bool; } +/// Encapsulates VM interaction with the external world. This includes VM storage and decomitting (loading) bytecodes +/// for execution. pub trait World: StorageInterface + Sized { - /// This will be called *every* time a contract is called. Caching and decoding is + /// Loads a bytecode with the specified hash. + /// + /// This method will be called *every* time a contract is called. Caching and decoding is /// the world implementor's job. fn decommit(&mut self, hash: U256) -> Program; + /// Loads bytecode bytes for the `decommit` opcode. fn decommit_code(&mut self, hash: U256) -> Vec; } diff --git a/crates/vm2/src/mode_requirements.rs b/crates/vm2/src/mode_requirements.rs index 5ba2c8f..efed437 100644 --- a/crates/vm2/src/mode_requirements.rs +++ b/crates/vm2/src/mode_requirements.rs @@ -1,16 +1,19 @@ +/// Requirements for the VM execution mode that can be placed by instructions. #[derive(Debug)] pub struct ModeRequirements(pub(crate) u8); impl ModeRequirements { + /// Creates new requirements. pub const fn new(kernel_only: bool, cannot_use_in_static: bool) -> Self { Self((kernel_only as u8) | ((cannot_use_in_static as u8) << 1)) } + /// Creates default requirements that always hold. pub const fn none() -> Self { Self::new(false, false) } - pub fn met(&self, is_kernel: bool, is_static: bool) -> bool { + pub(crate) fn met(&self, is_kernel: bool, is_static: bool) -> bool { let enabled_modes = (is_kernel as u8) | ((!is_static as u8) << 1); enabled_modes & self.0 == self.0 } diff --git a/crates/vm2/src/predication.rs b/crates/vm2/src/predication.rs index 64893b0..36dee60 100644 --- a/crates/vm2/src/predication.rs +++ b/crates/vm2/src/predication.rs @@ -4,32 +4,40 @@ const GT_BIT: u8 = 1 << 2; const ALWAYS_BIT: u8 = 1 << 3; #[derive(Debug, Clone, PartialEq)] -pub struct Flags(u8); +pub(crate) struct Flags(u8); impl Flags { - pub fn new(lt_of: bool, eq: bool, gt: bool) -> Self { + pub(crate) fn new(lt_of: bool, eq: bool, gt: bool) -> Self { Flags(lt_of as u8 | ((eq as u8) << 1) | ((gt as u8) << 2) | ALWAYS_BIT) } } -/// Predicate encoded so that comparing it to flags is efficient +/// Predicate for an instruction. Encoded so that comparing it to flags is efficient. #[derive(Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[repr(u8)] pub enum Predicate { + /// Always execute the associated instruction. Always = ALWAYS_BIT, + /// Execute the associated instruction if the "greater than" execution flag is set. IfGT = GT_BIT, + /// Execute the associated instruction if the "equal" execution flag is set. IfEQ = EQ_BIT, + /// Execute the associated instruction if the "less than" execution flag is set. IfLT = LT_BIT, + /// Execute the associated instruction if either of "greater than" or "equal" execution flags are set. IfGE = GT_BIT | EQ_BIT, + /// Execute the associated instruction if either of "less than" or "equal" execution flags are set. IfLE = LT_BIT | EQ_BIT, + /// Execute the associated instruction if the "equal" execution flag is not set. IfNotEQ = EQ_BIT << 4 | ALWAYS_BIT, - IfGtOrLT = GT_BIT | LT_BIT, + /// Execute the associated instruction if either of "less than" or "greater than" execution flags are set. + IfGTOrLT = GT_BIT | LT_BIT, } impl Predicate { #[inline(always)] - pub fn satisfied(&self, flags: &Flags) -> bool { + pub(crate) fn satisfied(&self, flags: &Flags) -> bool { let bits = *self as u8; bits & flags.0 != 0 && (bits >> 4) & flags.0 == 0 } diff --git a/crates/vm2/src/rollback.rs b/crates/vm2/src/rollback.rs index 9764bd2..0818949 100644 --- a/crates/vm2/src/rollback.rs +++ b/crates/vm2/src/rollback.rs @@ -15,7 +15,7 @@ pub(crate) struct RollbackableMap { } impl RollbackableMap { - pub fn insert(&mut self, key: K, value: V) -> Option { + pub(crate) fn insert(&mut self, key: K, value: V) -> Option { let old_value = self.map.insert(key.clone(), value); self.old_entries.push((key, old_value.clone())); old_value @@ -72,7 +72,7 @@ pub(crate) struct RollbackableSet { impl RollbackableSet { /// Adds `key` to the set and returns if it was added (not present earlier). - pub fn add(&mut self, key: T) -> bool { + pub(crate) fn add(&mut self, key: T) -> bool { let is_new = self.map.insert(key.clone(), ()).is_none(); if is_new { self.old_entries.push(key); @@ -133,7 +133,7 @@ impl Rollback for RollbackableLog { } impl RollbackableLog { - pub fn push(&mut self, entry: T) { + pub(crate) fn push(&mut self, entry: T) { self.entries.push(entry) } @@ -150,7 +150,7 @@ impl AsRef<[T]> for RollbackableLog { /// Rollbackable Plain Old Data simply stores copies of itself in snapshots. #[derive(Debug, Default, Copy, Clone)] -pub(crate) struct RollbackablePod(pub T); +pub(crate) struct RollbackablePod(pub(crate) T); impl Rollback for RollbackablePod { type Snapshot = T; diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index f392888..fd13894 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -65,11 +65,11 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { } impl> Callframe { - pub fn raw_first_instruction(&self) -> u64 { + pub(crate) fn raw_first_instruction(&self) -> u64 { self.program.raw_first_instruction } - pub fn dummy() -> Self { + pub(crate) fn dummy() -> Self { Self { address: H160::zero(), code_address: H160::zero(), diff --git a/crates/vm2/src/single_instruction_test/into_zk_evm.rs b/crates/vm2/src/single_instruction_test/into_zk_evm.rs index 1232160..48f5ceb 100644 --- a/crates/vm2/src/single_instruction_test/into_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/into_zk_evm.rs @@ -94,7 +94,7 @@ pub struct MockMemory { // One arbitrary heap value is not enough for zk_evm // because it reads two U256s to read one U256. #[derive(Debug, Copy, Clone)] -pub struct ExpectedHeapValue { +pub(crate) struct ExpectedHeapValue { heap: u32, start_index: u32, value: [u8; 32], @@ -199,10 +199,7 @@ impl Storage for MockWorldWrapper { &mut self, _: u32, mut query: zk_evm::aux_structures::LogQuery, - ) -> ( - zk_evm::aux_structures::LogQuery, - zk_evm::aux_structures::PubdataCost, - ) { + ) -> (zk_evm::aux_structures::LogQuery, PubdataCost) { if query.rw_flag { (query, PubdataCost(0)) } else { @@ -237,12 +234,12 @@ impl DecommittmentProcessor for MockDecommitter { Ok(partial_query) } - fn decommit_into_memory( + fn decommit_into_memory( &mut self, _: u32, _partial_query: zk_evm::aux_structures::DecommittmentQuery, _memory: &mut M, - ) -> anyhow::Result>> { + ) -> anyhow::Result>> { Ok(None) } } @@ -289,7 +286,7 @@ impl tracing::Tracer for NoTracer { pub struct NoOracle; impl PrecompilesProcessor for NoOracle { - fn execute_precompile( + fn execute_precompile( &mut self, _: u32, _: zk_evm::aux_structures::LogQuery, @@ -336,7 +333,7 @@ impl VmWitnessTracer<8, EncodingModeProduction> for NoOracle { &mut self, _: u32, _: zk_evm::aux_structures::LogQuery, - _: zk_evm::aux_structures::PubdataCost, + _: PubdataCost, ) { } @@ -347,7 +344,7 @@ impl VmWitnessTracer<8, EncodingModeProduction> for NoOracle { &mut self, _: u32, _: zk_evm::aux_structures::DecommittmentQuery, - _: Vec, + _: Vec, ) { } diff --git a/crates/vm2/src/single_instruction_test/mock_array.rs b/crates/vm2/src/single_instruction_test/mock_array.rs index 4b9eff1..8e267c6 100644 --- a/crates/vm2/src/single_instruction_test/mock_array.rs +++ b/crates/vm2/src/single_instruction_test/mock_array.rs @@ -3,20 +3,20 @@ use std::{cell::Cell, fmt::Debug}; use arbitrary::Arbitrary; #[derive(Clone, Debug)] -pub struct MockRead { +pub(crate) struct MockRead { pub(crate) value_read: T, index_read: Cell>, } impl MockRead { - pub fn new(value: T) -> Self { + pub(crate) fn new(value: T) -> Self { Self { value_read: value, index_read: Cell::new(None), } } - pub fn get(&self, index: I) -> &T { + pub(crate) fn get(&self, index: I) -> &T { if let Some(previous_index) = self.index_read.get() { assert_eq!(previous_index, index); } @@ -25,7 +25,7 @@ impl MockRead { &self.value_read } - pub fn get_mut(&mut self, index: I) -> &mut T { + pub(crate) fn get_mut(&mut self, index: I) -> &mut T { if let Some(previous_index) = self.index_read.get() { assert_eq!(previous_index, index); } @@ -34,7 +34,7 @@ impl MockRead { &mut self.value_read } - pub fn read_that_happened(&self) -> Option<(I, T)> { + pub(crate) fn read_that_happened(&self) -> Option<(I, T)> { self.index_read .get() .map(|index| (index, self.value_read.clone())) diff --git a/crates/vm2/src/single_instruction_test/mod.rs b/crates/vm2/src/single_instruction_test/mod.rs index 4db72b2..f8bfb1f 100644 --- a/crates/vm2/src/single_instruction_test/mod.rs +++ b/crates/vm2/src/single_instruction_test/mod.rs @@ -5,6 +5,8 @@ //! //! The same kind of mocking in applied to stack memory, the program, the world and callstack. +#![allow(missing_docs)] + pub use self::{ into_zk_evm::{add_heap_to_zk_evm, vm2_to_zk_evm, NoTracer}, universal_state::UniversalVmState, diff --git a/crates/vm2/src/single_instruction_test/print_mock_info.rs b/crates/vm2/src/single_instruction_test/print_mock_info.rs index d2cac07..b6566f3 100644 --- a/crates/vm2/src/single_instruction_test/print_mock_info.rs +++ b/crates/vm2/src/single_instruction_test/print_mock_info.rs @@ -15,7 +15,7 @@ impl> VirtualMachine { } impl> State { - pub fn print_mock_info(&self) { + pub(crate) fn print_mock_info(&self) { if let Some((heapid, heap)) = self.heaps.read.read_that_happened() { println!("Heap: {:?}", heapid); if let Some((address, value)) = heap.read.read_that_happened() { @@ -37,7 +37,7 @@ impl> State { } impl Callframe { - pub fn print_mock_info(&self) { + pub(crate) fn print_mock_info(&self) { if let Some((address, (value, tag))) = self.stack.read_that_happened() { println!(" {value:?} (is_pointer: {tag}) read from stack address {address}",); } diff --git a/crates/vm2/src/single_instruction_test/universal_state.rs b/crates/vm2/src/single_instruction_test/universal_state.rs index 4cd5e89..3f8f3b5 100644 --- a/crates/vm2/src/single_instruction_test/universal_state.rs +++ b/crates/vm2/src/single_instruction_test/universal_state.rs @@ -18,7 +18,7 @@ pub struct UniversalVmState { } #[derive(PartialEq, Debug)] -pub struct UniversalVmFrame { +pub(crate) struct UniversalVmFrame { address: H160, caller: H160, code_address: H160, diff --git a/crates/vm2/src/stack.rs b/crates/vm2/src/stack.rs index 9ae5d90..9fe5ee3 100644 --- a/crates/vm2/src/stack.rs +++ b/crates/vm2/src/stack.rs @@ -111,7 +111,7 @@ pub(crate) struct StackPool { } impl StackPool { - pub fn get(&mut self) -> Box { + pub(crate) fn get(&mut self) -> Box { self.stacks .pop() .map(|mut s| { @@ -121,7 +121,7 @@ impl StackPool { .unwrap_or_else(Stack::new) } - pub fn recycle(&mut self, stack: Box) { + pub(crate) fn recycle(&mut self, stack: Box) { self.stacks.push(stack); } } diff --git a/crates/vm2/src/testonly.rs b/crates/vm2/src/testonly.rs index 8fbd642..3b316bb 100644 --- a/crates/vm2/src/testonly.rs +++ b/crates/vm2/src/testonly.rs @@ -16,11 +16,12 @@ use crate::{address_into_u256, Program, StorageInterface, World}; /// Test [`World`] implementation. #[derive(Debug)] pub struct TestWorld { - pub address_to_hash: BTreeMap, - pub hash_to_contract: BTreeMap>, + pub(crate) address_to_hash: BTreeMap, + pub(crate) hash_to_contract: BTreeMap>, } impl TestWorld { + /// Creates a test world with the provided programs. pub fn new(contracts: &[(Address, Program)]) -> Self { let mut address_to_hash = BTreeMap::new(); let mut hash_to_contract = BTreeMap::new(); diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index d841308..6c054af 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -134,24 +134,11 @@ impl> StateInterface for VirtualMachine { } fn events(&self) -> impl Iterator { - self.world_diff.events().iter().map(|event| Event { - key: event.key, - value: event.value, - is_first: event.is_first, - shard_id: event.shard_id, - tx_number: event.tx_number, - }) + self.world_diff.events().iter().copied() } fn l2_to_l1_logs(&self) -> impl Iterator { - self.world_diff.l2_to_l1_logs().iter().map(|log| L2ToL1Log { - address: log.address, - key: log.key, - value: log.value, - is_service: log.is_service, - shard_id: log.shard_id, - tx_number: log.tx_number, - }) + self.world_diff.l2_to_l1_logs().iter().copied() } fn pubdata(&self) -> i32 { diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index ee9f3d2..3870ea9 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -18,7 +18,9 @@ use crate::{ /// [`VirtualMachine`] settings. #[derive(Debug, Clone)] pub struct Settings { + /// Bytecode hash of the default account abstraction contract. pub default_aa_code_hash: [u8; 32], + /// Bytecode hash of the EVM interpreter. pub evm_interpreter_code_hash: [u8; 32], /// Writing to this address in the bootloader's heap suspends execution pub hook_address: u32, diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index af8a226..c80f798 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -5,7 +5,7 @@ use zkevm_opcode_defs::system_params::{ STORAGE_ACCESS_COLD_READ_COST, STORAGE_ACCESS_COLD_WRITE_COST, STORAGE_ACCESS_WARM_READ_COST, STORAGE_ACCESS_WARM_WRITE_COST, }; -use zksync_vm2_interface::{CycleStats, Tracer}; +use zksync_vm2_interface::{CycleStats, Event, L2ToL1Log, Tracer}; use crate::{ rollback::{Rollback, RollbackableLog, RollbackableMap, RollbackablePod, RollbackableSet}, @@ -39,7 +39,7 @@ pub struct WorldDiff { } #[derive(Debug)] -pub struct ExternalSnapshot { +pub(crate) struct ExternalSnapshot { internal_snapshot: Snapshot, pub(crate) decommitted_hashes: as Rollback>::Snapshot, read_storage_slots: as Rollback>::Snapshot, @@ -48,27 +48,6 @@ pub struct ExternalSnapshot { pubdata_costs: as Rollback>::Snapshot, } -/// There is no address field because nobody is interested in events that don't come -/// from the event writer, so we simply do not record events coming frome anywhere else. -#[derive(Clone, PartialEq, Debug)] -pub struct Event { - pub key: U256, - pub value: U256, - pub is_first: bool, - pub shard_id: u8, - pub tx_number: u16, -} - -#[derive(Debug)] -pub struct L2ToL1Log { - pub key: U256, - pub value: U256, - pub is_service: bool, - pub address: H160, - pub shard_id: u8, - pub tx_number: u16, -} - impl WorldDiff { /// Returns the storage slot's value and a refund based on its hot/cold status. pub(crate) fn read_storage( @@ -176,22 +155,25 @@ impl WorldDiff { refund } - pub fn pubdata(&self) -> i32 { + pub(crate) fn pubdata(&self) -> i32 { self.pubdata.0 } + /// Returns recorded refunds for all storage operations. pub fn storage_refunds(&self) -> &[u32] { self.storage_refunds.as_ref() } + /// Returns recorded pubdata costs for all storage operations. pub fn pubdata_costs(&self) -> &[i32] { self.pubdata_costs.as_ref() } - pub fn get_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { + pub(crate) fn get_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { self.storage_changes.as_ref() } + /// Gets changes for all touched storage slots. pub fn get_storage_changes( &self, ) -> impl Iterator, U256))> + '_ { @@ -207,6 +189,7 @@ impl WorldDiff { }) } + /// Gets changes for storage slots touched after the specified `snapshot` was created. pub fn get_storage_changes_after( &self, snapshot: &Snapshot, @@ -242,7 +225,7 @@ impl WorldDiff { .insert((contract, key), value); } - pub fn get_transient_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { + pub(crate) fn get_transient_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { self.transient_storage_changes.as_ref() } @@ -250,10 +233,11 @@ impl WorldDiff { self.events.push(event); } - pub fn events(&self) -> &[Event] { + pub(crate) fn events(&self) -> &[Event] { self.events.as_ref() } + /// Returns events emitted after the specified `snapshot` was created. pub fn events_after(&self, snapshot: &Snapshot) -> &[Event] { self.events.logs_after(snapshot.events) } @@ -262,10 +246,11 @@ impl WorldDiff { self.l2_to_l1_logs.push(log); } - pub fn l2_to_l1_logs(&self) -> &[L2ToL1Log] { + pub(crate) fn l2_to_l1_logs(&self) -> &[L2ToL1Log] { self.l2_to_l1_logs.as_ref() } + /// Returns L2-to-L1 logs emitted after the specified `snapshot` was created. pub fn l2_to_l1_logs_after(&self, snapshot: &Snapshot) -> &[L2ToL1Log] { self.l2_to_l1_logs.logs_after(snapshot.l2_to_l1_logs) } From d04126e406c247dbcdb10de2bd91b5a6f58eb3a7 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 14:36:39 +0300 Subject: [PATCH 10/32] Document instruction; make `PtrOp` private --- crates/vm2-interface/src/tracer_interface.rs | 13 ++++---- crates/vm2/benches/nested_near_call.rs | 5 ++- crates/vm2/src/decode.rs | 32 +++++++++---------- crates/vm2/src/decommit.rs | 27 ---------------- crates/vm2/src/instruction.rs | 5 ++- crates/vm2/src/instruction_handlers/binop.rs | 5 +-- .../vm2/src/instruction_handlers/context.rs | 20 ++++++++++-- .../vm2/src/instruction_handlers/decommit.rs | 1 + crates/vm2/src/instruction_handlers/event.rs | 2 ++ .../vm2/src/instruction_handlers/far_call.rs | 1 + .../src/instruction_handlers/heap_access.rs | 29 ++++++++++------- crates/vm2/src/instruction_handlers/jump.rs | 1 + crates/vm2/src/instruction_handlers/mod.rs | 1 + .../vm2/src/instruction_handlers/near_call.rs | 1 + crates/vm2/src/instruction_handlers/nop.rs | 1 + .../vm2/src/instruction_handlers/pointer.rs | 26 +++++++++++++-- .../src/instruction_handlers/precompiles.rs | 1 + crates/vm2/src/instruction_handlers/ret.rs | 7 ++++ .../vm2/src/instruction_handlers/storage.rs | 20 +++++++++--- crates/vm2/src/lib.rs | 7 ++-- .../src/single_instruction_test/callframe.rs | 5 ++- .../vm2/src/single_instruction_test/stack.rs | 2 +- crates/vm2/src/single_instruction_test/vm.rs | 4 +-- crates/vm2/src/testonly.rs | 22 ++++++++++++- crates/vm2/src/tests/bytecode_behaviour.rs | 3 +- crates/vm2/src/tests/far_call_decommitment.rs | 5 ++- crates/vm2/src/tests/panic.rs | 3 +- crates/vm2/src/tests/stipend.rs | 5 ++- crates/vm2/src/tracing.rs | 5 ++- crates/vm2/src/world_diff.rs | 3 +- 30 files changed, 164 insertions(+), 98 deletions(-) diff --git a/crates/vm2-interface/src/tracer_interface.rs b/crates/vm2-interface/src/tracer_interface.rs index 7463c7f..22a8b44 100644 --- a/crates/vm2-interface/src/tracer_interface.rs +++ b/crates/vm2-interface/src/tracer_interface.rs @@ -176,7 +176,7 @@ pub enum Opcode { TransientStorageWrite, } -/// All supported calling modes for [`FarCall`] opcode. +/// All supported calling modes for [`FarCall`](opcodes::FarCall) opcode. #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum CallingMode { /// Normal calling mode. @@ -187,7 +187,7 @@ pub enum CallingMode { Mimic, } -/// All supported return types for the [`Ret`] opcode. +/// All supported return types for the [`Ret`](opcodes::Ret) opcode. #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum ReturnType { /// Normal return. @@ -229,16 +229,17 @@ impl OpcodeType for opcodes::Ret { const VALUE: Opcode = Opcode::Ret(T::VALUE); } -/// Implement this for a type that holds the state of your tracer. +/// EraVM instruction tracer. /// -/// [Tracer::before_instruction] is called just before the actual instruction is executed. -/// If the instruction is skipped, `before_instruction` will be called with [Nop](opcodes::Nop). -/// [Tracer::after_instruction] is called once the instruction is executed and the program +/// [`Self::before_instruction()`] is called just before the actual instruction is executed. +/// If the instruction is skipped, `before_instruction` will be called with [`Nop`](opcodes::Nop). +/// [`Self::after_instruction()`] is called once the instruction is executed and the program /// counter has advanced. /// /// # Examples /// /// Here `FarCallCounter` counts the number of far calls. +/// /// ``` /// # use zksync_vm2_interface::{Tracer, StateInterface, OpcodeType, Opcode}; /// struct FarCallCounter(usize); diff --git a/crates/vm2/benches/nested_near_call.rs b/crates/vm2/benches/nested_near_call.rs index 6dd28df..410e199 100644 --- a/crates/vm2/benches/nested_near_call.rs +++ b/crates/vm2/benches/nested_near_call.rs @@ -4,8 +4,7 @@ use divan::{black_box, Bencher}; use zkevm_opcode_defs::ethereum_types::Address; use zksync_vm2::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register, Register1, Register2}, - initial_decommit, - testonly::TestWorld, + testonly::{initial_decommit, TestWorld}, Instruction, ModeRequirements, Predicate::Always, Program, Settings, VirtualMachine, @@ -54,7 +53,7 @@ fn nested_near_call_with_storage_write(bencher: Bencher) { Register1(Register::new(1)), Arguments::new(Always, 5, ModeRequirements::none()), ), - Instruction::from_sstore( + Instruction::from_storage_write( // always use same storage slot to get a warm write discount Register1(Register::new(0)), Register2(Register::new(1)), diff --git a/crates/vm2/src/decode.rs b/crates/vm2/src/decode.rs index 44a64bd..c954f31 100644 --- a/crates/vm2/src/decode.rs +++ b/crates/vm2/src/decode.rs @@ -54,14 +54,14 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I let (parsed, _) = EncodingModeProduction::parse_preliminary_variant_and_absolute_number(raw); let predicate = match parsed.condition { - zkevm_opcode_defs::Condition::Always => crate::Predicate::Always, - zkevm_opcode_defs::Condition::Gt => crate::Predicate::IfGT, - zkevm_opcode_defs::Condition::Lt => crate::Predicate::IfLT, - zkevm_opcode_defs::Condition::Eq => crate::Predicate::IfEQ, - zkevm_opcode_defs::Condition::Ge => crate::Predicate::IfGE, - zkevm_opcode_defs::Condition::Le => crate::Predicate::IfLE, - zkevm_opcode_defs::Condition::Ne => crate::Predicate::IfNotEQ, - zkevm_opcode_defs::Condition::GtOrLt => crate::Predicate::IfGTOrLT, + zkevm_opcode_defs::Condition::Always => Predicate::Always, + zkevm_opcode_defs::Condition::Gt => Predicate::IfGT, + zkevm_opcode_defs::Condition::Lt => Predicate::IfLT, + zkevm_opcode_defs::Condition::Eq => Predicate::IfEQ, + zkevm_opcode_defs::Condition::Ge => Predicate::IfGE, + zkevm_opcode_defs::Condition::Le => Predicate::IfLE, + zkevm_opcode_defs::Condition::Ne => Predicate::IfNotEQ, + zkevm_opcode_defs::Condition::GtOrLt => Predicate::IfGTOrLT, }; let arguments = Arguments::new( predicate, @@ -235,13 +235,13 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I } } Opcode::Log(x) => match x { - zkevm_opcode_defs::LogOpcode::StorageRead => Instruction::from_sload( + zkevm_opcode_defs::LogOpcode::StorageRead => Instruction::from_storage_read( src1.try_into().unwrap(), out.try_into().unwrap(), arguments, ), zkevm_opcode_defs::LogOpcode::TransientStorageRead => { - Instruction::from_sload_transient( + Instruction::from_transient_storage_read( src1.try_into().unwrap(), out.try_into().unwrap(), arguments, @@ -249,11 +249,11 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I } zkevm_opcode_defs::LogOpcode::StorageWrite => { - Instruction::from_sstore(src1.try_into().unwrap(), src2, arguments) + Instruction::from_storage_write(src1.try_into().unwrap(), src2, arguments) } zkevm_opcode_defs::LogOpcode::TransientStorageWrite => { - Instruction::from_sstore_transient(src1.try_into().unwrap(), src2, arguments) + Instruction::from_transient_storage_write(src1.try_into().unwrap(), src2, arguments) } zkevm_opcode_defs::LogOpcode::ToL1Message => Instruction::from_l2_to_l1_message( @@ -284,20 +284,20 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I Opcode::UMA(x) => { let increment = parsed.variant.flags[UMA_INCREMENT_FLAG_IDX]; match x { - zkevm_opcode_defs::UMAOpcode::HeapRead => Instruction::from_heap_load( + zkevm_opcode_defs::UMAOpcode::HeapRead => Instruction::from_heap_read( src1.try_into().unwrap(), out.try_into().unwrap(), increment.then_some(out2), arguments, ), - zkevm_opcode_defs::UMAOpcode::HeapWrite => Instruction::from_heap_store( + zkevm_opcode_defs::UMAOpcode::HeapWrite => Instruction::from_heap_write( src1.try_into().unwrap(), src2, increment.then_some(out.try_into().unwrap()), arguments, is_bootloader, ), - zkevm_opcode_defs::UMAOpcode::AuxHeapRead => Instruction::from_aux_heap_load( + zkevm_opcode_defs::UMAOpcode::AuxHeapRead => Instruction::from_aux_heap_read( src1.try_into().unwrap(), out.try_into().unwrap(), increment.then_some(out2), @@ -309,7 +309,7 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I increment.then_some(out.try_into().unwrap()), arguments, ), - zkevm_opcode_defs::UMAOpcode::FatPointerRead => Instruction::from_load_pointer( + zkevm_opcode_defs::UMAOpcode::FatPointerRead => Instruction::from_pointer_read( src1.try_into().unwrap(), out.try_into().unwrap(), increment.then_some(out2), diff --git a/crates/vm2/src/decommit.rs b/crates/vm2/src/decommit.rs index fd364c7..9bbabea 100644 --- a/crates/vm2/src/decommit.rs +++ b/crates/vm2/src/decommit.rs @@ -138,33 +138,6 @@ pub(crate) struct UnpaidDecommit { code_key: U256, } -/// May be used to load code when the VM first starts up. -/// Doesn't check for any errors. -/// Doesn't cost anything but also doesn't make the code free in future decommits. -#[doc(hidden)] // should be used only in low-level testing / benches -pub fn initial_decommit>(world: &mut W, address: H160) -> Program { - let deployer_system_contract_address = - Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); - let code_info = world - .read_storage(deployer_system_contract_address, address_into_u256(address)) - .unwrap_or_default(); - - let mut code_info_bytes = [0; 32]; - code_info.to_big_endian(&mut code_info_bytes); - - code_info_bytes[1] = 0; - let code_key: U256 = U256::from_big_endian(&code_info_bytes); - - world.decommit(code_key) -} - -#[doc(hidden)] // should be used only in low-level testing / benches -pub fn address_into_u256(address: H160) -> U256 { - let mut buffer = [0; 32]; - buffer[12..].copy_from_slice(address.as_bytes()); - U256::from_big_endian(&buffer) -} - pub(crate) fn u256_into_address(source: U256) -> H160 { let mut result = H160::zero(); let mut bytes = [0; 32]; diff --git a/crates/vm2/src/instruction.rs b/crates/vm2/src/instruction.rs index 05789ed..443a798 100644 --- a/crates/vm2/src/instruction.rs +++ b/crates/vm2/src/instruction.rs @@ -2,7 +2,10 @@ use std::fmt; use crate::{addressing_modes::Arguments, vm::VirtualMachine}; -#[doc(hidden)] // should only be used for low-level testing / benchmarking +/// Single EraVM instruction (an opcode + [`Arguments`]). +/// +/// Managing instructions is warranted for low-level tests; prefer using [`Program`](crate::Program)s to decode instructions +/// from EraVM bytecodes. pub struct Instruction { pub(crate) handler: Handler, pub(crate) arguments: Arguments, diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index cbefe13..b2f3c4c 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -204,7 +204,7 @@ impl Binop for Div { macro_rules! from_binop { ($name:ident <$binop:ty>) => { - #[doc = concat!("Creates `", stringify!($binop), "` instruction with the provided params.")] + #[doc = concat!("Creates [`", stringify!($binop), "`] instruction with the provided params.")] #[inline(always)] pub fn $name( src1: AnySource, @@ -219,7 +219,7 @@ macro_rules! from_binop { }; ($name:ident <$binop:ty, $out2: ty>) => { - #[doc = concat!("Creates `", stringify!($binop), "` instruction with the provided params.")] + #[doc = concat!("Creates [`", stringify!($binop), "`] instruction with the provided params.")] #[inline(always)] pub fn $name( src1: AnySource, @@ -235,6 +235,7 @@ macro_rules! from_binop { }; } +/// Instructions for binary operations. impl> Instruction { #[inline(always)] pub(crate) fn from_binop( diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index 54ed87a..9506c92 100644 --- a/crates/vm2/src/instruction_handlers/context.rs +++ b/crates/vm2/src/instruction_handlers/context.rs @@ -1,4 +1,4 @@ -use primitive_types::U256; +use primitive_types::{H160, U256}; use zkevm_opcode_defs::VmMetaParameters; use zksync_vm2_interface::{ opcodes::{self, Caller, CodeAddress, ContextU128, ErgsLeft, This, SP}, @@ -8,12 +8,17 @@ use zksync_vm2_interface::{ use super::common::boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, - decommit::address_into_u256, instruction::ExecutionStatus, state::State, Instruction, VirtualMachine, World, }; +pub(crate) fn address_into_u256(address: H160) -> U256 { + let mut buffer = [0; 32]; + buffer[12..].copy_from_slice(address.as_bytes()); + U256::from_big_endian(&buffer) +} + fn context( vm: &mut VirtualMachine, world: &mut W, @@ -126,6 +131,7 @@ fn aux_mutating>( }) } +/// Context-related instructions. impl> Instruction { fn from_context(out: Register1, arguments: Arguments) -> Self { Self { @@ -134,30 +140,37 @@ impl> Instruction { } } + /// Creates a [`This`] instruction with the provided params. pub fn from_this(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + /// Creates a [`Caller`] instruction with the provided params. pub fn from_caller(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + /// Creates a [`CodeAddress`] instruction with the provided params. pub fn from_code_address(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + /// Creates an [`ErgsLeft`] instruction with the provided params. pub fn from_ergs_left(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + /// Creates a [`ContextU128`] instruction with the provided params. pub fn from_context_u128(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + /// Creates an [`SP`] instruction with the provided params. pub fn from_context_sp(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) } + /// Creates a [`ContextMeta`](opcodes::ContextMeta) instruction with the provided params. pub fn from_context_meta(out: Register1, arguments: Arguments) -> Self { Self { handler: context_meta, @@ -165,6 +178,7 @@ impl> Instruction { } } + /// Creates a [`SetContextU128`](opcodes::SetContextU128) instruction with the provided params. pub fn from_set_context_u128(src: Register1, arguments: Arguments) -> Self { Self { handler: set_context_u128, @@ -172,6 +186,7 @@ impl> Instruction { } } + /// Creates an [`IncrementTxNumber`](opcodes::IncrementTxNumber) instruction with the provided params. pub fn from_increment_tx_number(arguments: Arguments) -> Self { Self { handler: increment_tx_number, @@ -179,6 +194,7 @@ impl> Instruction { } } + /// Creates an [`AuxMutating0`](opcodes::AuxMutating0) instruction with the provided params. pub fn from_aux_mutating(arguments: Arguments) -> Self { Self { handler: aux_mutating, diff --git a/crates/vm2/src/instruction_handlers/decommit.rs b/crates/vm2/src/instruction_handlers/decommit.rs index 13b0ed1..430f420 100644 --- a/crates/vm2/src/instruction_handlers/decommit.rs +++ b/crates/vm2/src/instruction_handlers/decommit.rs @@ -53,6 +53,7 @@ fn decommit>( } impl> Instruction { + /// Creates a [`Decommit`](opcodes::Decommit) instruction with the provided params. pub fn from_decommit( abi: Register1, burn: Register2, diff --git a/crates/vm2/src/instruction_handlers/event.rs b/crates/vm2/src/instruction_handlers/event.rs index 58bda54..5336c12 100644 --- a/crates/vm2/src/instruction_handlers/event.rs +++ b/crates/vm2/src/instruction_handlers/event.rs @@ -52,6 +52,7 @@ fn l2_to_l1>( } impl> Instruction { + /// Creates an [`Event`](opcodes::Event) instruction with the provided params. pub fn from_event( key: Register1, value: Register2, @@ -67,6 +68,7 @@ impl> Instruction { } } + /// Creates an [`L2ToL1Message`](opcodes::L2ToL1Message) instruction with the provided params. pub fn from_l2_to_l1_message( key: Register1, value: Register2, diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 551ac98..6fa3684 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -268,6 +268,7 @@ impl FatPointer { use super::monomorphization::*; impl> Instruction { + /// Creates a [`FarCall`](FarCall) instruction with the provided mode and params. pub fn from_far_call( src1: Register1, src2: Register2, diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index aa11528..82f2015 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -1,5 +1,5 @@ use primitive_types::U256; -use zksync_vm2_interface::{opcodes, OpcodeType, Tracer}; +use zksync_vm2_interface::{opcodes, HeapId, OpcodeType, Tracer}; use super::{ common::{boilerplate, full_boilerplate}, @@ -13,7 +13,7 @@ use crate::{ fat_pointer::FatPointer, instruction::ExecutionStatus, state::State, - ExecutionEnd, HeapId, Instruction, VirtualMachine, World, + ExecutionEnd, Instruction, VirtualMachine, World, }; pub(crate) trait HeapFromState { @@ -206,28 +206,30 @@ fn load_pointer, const INCREMENT: bool>( } impl> Instruction { + /// Creates a [`HeapRead`](opcodes::HeapRead) instruction with the provided params. #[inline(always)] - pub fn from_heap_load( + pub fn from_heap_read( src: RegisterOrImmediate, out: Register1, incremented_out: Option, arguments: Arguments, ) -> Self { - Self::from_load::(src, out, incremented_out, arguments) + Self::from_read::(src, out, incremented_out, arguments) } + /// Creates an [`AuxHeapRead`](opcodes::AuxHeapRead) instruction with the provided params. #[inline(always)] - pub fn from_aux_heap_load( + pub fn from_aux_heap_read( src: RegisterOrImmediate, out: Register1, incremented_out: Option, arguments: Arguments, ) -> Self { - Self::from_load::(src, out, incremented_out, arguments) + Self::from_read::(src, out, incremented_out, arguments) } #[inline(always)] - fn from_load( + fn from_read( src: RegisterOrImmediate, out: Register1, incremented_out: Option, @@ -246,17 +248,19 @@ impl> Instruction { } } + /// Creates a [`HeapWrite`](opcodes::HeapWrite) instruction with the provided params. #[inline(always)] - pub fn from_heap_store( + pub fn from_heap_write( src1: RegisterOrImmediate, src2: Register2, incremented_out: Option, arguments: Arguments, should_hook: bool, ) -> Self { - Self::from_store::(src1, src2, incremented_out, arguments, should_hook) + Self::from_write::(src1, src2, incremented_out, arguments, should_hook) } + /// Creates an [`AuxHeapWrite`](opcodes::AuxHeapWrite) instruction with the provided params. #[inline(always)] pub fn from_aux_heap_store( src1: RegisterOrImmediate, @@ -264,11 +268,11 @@ impl> Instruction { incremented_out: Option, arguments: Arguments, ) -> Self { - Self::from_store::(src1, src2, incremented_out, arguments, false) + Self::from_write::(src1, src2, incremented_out, arguments, false) } #[inline(always)] - fn from_store( + fn from_write( src1: RegisterOrImmediate, src2: Register2, incremented_out: Option, @@ -285,8 +289,9 @@ impl> Instruction { } } + /// Creates an [`PointerRead`](opcodes::PointerRead) instruction with the provided params. #[inline(always)] - pub fn from_load_pointer( + pub fn from_pointer_read( src: Register1, out: Register1, incremented_out: Option, diff --git a/crates/vm2/src/instruction_handlers/jump.rs b/crates/vm2/src/instruction_handlers/jump.rs index 9a71157..25c5dc2 100644 --- a/crates/vm2/src/instruction_handlers/jump.rs +++ b/crates/vm2/src/instruction_handlers/jump.rs @@ -27,6 +27,7 @@ where } impl> Instruction { + /// Creates a [`Jump`](opcodes::Jump) instruction with the provided params. pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { handler: monomorphize!(jump [T W] match_source source), diff --git a/crates/vm2/src/instruction_handlers/mod.rs b/crates/vm2/src/instruction_handlers/mod.rs index bb7c498..536f42a 100644 --- a/crates/vm2/src/instruction_handlers/mod.rs +++ b/crates/vm2/src/instruction_handlers/mod.rs @@ -1,4 +1,5 @@ pub(crate) use self::{ + context::address_into_u256, heap_access::{AuxHeap, Heap}, ret::RETURN_COST, }; diff --git a/crates/vm2/src/instruction_handlers/near_call.rs b/crates/vm2/src/instruction_handlers/near_call.rs index 5519c1d..c4f4ea1 100644 --- a/crates/vm2/src/instruction_handlers/near_call.rs +++ b/crates/vm2/src/instruction_handlers/near_call.rs @@ -38,6 +38,7 @@ fn near_call>( } impl> Instruction { + /// Creates a [`NearCall`](opcodes::NearCall) instruction with the provided params. pub fn from_near_call( gas: Register1, destination: Immediate1, diff --git a/crates/vm2/src/instruction_handlers/nop.rs b/crates/vm2/src/instruction_handlers/nop.rs index 0794a00..c1c0711 100644 --- a/crates/vm2/src/instruction_handlers/nop.rs +++ b/crates/vm2/src/instruction_handlers/nop.rs @@ -24,6 +24,7 @@ fn nop>( } impl> Instruction { + /// Creates a [`Nop`](opcodes::Nop) instruction with the provided params. pub fn from_nop( pop: AdvanceStackPointer, push: AdvanceStackPointer, diff --git a/crates/vm2/src/instruction_handlers/pointer.rs b/crates/vm2/src/instruction_handlers/pointer.rs index d6aa1a6..8daae74 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -54,7 +54,7 @@ where }) } -pub trait PtrOp: OpcodeType { +pub(crate) trait PtrOp: OpcodeType { fn perform(in1: U256, in2: U256) -> Option; } @@ -109,9 +109,31 @@ impl PtrOp for PointerShrink { } } +macro_rules! from_ptr_op { + ($name:ident <$binop:ty>) => { + #[doc = concat!("Creates a [`", stringify!($binop), "`] instruction with the provided params.")] + #[inline(always)] + pub fn $name( + src1: AnySource, + src2: Register2, + out: AnyDestination, + arguments: Arguments, + swap: bool, + ) -> Self { + Self::from_ptr::<$binop>(src1, src2, out, arguments, swap) + } + }; +} + +/// Pointer-related instructions. impl> Instruction { + from_ptr_op!(from_pointer_add); + from_ptr_op!(from_pointer_sub); + from_ptr_op!(from_pointer_pack); + from_ptr_op!(from_pointer_shrink); + #[inline(always)] - pub fn from_ptr( + pub(crate) fn from_ptr( src1: AnySource, src2: Register2, out: AnyDestination, diff --git a/crates/vm2/src/instruction_handlers/precompiles.rs b/crates/vm2/src/instruction_handlers/precompiles.rs index 6eae545..95291cd 100644 --- a/crates/vm2/src/instruction_handlers/precompiles.rs +++ b/crates/vm2/src/instruction_handlers/precompiles.rs @@ -131,6 +131,7 @@ impl Memory for Heaps { } impl> Instruction { + /// Creates a [`PrecompileCall`](opcodes::PrecompileCall) instruction with the provided params. pub fn from_precompile_call( abi: Register1, burn: Register2, diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 030cc03..345e754 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -180,7 +180,9 @@ pub(crate) fn panic_from_failed_far_call>( pub(crate) const RETURN_COST: u32 = 5; +/// Variations of [`Ret`](opcodes::Ret) instructions. impl> Instruction { + /// Creates a normal [`Ret`](opcodes::Ret) instruction with the provided params. pub fn from_ret(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { @@ -188,6 +190,8 @@ impl> Instruction { arguments: arguments.write_source(&src1).write_source(&label), } } + + /// Creates a revert [`Ret`](opcodes::Ret) instruction with the provided params. pub fn from_revert(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { @@ -195,6 +199,8 @@ impl> Instruction { arguments: arguments.write_source(&src1).write_source(&label), } } + + /// Creates a panic [`Ret`](opcodes::Ret) instruction with the provided params. pub fn from_panic(label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { @@ -203,6 +209,7 @@ impl> Instruction { } } + /// Creates a *invalid* instruction that will panic by draining all gas. pub const fn from_invalid() -> Self { Self { // This field is never read because the instruction fails at the gas cost stage. diff --git a/crates/vm2/src/instruction_handlers/storage.rs b/crates/vm2/src/instruction_handlers/storage.rs index 9d5628b..00113e3 100644 --- a/crates/vm2/src/instruction_handlers/storage.rs +++ b/crates/vm2/src/instruction_handlers/storage.rs @@ -76,32 +76,44 @@ fn sload_transient>( } impl> Instruction { + /// Creates a [`StorageWrite`](opcodes::StorageWrite) instruction with the provided params. #[inline(always)] - pub fn from_sstore(src1: Register1, src2: Register2, arguments: Arguments) -> Self { + pub fn from_storage_write(src1: Register1, src2: Register2, arguments: Arguments) -> Self { Self { handler: sstore, arguments: arguments.write_source(&src1).write_source(&src2), } } + /// Creates a [`TransientStorageWrite`](opcodes::TransientStorageWrite) instruction with the provided params. #[inline(always)] - pub fn from_sstore_transient(src1: Register1, src2: Register2, arguments: Arguments) -> Self { + pub fn from_transient_storage_write( + src1: Register1, + src2: Register2, + arguments: Arguments, + ) -> Self { Self { handler: sstore_transient, arguments: arguments.write_source(&src1).write_source(&src2), } } + /// Creates a [`StorageRead`](opcodes::StorageRead) instruction with the provided params. #[inline(always)] - pub fn from_sload(src: Register1, dst: Register1, arguments: Arguments) -> Self { + pub fn from_storage_read(src: Register1, dst: Register1, arguments: Arguments) -> Self { Self { handler: sload, arguments: arguments.write_source(&src).write_destination(&dst), } } + /// Creates a [`TransientStorageRead`](opcodes::TransientStorageRead) instruction with the provided params. #[inline(always)] - pub fn from_sload_transient(src: Register1, dst: Register1, arguments: Arguments) -> Self { + pub fn from_transient_storage_read( + src: Register1, + dst: Register1, + arguments: Arguments, + ) -> Self { Self { handler: sload_transient, arguments: arguments.write_source(&src).write_destination(&dst), diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index f0e993b..ec834b2 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -5,16 +5,13 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use primitive_types::{H160, U256}; -pub use zksync_vm2_interface::{ - CallframeInterface, CycleStats, Event, HeapId, L2ToL1Log, Opcode, OpcodeType, ReturnType, - StateInterface, Tracer, -}; +pub use zksync_vm2_interface as interface; +use zksync_vm2_interface::Tracer; // Re-export missing modules if single instruction testing is enabled #[cfg(feature = "single_instruction_test")] pub(crate) use self::single_instruction_test::{heap, program, stack}; pub use self::{ - decommit::{address_into_u256, initial_decommit}, fat_pointer::FatPointer, instruction::{ExecutionEnd, Instruction}, mode_requirements::ModeRequirements, diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index fd13894..7d561a8 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -1,12 +1,11 @@ use arbitrary::Arbitrary; use primitive_types::H160; use zkevm_opcode_defs::EVM_SIMULATOR_STIPEND; -use zksync_vm2_interface::Tracer; +use zksync_vm2_interface::{HeapId, Tracer}; use super::stack::{Stack, StackPool}; use crate::{ - callframe::Callframe, decommit::is_kernel, predication::Flags, HeapId, Program, World, - WorldDiff, + callframe::Callframe, decommit::is_kernel, predication::Flags, Program, World, WorldDiff, }; impl<'a> Arbitrary<'a> for Flags { diff --git a/crates/vm2/src/single_instruction_test/stack.rs b/crates/vm2/src/single_instruction_test/stack.rs index 6618bd3..50001ea 100644 --- a/crates/vm2/src/single_instruction_test/stack.rs +++ b/crates/vm2/src/single_instruction_test/stack.rs @@ -1,9 +1,9 @@ use primitive_types::U256; +use zksync_vm2_interface::HeapId; use super::{ mock_array::MockRead, validation::is_valid_tagged_value, vm::arbitrary_register_value, }; -use crate::HeapId; #[derive(PartialEq, Debug, Clone)] pub struct Stack { diff --git a/crates/vm2/src/single_instruction_test/vm.rs b/crates/vm2/src/single_instruction_test/vm.rs index 3951c94..82d5435 100644 --- a/crates/vm2/src/single_instruction_test/vm.rs +++ b/crates/vm2/src/single_instruction_test/vm.rs @@ -1,11 +1,11 @@ use arbitrary::Arbitrary; use primitive_types::U256; -use zksync_vm2_interface::Tracer; +use zksync_vm2_interface::{HeapId, Tracer}; use super::{heap::Heaps, stack::StackPool}; use crate::{ addressing_modes::Arguments, callframe::Callframe, fat_pointer::FatPointer, state::State, - HeapId, Instruction, ModeRequirements, Predicate, Settings, VirtualMachine, World, + Instruction, ModeRequirements, Predicate, Settings, VirtualMachine, World, }; impl VirtualMachine { diff --git a/crates/vm2/src/testonly.rs b/crates/vm2/src/testonly.rs index 3b316bb..21a8daa 100644 --- a/crates/vm2/src/testonly.rs +++ b/crates/vm2/src/testonly.rs @@ -11,7 +11,7 @@ use zkevm_opcode_defs::{ }; use zksync_vm2_interface::Tracer; -use crate::{address_into_u256, Program, StorageInterface, World}; +use crate::{instruction_handlers::address_into_u256, Program, StorageInterface, World}; /// Test [`World`] implementation. #[derive(Debug)] @@ -94,3 +94,23 @@ impl StorageInterface for TestWorld { false } } + +/// May be used to load code when the VM first starts up. +/// Doesn't check for any errors. +/// Doesn't cost anything but also doesn't make the code free in future decommits. +#[doc(hidden)] // should be used only in low-level testing / benches +pub fn initial_decommit>(world: &mut W, address: H160) -> Program { + let deployer_system_contract_address = + Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); + let code_info = world + .read_storage(deployer_system_contract_address, address_into_u256(address)) + .unwrap_or_default(); + + let mut code_info_bytes = [0; 32]; + code_info.to_big_endian(&mut code_info_bytes); + + code_info_bytes[1] = 0; + let code_key: U256 = U256::from_big_endian(&code_info_bytes); + + world.decommit(code_key) +} diff --git a/crates/vm2/src/tests/bytecode_behaviour.rs b/crates/vm2/src/tests/bytecode_behaviour.rs index d935e7f..b8b1e24 100644 --- a/crates/vm2/src/tests/bytecode_behaviour.rs +++ b/crates/vm2/src/tests/bytecode_behaviour.rs @@ -2,7 +2,8 @@ use zkevm_opcode_defs::ethereum_types::Address; use zksync_vm2_interface::{CallframeInterface, StateInterface}; use crate::{ - initial_decommit, testonly::TestWorld, ExecutionEnd, Program, Settings, VirtualMachine, + testonly::{initial_decommit, TestWorld}, + ExecutionEnd, Program, Settings, VirtualMachine, }; #[test] diff --git a/crates/vm2/src/tests/far_call_decommitment.rs b/crates/vm2/src/tests/far_call_decommitment.rs index 98d4248..55d5399 100644 --- a/crates/vm2/src/tests/far_call_decommitment.rs +++ b/crates/vm2/src/tests/far_call_decommitment.rs @@ -8,8 +8,7 @@ use crate::{ addressing_modes::{ Arguments, CodePage, Immediate1, Register, Register1, Register2, RegisterAndImmediate, }, - initial_decommit, - testonly::TestWorld, + testonly::{initial_decommit, TestWorld}, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, }; @@ -64,7 +63,7 @@ fn create_test_world() -> TestWorld<()> { Arguments::new(Predicate::Always, 200, ModeRequirements::none()), ), // 3: Hook (0) - Instruction::from_heap_store( + Instruction::from_heap_write( Register1(r0).into(), Register2(r0), None, diff --git a/crates/vm2/src/tests/panic.rs b/crates/vm2/src/tests/panic.rs index 5f79462..cfa10f5 100644 --- a/crates/vm2/src/tests/panic.rs +++ b/crates/vm2/src/tests/panic.rs @@ -3,8 +3,7 @@ use zkevm_opcode_defs::ethereum_types::Address; use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register, Register1}, - initial_decommit, - testonly::TestWorld, + testonly::{initial_decommit, TestWorld}, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, }; diff --git a/crates/vm2/src/tests/stipend.rs b/crates/vm2/src/tests/stipend.rs index 10d8f22..e3f4e87 100644 --- a/crates/vm2/src/tests/stipend.rs +++ b/crates/vm2/src/tests/stipend.rs @@ -3,12 +3,11 @@ use zkevm_opcode_defs::ethereum_types::Address; use zksync_vm2_interface::{opcodes, CallframeInterface, StateInterface}; use crate::{ - address_into_u256, addressing_modes::{ Arguments, CodePage, Immediate1, Register, Register1, Register2, RegisterAndImmediate, }, - initial_decommit, - testonly::TestWorld, + instruction_handlers::address_into_u256, + testonly::{initial_decommit, TestWorld}, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, }; diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 6c054af..6b748c4 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -360,7 +360,10 @@ mod test { use zksync_vm2_interface::HeapId; use super::*; - use crate::{initial_decommit, testonly::TestWorld, Instruction, Program, VirtualMachine}; + use crate::{ + testonly::{initial_decommit, TestWorld}, + Instruction, Program, VirtualMachine, + }; #[test] fn callframe_picking() { diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index c80f798..037565d 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -169,7 +169,8 @@ impl WorldDiff { self.pubdata_costs.as_ref() } - pub(crate) fn get_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { + #[doc(hidden)] // duplicates `StateInterface::get_storage_state()`, but we use random access in some places + pub fn get_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { self.storage_changes.as_ref() } From 779e5c8c0ca6a5fe664feee6e3cae325db19e5bc Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 14:44:34 +0300 Subject: [PATCH 11/32] Export `WorldDiff`-related types --- crates/vm2/src/lib.rs | 2 +- crates/vm2/src/world_diff.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index ec834b2..338bfda 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -18,7 +18,7 @@ pub use self::{ predication::Predicate, program::Program, vm::{Settings, VirtualMachine}, - world_diff::WorldDiff, + world_diff::{Snapshot, StorageChange, WorldDiff}, }; pub mod addressing_modes; diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index 037565d..2e4cf6f 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -335,6 +335,8 @@ impl WorldDiff { } } +/// Opaque snapshot of a [`WorldDiff`] output by its [eponymous method](WorldDiff::snapshot()). +/// Can be provided to [`WorldDiff::events_after()`] etc. to get data after the snapshot was created. #[derive(Clone, PartialEq, Debug)] pub struct Snapshot { storage_changes: as Rollback>::Snapshot, @@ -345,11 +347,14 @@ pub struct Snapshot { pubdata: as Rollback>::Snapshot, } +/// Change in a single storage slot. #[derive(Debug, PartialEq)] pub struct StorageChange { + /// Value before the slot was written to. `None` if the slot was not written to previously. pub before: Option, + /// Value written to the slot. pub after: U256, - /// `true` if the slot is not set in the World. + /// `true` if the slot is not set in the [`World`](crate::World). /// A write may be initial even if it isn't the first write to a slot! pub is_initial: bool, } From 1c996c0e27e3fbb5ea4c7aaaa6664f2cab24514b Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 16:30:25 +0300 Subject: [PATCH 12/32] Enable more clippy lints --- .clippy.toml | 2 + crates/vm2-interface/src/lib.rs | 4 +- crates/vm2/benches/nested_near_call.rs | 8 ++-- crates/vm2/src/addressing_modes.rs | 43 +++++++++++-------- crates/vm2/src/bitset.rs | 2 +- crates/vm2/src/callframe.rs | 3 +- crates/vm2/src/decode.rs | 2 +- crates/vm2/src/decommit.rs | 15 ++++--- crates/vm2/src/instruction_handlers/binop.rs | 15 ++++--- .../vm2/src/instruction_handlers/context.rs | 5 ++- crates/vm2/src/instruction_handlers/event.rs | 2 +- .../vm2/src/instruction_handlers/far_call.rs | 3 +- .../src/instruction_handlers/heap_access.rs | 10 ++--- crates/vm2/src/instruction_handlers/jump.rs | 6 ++- .../vm2/src/instruction_handlers/pointer.rs | 7 ++- .../src/instruction_handlers/precompiles.rs | 16 +++++-- crates/vm2/src/instruction_handlers/ret.rs | 12 ++++-- crates/vm2/src/program.rs | 3 +- crates/vm2/src/rollback.rs | 10 +++-- crates/vm2/src/state.rs | 15 ++++--- crates/vm2/src/testonly.rs | 12 ++++-- crates/vm2/src/tests/bytecode_behaviour.rs | 6 +-- crates/vm2/src/tests/far_call_decommitment.rs | 8 ++-- crates/vm2/src/tests/panic.rs | 4 +- crates/vm2/src/tests/stipend.rs | 10 ++--- crates/vm2/src/tracing.rs | 18 +++++--- crates/vm2/src/vm.rs | 8 ++-- crates/vm2/src/world_diff.rs | 8 ++-- 28 files changed, 158 insertions(+), 99 deletions(-) create mode 100644 .clippy.toml diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..f70a229 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,2 @@ +# Configures additional identifiers excempt from `clippy::doc_markdown` lint +doc-valid-idents = ["EraVM", "ZKsync", ".."] diff --git a/crates/vm2-interface/src/lib.rs b/crates/vm2-interface/src/lib.rs index 1a83f2f..5e1a8fb 100644 --- a/crates/vm2-interface/src/lib.rs +++ b/crates/vm2-interface/src/lib.rs @@ -15,14 +15,14 @@ //! With any sane design it would be trivial to take a tracer written for version 1 and //! update it to work with version 2. However, then it can no longer be used with VM1. //! -//! This exact thing caused us a lot of trouble when we put many versions of zk_evm in multivm. +//! This exact thing caused us a lot of trouble when we put many versions of `zk_evm` in `multivm`. //! //! ## How do I add a new feature to the interface? //! //! Do not change the existing traits. In fact, you should delete existing code in the new //! version that you publish and import it from the previous version instead. //! -//! This is how you would add a new method to StateInterface and a new opcode. +//! This is how you would add a new method to [`StateInterface`] and a new opcode. //! //! ``` //! # use zksync_vm2_interface as zksync_vm2_interface_v1; diff --git a/crates/vm2/benches/nested_near_call.rs b/crates/vm2/benches/nested_near_call.rs index 410e199..4ba1760 100644 --- a/crates/vm2/benches/nested_near_call.rs +++ b/crates/vm2/benches/nested_near_call.rs @@ -23,7 +23,7 @@ fn nested_near_call(bencher: Bencher) { vec![], ); - let address = Address::from_low_u64_be(0xabe123ff); + let address = Address::from_low_u64_be(0x_abe1_23ff); bencher.bench(|| { let mut world = TestWorld::new(&[(address, program.clone())]); @@ -32,7 +32,7 @@ fn nested_near_call(bencher: Bencher) { address, program, Address::zero(), - vec![], + &[], 10_000_000, Settings { default_aa_code_hash: [0; 32], @@ -70,7 +70,7 @@ fn nested_near_call_with_storage_write(bencher: Bencher) { vec![], ); - let address = Address::from_low_u64_be(0xabe123ff); + let address = Address::from_low_u64_be(0x_abe1_23ff); bencher.bench(|| { let mut world = TestWorld::new(&[(address, program.clone())]); @@ -79,7 +79,7 @@ fn nested_near_call_with_storage_write(bencher: Bencher) { address, program, Address::zero(), - vec![], + &[], 80_000_000, Settings { default_aa_code_hash: [0; 32], diff --git a/crates/vm2/src/addressing_modes.rs b/crates/vm2/src/addressing_modes.rs index 182babf..15ad86a 100644 --- a/crates/vm2/src/addressing_modes.rs +++ b/crates/vm2/src/addressing_modes.rs @@ -29,7 +29,7 @@ pub(crate) trait Source { ) -> (U256, bool) { let (mut value, is_pointer) = Self::get_with_pointer_flag(args, state); if is_pointer && !state.in_kernel_mode() { - erase_fat_pointer_metadata(&mut value) + erase_fat_pointer_metadata(&mut value); } (value, is_pointer && state.in_kernel_mode()) } @@ -69,7 +69,7 @@ pub(crate) trait SourceWriter { impl SourceWriter for Option { fn write_source(&self, args: &mut Arguments) { if let Some(x) = self { - x.write_source(args) + x.write_source(args); } } } @@ -82,7 +82,7 @@ pub(crate) trait DestinationWriter { impl DestinationWriter for Option { fn write_destination(&self, args: &mut Arguments) { if let Some(x) = self { - x.write_destination(args) + x.write_destination(args); } } } @@ -99,13 +99,14 @@ pub struct Arguments { static_gas_cost: u8, } -pub(crate) const L1_MESSAGE_COST: u32 = 156250; -pub(crate) const SSTORE_COST: u32 = 5511; -pub(crate) const SLOAD_COST: u32 = 2008; -pub(crate) const INVALID_INSTRUCTION_COST: u32 = 4294967295; +pub(crate) const L1_MESSAGE_COST: u32 = 156_250; +pub(crate) const SSTORE_COST: u32 = 5_511; +pub(crate) const SLOAD_COST: u32 = 2_008; +pub(crate) const INVALID_INSTRUCTION_COST: u32 = 4_294_967_295; impl Arguments { /// Creates arguments from the provided info. + #[allow(clippy::missing_panics_doc)] // never panics on properly created inputs pub const fn new( predicate: Predicate, gas_cost: u32, @@ -125,6 +126,7 @@ impl Arguments { } } + #[allow(clippy::cast_possible_truncation)] // checked const fn encode_static_gas_cost(x: u32) -> u8 { match x { L1_MESSAGE_COST => 1, @@ -134,7 +136,7 @@ impl Arguments { 1..=4 => panic!("Reserved gas cost values overlap with actual gas costs"), x => { if x > u8::MAX as u32 { - panic!("Gas cost doesn't fit into 8 bits") + panic!("Gas cost doesn't fit into 8 bits"); } else { x as u8 } @@ -222,7 +224,7 @@ impl Destination for Register1 { impl DestinationWriter for Register1 { fn write_destination(&self, args: &mut Arguments) { - args.destination_registers.set_register1(self.0) + args.destination_registers.set_register1(self.0); } } @@ -238,7 +240,7 @@ impl Destination for Register2 { impl DestinationWriter for Register2 { fn write_destination(&self, args: &mut Arguments) { - args.destination_registers.set_register2(self.0) + args.destination_registers.set_register2(self.0); } } @@ -254,7 +256,7 @@ pub struct Immediate2(pub u16); impl Source for Immediate1 { fn get(args: &Arguments, _state: &mut impl Addressable) -> U256 { - U256([args.immediate1 as u64, 0, 0, 0]) + U256([args.immediate1.into(), 0, 0, 0]) } } @@ -266,7 +268,7 @@ impl SourceWriter for Immediate1 { impl Source for Immediate2 { fn get(args: &Arguments, _state: &mut impl Addressable) -> U256 { - U256([args.immediate2 as u64, 0, 0, 0]) + U256([args.immediate2.into(), 0, 0, 0]) } } @@ -304,7 +306,7 @@ impl DestinationWriter for T { fn write_destination(&self, args: &mut Arguments) { args.immediate2 = self.inner().immediate; args.destination_registers - .set_register1(self.inner().register) + .set_register1(self.inner().register); } } @@ -351,6 +353,7 @@ pub(crate) fn destination_stack_address(args: &Arguments, state: &mut impl Addre /// Computes register + immediate (mod 2^16). /// Stack addresses are always in that remainder class anyway. +#[allow(clippy::cast_possible_truncation)] fn compute_stack_address(state: &mut impl Addressable, register: Register, immediate: u16) -> u16 { (register.value(state).low_u32() as u16).wrapping_add(immediate) } @@ -447,7 +450,7 @@ impl Source for CodePage { state .code_page() .get(address as usize) - .cloned() + .copied() .unwrap_or(U256::zero()) } } @@ -458,27 +461,31 @@ pub struct Register(u8); impl Register { /// Creates a register with the specified 0-based index. + /// + /// # Panics + /// + /// Panics if `n >= 16`; EraVM has 16 registers. pub const fn new(n: u8) -> Self { assert!(n < 16, "EraVM has 16 registers"); Self(n) } - fn value(&self, state: &mut impl Addressable) -> U256 { + fn value(self, state: &mut impl Addressable) -> U256 { unsafe { *state.registers().get_unchecked(self.0 as usize) } } - fn pointer_flag(&self, state: &mut impl Addressable) -> bool { + fn pointer_flag(self, state: &mut impl Addressable) -> bool { *state.register_pointer_flags() & (1 << self.0) != 0 } - fn set(&self, state: &mut impl Addressable, value: U256) { + fn set(self, state: &mut impl Addressable, value: U256) { if self.0 != 0 { unsafe { *state.registers().get_unchecked_mut(self.0 as usize) = value }; *state.register_pointer_flags() &= !(1 << self.0); } } - fn set_ptr(&self, state: &mut impl Addressable, value: U256) { + fn set_ptr(self, state: &mut impl Addressable, value: U256) { if self.0 != 0 { unsafe { *state.registers().get_unchecked_mut(self.0 as usize) = value }; *state.register_pointer_flags() |= 1 << self.0; diff --git a/crates/vm2/src/bitset.rs b/crates/vm2/src/bitset.rs index 2b14c6d..49614dc 100644 --- a/crates/vm2/src/bitset.rs +++ b/crates/vm2/src/bitset.rs @@ -24,7 +24,7 @@ impl Bitset { #[inline(always)] fn slot_and_bit(i: u16) -> (usize, u64) { - ((i >> 6) as usize, 1u64 << (i & 0b111111)) + ((i >> 6) as usize, 1u64 << (i & 0b_0011_1111)) } impl Default for Bitset { diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index b32464a..42570ab 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -133,6 +133,7 @@ impl> Callframe { } // FIXME: can overflow on invalid instruction + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] pub(crate) fn get_pc_as_u16(&self) -> u16 { unsafe { self.pc.offset_from(self.program.instruction(0).unwrap()) as u16 } } @@ -143,7 +144,7 @@ impl> Callframe { self.pc = self .program .instruction(index) - .unwrap_or_else(|| self.program.invalid_instruction()) + .unwrap_or_else(|| self.program.invalid_instruction()); } pub(crate) fn set_invalid_pc(&mut self) { diff --git a/crates/vm2/src/decode.rs b/crates/vm2/src/decode.rs index c954f31..8f6fc50 100644 --- a/crates/vm2/src/decode.rs +++ b/crates/vm2/src/decode.rs @@ -115,7 +115,7 @@ pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> I src1, src2, out, - $snd, + &$snd, arguments, parsed.variant.flags[SWAP_OPERANDS_FLAG_IDX_FOR_ARITH_OPCODES], parsed.variant.flags[SET_FLAGS_FLAG_IDX], diff --git a/crates/vm2/src/decommit.rs b/crates/vm2/src/decommit.rs index 9bbabea..85a151d 100644 --- a/crates/vm2/src/decommit.rs +++ b/crates/vm2/src/decommit.rs @@ -17,7 +17,7 @@ impl WorldDiff { is_constructor_call: bool, ) -> Option<(UnpaidDecommit, bool)> { let deployer_system_contract_address = - Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); + Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW.into()); let mut is_evm = false; @@ -79,7 +79,7 @@ impl WorldDiff { 0 } else { let code_length_in_words = u16::from_be_bytes([code_info[2], code_info[3]]); - code_length_in_words as u32 * zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT + u32::from(code_length_in_words) * zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT }; Some((UnpaidDecommit { cost, code_key }, is_evm)) @@ -98,7 +98,8 @@ impl WorldDiff { let code = world.decommit_code(code_hash); if is_new { // Decommitter can process two words per cycle - tracer.on_extra_prover_cycles(CycleStats::Decommit((code.len() as u32 + 63) / 64)); + let code_len = u32::try_from(code.len()).expect("bytecode length overflow"); + tracer.on_extra_prover_cycles(CycleStats::Decommit((code_len + 63) / 64)); } (code, is_new) } @@ -123,16 +124,16 @@ impl WorldDiff { let decommit = world.decommit(decommit.code_key); if is_new { - tracer.on_extra_prover_cycles(CycleStats::Decommit( - (decommit.code_page().len() as u32 + 1) / 2, - )); + let code_len = + u32::try_from(decommit.code_page().len()).expect("bytecode length overflow"); + tracer.on_extra_prover_cycles(CycleStats::Decommit((code_len + 1) / 2)); } Some(decommit) } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub(crate) struct UnpaidDecommit { cost: u32, code_key: U256, diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index b2f3c4c..7c3075d 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -4,7 +4,12 @@ use zksync_vm2_interface::{ OpcodeType, Tracer, }; -use super::{common::boilerplate, monomorphization::*}; +use super::{ + common::boilerplate, + monomorphization::{ + match_boolean, match_destination, match_source, monomorphize, parameterize, + }, +}; use crate::{ addressing_modes::{ AbsoluteStack, Addressable, AdvanceStackPointer, AnyDestination, AnySource, Arguments, @@ -214,7 +219,7 @@ macro_rules! from_binop { swap: bool, set_flags: bool, ) -> Self { - Self::from_binop::<$binop>(src1, src2, out, (), arguments, swap, set_flags) + Self::from_binop::<$binop>(src1, src2, out, &(), arguments, swap, set_flags) } }; @@ -230,7 +235,7 @@ macro_rules! from_binop { swap: bool, set_flags: bool, ) -> Self { - Self::from_binop::<$binop>(src1, src2, out, out2, arguments, swap, set_flags) + Self::from_binop::<$binop>(src1, src2, out, &out2, arguments, swap, set_flags) } }; } @@ -242,7 +247,7 @@ impl> Instruction { src1: AnySource, src2: Register2, out: AnyDestination, - out2: ::Destination, + out2: &::Destination, arguments: Arguments, swap: bool, set_flags: bool, @@ -253,7 +258,7 @@ impl> Instruction { .write_source(&src1) .write_source(&src2) .write_destination(&out) - .write_destination(&out2), + .write_destination(out2), } } diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index 9506c92..f6292cc 100644 --- a/crates/vm2/src/instruction_handlers/context.rs +++ b/crates/vm2/src/instruction_handlers/context.rs @@ -31,7 +31,7 @@ where { boilerplate::(vm, world, tracer, |vm, args| { let result = Op::get(&vm.state); - Register1::set(args, &mut vm.state, result) + Register1::set(args, &mut vm.state, result); }) } @@ -59,7 +59,7 @@ impl ContextOp for CodeAddress { impl ContextOp for ErgsLeft { fn get>(state: &State) -> U256 { - U256([state.current_frame.gas as u64, 0, 0, 0]) + U256([state.current_frame.gas.into(), 0, 0, 0]) } } @@ -75,6 +75,7 @@ impl ContextOp for SP { } } +#[allow(clippy::cast_sign_loss)] // intentional fn context_meta>( vm: &mut VirtualMachine, world: &mut W, diff --git a/crates/vm2/src/instruction_handlers/event.rs b/crates/vm2/src/instruction_handlers/event.rs index 5336c12..a12e054 100644 --- a/crates/vm2/src/instruction_handlers/event.rs +++ b/crates/vm2/src/instruction_handlers/event.rs @@ -15,7 +15,7 @@ fn event>( tracer: &mut T, ) -> ExecutionStatus { boilerplate_ext::(vm, world, tracer, |vm, args, _, _| { - if vm.state.current_frame.address == H160::from_low_u64_be(ADDRESS_EVENT_WRITER as u64) { + if vm.state.current_frame.address == H160::from_low_u64_be(ADDRESS_EVENT_WRITER.into()) { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); let is_first = Immediate1::get(args, &mut vm.state).low_u32() == 1; diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 6fa3684..8a47da1 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -11,6 +11,7 @@ use zksync_vm2_interface::{ use super::{ common::boilerplate_ext, heap_access::grow_heap, + monomorphization::{match_boolean, monomorphize, parameterize}, ret::{panic_from_failed_far_call, RETURN_COST}, AuxHeap, Heap, }; @@ -265,8 +266,6 @@ impl FatPointer { } } -use super::monomorphization::*; - impl> Instruction { /// Creates a [`FarCall`](FarCall) instruction with the provided mode and params. pub fn from_far_call( diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index 82f2015..1038622 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -3,7 +3,7 @@ use zksync_vm2_interface::{opcodes, HeapId, OpcodeType, Tracer}; use super::{ common::{boilerplate, full_boilerplate}, - monomorphization::*, + monomorphization::{match_boolean, match_reg_imm, monomorphize, parameterize}, }; use crate::{ addressing_modes::{ @@ -100,7 +100,7 @@ where Register1::set(args, &mut vm.state, value); if INCREMENT { - Register2::set(args, &mut vm.state, pointer + 32) + Register2::set(args, &mut vm.state, pointer + 32); } }) } @@ -142,7 +142,7 @@ where vm.state.heaps.write_u256(heap, address, value); if INCREMENT { - Register1::set(args, &mut vm.state, pointer + 32) + Register1::set(args, &mut vm.state, pointer + 32); } if HOOKING_ENABLED && address == vm.settings.hook_address { @@ -154,7 +154,7 @@ where } /// Pays for more heap space. Doesn't acually grow the heap. -/// That distinction is necessary because the bootloader gets u32::MAX heap for free. +/// That distinction is necessary because the bootloader gets `u32::MAX` heap for free. pub(crate) fn grow_heap(state: &mut State, new_bound: u32) -> Result<(), ()> where T: Tracer, @@ -200,7 +200,7 @@ fn load_pointer, const INCREMENT: bool>( if INCREMENT { // This addition does not overflow because we checked that the offset is small enough above. - Register2::set_fat_ptr(args, &mut vm.state, input + 32) + Register2::set_fat_ptr(args, &mut vm.state, input + 32); } }) } diff --git a/crates/vm2/src/instruction_handlers/jump.rs b/crates/vm2/src/instruction_handlers/jump.rs index 25c5dc2..88a0b59 100644 --- a/crates/vm2/src/instruction_handlers/jump.rs +++ b/crates/vm2/src/instruction_handlers/jump.rs @@ -1,6 +1,9 @@ use zksync_vm2_interface::{opcodes, Tracer}; -use super::{common::boilerplate, monomorphization::*}; +use super::{ + common::boilerplate, + monomorphization::{match_source, monomorphize, parameterize}, +}; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, @@ -17,6 +20,7 @@ where In: Source, { boilerplate::(vm, world, tracer, |vm, args| { + #[allow(clippy::cast_possible_truncation)] // intentional let target = In::get(args, &mut vm.state).low_u32() as u16; let next_instruction = vm.state.current_frame.get_pc_as_u16(); diff --git a/crates/vm2/src/instruction_handlers/pointer.rs b/crates/vm2/src/instruction_handlers/pointer.rs index 8daae74..438c0ef 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -4,7 +4,12 @@ use zksync_vm2_interface::{ OpcodeType, Tracer, }; -use super::{common::boilerplate, monomorphization::*}; +use super::{ + common::boilerplate, + monomorphization::{ + match_boolean, match_destination, match_source, monomorphize, parameterize, + }, +}; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, diff --git a/crates/vm2/src/instruction_handlers/precompiles.rs b/crates/vm2/src/instruction_handlers/precompiles.rs index 95291cd..02370cd 100644 --- a/crates/vm2/src/instruction_handlers/precompiles.rs +++ b/crates/vm2/src/instruction_handlers/precompiles.rs @@ -1,3 +1,4 @@ +use primitive_types::{H160, U256}; use zk_evm_abstractions::{ aux::Timestamp, precompiles::{ @@ -37,7 +38,11 @@ fn precompile_call>( vm.state.current_frame.pc = &*vm.panic; return; }; - vm.world_diff.pubdata.0 += aux_data.extra_pubdata_cost as i32; + + #[allow(clippy::cast_possible_wrap)] + { + vm.world_diff.pubdata.0 += aux_data.extra_pubdata_cost as i32; + } let mut abi = PrecompileCallABI::from_u256(Register1::get(args, &mut vm.state)); if abi.memory_page_to_read == 0 { @@ -54,9 +59,9 @@ fn precompile_call>( tx_number_in_block: Default::default(), aux_byte: Default::default(), shard_id: Default::default(), - address: Default::default(), - read_value: Default::default(), - written_value: Default::default(), + address: H160::default(), + read_value: U256::default(), + written_value: U256::default(), rw_flag: Default::default(), rollback: Default::default(), is_service: Default::default(), @@ -65,6 +70,9 @@ fn precompile_call>( let address_bytes = vm.state.current_frame.address.0; let address_low = u16::from_le_bytes([address_bytes[19], address_bytes[18]]); let heaps = &mut vm.state.heaps; + + #[allow(clippy::cast_possible_truncation)] + // if we're having `> u32::MAX` cycles, we've got larger issues match address_low { KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS => { tracer.on_extra_prover_cycles(CycleStats::Keccak256( diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 345e754..e7083d6 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -4,7 +4,11 @@ use zksync_vm2_interface::{ ReturnType, Tracer, }; -use super::{common::full_boilerplate, far_call::get_far_call_calldata, monomorphization::*}; +use super::{ + common::full_boilerplate, + far_call::get_far_call_calldata, + monomorphization::{match_boolean, monomorphize, parameterize}, +}; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Source, INVALID_INSTRUCTION_COST}, callframe::FrameRemnant, @@ -84,7 +88,7 @@ where .read_range_big_endian( return_value.start..return_value.start + return_value.length, ) - .to_vec(); + .clone(); if return_type == ReturnType::Revert { ExecutionStatus::Stopped(ExecutionEnd::Reverted(output)) } else { @@ -104,14 +108,14 @@ where vm.state.register_pointer_flags = 2; if return_type.is_failure() { - vm.state.current_frame.set_pc_from_u16(exception_handler) + vm.state.current_frame.set_pc_from_u16(exception_handler); } (snapshot, leftover_gas) }; if return_type.is_failure() { - vm.world_diff.rollback(snapshot); + vm.world_diff.rollback(&snapshot); } vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index cc35e55..fee72b2 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -56,7 +56,8 @@ impl fmt::Debug for Program { impl> Program { /// Creates a new program. - pub fn new(bytecode: Vec, enable_hooks: bool) -> Self { + #[allow(clippy::missing_panics_doc)] // false positive + pub fn new(bytecode: &[u8], enable_hooks: bool) -> Self { let instructions = decode_program( &bytecode .chunks_exact(8) diff --git a/crates/vm2/src/rollback.rs b/crates/vm2/src/rollback.rs index 0818949..06c578c 100644 --- a/crates/vm2/src/rollback.rs +++ b/crates/vm2/src/rollback.rs @@ -1,3 +1,5 @@ +#![allow(clippy::zero_sized_map_values)] // FIXME: why? + use std::collections::BTreeMap; /// A trait for things that can be rolled back to snapshots @@ -113,7 +115,7 @@ pub(crate) struct RollbackableLog { impl Default for RollbackableLog { fn default() -> Self { Self { - entries: Default::default(), + entries: Vec::default(), } } } @@ -126,7 +128,7 @@ impl Rollback for RollbackableLog { } fn rollback(&mut self, snapshot: Self::Snapshot) { - self.entries.truncate(snapshot) + self.entries.truncate(snapshot); } fn delete_history(&mut self) {} @@ -134,7 +136,7 @@ impl Rollback for RollbackableLog { impl RollbackableLog { pub(crate) fn push(&mut self, entry: T) { - self.entries.push(entry) + self.entries.push(entry); } pub(crate) fn logs_after(&self, snapshot: as Rollback>::Snapshot) -> &[T] { @@ -160,7 +162,7 @@ impl Rollback for RollbackablePod { } fn rollback(&mut self, snapshot: Self::Snapshot) { - self.0 = snapshot + self.0 = snapshot; } fn delete_history(&mut self) {} diff --git a/crates/vm2/src/state.rs b/crates/vm2/src/state.rs index 3d58665..bceec40 100644 --- a/crates/vm2/src/state.rs +++ b/crates/vm2/src/state.rs @@ -43,7 +43,7 @@ impl> State { memory_page: HeapId::FIRST_CALLDATA, offset: 0, start: 0, - length: calldata.len() as u32, + length: u32::try_from(calldata.len()).expect("calldata length overflow"), } .into_u256(); @@ -93,7 +93,7 @@ impl> State { + self .previous_frames .iter() - .map(|frame| frame.contained_gas()) + .map(Callframe::contained_gas) .sum::() } @@ -178,6 +178,7 @@ impl> Addressable for State { fn registers(&mut self) -> &mut [U256; 16] { &mut self.registers } + fn register_pointer_flags(&mut self) -> &mut u16 { &mut self.register_pointer_flags } @@ -185,9 +186,11 @@ impl> Addressable for State { fn read_stack(&mut self, slot: u16) -> U256 { self.current_frame.stack.get(slot) } + fn write_stack(&mut self, slot: u16, value: U256) { - self.current_frame.stack.set(slot, value) + self.current_frame.stack.set(slot, value); } + fn stack_pointer(&mut self) -> &mut u16 { &mut self.current_frame.sp } @@ -195,11 +198,13 @@ impl> Addressable for State { fn read_stack_pointer_flag(&mut self, slot: u16) -> bool { self.current_frame.stack.get_pointer_flag(slot) } + fn set_stack_pointer_flag(&mut self, slot: u16) { - self.current_frame.stack.set_pointer_flag(slot) + self.current_frame.stack.set_pointer_flag(slot); } + fn clear_stack_pointer_flag(&mut self, slot: u16) { - self.current_frame.stack.clear_pointer_flag(slot) + self.current_frame.stack.clear_pointer_flag(slot); } fn code_page(&self) -> &[U256] { diff --git a/crates/vm2/src/testonly.rs b/crates/vm2/src/testonly.rs index 21a8daa..88a932b 100644 --- a/crates/vm2/src/testonly.rs +++ b/crates/vm2/src/testonly.rs @@ -22,6 +22,10 @@ pub struct TestWorld { impl TestWorld { /// Creates a test world with the provided programs. + /// + /// # Panics + /// + /// Panics if the provided `Program`s are malformed. pub fn new(contracts: &[(Address, Program)]) -> Self { let mut address_to_hash = BTreeMap::new(); let mut hash_to_contract = BTreeMap::new(); @@ -33,7 +37,9 @@ impl TestWorld { let mut code_info_bytes = [0; 32]; code_info_bytes[24..].copy_from_slice(&hasher.finish().to_be_bytes()); - code_info_bytes[2..=3].copy_from_slice(&(code.code_page().len() as u16).to_be_bytes()); + let code_len = u16::try_from(code.code_page().len()) + .expect("code length must not exceed u16::MAX"); + code_info_bytes[2..=3].copy_from_slice(&code_len.to_be_bytes()); code_info_bytes[0] = 1; let hash = U256::from_big_endian(&code_info_bytes); @@ -72,7 +78,7 @@ impl World for TestWorld { impl StorageInterface for TestWorld { fn read_storage(&mut self, contract: H160, key: U256) -> Option { let deployer_system_contract_address = - Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); + Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW.into()); if contract == deployer_system_contract_address { Some( @@ -101,7 +107,7 @@ impl StorageInterface for TestWorld { #[doc(hidden)] // should be used only in low-level testing / benches pub fn initial_decommit>(world: &mut W, address: H160) -> Program { let deployer_system_contract_address = - Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); + Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW.into()); let code_info = world .read_storage(deployer_system_contract_address, address_into_u256(address)) .unwrap_or_default(); diff --git a/crates/vm2/src/tests/bytecode_behaviour.rs b/crates/vm2/src/tests/bytecode_behaviour.rs index b8b1e24..decc1e3 100644 --- a/crates/vm2/src/tests/bytecode_behaviour.rs +++ b/crates/vm2/src/tests/bytecode_behaviour.rs @@ -12,8 +12,8 @@ fn call_to_invalid_address() { // Thus, setting the error handler to the call instruction itself should // result in an infinite loop. - let address = Address::from_low_u64_be(0x1234567890abcdef); - let bytecode = include_bytes!("bytecodes/call_far").to_vec(); + let address = Address::from_low_u64_be(0x_1234_5678_90ab_cdef); + let bytecode = include_bytes!("bytecodes/call_far"); let mut world = TestWorld::new(&[(address, Program::new(bytecode, false))]); let program = initial_decommit(&mut world, address); @@ -21,7 +21,7 @@ fn call_to_invalid_address() { address, program, Address::zero(), - vec![], + &[], 10000, Settings { default_aa_code_hash: [0; 32], diff --git a/crates/vm2/src/tests/far_call_decommitment.rs b/crates/vm2/src/tests/far_call_decommitment.rs index 55d5399..2e8b506 100644 --- a/crates/vm2/src/tests/far_call_decommitment.rs +++ b/crates/vm2/src/tests/far_call_decommitment.rs @@ -120,7 +120,7 @@ fn test() { MAIN_ADDRESS, main_program, Address::zero(), - vec![], + &[], initial_gas, Settings { default_aa_code_hash: [0; 32], @@ -132,7 +132,7 @@ fn test() { let result = vm.run(&mut world, &mut ()); let remaining_gas = vm.current_frame().gas(); assert_eq!(result, ExecutionEnd::SuspendedOnHook(0)); - let expected_decommit_cost = LARGE_BYTECODE_LEN as u32 * 4; + let expected_decommit_cost = u32::try_from(LARGE_BYTECODE_LEN).unwrap() * 4; assert!( remaining_gas < initial_gas - expected_decommit_cost, "{remaining_gas}" @@ -156,7 +156,7 @@ fn test_with_initial_out_of_gas_error() { MAIN_ADDRESS, main_program, Address::zero(), - vec![], + &[], 10_000, Settings { default_aa_code_hash: [0; 32], @@ -183,7 +183,7 @@ fn test_with_initial_out_of_gas_error() { let result = vm.run(&mut world, &mut ()); let remaining_gas = vm.current_frame().gas(); assert_eq!(result, ExecutionEnd::SuspendedOnHook(0)); - let expected_decommit_cost = LARGE_BYTECODE_LEN as u32 * 4; + let expected_decommit_cost = u32::try_from(LARGE_BYTECODE_LEN).unwrap() * 4; assert!( remaining_gas < initial_gas - expected_decommit_cost, "{remaining_gas}" diff --git a/crates/vm2/src/tests/panic.rs b/crates/vm2/src/tests/panic.rs index cfa10f5..b7a4cfd 100644 --- a/crates/vm2/src/tests/panic.rs +++ b/crates/vm2/src/tests/panic.rs @@ -29,7 +29,7 @@ proptest! { let program = Program::from_raw(instructions, vec![]); - let address = Address::from_low_u64_be(0x1234567890abcdef); + let address = Address::from_low_u64_be(0x_1234_5678_90ab_cdef); let mut world = TestWorld::new(&[(address, program)]); let program = initial_decommit(&mut world, address); @@ -37,7 +37,7 @@ proptest! { address, program, Address::zero(), - vec![], + &[], 1000, Settings { default_aa_code_hash: [0; 32], diff --git a/crates/vm2/src/tests/stipend.rs b/crates/vm2/src/tests/stipend.rs index e3f4e87..d76b1fb 100644 --- a/crates/vm2/src/tests/stipend.rs +++ b/crates/vm2/src/tests/stipend.rs @@ -18,9 +18,9 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { let r1 = Register::new(1); let r2 = Register::new(2); - let ethereum_address = 0xeeeeee; + let ethereum_address = 0x_00ee_eeee; let mut abi = U256::zero(); - abi.0[3] = gas_to_pass as u64; + abi.0[3] = gas_to_pass.into(); let main_program = Program::from_raw( vec![ @@ -85,8 +85,8 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { vec![], ); - let main_address = Address::from_low_u64_be(0xfeddeadbeef); - let interpreter_address = Address::from_low_u64_be(0x1234567890abcdef); + let main_address = Address::from_low_u64_be(0x_0fed_dead_beef); + let interpreter_address = Address::from_low_u64_be(0x_1234_5678_90ab_cdef); let mut world = TestWorld::new(&[ (interpreter_address, interpreter), (main_address, main_program), @@ -105,7 +105,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { main_address, program, Address::zero(), - vec![], + &[], INITIAL_GAS, Settings { default_aa_code_hash: [0; 32], diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 6b748c4..e6aa1e3 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -1,7 +1,9 @@ use std::cmp::Ordering; use primitive_types::{H160, U256}; -use zksync_vm2_interface::*; +use zksync_vm2_interface::{ + CallframeInterface, Event, Flags, HeapId, L2ToL1Log, StateInterface, Tracer, +}; use crate::{ callframe::{Callframe, NearCallFrame}, @@ -130,7 +132,7 @@ impl> StateInterface for VirtualMachine { fn write_transient_storage(&mut self, address: H160, slot: U256, value: U256) { self.world_diff - .write_transient_storage(address, slot, value) + .write_transient_storage(address, slot, value); } fn events(&self) -> impl Iterator { @@ -283,6 +285,12 @@ impl> CallframeInterface for CallframeWrapper<'_, T, W> { } } + // we don't expect the VM to run on 16-bit machines, and sign loss / wrap is checked + #[allow( + clippy::cast_sign_loss, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap + )] fn program_counter(&self) -> Option { if let Some(call) = self.near_call_on_top() { Some(call.previous_frame_pc) @@ -357,7 +365,7 @@ impl CallframeWrapper<'_, T, W> { mod test { use primitive_types::H160; use zkevm_opcode_defs::ethereum_types::Address; - use zksync_vm2_interface::HeapId; + use zksync_vm2_interface::opcodes; use super::*; use crate::{ @@ -369,7 +377,7 @@ mod test { fn callframe_picking() { let program = Program::from_raw(vec![Instruction::from_invalid()], vec![]); - let address = Address::from_low_u64_be(0x1234567890abcdef); + let address = Address::from_low_u64_be(0x_1234_5678_90ab_cdef); let mut world = TestWorld::new(&[(address, program)]); let program = initial_decommit(&mut world, address); @@ -377,7 +385,7 @@ mod test { address, program.clone(), Address::zero(), - vec![], + &[], 1000, crate::Settings { default_aa_code_hash: [0; 32], diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 3870ea9..366cd57 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -45,7 +45,7 @@ impl> VirtualMachine { address: H160, program: Program, caller: H160, - calldata: Vec, + calldata: &[u8], gas: u32, settings: Settings, ) -> Self { @@ -58,7 +58,7 @@ impl> VirtualMachine { state: State::new( address, caller, - &calldata, + calldata, gas, program, world_before_this_frame, @@ -169,7 +169,7 @@ impl> VirtualMachine { .snapshot .take() .expect("`rollback()` called without a snapshot"); - self.world_diff.external_rollback(snapshot.world_snapshot); + self.world_diff.external_rollback(&snapshot.world_snapshot); self.state.rollback(snapshot.state_snapshot); self.delete_history(); } @@ -282,7 +282,7 @@ impl> VirtualMachine { pub(crate) fn start_new_tx(&mut self) { self.state.transaction_number = self.state.transaction_number.wrapping_add(1); - self.world_diff.clear_transient_storage() + self.world_diff.clear_transient_storage(); } } diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index 2e4cf6f..de63642 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -274,7 +274,7 @@ impl WorldDiff { } } - pub(crate) fn rollback(&mut self, snapshot: Snapshot) { + pub(crate) fn rollback(&mut self, snapshot: &Snapshot) { self.storage_changes.rollback(snapshot.storage_changes); self.paid_changes.rollback(snapshot.paid_changes); self.events.rollback(snapshot.events); @@ -304,8 +304,8 @@ impl WorldDiff { } } - pub(crate) fn external_rollback(&mut self, snapshot: ExternalSnapshot) { - self.rollback(snapshot.internal_snapshot); + pub(crate) fn external_rollback(&mut self, snapshot: &ExternalSnapshot) { + self.rollback(&snapshot.internal_snapshot); self.storage_refunds.rollback(snapshot.storage_refunds); self.pubdata_costs.rollback(snapshot.pubdata_costs); self.decommitted_hashes @@ -331,7 +331,7 @@ impl WorldDiff { } pub(crate) fn clear_transient_storage(&mut self) { - self.transient_storage_changes = Default::default(); + self.transient_storage_changes = RollbackableMap::default(); } } From 6139f00277237ef14ad5ab47d069d48b2bedc2e1 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 16:35:51 +0300 Subject: [PATCH 13/32] Enable even more clippy lints --- crates/vm2/src/fat_pointer.rs | 4 +++- crates/vm2/src/heap.rs | 28 +++++++++++++++------------- crates/vm2/src/mode_requirements.rs | 6 +++--- crates/vm2/src/predication.rs | 15 +++++---------- crates/vm2/src/stack.rs | 16 +++++++--------- crates/vm2/src/world_diff.rs | 29 ++++++++++++++++------------- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/crates/vm2/src/fat_pointer.rs b/crates/vm2/src/fat_pointer.rs index 662bb7f..0bcfd38 100644 --- a/crates/vm2/src/fat_pointer.rs +++ b/crates/vm2/src/fat_pointer.rs @@ -1,3 +1,5 @@ +use std::ptr; + use primitive_types::U256; use zksync_vm2_interface::HeapId; @@ -26,7 +28,7 @@ impl From<&mut U256> for &mut FatPointer { impl From for FatPointer { fn from(value: U256) -> Self { unsafe { - let ptr: *const FatPointer = (&value as *const U256).cast(); + let ptr: *const FatPointer = ptr::addr_of!(value).cast(); ptr.read() } } diff --git a/crates/vm2/src/heap.rs b/crates/vm2/src/heap.rs index fdcc948..b70610c 100644 --- a/crates/vm2/src/heap.rs +++ b/crates/vm2/src/heap.rs @@ -138,7 +138,7 @@ impl Heap { /// Needed only by tracers pub(crate) fn read_byte(&self, address: u32) -> u8 { let (page, offset) = address_to_page_offset(address); - self.page(page).map(|page| page.0[offset]).unwrap_or(0) + self.page(page).map_or(0, |page| page.0[offset]) } fn page(&self, idx: usize) -> Option<&HeapPage> { @@ -184,7 +184,7 @@ fn address_to_page_offset(address: u32) -> (usize, usize) { #[derive(Debug, Clone)] pub(crate) struct Heaps { - heaps: Vec, + inner: Vec, pagepool: PagePool, bootloader_heap_rollback_info: Vec<(u32, U256)>, bootloader_aux_rollback_info: Vec<(u32, U256)>, @@ -196,7 +196,7 @@ impl Heaps { // means the current heap in precompile calls let mut pagepool = PagePool::default(); Self { - heaps: vec![ + inner: vec![ Heap::default(), Heap::from_bytes(calldata, &mut pagepool), Heap::default(), @@ -217,14 +217,15 @@ impl Heaps { } fn allocate_inner(&mut self, memory: &[u8]) -> HeapId { - let id = HeapId::from_u32_unchecked(self.heaps.len() as u32); - self.heaps + let id = u32::try_from(self.inner.len()).expect("heap ID overflow"); + let id = HeapId::from_u32_unchecked(id); + self.inner .push(Heap::from_bytes(memory, &mut self.pagepool)); id } pub(crate) fn deallocate(&mut self, heap: HeapId) { - let heap = mem::take(&mut self.heaps[heap.as_u32() as usize]); + let heap = mem::take(&mut self.inner[heap.as_u32() as usize]); for page in heap.pages.into_iter().flatten() { self.pagepool.recycle_page(page); } @@ -240,7 +241,7 @@ impl Heaps { self.bootloader_aux_rollback_info .push((start_address, prev_value)); } - self.heaps[heap.as_u32() as usize].write_u256(start_address, value, &mut self.pagepool); + self.inner[heap.as_u32() as usize].write_u256(start_address, value, &mut self.pagepool); } pub(crate) fn snapshot(&self) -> (usize, usize) { @@ -252,7 +253,7 @@ impl Heaps { pub(crate) fn rollback(&mut self, (heap_snap, aux_snap): (usize, usize)) { for (address, value) in self.bootloader_heap_rollback_info.drain(heap_snap..).rev() { - self.heaps[HeapId::FIRST.as_u32() as usize].write_u256( + self.inner[HeapId::FIRST.as_u32() as usize].write_u256( address, value, &mut self.pagepool, @@ -260,7 +261,7 @@ impl Heaps { } for (address, value) in self.bootloader_aux_rollback_info.drain(aux_snap..).rev() { - self.heaps[HeapId::FIRST_AUX.as_u32() as usize].write_u256( + self.inner[HeapId::FIRST_AUX.as_u32() as usize].write_u256( address, value, &mut self.pagepool, @@ -278,7 +279,7 @@ impl Index for Heaps { type Output = Heap; fn index(&self, index: HeapId) -> &Self::Output { - &self.heaps[index.as_u32() as usize] + &self.inner[index.as_u32() as usize] } } @@ -286,9 +287,9 @@ impl Index for Heaps { // we allow additional empty heaps at the end of `Heaps`. impl PartialEq for Heaps { fn eq(&self, other: &Self) -> bool { - for i in 0..self.heaps.len().max(other.heaps.len()) { - if self.heaps.get(i).unwrap_or(&Heap::default()) - != other.heaps.get(i).unwrap_or(&Heap::default()) + for i in 0..self.inner.len().max(other.inner.len()) { + if self.inner.get(i).unwrap_or(&Heap::default()) + != other.inner.get(i).unwrap_or(&Heap::default()) { return false; } @@ -329,6 +330,7 @@ impl PagePool { } #[cfg(test)] +#[allow(clippy::cast_possible_truncation)] mod tests { use super::*; diff --git a/crates/vm2/src/mode_requirements.rs b/crates/vm2/src/mode_requirements.rs index efed437..e0a7887 100644 --- a/crates/vm2/src/mode_requirements.rs +++ b/crates/vm2/src/mode_requirements.rs @@ -1,5 +1,5 @@ /// Requirements for the VM execution mode that can be placed by instructions. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct ModeRequirements(pub(crate) u8); impl ModeRequirements { @@ -13,8 +13,8 @@ impl ModeRequirements { Self::new(false, false) } - pub(crate) fn met(&self, is_kernel: bool, is_static: bool) -> bool { - let enabled_modes = (is_kernel as u8) | ((!is_static as u8) << 1); + pub(crate) fn met(self, is_kernel: bool, is_static: bool) -> bool { + let enabled_modes = u8::from(is_kernel) | (u8::from(!is_static) << 1); enabled_modes & self.0 == self.0 } } diff --git a/crates/vm2/src/predication.rs b/crates/vm2/src/predication.rs index 36dee60..1ac1600 100644 --- a/crates/vm2/src/predication.rs +++ b/crates/vm2/src/predication.rs @@ -8,16 +8,17 @@ pub(crate) struct Flags(u8); impl Flags { pub(crate) fn new(lt_of: bool, eq: bool, gt: bool) -> Self { - Flags(lt_of as u8 | ((eq as u8) << 1) | ((gt as u8) << 2) | ALWAYS_BIT) + Flags(u8::from(lt_of) | (u8::from(eq) << 1) | (u8::from(gt) << 2) | ALWAYS_BIT) } } /// Predicate for an instruction. Encoded so that comparing it to flags is efficient. -#[derive(Copy, Clone, Debug, Hash)] +#[derive(Copy, Clone, Debug, Default, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[repr(u8)] pub enum Predicate { /// Always execute the associated instruction. + #[default] Always = ALWAYS_BIT, /// Execute the associated instruction if the "greater than" execution flag is set. IfGT = GT_BIT, @@ -37,18 +38,12 @@ pub enum Predicate { impl Predicate { #[inline(always)] - pub(crate) fn satisfied(&self, flags: &Flags) -> bool { - let bits = *self as u8; + pub(crate) fn satisfied(self, flags: &Flags) -> bool { + let bits = self as u8; bits & flags.0 != 0 && (bits >> 4) & flags.0 == 0 } } -impl Default for Predicate { - fn default() -> Self { - Self::Always - } -} - #[cfg(feature = "single_instruction_test")] impl From<&Flags> for zk_evm::flags::Flags { fn from(flags: &Flags) -> Self { diff --git a/crates/vm2/src/stack.rs b/crates/vm2/src/stack.rs index 9fe5ee3..59bb5db 100644 --- a/crates/vm2/src/stack.rs +++ b/crates/vm2/src/stack.rs @@ -19,6 +19,7 @@ const NUMBER_OF_DIRTY_AREAS: usize = 64; const DIRTY_AREA_SIZE: usize = (1 << 16) / NUMBER_OF_DIRTY_AREAS; impl Stack { + #[allow(clippy::cast_ptr_alignment)] // aligned per `Stack` layout pub(crate) fn new() -> Box { unsafe { Box::from_raw(alloc_zeroed(Layout::new::()).cast::()) } } @@ -39,14 +40,14 @@ impl Stack { fn zero(&mut self) { for i in 0..NUMBER_OF_DIRTY_AREAS { if self.dirty_areas & (1 << i) != 0 { - for slot in self.slots[i * DIRTY_AREA_SIZE..(i + 1) * DIRTY_AREA_SIZE].iter_mut() { + for slot in &mut self.slots[i * DIRTY_AREA_SIZE..(i + 1) * DIRTY_AREA_SIZE] { *slot = U256::zero(); } } } self.dirty_areas = 0; - self.pointer_flags = Default::default(); + self.pointer_flags = Bitset::default(); } #[inline(always)] @@ -112,13 +113,10 @@ pub(crate) struct StackPool { impl StackPool { pub(crate) fn get(&mut self) -> Box { - self.stacks - .pop() - .map(|mut s| { - s.zero(); - s - }) - .unwrap_or_else(Stack::new) + self.stacks.pop().map_or_else(Stack::new, |mut s| { + s.zero(); + s + }) } pub(crate) fn recycle(&mut self, stack: Box) { diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index de63642..9aeaf17 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -136,22 +136,25 @@ impl WorldDiff { .insert((contract, key), update_cost) .unwrap_or(0); - let refund = if !self.written_storage_slots.add((contract, key)) { - WARM_WRITE_REFUND - } else { + let refund = if self.written_storage_slots.add((contract, key)) { tracer.on_extra_prover_cycles(CycleStats::StorageWrite); - if !self.read_storage_slots.add((contract, key)) { - COLD_WRITE_AFTER_WARM_READ_REFUND - } else { + if self.read_storage_slots.add((contract, key)) { 0 + } else { + COLD_WRITE_AFTER_WARM_READ_REFUND } + } else { + WARM_WRITE_REFUND }; - let pubdata_cost = (update_cost as i32) - (prepaid as i32); - self.pubdata.0 += pubdata_cost; - self.storage_refunds.push(refund); - self.pubdata_costs.push(pubdata_cost); + #[allow(clippy::cast_possible_wrap)] + { + let pubdata_cost = (update_cost as i32) - (prepaid as i32); + self.pubdata.0 += pubdata_cost; + self.storage_refunds.push(refund); + self.pubdata_costs.push(pubdata_cost); + } refund } @@ -436,10 +439,10 @@ mod tests { .collect::>(); for (key, value) in second_changes { let initial = initial_values.get(&key).copied(); - if initial.unwrap_or_default() != value { - combined.insert(key, (initial, value)); - } else { + if initial.unwrap_or_default() == value { combined.remove(&key); + } else { + combined.insert(key, (initial, value)); } } From 71f4e3854e91b060ef604b35111ace6c44e4762f Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 16:50:03 +0300 Subject: [PATCH 14/32] Simplify immediate getters --- Cargo.toml | 7 +++++++ crates/vm2/src/addressing_modes.rs | 12 ++++++++++++ crates/vm2/src/decode.rs | 12 +++++------- crates/vm2/src/instruction_handlers/far_call.rs | 10 +++++----- crates/vm2/src/instruction_handlers/near_call.rs | 12 +++++------- crates/vm2/src/instruction_handlers/ret.rs | 4 ++-- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6744f44..afa203a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,3 +44,10 @@ missing_docs = "warn" missing_debug_implementations = "warn" unreachable_pub = "warn" unused_qualifications = "warn" + +[workspace.lints.clippy] +all = "warn" +pedantic = "warn" +must_use_candidate = "allow" +module_name_repetitions = "allow" +inline_always = "allow" diff --git a/crates/vm2/src/addressing_modes.rs b/crates/vm2/src/addressing_modes.rs index 15ad86a..6b9210c 100644 --- a/crates/vm2/src/addressing_modes.rs +++ b/crates/vm2/src/addressing_modes.rs @@ -254,6 +254,18 @@ pub struct Immediate1(pub u16); #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct Immediate2(pub u16); +impl Immediate1 { + pub(crate) fn get_u16(args: &Arguments) -> u16 { + args.immediate1 + } +} + +impl Immediate2 { + pub(crate) fn get_u16(args: &Arguments) -> u16 { + args.immediate2 + } +} + impl Source for Immediate1 { fn get(args: &Arguments, _state: &mut impl Addressable) -> U256 { U256([args.immediate1.into(), 0, 0, 0]) diff --git a/crates/vm2/src/decode.rs b/crates/vm2/src/decode.rs index 8f6fc50..6de2a60 100644 --- a/crates/vm2/src/decode.rs +++ b/crates/vm2/src/decode.rs @@ -1,7 +1,7 @@ use zkevm_opcode_defs::{ decoding::{EncodingModeProduction, VmEncodingMode}, ImmMemHandlerFlags, Opcode, - Operand::*, + Operand::{Full, RegOnly, RegOrImm}, RegOrImmFlags, FAR_CALL_SHARD_FLAG_IDX, FAR_CALL_STATIC_FLAG_IDX, FIRST_MESSAGE_FLAG_IDX, RET_TO_LABEL_BIT_IDX, SET_FLAGS_FLAG_IDX, SWAP_OPERANDS_FLAG_IDX_FOR_ARITH_OPCODES, SWAP_OPERANDS_FLAG_IDX_FOR_PTR_OPCODE, UMA_INCREMENT_FLAG_IDX, @@ -18,7 +18,7 @@ use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, Immediate1, Immediate2, Register, Register1, Register2, RegisterAndImmediate, - RelativeStack, Source, SourceWriter, + RelativeStack, SourceWriter, }, instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, @@ -41,15 +41,13 @@ fn unimplemented_handler>( _: &mut T, ) -> ExecutionStatus { let variant: Opcode = unsafe { - std::mem::transmute( - Immediate1::get(&(*vm.state.current_frame.pc).arguments, &mut vm.state).low_u32() - as u16, - ) + std::mem::transmute(Immediate1::get_u16(&(*vm.state.current_frame.pc).arguments)) }; - eprintln!("Unimplemented instruction: {:?}!", variant); + eprintln!("Unimplemented instruction: {variant:?}"); ExecutionStatus::Stopped(ExecutionEnd::Panicked) } +#[allow(clippy::too_many_lines)] pub(crate) fn decode>(raw: u64, is_bootloader: bool) -> Instruction { let (parsed, _) = EncodingModeProduction::parse_preliminary_variant_and_absolute_number(raw); diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 8a47da1..8c2d897 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -50,7 +50,7 @@ fn far_call< let address_mask: U256 = U256::MAX >> (256 - 160); let destination_address = Register2::get(args, &mut vm.state) & address_mask; - let exception_handler = Immediate1::get(args, &mut vm.state).low_u32() as u16; + let exception_handler = Immediate1::get_u16(args); let mut abi = get_far_call_arguments(raw_abi); abi.is_constructor_call = abi.is_constructor_call && vm.state.current_frame.is_kernel; @@ -167,7 +167,8 @@ pub(crate) struct FarCallABI { pub(crate) is_system_call: bool, } -pub(crate) fn get_far_call_arguments(abi: U256) -> FarCallABI { +#[allow(clippy::cast_possible_truncation)] // intentional +fn get_far_call_arguments(abi: U256) -> FarCallABI { let gas_to_pass = abi.0[3] as u32; let settings = (abi.0[3] >> 32) as u32; let [_, shard_id, constructor_call_byte, system_call_byte] = settings.to_le_bytes(); @@ -192,7 +193,7 @@ pub(crate) fn get_far_call_calldata>( ) -> Option { let mut pointer = FatPointer::from(raw_abi); - match FatPointerSource::from_abi((raw_abi.0[3] >> 32) as u8) { + match FatPointerSource::from_abi((raw_abi.0[3] >> 32) & 0xff) { FatPointerSource::ForwardFatPointer => { if !is_pointer || pointer.offset > pointer.length || already_failed { return None; @@ -248,9 +249,8 @@ enum FatPointerTarget { } impl FatPointerSource { - pub(crate) const fn from_abi(value: u8) -> Self { + const fn from_abi(value: u64) -> Self { match value { - 0 => Self::MakeNewPointer(FatPointerTarget::ToHeap), 1 => Self::ForwardFatPointer, 2 => Self::MakeNewPointer(FatPointerTarget::ToAuxHeap), _ => Self::MakeNewPointer(FatPointerTarget::ToHeap), // default diff --git a/crates/vm2/src/instruction_handlers/near_call.rs b/crates/vm2/src/instruction_handlers/near_call.rs index c4f4ea1..83d3b06 100644 --- a/crates/vm2/src/instruction_handlers/near_call.rs +++ b/crates/vm2/src/instruction_handlers/near_call.rs @@ -14,9 +14,9 @@ fn near_call>( tracer: &mut T, ) -> ExecutionStatus { boilerplate::(vm, world, tracer, |vm, args| { - let gas_to_pass = Register1::get(args, &mut vm.state).0[0] as u32; - let destination = Immediate1::get(args, &mut vm.state); - let error_handler = Immediate2::get(args, &mut vm.state); + let gas_to_pass = Register1::get(args, &mut vm.state).low_u32(); + let destination = Immediate1::get_u16(args); + let error_handler = Immediate2::get_u16(args); let new_frame_gas = if gas_to_pass == 0 { vm.state.current_frame.gas @@ -25,15 +25,13 @@ fn near_call>( }; vm.state.current_frame.push_near_call( new_frame_gas, - error_handler.low_u32() as u16, + error_handler, vm.world_diff.snapshot(), ); vm.state.flags = Flags::new(false, false, false); - vm.state - .current_frame - .set_pc_from_u16(destination.low_u32() as u16); + vm.state.current_frame.set_pc_from_u16(destination); }) } diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index e7083d6..1bbf0fb 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -36,10 +36,10 @@ where }) = vm.state.current_frame.pop_near_call() { if TO_LABEL { - let pc = Immediate1::get(args, &mut vm.state).low_u32() as u16; + let pc = Immediate1::get_u16(args); vm.state.current_frame.set_pc_from_u16(pc); } else if return_type.is_failure() { - vm.state.current_frame.set_pc_from_u16(exception_handler) + vm.state.current_frame.set_pc_from_u16(exception_handler); } (snapshot, near_call_leftover_gas) From 50579234699b1c75a06008da99fbed99a1b91d6c Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 16:45:22 +0300 Subject: [PATCH 15/32] Simplify binary ops --- crates/vm2/src/instruction_handlers/binop.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index 7c3075d..f8b1242 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -108,7 +108,7 @@ impl Binop for Xor { impl Binop for ShiftLeft { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let result = *a << b.low_u32() as u8; + let result = *a << (b.low_u32() & 0xff); (result, (), Flags::new(false, result.is_zero(), false)) } type Out2 = (); @@ -117,7 +117,7 @@ impl Binop for ShiftLeft { impl Binop for ShiftRight { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let result = *a >> b.low_u32() as u8; + let result = *a >> (b.low_u32() & 0xff); (result, (), Flags::new(false, result.is_zero(), false)) } type Out2 = (); @@ -126,8 +126,8 @@ impl Binop for ShiftRight { impl Binop for RotateLeft { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let shift = b.low_u32() as u8; - let result = *a << shift | *a >> (256 - shift as u16); + let shift = b.low_u32() & 0xff; + let result = *a << shift | *a >> (256 - shift); (result, (), Flags::new(false, result.is_zero(), false)) } type Out2 = (); @@ -136,8 +136,8 @@ impl Binop for RotateLeft { impl Binop for RotateRight { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let shift = b.low_u32() as u8; - let result = *a >> shift | *a << (256 - shift as u16); + let shift = b.low_u32() & 0xff; + let result = *a >> shift | *a << (256 - shift); (result, (), Flags::new(false, result.is_zero(), false)) } type Out2 = (); @@ -193,15 +193,15 @@ impl Binop for Mul { impl Binop for Div { fn perform(a: &U256, b: &U256) -> (U256, Self::Out2, Flags) { - if *b != U256::zero() { + if b.is_zero() { + (U256::zero(), U256::zero(), Flags::new(true, false, false)) + } else { let (quotient, remainder) = a.div_mod(*b); ( quotient, remainder, Flags::new(false, quotient.is_zero(), remainder.is_zero()), ) - } else { - (U256::zero(), U256::zero(), Flags::new(true, false, false)) } } type Out2 = U256; From f2450ae0b334bf2aa2abaf7a1e7ee2f04294eee8 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 18:31:26 +0300 Subject: [PATCH 16/32] Revert "Remove UB in invalid instruction handling" --- crates/vm2/src/callframe.rs | 7 ++-- crates/vm2/src/instruction_handlers/mod.rs | 2 +- crates/vm2/src/instruction_handlers/ret.rs | 12 ++++++- crates/vm2/src/lib.rs | 29 +++++++++++++++- crates/vm2/src/program.rs | 34 +++++-------------- .../src/single_instruction_test/program.rs | 6 +--- 6 files changed, 51 insertions(+), 39 deletions(-) diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 42570ab..320dee9 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -4,6 +4,7 @@ use zksync_vm2_interface::{HeapId, Tracer}; use crate::{ decommit::is_kernel, + instruction_handlers::invalid_instruction, program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, @@ -144,11 +145,7 @@ impl> Callframe { self.pc = self .program .instruction(index) - .unwrap_or_else(|| self.program.invalid_instruction()); - } - - pub(crate) fn set_invalid_pc(&mut self) { - self.pc = self.program.invalid_instruction(); + .unwrap_or_else(invalid_instruction); } /// The total amount of gas in this frame, including gas currently inaccessible because of a near call. diff --git a/crates/vm2/src/instruction_handlers/mod.rs b/crates/vm2/src/instruction_handlers/mod.rs index 536f42a..9a27ea2 100644 --- a/crates/vm2/src/instruction_handlers/mod.rs +++ b/crates/vm2/src/instruction_handlers/mod.rs @@ -1,7 +1,7 @@ pub(crate) use self::{ context::address_into_u256, heap_access::{AuxHeap, Heap}, - ret::RETURN_COST, + ret::{invalid_instruction, RETURN_COST}, }; mod binop; diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 1bbf0fb..2508116 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -1,3 +1,5 @@ +use std::convert::Infallible; + use primitive_types::U256; use zksync_vm2_interface::{ opcodes::{self, Normal, Panic, Revert, TypeLevelReturnType}, @@ -81,7 +83,7 @@ where // But to continue execution would be nonsensical and can cause UB because there // is no next instruction after a panic arising from some other instruction. - vm.state.current_frame.set_invalid_pc(); + vm.state.current_frame.pc = invalid_instruction(); return if let Some(return_value) = return_value_or_panic { let output = vm.state.heaps[return_value.memory_page] @@ -182,6 +184,14 @@ pub(crate) fn panic_from_failed_far_call>( tracer.after_instruction::, _>(vm); } +/// Panics, burning all available gas. +static INVALID_INSTRUCTION: Instruction<(), Infallible> = Instruction::from_invalid(); + +pub(crate) fn invalid_instruction<'a, T, W>() -> &'a Instruction { + // Safety: the handler of an invalid instruction is never read. + unsafe { &*std::ptr::addr_of!(INVALID_INSTRUCTION).cast() } +} + pub(crate) const RETURN_COST: u32 = 5; /// Variations of [`Ret`](opcodes::Ret) instructions. diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index 338bfda..32c0ac7 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -2,7 +2,10 @@ //! //! This crate provides high-performance [`VirtualMachine`] for ZKsync Era. -use std::hash::{DefaultHasher, Hash, Hasher}; +use std::{ + convert::Infallible, + hash::{DefaultHasher, Hash, Hasher}, +}; use primitive_types::{H160, U256}; pub use zksync_vm2_interface as interface; @@ -63,6 +66,20 @@ pub trait StorageInterface { fn is_free_storage_slot(&self, contract: &H160, key: &U256) -> bool; } +impl StorageInterface for Infallible { + fn read_storage(&mut self, _contract: H160, _key: U256) -> Option { + unreachable!("`Infallible` cannot be constructed") + } + + fn cost_of_writing_storage(&mut self, _initial_value: Option, _new_value: U256) -> u32 { + unreachable!("`Infallible` cannot be constructed") + } + + fn is_free_storage_slot(&self, _contract: &H160, _key: &U256) -> bool { + unreachable!("`Infallible` cannot be constructed") + } +} + /// Encapsulates VM interaction with the external world. This includes VM storage and decomitting (loading) bytecodes /// for execution. pub trait World: StorageInterface + Sized { @@ -76,6 +93,16 @@ pub trait World: StorageInterface + Sized { fn decommit_code(&mut self, hash: U256) -> Vec; } +impl World for Infallible { + fn decommit(&mut self, _hash: U256) -> Program { + unreachable!("`Infallible` cannot be constructed") + } + + fn decommit_code(&mut self, _hash: U256) -> Vec { + unreachable!("`Infallible` cannot be constructed") + } +} + /// Deterministic (across program runs and machines) hash that can be used for `Debug` implementations /// to concisely represent large amounts of data. #[cfg_attr(feature = "single_instruction_test", allow(dead_code))] // Currently used entirely in types overridden by `single_instruction_test` feature diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index fee72b2..5295f45 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -4,10 +4,7 @@ use primitive_types::U256; use zksync_vm2_interface::Tracer; use crate::{ - addressing_modes::{Arguments, INVALID_INSTRUCTION_COST}, - decode::decode, - hash_for_debugging, - instruction::ExecutionStatus, + addressing_modes::Arguments, decode::decode, hash_for_debugging, instruction::ExecutionStatus, Instruction, ModeRequirements, Predicate, VirtualMachine, World, }; @@ -90,17 +87,8 @@ impl> Program { } } - /// Constructs a program from the provided instructions. - /// - /// This will insert a last invalid instruction if necessary to maintain `Program` invariants. #[doc(hidden)] // should only be used in low-level tests / benchmarks - pub fn from_raw(mut instructions: Vec>, code_page: Vec) -> Self { - if instructions.last().map_or(true, |instr| { - instr.arguments.get_static_gas_cost() != INVALID_INSTRUCTION_COST - }) { - instructions.push(Instruction::from_invalid()); - } - + pub fn from_raw(instructions: Vec>, code_page: Vec) -> Self { Self { instructions: instructions.into(), code_page: code_page.into(), @@ -115,18 +103,13 @@ impl> Program { pub fn code_page(&self) -> &[U256] { &self.code_page } - - pub(crate) fn invalid_instruction(&self) -> &Instruction { - // `unwrap()` is safe (unless the program is constructed manually); the last - self.instructions.last().unwrap() - } } // This implementation compares pointers instead of programs. // // That works well enough for the tests that this is written for. // I don't want to implement PartialEq for Instruction because -// comparing function pointers can work in surprising ways. +// comparing function pointers can work in suprising ways. impl PartialEq for Program { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.code_page, &other.code_page) @@ -156,14 +139,13 @@ fn decode_program>( raw: &[u64], is_bootloader: bool, ) -> Vec> { - let should_wrap = raw.len() >= 1 << 16; - - // Always insert invalid instruction at the end (unreachable in the case of long programs) so that - // it's possible to set `callframe.pc` without introducing UB. raw.iter() .take(1 << 16) .map(|i| decode(*i, is_bootloader)) - .chain(should_wrap.then(jump_to_beginning)) - .chain([Instruction::from_invalid()]) + .chain(std::iter::once(if raw.len() >= 1 << 16 { + jump_to_beginning() + } else { + Instruction::from_invalid() + })) .collect() } diff --git a/crates/vm2/src/single_instruction_test/program.rs b/crates/vm2/src/single_instruction_test/program.rs index cdc3855..87f8831 100644 --- a/crates/vm2/src/single_instruction_test/program.rs +++ b/crates/vm2/src/single_instruction_test/program.rs @@ -50,7 +50,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Program { } impl Program { - pub(crate) fn instruction(&self, n: u16) -> Option<&Instruction> { + pub fn instruction(&self, n: u16) -> Option<&Instruction> { if n == 0 { Some(&self.first_instruction.get(n).as_ref()[0]) } else { @@ -62,10 +62,6 @@ impl Program { } } - pub(crate) fn invalid_instruction(&self) -> &Instruction { - &self.first_instruction.value_read[1] - } - pub fn code_page(&self) -> &Arc<[U256]> { &self.code_page } From 210fd04bc47f8aab5740d743a104270333c54c23 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 19:21:05 +0300 Subject: [PATCH 17/32] Remove UB in `offset_from` --- crates/vm2/src/callframe.rs | 18 +++++++++++++++--- crates/vm2/src/tracing.rs | 6 +----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 320dee9..a9e51b1 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -1,3 +1,5 @@ +use std::mem; + use primitive_types::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; use zksync_vm2_interface::{HeapId, Tracer}; @@ -133,10 +135,20 @@ impl> Callframe { }) } - // FIXME: can overflow on invalid instruction - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + /// Gets a raw inferred program counter. This value can be garbage if the frame is on an invalid instruction or free panic. + #[allow(clippy::cast_possible_wrap)] // false positive: `Instruction` isn't that large + pub(crate) fn get_raw_pc(&self) -> isize { + // We cannot use `<*const _>::offset_from` because `self.pc` isn't guaranteed to be allocated within `self.program` + // (invalid instructions and free panics aren't). + let offset_in_bytes = + self.pc as isize - self.program.instruction(0).unwrap() as *const _ as isize; + offset_in_bytes / mem::size_of::>() as isize + } + + // FIXME: can overflow / underflow on invalid instruction / free panic + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub(crate) fn get_pc_as_u16(&self) -> u16 { - unsafe { self.pc.offset_from(self.program.instruction(0).unwrap()) as u16 } + self.get_raw_pc() as u16 } /// Sets the next instruction to execute to the instruction at the given index. diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index e6aa1e3..a84871e 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -295,11 +295,7 @@ impl> CallframeInterface for CallframeWrapper<'_, T, W> { if let Some(call) = self.near_call_on_top() { Some(call.previous_frame_pc) } else { - let offset = unsafe { - self.frame - .pc - .offset_from(self.frame.program.instruction(0).unwrap()) - }; + let offset = self.frame.get_raw_pc(); if offset < 0 || offset > u16::MAX as isize || self.frame.program.instruction(offset as u16).is_none() From 753af1bdad90050bd54c4416c8f428ce634d8664 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 19:43:28 +0300 Subject: [PATCH 18/32] Fix clippy lints in test code --- crates/vm2/src/addressing_modes.rs | 1 + crates/vm2/src/single_instruction_test/callframe.rs | 2 ++ crates/vm2/src/single_instruction_test/heap.rs | 2 ++ crates/vm2/src/single_instruction_test/into_zk_evm.rs | 4 ++-- crates/vm2/src/single_instruction_test/print_mock_info.rs | 4 ++-- crates/vm2/src/single_instruction_test/stack.rs | 1 + crates/vm2/src/single_instruction_test/validation.rs | 6 +++--- crates/vm2/src/single_instruction_test/vm.rs | 8 ++++---- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/vm2/src/addressing_modes.rs b/crates/vm2/src/addressing_modes.rs index 6b9210c..543bdd4 100644 --- a/crates/vm2/src/addressing_modes.rs +++ b/crates/vm2/src/addressing_modes.rs @@ -507,6 +507,7 @@ impl Register { #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for Register { + #[allow(clippy::cast_possible_truncation)] // false positive: the value is <16 fn arbitrary(u: &mut Unstructured<'a>) -> Result { Ok(Register(u.choose_index(16)? as u8)) } diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index 7d561a8..6636859 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -24,6 +24,8 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { // zk_evm considers smaller pages to be older // vm2 doesn't care about the order // but the calldata heap must be different from the heap and aux heap + #[allow(clippy::range_minus_one)] + // cannot use exclusive range because of `int_in_range()` signature let calldata_heap = HeapId::from_u32_unchecked(u.int_in_range(0..=base_page - 1)?); let program: Program = u.arbitrary()?; diff --git a/crates/vm2/src/single_instruction_test/heap.rs b/crates/vm2/src/single_instruction_test/heap.rs index fb143ff..1fe841b 100644 --- a/crates/vm2/src/single_instruction_test/heap.rs +++ b/crates/vm2/src/single_instruction_test/heap.rs @@ -12,6 +12,7 @@ pub struct Heap { pub(crate) write: Option<(u32, U256)>, } +#[allow(clippy::unused_self)] // to align signatures with real implementation impl Heap { fn write_u256(&mut self, start_address: u32, value: U256) { assert!(self.write.is_none()); @@ -57,6 +58,7 @@ pub struct Heaps { pub(crate) read: MockRead, } +#[allow(clippy::unused_self)] // to align signatures with real implementation impl Heaps { pub(crate) fn new(_: &[u8]) -> Self { unimplemented!("Should use arbitrary heap, not fresh heap in testing.") diff --git a/crates/vm2/src/single_instruction_test/into_zk_evm.rs b/crates/vm2/src/single_instruction_test/into_zk_evm.rs index 48f5ceb..790a1d4 100644 --- a/crates/vm2/src/single_instruction_test/into_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/into_zk_evm.rs @@ -124,7 +124,7 @@ impl Memory for MockMemory { ) -> zk_evm::aux_structures::MemoryQuery { match query.location.memory_type { MemoryType::Stack => { - let slot = query.location.index.0 as u16; + let slot = u16::try_from(query.location.index.0).unwrap(); if query.rw_flag { self.stack.set(slot, query.value); if query.value_is_pointer { @@ -177,7 +177,7 @@ impl Memory for MockMemory { query.value = self .code_page .get(query.location.index.0 as usize) - .cloned() + .copied() .unwrap_or_default(); query } diff --git a/crates/vm2/src/single_instruction_test/print_mock_info.rs b/crates/vm2/src/single_instruction_test/print_mock_info.rs index b6566f3..fc2021b 100644 --- a/crates/vm2/src/single_instruction_test/print_mock_info.rs +++ b/crates/vm2/src/single_instruction_test/print_mock_info.rs @@ -16,8 +16,8 @@ impl> VirtualMachine { impl> State { pub(crate) fn print_mock_info(&self) { - if let Some((heapid, heap)) = self.heaps.read.read_that_happened() { - println!("Heap: {:?}", heapid); + if let Some((heap_id, heap)) = self.heaps.read.read_that_happened() { + println!("Heap: {heap_id:?}"); if let Some((address, value)) = heap.read.read_that_happened() { println!(" {value:?} read from {address:?}"); } diff --git a/crates/vm2/src/single_instruction_test/stack.rs b/crates/vm2/src/single_instruction_test/stack.rs index 50001ea..ff64292 100644 --- a/crates/vm2/src/single_instruction_test/stack.rs +++ b/crates/vm2/src/single_instruction_test/stack.rs @@ -13,6 +13,7 @@ pub struct Stack { pointer_tag_written: bool, } +#[allow(clippy::unused_self)] // to align signatures with real implementation impl Stack { pub(crate) fn new_arbitrary( u: &mut arbitrary::Unstructured, diff --git a/crates/vm2/src/single_instruction_test/validation.rs b/crates/vm2/src/single_instruction_test/validation.rs index 8ed4d41..50f719d 100644 --- a/crates/vm2/src/single_instruction_test/validation.rs +++ b/crates/vm2/src/single_instruction_test/validation.rs @@ -14,10 +14,10 @@ pub(crate) fn is_valid_tagged_value((value, is_pointer): (U256, bool)) -> bool { impl State { pub(crate) fn is_valid(&self) -> bool { self.current_frame.is_valid() - && self.previous_frames.iter().all(|frame| frame.is_valid()) - && (0..16).all(|i| { + && self.previous_frames.iter().all(Callframe::is_valid) + && (0_u16..16).all(|i| { is_valid_tagged_value(( - self.registers[i as usize], + self.registers[usize::from(i)], self.register_pointer_flags & (1 << i) != 0, )) }) diff --git a/crates/vm2/src/single_instruction_test/vm.rs b/crates/vm2/src/single_instruction_test/vm.rs index 82d5435..9a67c74 100644 --- a/crates/vm2/src/single_instruction_test/vm.rs +++ b/crates/vm2/src/single_instruction_test/vm.rs @@ -5,7 +5,7 @@ use zksync_vm2_interface::{HeapId, Tracer}; use super::{heap::Heaps, stack::StackPool}; use crate::{ addressing_modes::Arguments, callframe::Callframe, fat_pointer::FatPointer, state::State, - Instruction, ModeRequirements, Predicate, Settings, VirtualMachine, World, + Instruction, ModeRequirements, Predicate, Settings, VirtualMachine, World, WorldDiff, }; impl VirtualMachine { @@ -58,7 +58,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for VirtualMachine { current_frame.heap.as_u32() - 2, )?; *register = value; - register_pointer_flags |= (is_pointer as u16) << i; + register_pointer_flags |= u16::from(is_pointer) << i; } let heaps = Heaps::from_id(current_frame.heap, u)?; @@ -77,7 +77,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for VirtualMachine { context_u128: u.arbitrary()?, }, settings: u.arbitrary()?, - world_diff: Default::default(), + world_diff: WorldDiff::default(), stack_pool: StackPool {}, panic: Box::new(Instruction::from_panic( None, @@ -90,7 +90,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for VirtualMachine { /// Generates a pointer or non-pointer value. /// The pointers always point to the calldata heap or a heap larger than the base page. -/// This is because heap < base_page in zk_evm means the same as heap == calldata_heap in vm2. +/// This is because `heap < base_page` in `zk_evm` means the same as `heap == calldata_heap` in vm2. pub(crate) fn arbitrary_register_value( u: &mut arbitrary::Unstructured, calldata_heap: HeapId, From edc29605a329d382c3a9bde8800d1e9a45411dd6 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 19:55:21 +0300 Subject: [PATCH 19/32] Fix clippy lints config --- .github/workflows/ci.yml | 2 +- Cargo.toml | 5 ++--- crates/vm2/src/callframe.rs | 4 ++-- crates/vm2/src/fat_pointer.rs | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 128a53b..9c9dda2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Install cargo-afl - run: cargo install cargo-afl --version=^0.15 --force + run: cargo install cargo-afl --version=^0.15 --locked --force - name: Build project run: | diff --git a/Cargo.toml b/Cargo.toml index afa203a..290b73e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,11 +43,10 @@ zksync_vm2 = { version = "0.1.0", path = "crates/vm2" } missing_docs = "warn" missing_debug_implementations = "warn" unreachable_pub = "warn" -unused_qualifications = "warn" [workspace.lints.clippy] -all = "warn" -pedantic = "warn" +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } must_use_candidate = "allow" module_name_repetitions = "allow" inline_always = "allow" diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index a9e51b1..8dbaadf 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -1,4 +1,4 @@ -use std::mem; +use std::{mem, ptr}; use primitive_types::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; @@ -141,7 +141,7 @@ impl> Callframe { // We cannot use `<*const _>::offset_from` because `self.pc` isn't guaranteed to be allocated within `self.program` // (invalid instructions and free panics aren't). let offset_in_bytes = - self.pc as isize - self.program.instruction(0).unwrap() as *const _ as isize; + self.pc as isize - ptr::from_ref(self.program.instruction(0).unwrap()) as isize; offset_in_bytes / mem::size_of::>() as isize } diff --git a/crates/vm2/src/fat_pointer.rs b/crates/vm2/src/fat_pointer.rs index 0bcfd38..2bd008f 100644 --- a/crates/vm2/src/fat_pointer.rs +++ b/crates/vm2/src/fat_pointer.rs @@ -20,7 +20,7 @@ pub struct FatPointer { #[cfg(target_endian = "little")] impl From<&mut U256> for &mut FatPointer { fn from(value: &mut U256) -> Self { - unsafe { &mut *(value as *mut U256).cast() } + unsafe { &mut *ptr::from_mut(value).cast() } } } From ab46e978713ad95d31c6a56be64a60323dc2db50 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 13 Sep 2024 20:13:29 +0300 Subject: [PATCH 20/32] Add doc generation to CI and readme badges --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ README.md | 3 +++ crates/vm2-interface/README.md | 6 ++++++ crates/vm2/README.md | 6 ++++++ 4 files changed, 46 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c9dda2..2d13bbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,3 +65,34 @@ jobs: run: | AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \ cargo afl fuzz -i tests/afl-fuzz/in -o tests/afl-fuzz/out -V 60 target/debug/zksync_vm2_afl_fuzz + + document: + needs: + - build_and_test + if: github.event_name == 'push' && github.ref_type == 'branch' + permissions: + contents: write + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Build docs + run: | + cargo clean --doc && \ + cargo rustdoc -p zksync_vm2_interface && \ + cargo rustdoc -p zksync_vm2 + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: target/doc + single-commit: true diff --git a/README.md b/README.md index 397d459..5b238fa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # High-Performance ZKsync Era VM (EraVM) +[![Build Status](https://github.com/matter-labs/vm2/actions/workflows/ci.yml/badge.svg)](https://github.com/matter-labs/vm2/actions) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue)](https://github.com/matter-labs/vm2#license) + A high-performance rewrite of the out-of-circuit VM for ZKsync Era (aka EraVM). See [Era docs](https://github.com/matter-labs/zksync-era/tree/main/docs/specs/zk_evm) for the VM overview and formal specification. diff --git a/crates/vm2-interface/README.md b/crates/vm2-interface/README.md index dd25188..01735e6 100644 --- a/crates/vm2-interface/README.md +++ b/crates/vm2-interface/README.md @@ -1,5 +1,11 @@ # Stable Interface for ZKsync Era VM +[![Build Status](https://github.com/matter-labs/vm2/actions/workflows/ci.yml/badge.svg)](https://github.com/matter-labs/vm2/actions) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue)](https://github.com/matter-labs/vm2#license) + +**Documentation:** +[![crate docs (main)](https://img.shields.io/badge/main-yellow.svg?label=docs)](https://matter-labs.github.io/vm2/zksync_vm2_interface/) + This library provides a stable interface for EraVM. It defines an interface for tracers that will never change but may be extended. ## License diff --git a/crates/vm2/README.md b/crates/vm2/README.md index 1c693c4..42db8f9 100644 --- a/crates/vm2/README.md +++ b/crates/vm2/README.md @@ -1,5 +1,11 @@ # High-Performance ZKsync Era VM +[![Build Status](https://github.com/matter-labs/vm2/actions/workflows/ci.yml/badge.svg)](https://github.com/matter-labs/vm2/actions) +[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue)](https://github.com/matter-labs/vm2#license) + +**Documentation:** +[![crate docs (main)](https://img.shields.io/badge/main-yellow.svg?label=docs)](https://matter-labs.github.io/vm2/zksync_vm2/) + A high-performance rewrite of the out-of-circuit VM for ZKsync Era. ## License From b692d88dd6946592dce8504f38249ca4f254a059 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 11:06:01 +0300 Subject: [PATCH 21/32] Remove unnecessary tracer / world bounds --- crates/vm2/src/callframe.rs | 6 ++--- crates/vm2/src/decode.rs | 4 +-- crates/vm2/src/instruction_handlers/binop.rs | 1 - crates/vm2/src/instruction_handlers/common.rs | 5 +--- .../vm2/src/instruction_handlers/context.rs | 27 +++++++++---------- crates/vm2/src/instruction_handlers/event.rs | 8 +++--- .../vm2/src/instruction_handlers/far_call.rs | 17 ++++++------ .../src/instruction_handlers/heap_access.rs | 10 +++---- crates/vm2/src/instruction_handlers/jump.rs | 5 ++-- .../vm2/src/instruction_handlers/near_call.rs | 6 ++--- crates/vm2/src/instruction_handlers/nop.rs | 6 ++--- .../vm2/src/instruction_handlers/pointer.rs | 5 ++-- .../src/instruction_handlers/precompiles.rs | 6 ++--- crates/vm2/src/instruction_handlers/ret.rs | 10 +++---- .../vm2/src/instruction_handlers/storage.rs | 4 +-- crates/vm2/src/program.rs | 6 +++-- .../src/single_instruction_test/callframe.rs | 12 +++++---- .../print_mock_info.rs | 8 +++--- .../src/single_instruction_test/program.rs | 2 +- .../state_to_zk_evm.rs | 5 ++-- crates/vm2/src/state.rs | 7 +++-- crates/vm2/src/tracing.rs | 6 ++--- crates/vm2/src/vm.rs | 3 +++ 23 files changed, 79 insertions(+), 90 deletions(-) diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 8dbaadf..0c06c6b 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -2,7 +2,7 @@ use std::{mem, ptr}; use primitive_types::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; -use zksync_vm2_interface::{HeapId, Tracer}; +use zksync_vm2_interface::HeapId; use crate::{ decommit::is_kernel, @@ -10,7 +10,7 @@ use crate::{ program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, - Instruction, World, + Instruction, }; #[derive(Debug)] @@ -56,7 +56,7 @@ pub(crate) struct NearCallFrame { world_before_this_frame: Snapshot, } -impl> Callframe { +impl Callframe { #[allow(clippy::too_many_arguments)] pub(crate) fn new( address: H160, diff --git a/crates/vm2/src/decode.rs b/crates/vm2/src/decode.rs index 6de2a60..329a570 100644 --- a/crates/vm2/src/decode.rs +++ b/crates/vm2/src/decode.rs @@ -25,7 +25,7 @@ use crate::{ Instruction, Predicate, VirtualMachine, World, }; -fn unimplemented_instruction>(variant: Opcode) -> Instruction { +fn unimplemented_instruction(variant: Opcode) -> Instruction { let mut arguments = Arguments::new(Predicate::Always, 0, ModeRequirements::none()); let variant_as_number: u16 = unsafe { std::mem::transmute(variant) }; Immediate1(variant_as_number).write_source(&mut arguments); @@ -35,7 +35,7 @@ fn unimplemented_instruction>(variant: Opcode) -> Instruc } } -fn unimplemented_handler>( +fn unimplemented_handler( vm: &mut VirtualMachine, _: &mut W, _: &mut T, diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index f8b1242..8a2bd69 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -28,7 +28,6 @@ fn binop( ) -> ExecutionStatus where T: Tracer, - W: World, Op: Binop, In1: Source, Out: Destination, diff --git a/crates/vm2/src/instruction_handlers/common.rs b/crates/vm2/src/instruction_handlers/common.rs index 11f962e..72da3b4 100644 --- a/crates/vm2/src/instruction_handlers/common.rs +++ b/crates/vm2/src/instruction_handlers/common.rs @@ -1,7 +1,7 @@ use zksync_vm2_interface::{opcodes, OpcodeType, Tracer}; use super::ret::free_panic; -use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; +use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine}; #[inline(always)] pub(crate) fn boilerplate( @@ -13,7 +13,6 @@ pub(crate) fn boilerplate( where Opcode: OpcodeType, T: Tracer, - W: World, { full_boilerplate::(vm, world, tracer, |vm, args, _, _| { business_logic(vm, args); @@ -31,7 +30,6 @@ pub(crate) fn boilerplate_ext( where Opcode: OpcodeType, T: Tracer, - W: World, { full_boilerplate::(vm, world, tracer, |vm, args, world, tracer| { business_logic(vm, args, world, tracer); @@ -54,7 +52,6 @@ pub(crate) fn full_boilerplate( where Opcode: OpcodeType, T: Tracer, - W: World, { let args = unsafe { &(*vm.state.current_frame.pc).arguments }; diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index f6292cc..d8f1143 100644 --- a/crates/vm2/src/instruction_handlers/context.rs +++ b/crates/vm2/src/instruction_handlers/context.rs @@ -10,7 +10,7 @@ use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, instruction::ExecutionStatus, state::State, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; pub(crate) fn address_into_u256(address: H160) -> U256 { @@ -26,7 +26,6 @@ fn context( ) -> ExecutionStatus where T: Tracer, - W: World, Op: ContextOp, { boilerplate::(vm, world, tracer, |vm, args| { @@ -36,47 +35,47 @@ where } trait ContextOp: OpcodeType { - fn get>(state: &State) -> U256; + fn get(state: &State) -> U256; } impl ContextOp for This { - fn get>(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.address) } } impl ContextOp for Caller { - fn get>(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.caller) } } impl ContextOp for CodeAddress { - fn get>(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.code_address) } } impl ContextOp for ErgsLeft { - fn get>(state: &State) -> U256 { + fn get(state: &State) -> U256 { U256([state.current_frame.gas.into(), 0, 0, 0]) } } impl ContextOp for ContextU128 { - fn get>(state: &State) -> U256 { + fn get(state: &State) -> U256 { state.get_context_u128().into() } } impl ContextOp for SP { - fn get>(state: &State) -> U256 { + fn get(state: &State) -> U256 { state.current_frame.sp.into() } } #[allow(clippy::cast_sign_loss)] // intentional -fn context_meta>( +fn context_meta( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -101,7 +100,7 @@ fn context_meta>( }) } -fn set_context_u128>( +fn set_context_u128( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -112,7 +111,7 @@ fn set_context_u128>( }) } -fn increment_tx_number>( +fn increment_tx_number( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -122,7 +121,7 @@ fn increment_tx_number>( }) } -fn aux_mutating>( +fn aux_mutating( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -133,7 +132,7 @@ fn aux_mutating>( } /// Context-related instructions. -impl> Instruction { +impl Instruction { fn from_context(out: Register1, arguments: Arguments) -> Self { Self { handler: context::, diff --git a/crates/vm2/src/instruction_handlers/event.rs b/crates/vm2/src/instruction_handlers/event.rs index a12e054..dc3d44e 100644 --- a/crates/vm2/src/instruction_handlers/event.rs +++ b/crates/vm2/src/instruction_handlers/event.rs @@ -6,10 +6,10 @@ use super::common::boilerplate_ext; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; -fn event>( +fn event( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -31,7 +31,7 @@ fn event>( }) } -fn l2_to_l1>( +fn l2_to_l1( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -51,7 +51,7 @@ fn l2_to_l1>( }) } -impl> Instruction { +impl Instruction { /// Creates an [`Event`](opcodes::Event) instruction with the provided params. pub fn from_event( key: Register1, diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 8c2d897..66f4cb7 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -34,17 +34,16 @@ use crate::{ /// /// Even though all errors happen before the new stack frame, they cause a panic in the new frame, /// not in the caller! -fn far_call< - T: Tracer, - W: World, - M: TypeLevelCallingMode, - const IS_STATIC: bool, - const IS_SHARD: bool, ->( +fn far_call( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus { +) -> ExecutionStatus +where + T: Tracer, + W: World, + M: TypeLevelCallingMode, +{ boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, world, tracer| { let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); @@ -185,7 +184,7 @@ fn get_far_call_arguments(abi: U256) -> FarCallABI { /// /// This function needs to be called even if we already know we will panic because /// overflowing start + length makes the heap resize even when already panicking. -pub(crate) fn get_far_call_calldata>( +pub(crate) fn get_far_call_calldata( raw_abi: U256, is_pointer: bool, vm: &mut VirtualMachine, diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index 1038622..9f14def 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -13,7 +13,7 @@ use crate::{ fat_pointer::FatPointer, instruction::ExecutionStatus, state::State, - ExecutionEnd, Instruction, VirtualMachine, World, + ExecutionEnd, Instruction, VirtualMachine, }; pub(crate) trait HeapFromState { @@ -70,7 +70,6 @@ fn load( ) -> ExecutionStatus where T: Tracer, - W: World, H: HeapFromState, In: Source, { @@ -112,7 +111,6 @@ fn store( ) -> ExecutionStatus where T: Tracer, - W: World, H: HeapFromState, In: Source, { @@ -157,8 +155,6 @@ where /// That distinction is necessary because the bootloader gets `u32::MAX` heap for free. pub(crate) fn grow_heap(state: &mut State, new_bound: u32) -> Result<(), ()> where - T: Tracer, - W: World, H: HeapFromState, { let already_paid = H::get_heap_size(state); @@ -171,7 +167,7 @@ where Ok(()) } -fn load_pointer, const INCREMENT: bool>( +fn load_pointer( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -205,7 +201,7 @@ fn load_pointer, const INCREMENT: bool>( }) } -impl> Instruction { +impl Instruction { /// Creates a [`HeapRead`](opcodes::HeapRead) instruction with the provided params. #[inline(always)] pub fn from_heap_read( diff --git a/crates/vm2/src/instruction_handlers/jump.rs b/crates/vm2/src/instruction_handlers/jump.rs index 88a0b59..19469be 100644 --- a/crates/vm2/src/instruction_handlers/jump.rs +++ b/crates/vm2/src/instruction_handlers/jump.rs @@ -10,13 +10,12 @@ use crate::{ Immediate1, Register1, RelativeStack, Source, }, instruction::{ExecutionStatus, Instruction}, - VirtualMachine, World, + VirtualMachine, }; fn jump(vm: &mut VirtualMachine, world: &mut W, tracer: &mut T) -> ExecutionStatus where T: Tracer, - W: World, In: Source, { boilerplate::(vm, world, tracer, |vm, args| { @@ -30,7 +29,7 @@ where }) } -impl> Instruction { +impl Instruction { /// Creates a [`Jump`](opcodes::Jump) instruction with the provided params. pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { diff --git a/crates/vm2/src/instruction_handlers/near_call.rs b/crates/vm2/src/instruction_handlers/near_call.rs index 83d3b06..ac4a637 100644 --- a/crates/vm2/src/instruction_handlers/near_call.rs +++ b/crates/vm2/src/instruction_handlers/near_call.rs @@ -5,10 +5,10 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register1, Source}, instruction::ExecutionStatus, predication::Flags, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; -fn near_call>( +fn near_call( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -35,7 +35,7 @@ fn near_call>( }) } -impl> Instruction { +impl Instruction { /// Creates a [`NearCall`](opcodes::NearCall) instruction with the provided params. pub fn from_near_call( gas: Register1, diff --git a/crates/vm2/src/instruction_handlers/nop.rs b/crates/vm2/src/instruction_handlers/nop.rs index c1c0711..d1ec55b 100644 --- a/crates/vm2/src/instruction_handlers/nop.rs +++ b/crates/vm2/src/instruction_handlers/nop.rs @@ -4,10 +4,10 @@ use super::common::boilerplate; use crate::{ addressing_modes::{destination_stack_address, AdvanceStackPointer, Arguments, Source}, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; -fn nop>( +fn nop( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -23,7 +23,7 @@ fn nop>( }) } -impl> Instruction { +impl Instruction { /// Creates a [`Nop`](opcodes::Nop) instruction with the provided params. pub fn from_nop( pop: AdvanceStackPointer, diff --git a/crates/vm2/src/instruction_handlers/pointer.rs b/crates/vm2/src/instruction_handlers/pointer.rs index 438c0ef..ffa1432 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -17,7 +17,7 @@ use crate::{ }, fat_pointer::FatPointer, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; fn ptr( @@ -27,7 +27,6 @@ fn ptr( ) -> ExecutionStatus where T: Tracer, - W: World, Op: PtrOp, In1: Source, Out: Destination, @@ -131,7 +130,7 @@ macro_rules! from_ptr_op { } /// Pointer-related instructions. -impl> Instruction { +impl Instruction { from_ptr_op!(from_pointer_add); from_ptr_op!(from_pointer_sub); from_ptr_op!(from_pointer_pack); diff --git a/crates/vm2/src/instruction_handlers/precompiles.rs b/crates/vm2/src/instruction_handlers/precompiles.rs index 02370cd..4ad5e7f 100644 --- a/crates/vm2/src/instruction_handlers/precompiles.rs +++ b/crates/vm2/src/instruction_handlers/precompiles.rs @@ -22,10 +22,10 @@ use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; -fn precompile_call>( +fn precompile_call( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -138,7 +138,7 @@ impl Memory for Heaps { } } -impl> Instruction { +impl Instruction { /// Creates a [`PrecompileCall`](opcodes::PrecompileCall) instruction with the provided params. pub fn from_precompile_call( abi: Register1, diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 2508116..cfaeeba 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -17,7 +17,7 @@ use crate::{ instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, predication::Flags, - Instruction, Predicate, VirtualMachine, World, + Instruction, Predicate, VirtualMachine, }; fn naked_ret( @@ -26,7 +26,6 @@ fn naked_ret( ) -> ExecutionStatus where T: Tracer, - W: World, RT: TypeLevelReturnType, { let mut return_type = RT::VALUE; @@ -133,7 +132,6 @@ fn ret( ) -> ExecutionStatus where T: Tracer, - W: World, RT: TypeLevelReturnType, { full_boilerplate::, _, _>(vm, world, tracer, |vm, args, _, _| { @@ -150,7 +148,7 @@ where /// - the far call stack overflows /// /// For all other panics, point the instruction pointer at [PANIC] instead. -pub(crate) fn free_panic>( +pub(crate) fn free_panic( vm: &mut VirtualMachine, tracer: &mut T, ) -> ExecutionStatus { @@ -166,7 +164,7 @@ pub(crate) fn free_panic>( /// Formally, a far call pushes a new frame and returns from it immediately if it panics. /// This function instead panics without popping a frame to save on allocation. -pub(crate) fn panic_from_failed_far_call>( +pub(crate) fn panic_from_failed_far_call( vm: &mut VirtualMachine, tracer: &mut T, exception_handler: u16, @@ -195,7 +193,7 @@ pub(crate) fn invalid_instruction<'a, T, W>() -> &'a Instruction { pub(crate) const RETURN_COST: u32 = 5; /// Variations of [`Ret`](opcodes::Ret) instructions. -impl> Instruction { +impl Instruction { /// Creates a normal [`Ret`](opcodes::Ret) instruction with the provided params. pub fn from_ret(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); diff --git a/crates/vm2/src/instruction_handlers/storage.rs b/crates/vm2/src/instruction_handlers/storage.rs index 00113e3..0f8056b 100644 --- a/crates/vm2/src/instruction_handlers/storage.rs +++ b/crates/vm2/src/instruction_handlers/storage.rs @@ -27,7 +27,7 @@ fn sstore>( }) } -fn sstore_transient>( +fn sstore_transient( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -60,7 +60,7 @@ fn sload>( }) } -fn sload_transient>( +fn sload_transient( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index 5295f45..eee95ae 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -94,7 +94,9 @@ impl> Program { code_page: code_page.into(), } } +} +impl Program { pub(crate) fn instruction(&self, n: u16) -> Option<&Instruction> { self.instructions.get::(n.into()) } @@ -118,14 +120,14 @@ impl PartialEq for Program { } /// "Jump to start" instruction placed at the end of programs exceeding `1 << 16` instructions. -fn jump_to_beginning>() -> Instruction { +fn jump_to_beginning() -> Instruction { Instruction { handler: jump_to_beginning_handler, arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()), } } -fn jump_to_beginning_handler>( +fn jump_to_beginning_handler( vm: &mut VirtualMachine, _: &mut W, _: &mut T, diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index 6636859..e8caccb 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -65,11 +65,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { } } -impl> Callframe { - pub(crate) fn raw_first_instruction(&self) -> u64 { - self.program.raw_first_instruction - } - +impl Callframe { pub(crate) fn dummy() -> Self { Self { address: H160::zero(), @@ -96,3 +92,9 @@ impl> Callframe { } } } + +impl Callframe { + pub(crate) fn raw_first_instruction(&self) -> u64 { + self.program.raw_first_instruction + } +} diff --git a/crates/vm2/src/single_instruction_test/print_mock_info.rs b/crates/vm2/src/single_instruction_test/print_mock_info.rs index fc2021b..57eba59 100644 --- a/crates/vm2/src/single_instruction_test/print_mock_info.rs +++ b/crates/vm2/src/single_instruction_test/print_mock_info.rs @@ -1,8 +1,6 @@ -use zksync_vm2_interface::Tracer; +use crate::{callframe::Callframe, state::State, VirtualMachine}; -use crate::{callframe::Callframe, state::State, VirtualMachine, World}; - -impl> VirtualMachine { +impl VirtualMachine { pub fn print_mock_info(&self) { self.state.print_mock_info(); println!("Events: {:?}", self.world_diff.events()); @@ -14,7 +12,7 @@ impl> VirtualMachine { } } -impl> State { +impl State { pub(crate) fn print_mock_info(&self) { if let Some((heap_id, heap)) = self.heaps.read.read_that_happened() { println!("Heap: {heap_id:?}"); diff --git a/crates/vm2/src/single_instruction_test/program.rs b/crates/vm2/src/single_instruction_test/program.rs index 87f8831..d7679cb 100644 --- a/crates/vm2/src/single_instruction_test/program.rs +++ b/crates/vm2/src/single_instruction_test/program.rs @@ -67,7 +67,7 @@ impl Program { } } -impl> Program { +impl Program { pub fn for_decommit() -> Self { Self { raw_first_instruction: 0, diff --git a/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs b/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs index a48485f..8db4d6c 100644 --- a/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs @@ -6,15 +6,14 @@ use zk_evm::{ vm_state::{execution_stack::CallStackEntry, Callstack, PrimitiveValue, VmLocalState}, }; use zkevm_opcode_defs::decoding::EncodingModeProduction; -use zksync_vm2_interface::Tracer; use crate::{ callframe::{Callframe, NearCallFrame}, state::State, - Instruction, World, + Instruction, }; -pub(crate) fn vm2_state_to_zk_evm_state>( +pub(crate) fn vm2_state_to_zk_evm_state( state: &State, panic: &Instruction, ) -> VmLocalState<8, EncodingModeProduction> { diff --git a/crates/vm2/src/state.rs b/crates/vm2/src/state.rs index bceec40..5b8d7ed 100644 --- a/crates/vm2/src/state.rs +++ b/crates/vm2/src/state.rs @@ -1,5 +1,5 @@ use primitive_types::{H160, U256}; -use zksync_vm2_interface::{HeapId, Tracer}; +use zksync_vm2_interface::HeapId; use crate::{ addressing_modes::Addressable, @@ -10,7 +10,6 @@ use crate::{ program::Program, stack::Stack, world_diff::Snapshot, - World, }; /// State of a [`VirtualMachine`](crate::VirtualMachine). @@ -28,7 +27,7 @@ pub(crate) struct State { pub(crate) context_u128: u128, } -impl> State { +impl State { pub(crate) fn new( address: H160, caller: H160, @@ -174,7 +173,7 @@ impl PartialEq for State { } } -impl> Addressable for State { +impl Addressable for State { fn registers(&mut self) -> &mut [U256; 16] { &mut self.registers } diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index a84871e..60d43e8 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -9,10 +9,10 @@ use crate::{ callframe::{Callframe, NearCallFrame}, decommit::is_kernel, predication::{self, Predicate}, - VirtualMachine, World, + VirtualMachine, }; -impl> StateInterface for VirtualMachine { +impl StateInterface for VirtualMachine { fn read_register(&self, register: u8) -> (U256, bool) { ( self.state.registers[register as usize], @@ -157,7 +157,7 @@ struct CallframeWrapper<'a, T, W> { near_call: Option, } -impl> CallframeInterface for CallframeWrapper<'_, T, W> { +impl CallframeInterface for CallframeWrapper<'_, T, W> { fn address(&self) -> H160 { self.frame.address } diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 366cd57..463c864 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -197,7 +197,10 @@ impl> VirtualMachine { self.world_diff.delete_history(); self.state.delete_history(); } +} +// Private methods. We don't constrain `T` and `W` to ease potential refactoring. +impl VirtualMachine { #[allow(clippy::too_many_arguments)] pub(crate) fn push_frame( &mut self, From ccc0bd0ea9c83a64de1e9f0cb53eb4347ee5553a Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 11:07:21 +0300 Subject: [PATCH 22/32] Use `match *self {}` --- crates/vm2/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index 32c0ac7..e2f9f88 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -68,15 +68,15 @@ pub trait StorageInterface { impl StorageInterface for Infallible { fn read_storage(&mut self, _contract: H160, _key: U256) -> Option { - unreachable!("`Infallible` cannot be constructed") + match *self {} } fn cost_of_writing_storage(&mut self, _initial_value: Option, _new_value: U256) -> u32 { - unreachable!("`Infallible` cannot be constructed") + match *self {} } fn is_free_storage_slot(&self, _contract: &H160, _key: &U256) -> bool { - unreachable!("`Infallible` cannot be constructed") + match *self {} } } @@ -95,11 +95,11 @@ pub trait World: StorageInterface + Sized { impl World for Infallible { fn decommit(&mut self, _hash: U256) -> Program { - unreachable!("`Infallible` cannot be constructed") + match *self {} } fn decommit_code(&mut self, _hash: U256) -> Vec { - unreachable!("`Infallible` cannot be constructed") + match *self {} } } From 7086e4adc3b297a738428cf650ff09c181fc675a Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 11:10:08 +0300 Subject: [PATCH 23/32] Use "heap" instead of "heap page" --- crates/vm2-interface/src/state_interface.rs | 28 +++++++++++---------- crates/vm2/src/fat_pointer.rs | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/vm2-interface/src/state_interface.rs b/crates/vm2-interface/src/state_interface.rs index 57197c2..9bc0337 100644 --- a/crates/vm2-interface/src/state_interface.rs +++ b/crates/vm2-interface/src/state_interface.rs @@ -15,12 +15,12 @@ pub trait StateInterface { /// zero is the current frame, one is the frame before that etc. fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_; - /// Reads a single byte from the specified heap page at the specified 0-based offset. + /// Reads a single byte from the specified heap at the specified 0-based offset. fn read_heap_byte(&self, heap: HeapId, offset: u32) -> u8; - /// Reads an entire `U256` word in the big-endian order from the specified heap page / `offset` + /// Reads an entire `U256` word in the big-endian order from the specified heap / `offset` /// (which is the index of the most significant byte of the read value). fn read_heap_u256(&self, heap: HeapId, offset: u32) -> U256; - /// Writes an entire `U256` word in the big-endian order to the specified heap page at the specified `offset` + /// Writes an entire `U256` word in the big-endian order to the specified heap at the specified `offset` /// (which is the index of the most significant byte of the written value). fn write_heap_u256(&mut self, heap: HeapId, offset: u32, value: U256); @@ -128,34 +128,36 @@ pub trait CallframeInterface { /// Sets the stack pointer. fn set_stack_pointer(&mut self, value: u16); - /// Returns ID of the main heap page used in this call. + /// Returns ID of the main heap used in this call. fn heap(&self) -> HeapId; - /// Returns the main heap page boundary (number of paid bytes). + /// Returns the main heap boundary (number of paid bytes). fn heap_bound(&self) -> u32; - /// Sets the main heap page boundary. + /// Sets the main heap boundary. fn set_heap_bound(&mut self, value: u32); - /// Returns ID of the auxiliary heap page used in this call. + /// Returns ID of the auxiliary heap used in this call. fn aux_heap(&self) -> HeapId; - /// Returns the auxiliary heap page boundary (number of paid bytes). + /// Returns the auxiliary heap boundary (number of paid bytes). fn aux_heap_bound(&self) -> u32; - /// Sets the auxiliary heap page boundary. + /// Sets the auxiliary heap boundary. fn set_aux_heap_bound(&mut self, value: u32); /// Reads a word from the code page of the executing contract. fn read_code_page(&self, slot: u16) -> U256; } -/// Identifier of a VM heap page. +/// Identifier of a VM heap. +/// +/// EraVM docs sometimes refer to heaps as *heap pages*; docs in these crate don't to avoid confusion with internal heap structure. #[derive(Copy, Clone, PartialEq, Debug)] pub struct HeapId(u32); impl HeapId { - /// Identifier of the calldata heap page used by the first executed program (i.e., the bootloader). + /// Identifier of the calldata heap used by the first executed program (i.e., the bootloader). pub const FIRST_CALLDATA: Self = Self(1); - /// Identifier of the heap page used by the first executed program (i.e., the bootloader). + /// Identifier of the heap used by the first executed program (i.e., the bootloader). pub const FIRST: Self = Self(2); - /// Identifier of the auxiliary heap page used by the first executed program (i.e., the bootloader) + /// Identifier of the auxiliary heap used by the first executed program (i.e., the bootloader) pub const FIRST_AUX: Self = Self(3); /// Only for dealing with external data structures, never use internally. diff --git a/crates/vm2/src/fat_pointer.rs b/crates/vm2/src/fat_pointer.rs index 2bd008f..b5a60c9 100644 --- a/crates/vm2/src/fat_pointer.rs +++ b/crates/vm2/src/fat_pointer.rs @@ -9,7 +9,7 @@ use zksync_vm2_interface::HeapId; pub struct FatPointer { /// Additional pointer offset inside the `start..(start + length)` range. pub offset: u32, - /// ID of the heap page this points to. + /// ID of the heap this points to. pub memory_page: HeapId, /// 0-based index of the pointer start byte at the `memory` page. pub start: u32, From 13da271dae4cf5fe8ceefe93d6c30f956c09d95d Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 11:20:49 +0300 Subject: [PATCH 24/32] Brush up interface terminology --- crates/vm2-interface/src/state_interface.rs | 25 +++++++++++---------- crates/vm2/src/tracing.rs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/vm2-interface/src/state_interface.rs b/crates/vm2-interface/src/state_interface.rs index 9bc0337..a5bc7fe 100644 --- a/crates/vm2-interface/src/state_interface.rs +++ b/crates/vm2-interface/src/state_interface.rs @@ -79,40 +79,41 @@ pub trait CallframeInterface { fn set_address(&mut self, address: H160); /// Address of the contract being executed. fn code_address(&self) -> H160; - /// Sets the address of the contract being executed. + /// Sets the address of the contract being executed. Does not cause the contract at the specified address get loaded per se, just updates + /// the value used internally by the VM (e.g., returned by the [`CodeAddress`](crate::opcodes::CodeAddress) opcode). fn set_code_address(&mut self, address: H160); /// Address of the calling contract. Respects delegate and mimic calls. fn caller(&self) -> H160; - /// Sets the address of the calling contract + /// Sets the address of the calling contract. fn set_caller(&mut self, address: H160); /// Returns the current program counter (i.e., 0-based index of the instruction being executed). - /// During panic and arbitrary code execution this returns `None`. + /// During panic this returns `None`. fn program_counter(&self) -> Option; /// Sets the program counter. /// The VM will execute an invalid instruction if you jump out of the program. fn set_program_counter(&mut self, value: u16); - /// Returns a 0-based index of the starting exception handler instruction. + /// Returns the program counter that the parent frame should continue from if this frame fails. fn exception_handler(&self) -> u16; - /// Sets the exception handler pointer. + /// Sets the exception handler as specified [above](Self::exception_handler()). fn set_exception_handler(&mut self, value: u16); /// Checks whether the call is static. fn is_static(&self) -> bool; - /// Checks whether the call is executed in the kernel mode. + /// Checks whether the call is executed in kernel mode. fn is_kernel(&self) -> bool; /// Returns the remaining amount of gas. fn gas(&self) -> u32; /// Sets the remaining amount of gas. fn set_gas(&mut self, new_gas: u32); - /// Additional + /// Additional gas provided for the duration of this callframe. fn stipend(&self) -> u32; - /// Returns the context register for this call. + /// Returns the context value for this call. This context is accessible via [`ContextU128`](crate::opcodes::ContextU128) opcode. fn context_u128(&self) -> u128; - /// Sets the context register for this call. + /// Sets the context value for this call. fn set_context_u128(&mut self, value: u128); /// Checks whether this frame corresponds to a near call. @@ -142,8 +143,8 @@ pub trait CallframeInterface { /// Sets the auxiliary heap boundary. fn set_aux_heap_bound(&mut self, value: u32); - /// Reads a word from the code page of the executing contract. - fn read_code_page(&self, slot: u16) -> U256; + /// Reads a word from the bytecode of the executing contract. + fn read_contract_code(&self, slot: u16) -> U256; } /// Identifier of a VM heap. @@ -416,7 +417,7 @@ impl CallframeInterface for DummyState { unimplemented!() } - fn read_code_page(&self, _: u16) -> U256 { + fn read_contract_code(&self, _: u16) -> U256 { unimplemented!() } } diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 60d43e8..40940f0 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -243,7 +243,7 @@ impl CallframeInterface for CallframeWrapper<'_, T, W> { self.frame.aux_heap_size = value; } - fn read_code_page(&self, slot: u16) -> U256 { + fn read_contract_code(&self, slot: u16) -> U256 { self.frame.program.code_page()[slot as usize] } From 9cf7142cf61cbb43a28e93bd5acd4cf06c4470c9 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 11:47:51 +0300 Subject: [PATCH 25/32] Fix misc review suggestions --- Cargo.toml | 1 + crates/vm2/src/decommit.rs | 9 +++---- crates/vm2/src/fat_pointer.rs | 5 +--- crates/vm2/src/heap.rs | 24 +++++++++---------- crates/vm2/src/instruction.rs | 2 +- crates/vm2/src/instruction_handlers/binop.rs | 8 +++---- crates/vm2/src/instruction_handlers/common.rs | 24 +++++-------------- .../vm2/src/instruction_handlers/context.rs | 6 +++-- .../vm2/src/instruction_handlers/far_call.rs | 7 ++++-- crates/vm2/src/instruction_handlers/ret.rs | 2 +- crates/vm2/src/mode_requirements.rs | 2 +- crates/vm2/src/program.rs | 3 ++- .../single_instruction_test/into_zk_evm.rs | 3 ++- crates/vm2/src/single_instruction_test/mod.rs | 2 +- crates/vm2/src/stack.rs | 2 +- crates/vm2/src/vm.rs | 4 ++-- crates/vm2/src/world_diff.rs | 7 +++--- 17 files changed, 53 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 290b73e..f8d759c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,4 @@ pedantic = { level = "warn", priority = -1 } must_use_candidate = "allow" module_name_repetitions = "allow" inline_always = "allow" +struct_field_names = "allow" diff --git a/crates/vm2/src/decommit.rs b/crates/vm2/src/decommit.rs index 85a151d..a4083e7 100644 --- a/crates/vm2/src/decommit.rs +++ b/crates/vm2/src/decommit.rs @@ -97,9 +97,9 @@ impl WorldDiff { let is_new = self.decommitted_hashes.insert(code_hash, true) != Some(true); let code = world.decommit_code(code_hash); if is_new { - // Decommitter can process two words per cycle let code_len = u32::try_from(code.len()).expect("bytecode length overflow"); - tracer.on_extra_prover_cycles(CycleStats::Decommit((code_len + 63) / 64)); + // Decommitter can process two words per cycle, hence division by 2 * 32 = 64. + tracer.on_extra_prover_cycles(CycleStats::Decommit(code_len.div_ceil(64))); } (code, is_new) } @@ -124,9 +124,10 @@ impl WorldDiff { let decommit = world.decommit(decommit.code_key); if is_new { - let code_len = + let code_len_in_words = u32::try_from(decommit.code_page().len()).expect("bytecode length overflow"); - tracer.on_extra_prover_cycles(CycleStats::Decommit((code_len + 1) / 2)); + // Decommitter can process two words per cycle. + tracer.on_extra_prover_cycles(CycleStats::Decommit(code_len_in_words.div_ceil(2))); } Some(decommit) diff --git a/crates/vm2/src/fat_pointer.rs b/crates/vm2/src/fat_pointer.rs index b5a60c9..410557b 100644 --- a/crates/vm2/src/fat_pointer.rs +++ b/crates/vm2/src/fat_pointer.rs @@ -27,10 +27,7 @@ impl From<&mut U256> for &mut FatPointer { #[cfg(target_endian = "little")] impl From for FatPointer { fn from(value: U256) -> Self { - unsafe { - let ptr: *const FatPointer = ptr::addr_of!(value).cast(); - ptr.read() - } + unsafe { std::mem::transmute(value.low_u128()) } } } diff --git a/crates/vm2/src/heap.rs b/crates/vm2/src/heap.rs index b70610c..ce6c68c 100644 --- a/crates/vm2/src/heap.rs +++ b/crates/vm2/src/heap.rs @@ -184,7 +184,7 @@ fn address_to_page_offset(address: u32) -> (usize, usize) { #[derive(Debug, Clone)] pub(crate) struct Heaps { - inner: Vec, + heaps: Vec, pagepool: PagePool, bootloader_heap_rollback_info: Vec<(u32, U256)>, bootloader_aux_rollback_info: Vec<(u32, U256)>, @@ -196,7 +196,7 @@ impl Heaps { // means the current heap in precompile calls let mut pagepool = PagePool::default(); Self { - inner: vec![ + heaps: vec![ Heap::default(), Heap::from_bytes(calldata, &mut pagepool), Heap::default(), @@ -217,15 +217,15 @@ impl Heaps { } fn allocate_inner(&mut self, memory: &[u8]) -> HeapId { - let id = u32::try_from(self.inner.len()).expect("heap ID overflow"); + let id = u32::try_from(self.heaps.len()).expect("heap ID overflow"); let id = HeapId::from_u32_unchecked(id); - self.inner + self.heaps .push(Heap::from_bytes(memory, &mut self.pagepool)); id } pub(crate) fn deallocate(&mut self, heap: HeapId) { - let heap = mem::take(&mut self.inner[heap.as_u32() as usize]); + let heap = mem::take(&mut self.heaps[heap.as_u32() as usize]); for page in heap.pages.into_iter().flatten() { self.pagepool.recycle_page(page); } @@ -241,7 +241,7 @@ impl Heaps { self.bootloader_aux_rollback_info .push((start_address, prev_value)); } - self.inner[heap.as_u32() as usize].write_u256(start_address, value, &mut self.pagepool); + self.heaps[heap.as_u32() as usize].write_u256(start_address, value, &mut self.pagepool); } pub(crate) fn snapshot(&self) -> (usize, usize) { @@ -253,7 +253,7 @@ impl Heaps { pub(crate) fn rollback(&mut self, (heap_snap, aux_snap): (usize, usize)) { for (address, value) in self.bootloader_heap_rollback_info.drain(heap_snap..).rev() { - self.inner[HeapId::FIRST.as_u32() as usize].write_u256( + self.heaps[HeapId::FIRST.as_u32() as usize].write_u256( address, value, &mut self.pagepool, @@ -261,7 +261,7 @@ impl Heaps { } for (address, value) in self.bootloader_aux_rollback_info.drain(aux_snap..).rev() { - self.inner[HeapId::FIRST_AUX.as_u32() as usize].write_u256( + self.heaps[HeapId::FIRST_AUX.as_u32() as usize].write_u256( address, value, &mut self.pagepool, @@ -279,7 +279,7 @@ impl Index for Heaps { type Output = Heap; fn index(&self, index: HeapId) -> &Self::Output { - &self.inner[index.as_u32() as usize] + &self.heaps[index.as_u32() as usize] } } @@ -287,9 +287,9 @@ impl Index for Heaps { // we allow additional empty heaps at the end of `Heaps`. impl PartialEq for Heaps { fn eq(&self, other: &Self) -> bool { - for i in 0..self.inner.len().max(other.inner.len()) { - if self.inner.get(i).unwrap_or(&Heap::default()) - != other.inner.get(i).unwrap_or(&Heap::default()) + for i in 0..self.heaps.len().max(other.heaps.len()) { + if self.heaps.get(i).unwrap_or(&Heap::default()) + != other.heaps.get(i).unwrap_or(&Heap::default()) { return false; } diff --git a/crates/vm2/src/instruction.rs b/crates/vm2/src/instruction.rs index 443a798..530ae00 100644 --- a/crates/vm2/src/instruction.rs +++ b/crates/vm2/src/instruction.rs @@ -28,7 +28,7 @@ pub(crate) enum ExecutionStatus { Stopped(ExecutionEnd), } -/// End of a VM execution returned from [`VirtualMachine::run()`]. +/// VM stop reason returned from [`VirtualMachine::run()`]. #[derive(Debug, PartialEq)] pub enum ExecutionEnd { /// The executed program has finished and returned the specified data. diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index 8a2bd69..a2c078f 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -107,7 +107,7 @@ impl Binop for Xor { impl Binop for ShiftLeft { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let result = *a << (b.low_u32() & 0xff); + let result = *a << (b.low_u32() % 256); (result, (), Flags::new(false, result.is_zero(), false)) } type Out2 = (); @@ -116,7 +116,7 @@ impl Binop for ShiftLeft { impl Binop for ShiftRight { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let result = *a >> (b.low_u32() & 0xff); + let result = *a >> (b.low_u32() % 256); (result, (), Flags::new(false, result.is_zero(), false)) } type Out2 = (); @@ -125,7 +125,7 @@ impl Binop for ShiftRight { impl Binop for RotateLeft { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let shift = b.low_u32() & 0xff; + let shift = b.low_u32() % 256; let result = *a << shift | *a >> (256 - shift); (result, (), Flags::new(false, result.is_zero(), false)) } @@ -135,7 +135,7 @@ impl Binop for RotateLeft { impl Binop for RotateRight { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { - let shift = b.low_u32() & 0xff; + let shift = b.low_u32() % 256; let result = *a >> shift | *a << (256 - shift); (result, (), Flags::new(false, result.is_zero(), false)) } diff --git a/crates/vm2/src/instruction_handlers/common.rs b/crates/vm2/src/instruction_handlers/common.rs index 72da3b4..4f86511 100644 --- a/crates/vm2/src/instruction_handlers/common.rs +++ b/crates/vm2/src/instruction_handlers/common.rs @@ -4,16 +4,12 @@ use super::ret::free_panic; use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine}; #[inline(always)] -pub(crate) fn boilerplate( +pub(crate) fn boilerplate( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments), -) -> ExecutionStatus -where - Opcode: OpcodeType, - T: Tracer, -{ +) -> ExecutionStatus { full_boilerplate::(vm, world, tracer, |vm, args, _, _| { business_logic(vm, args); ExecutionStatus::Running @@ -21,16 +17,12 @@ where } #[inline(always)] -pub(crate) fn boilerplate_ext( +pub(crate) fn boilerplate_ext( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W, &mut T), -) -> ExecutionStatus -where - Opcode: OpcodeType, - T: Tracer, -{ +) -> ExecutionStatus { full_boilerplate::(vm, world, tracer, |vm, args, world, tracer| { business_logic(vm, args, world, tracer); ExecutionStatus::Running @@ -38,7 +30,7 @@ where } #[inline(always)] -pub(crate) fn full_boilerplate( +pub(crate) fn full_boilerplate( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -48,11 +40,7 @@ pub(crate) fn full_boilerplate( &mut W, &mut T, ) -> ExecutionStatus, -) -> ExecutionStatus -where - Opcode: OpcodeType, - T: Tracer, -{ +) -> ExecutionStatus { let args = unsafe { &(*vm.state.current_frame.pc).arguments }; if vm.state.use_gas(args.get_static_gas_cost()).is_err() diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index d8f1143..5346110 100644 --- a/crates/vm2/src/instruction_handlers/context.rs +++ b/crates/vm2/src/instruction_handlers/context.rs @@ -74,7 +74,6 @@ impl ContextOp for SP { } } -#[allow(clippy::cast_sign_loss)] // intentional fn context_meta( vm: &mut VirtualMachine, world: &mut W, @@ -89,7 +88,10 @@ fn context_meta( code_shard_id: 0, // This field is actually pubdata! aux_field_0: if vm.state.current_frame.is_kernel { - vm.world_diff.pubdata.0 as u32 + #[allow(clippy::cast_sign_loss)] // wrapping conversion is intentional + { + vm.world_diff.pubdata.0 as u32 + } } else { 0 }, diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index 66f4cb7..a587979 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -191,8 +191,11 @@ pub(crate) fn get_far_call_calldata( already_failed: bool, ) -> Option { let mut pointer = FatPointer::from(raw_abi); + #[allow(clippy::cast_possible_truncation)] + // intentional: the source is encoded in the lower byte of the extracted value + let raw_source = (raw_abi.0[3] >> 32) as u8; - match FatPointerSource::from_abi((raw_abi.0[3] >> 32) & 0xff) { + match FatPointerSource::from_abi(raw_source) { FatPointerSource::ForwardFatPointer => { if !is_pointer || pointer.offset > pointer.length || already_failed { return None; @@ -248,7 +251,7 @@ enum FatPointerTarget { } impl FatPointerSource { - const fn from_abi(value: u64) -> Self { + const fn from_abi(value: u8) -> Self { match value { 1 => Self::ForwardFatPointer, 2 => Self::MakeNewPointer(FatPointerTarget::ToAuxHeap), diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index cfaeeba..18cf30c 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -116,7 +116,7 @@ where }; if return_type.is_failure() { - vm.world_diff.rollback(&snapshot); + vm.world_diff.rollback(snapshot); } vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); diff --git a/crates/vm2/src/mode_requirements.rs b/crates/vm2/src/mode_requirements.rs index e0a7887..918ad6b 100644 --- a/crates/vm2/src/mode_requirements.rs +++ b/crates/vm2/src/mode_requirements.rs @@ -1,4 +1,4 @@ -/// Requirements for the VM execution mode that can be placed by instructions. +/// VM execution mode requirements (kernel only, not in static call) that can be placed on instructions. #[derive(Debug, Clone, Copy)] pub struct ModeRequirements(pub(crate) u8); diff --git a/crates/vm2/src/program.rs b/crates/vm2/src/program.rs index eee95ae..7a2b0dc 100644 --- a/crates/vm2/src/program.rs +++ b/crates/vm2/src/program.rs @@ -119,7 +119,8 @@ impl PartialEq for Program { } } -/// "Jump to start" instruction placed at the end of programs exceeding `1 << 16` instructions. +/// Wraparound instruction placed at the end of programs exceeding `1 << 16` instructions to simulate the 16-bit program counter overflowing. +/// Does not invoke tracers because it is an implementation detail, not an actual instruction. fn jump_to_beginning() -> Instruction { Instruction { handler: jump_to_beginning_handler, diff --git a/crates/vm2/src/single_instruction_test/into_zk_evm.rs b/crates/vm2/src/single_instruction_test/into_zk_evm.rs index 790a1d4..0d26762 100644 --- a/crates/vm2/src/single_instruction_test/into_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/into_zk_evm.rs @@ -124,7 +124,8 @@ impl Memory for MockMemory { ) -> zk_evm::aux_structures::MemoryQuery { match query.location.memory_type { MemoryType::Stack => { - let slot = u16::try_from(query.location.index.0).unwrap(); + #[allow(clippy::cast_possible_truncation)] // intentional + let slot = query.location.index.0 as u16; if query.rw_flag { self.stack.set(slot, query.value); if query.value_is_pointer { diff --git a/crates/vm2/src/single_instruction_test/mod.rs b/crates/vm2/src/single_instruction_test/mod.rs index f8bfb1f..527a05b 100644 --- a/crates/vm2/src/single_instruction_test/mod.rs +++ b/crates/vm2/src/single_instruction_test/mod.rs @@ -3,7 +3,7 @@ //! It would be wasteful to randomly generate the whole heap. Instead, we only generate //! the part of the heap that is actually accessed, which is at most 32 bytes! //! -//! The same kind of mocking in applied to stack memory, the program, the world and callstack. +//! The same kind of mocking is applied to stack memory, the program, the world and the callstack. #![allow(missing_docs)] diff --git a/crates/vm2/src/stack.rs b/crates/vm2/src/stack.rs index 59bb5db..d3d7a83 100644 --- a/crates/vm2/src/stack.rs +++ b/crates/vm2/src/stack.rs @@ -21,7 +21,7 @@ const DIRTY_AREA_SIZE: usize = (1 << 16) / NUMBER_OF_DIRTY_AREAS; impl Stack { #[allow(clippy::cast_ptr_alignment)] // aligned per `Stack` layout pub(crate) fn new() -> Box { - unsafe { Box::from_raw(alloc_zeroed(Layout::new::()).cast::()) } + unsafe { Box::from_raw(alloc_zeroed(Layout::new::()).cast::()) } } #[inline(always)] diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 463c864..2a8f2d6 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -153,7 +153,7 @@ impl> VirtualMachine { }); } - /// Returns the VM to the state it was in when [`Self::make_snapshot()`] was called. If there is + /// Returns the VM to the state it was in when [`Self::make_snapshot()`] was called. /// /// # Panics /// @@ -169,7 +169,7 @@ impl> VirtualMachine { .snapshot .take() .expect("`rollback()` called without a snapshot"); - self.world_diff.external_rollback(&snapshot.world_snapshot); + self.world_diff.external_rollback(snapshot.world_snapshot); self.state.rollback(snapshot.state_snapshot); self.delete_history(); } diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index 9aeaf17..948def9 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -277,7 +277,8 @@ impl WorldDiff { } } - pub(crate) fn rollback(&mut self, snapshot: &Snapshot) { + #[allow(clippy::needless_pass_by_value)] // intentional: we require a snapshot to be rolled back to no more than once + pub(crate) fn rollback(&mut self, snapshot: Snapshot) { self.storage_changes.rollback(snapshot.storage_changes); self.paid_changes.rollback(snapshot.paid_changes); self.events.rollback(snapshot.events); @@ -307,8 +308,8 @@ impl WorldDiff { } } - pub(crate) fn external_rollback(&mut self, snapshot: &ExternalSnapshot) { - self.rollback(&snapshot.internal_snapshot); + pub(crate) fn external_rollback(&mut self, snapshot: ExternalSnapshot) { + self.rollback(snapshot.internal_snapshot); self.storage_refunds.rollback(snapshot.storage_refunds); self.pubdata_costs.rollback(snapshot.pubdata_costs); self.decommitted_hashes From 0f2f2e9867d25899e9358c4b85b361b719fe6a7d Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 11:50:28 +0300 Subject: [PATCH 26/32] Remove `inline(always)` from `Instruction` constructors --- crates/vm2/src/instruction_handlers/binop.rs | 3 --- crates/vm2/src/instruction_handlers/heap_access.rs | 7 ------- crates/vm2/src/instruction_handlers/pointer.rs | 2 -- crates/vm2/src/instruction_handlers/storage.rs | 4 ---- 4 files changed, 16 deletions(-) diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index a2c078f..a1ec08a 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -209,7 +209,6 @@ impl Binop for Div { macro_rules! from_binop { ($name:ident <$binop:ty>) => { #[doc = concat!("Creates [`", stringify!($binop), "`] instruction with the provided params.")] - #[inline(always)] pub fn $name( src1: AnySource, src2: Register2, @@ -224,7 +223,6 @@ macro_rules! from_binop { ($name:ident <$binop:ty, $out2: ty>) => { #[doc = concat!("Creates [`", stringify!($binop), "`] instruction with the provided params.")] - #[inline(always)] pub fn $name( src1: AnySource, src2: Register2, @@ -241,7 +239,6 @@ macro_rules! from_binop { /// Instructions for binary operations. impl> Instruction { - #[inline(always)] pub(crate) fn from_binop( src1: AnySource, src2: Register2, diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index 9f14def..5ac8f0a 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -203,7 +203,6 @@ fn load_pointer( impl Instruction { /// Creates a [`HeapRead`](opcodes::HeapRead) instruction with the provided params. - #[inline(always)] pub fn from_heap_read( src: RegisterOrImmediate, out: Register1, @@ -214,7 +213,6 @@ impl Instruction { } /// Creates an [`AuxHeapRead`](opcodes::AuxHeapRead) instruction with the provided params. - #[inline(always)] pub fn from_aux_heap_read( src: RegisterOrImmediate, out: Register1, @@ -224,7 +222,6 @@ impl Instruction { Self::from_read::(src, out, incremented_out, arguments) } - #[inline(always)] fn from_read( src: RegisterOrImmediate, out: Register1, @@ -245,7 +242,6 @@ impl Instruction { } /// Creates a [`HeapWrite`](opcodes::HeapWrite) instruction with the provided params. - #[inline(always)] pub fn from_heap_write( src1: RegisterOrImmediate, src2: Register2, @@ -257,7 +253,6 @@ impl Instruction { } /// Creates an [`AuxHeapWrite`](opcodes::AuxHeapWrite) instruction with the provided params. - #[inline(always)] pub fn from_aux_heap_store( src1: RegisterOrImmediate, src2: Register2, @@ -267,7 +262,6 @@ impl Instruction { Self::from_write::(src1, src2, incremented_out, arguments, false) } - #[inline(always)] fn from_write( src1: RegisterOrImmediate, src2: Register2, @@ -286,7 +280,6 @@ impl Instruction { } /// Creates an [`PointerRead`](opcodes::PointerRead) instruction with the provided params. - #[inline(always)] pub fn from_pointer_read( src: Register1, out: Register1, diff --git a/crates/vm2/src/instruction_handlers/pointer.rs b/crates/vm2/src/instruction_handlers/pointer.rs index ffa1432..4869870 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -116,7 +116,6 @@ impl PtrOp for PointerShrink { macro_rules! from_ptr_op { ($name:ident <$binop:ty>) => { #[doc = concat!("Creates a [`", stringify!($binop), "`] instruction with the provided params.")] - #[inline(always)] pub fn $name( src1: AnySource, src2: Register2, @@ -136,7 +135,6 @@ impl Instruction { from_ptr_op!(from_pointer_pack); from_ptr_op!(from_pointer_shrink); - #[inline(always)] pub(crate) fn from_ptr( src1: AnySource, src2: Register2, diff --git a/crates/vm2/src/instruction_handlers/storage.rs b/crates/vm2/src/instruction_handlers/storage.rs index 0f8056b..07eaeb6 100644 --- a/crates/vm2/src/instruction_handlers/storage.rs +++ b/crates/vm2/src/instruction_handlers/storage.rs @@ -77,7 +77,6 @@ fn sload_transient( impl> Instruction { /// Creates a [`StorageWrite`](opcodes::StorageWrite) instruction with the provided params. - #[inline(always)] pub fn from_storage_write(src1: Register1, src2: Register2, arguments: Arguments) -> Self { Self { handler: sstore, @@ -86,7 +85,6 @@ impl> Instruction { } /// Creates a [`TransientStorageWrite`](opcodes::TransientStorageWrite) instruction with the provided params. - #[inline(always)] pub fn from_transient_storage_write( src1: Register1, src2: Register2, @@ -99,7 +97,6 @@ impl> Instruction { } /// Creates a [`StorageRead`](opcodes::StorageRead) instruction with the provided params. - #[inline(always)] pub fn from_storage_read(src: Register1, dst: Register1, arguments: Arguments) -> Self { Self { handler: sload, @@ -108,7 +105,6 @@ impl> Instruction { } /// Creates a [`TransientStorageRead`](opcodes::TransientStorageRead) instruction with the provided params. - #[inline(always)] pub fn from_transient_storage_read( src: Register1, dst: Register1, From 6664f260513a59b3cdf04810faa0cd30494a4bdc Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 12:13:50 +0300 Subject: [PATCH 27/32] Enable index page for docs --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d13bbc..8a7fa3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,8 @@ on: env: CARGO_TERM_COLOR: always + # Nightly Rust necessary for building docs. + RUST_NIGHTLY_VERSION: nightly-2024-08-01 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -80,15 +82,15 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@master with: - toolchain: stable + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Build docs run: | cargo clean --doc && \ - cargo rustdoc -p zksync_vm2_interface && \ - cargo rustdoc -p zksync_vm2 + cargo rustdoc -p zksync_vm2_interface -- -Z unstable-options --enable-index-page && \ + cargo rustdoc -p zksync_vm2 -- -Z unstable-options --enable-index-page - name: Deploy uses: JamesIves/github-pages-deploy-action@v4 From fbabb85bae1378f076baf02fdacb3c0fe0470e08 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 13:50:20 +0300 Subject: [PATCH 28/32] Revert `invalid_instruction()` changes --- crates/vm2/src/instruction_handlers/ret.rs | 20 ++++----------- crates/vm2/src/lib.rs | 29 +--------------------- 2 files changed, 6 insertions(+), 43 deletions(-) diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 18cf30c..8f523d5 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -1,5 +1,3 @@ -use std::convert::Infallible; - use primitive_types::U256; use zksync_vm2_interface::{ opcodes::{self, Normal, Panic, Revert, TypeLevelReturnType}, @@ -20,14 +18,10 @@ use crate::{ Instruction, Predicate, VirtualMachine, }; -fn naked_ret( +fn naked_ret( vm: &mut VirtualMachine, args: &Arguments, -) -> ExecutionStatus -where - T: Tracer, - RT: TypeLevelReturnType, -{ +) -> ExecutionStatus { let mut return_type = RT::VALUE; let near_call_leftover_gas = vm.state.current_frame.gas; @@ -125,15 +119,11 @@ where ExecutionStatus::Running } -fn ret( +fn ret( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus -where - T: Tracer, - RT: TypeLevelReturnType, -{ +) -> ExecutionStatus { full_boilerplate::, _, _>(vm, world, tracer, |vm, args, _, _| { naked_ret::(vm, args) }) @@ -183,7 +173,7 @@ pub(crate) fn panic_from_failed_far_call( } /// Panics, burning all available gas. -static INVALID_INSTRUCTION: Instruction<(), Infallible> = Instruction::from_invalid(); +static INVALID_INSTRUCTION: Instruction<(), ()> = Instruction::from_invalid(); pub(crate) fn invalid_instruction<'a, T, W>() -> &'a Instruction { // Safety: the handler of an invalid instruction is never read. diff --git a/crates/vm2/src/lib.rs b/crates/vm2/src/lib.rs index e2f9f88..338bfda 100644 --- a/crates/vm2/src/lib.rs +++ b/crates/vm2/src/lib.rs @@ -2,10 +2,7 @@ //! //! This crate provides high-performance [`VirtualMachine`] for ZKsync Era. -use std::{ - convert::Infallible, - hash::{DefaultHasher, Hash, Hasher}, -}; +use std::hash::{DefaultHasher, Hash, Hasher}; use primitive_types::{H160, U256}; pub use zksync_vm2_interface as interface; @@ -66,20 +63,6 @@ pub trait StorageInterface { fn is_free_storage_slot(&self, contract: &H160, key: &U256) -> bool; } -impl StorageInterface for Infallible { - fn read_storage(&mut self, _contract: H160, _key: U256) -> Option { - match *self {} - } - - fn cost_of_writing_storage(&mut self, _initial_value: Option, _new_value: U256) -> u32 { - match *self {} - } - - fn is_free_storage_slot(&self, _contract: &H160, _key: &U256) -> bool { - match *self {} - } -} - /// Encapsulates VM interaction with the external world. This includes VM storage and decomitting (loading) bytecodes /// for execution. pub trait World: StorageInterface + Sized { @@ -93,16 +76,6 @@ pub trait World: StorageInterface + Sized { fn decommit_code(&mut self, hash: U256) -> Vec; } -impl World for Infallible { - fn decommit(&mut self, _hash: U256) -> Program { - match *self {} - } - - fn decommit_code(&mut self, _hash: U256) -> Vec { - match *self {} - } -} - /// Deterministic (across program runs and machines) hash that can be used for `Debug` implementations /// to concisely represent large amounts of data. #[cfg_attr(feature = "single_instruction_test", allow(dead_code))] // Currently used entirely in types overridden by `single_instruction_test` feature From 2426c148ea9407b9c83688f440b0dbb70c204e34 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 13:54:58 +0300 Subject: [PATCH 29/32] Use `BTreeSet` in `RollbackableSet` --- crates/vm2/src/rollback.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/vm2/src/rollback.rs b/crates/vm2/src/rollback.rs index 06c578c..6344a7e 100644 --- a/crates/vm2/src/rollback.rs +++ b/crates/vm2/src/rollback.rs @@ -1,6 +1,4 @@ -#![allow(clippy::zero_sized_map_values)] // FIXME: why? - -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; /// A trait for things that can be rolled back to snapshots pub(crate) trait Rollback { @@ -68,14 +66,14 @@ impl AsRef> for RollbackableMap { #[derive(Debug, Default)] pub(crate) struct RollbackableSet { - map: BTreeMap, + map: BTreeSet, old_entries: Vec, } impl RollbackableSet { /// Adds `key` to the set and returns if it was added (not present earlier). pub(crate) fn add(&mut self, key: T) -> bool { - let is_new = self.map.insert(key.clone(), ()).is_none(); + let is_new = self.map.insert(key.clone()); if is_new { self.old_entries.push(key); } @@ -101,8 +99,8 @@ impl Rollback for RollbackableSet { } } -impl AsRef> for RollbackableSet { - fn as_ref(&self) -> &BTreeMap { +impl AsRef> for RollbackableSet { + fn as_ref(&self) -> &BTreeSet { &self.map } } From f0d8f293cd608a5679522bceda3d99af01925543 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 14:22:44 +0300 Subject: [PATCH 30/32] Downgrade FIXME to TODO, provide more details --- crates/vm2/src/callframe.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 0c06c6b..a83c6aa 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -145,7 +145,8 @@ impl Callframe { offset_in_bytes / mem::size_of::>() as isize } - // FIXME: can overflow / underflow on invalid instruction / free panic + // TODO: can overflow / underflow after an invalid instruction or free panic. Ordinarily, this will lead to VM termination (for an invalid instruction) + // or the callframe getting popped (for a panic), but it's still technically possible to invoke this method afterwards in certain cases (e.g., in the bootloader callframe). #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub(crate) fn get_pc_as_u16(&self) -> u16 { self.get_raw_pc() as u16 From 1ae1ba8aac0852f906485899726560de65febfdb Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 17 Sep 2024 14:53:37 +0300 Subject: [PATCH 31/32] Revert unnecessary changes --- crates/vm2/src/instruction_handlers/far_call.rs | 2 +- .../vm2/src/instruction_handlers/heap_access.rs | 17 ++++++----------- crates/vm2/src/instruction_handlers/jump.rs | 10 +++++----- crates/vm2/src/instruction_handlers/pointer.rs | 10 ++-------- crates/vm2/src/stack.rs | 2 +- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index a587979..1fc8055 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -269,7 +269,7 @@ impl FatPointer { } impl> Instruction { - /// Creates a [`FarCall`](FarCall) instruction with the provided mode and params. + /// Creates a [`FarCall`] instruction with the provided mode and params. pub fn from_far_call( src1: Register1, src2: Register2, diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index 5ac8f0a..0fbd265 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -63,16 +63,11 @@ fn bigger_than_last_address(x: U256) -> bool { x.0[0] > LAST_ADDRESS.into() || x.0[1] != 0 || x.0[2] != 0 || x.0[3] != 0 } -fn load( +fn load( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus -where - T: Tracer, - H: HeapFromState, - In: Source, -{ +) -> ExecutionStatus { boilerplate::(vm, world, tracer, |vm, args| { // Pointers need not be masked here even though we do not care about them being pointers. // They will panic, though because they are larger than 2^32. @@ -153,10 +148,10 @@ where /// Pays for more heap space. Doesn't acually grow the heap. /// That distinction is necessary because the bootloader gets `u32::MAX` heap for free. -pub(crate) fn grow_heap(state: &mut State, new_bound: u32) -> Result<(), ()> -where - H: HeapFromState, -{ +pub(crate) fn grow_heap( + state: &mut State, + new_bound: u32, +) -> Result<(), ()> { let already_paid = H::get_heap_size(state); if *already_paid < new_bound { let to_pay = new_bound - *already_paid; diff --git a/crates/vm2/src/instruction_handlers/jump.rs b/crates/vm2/src/instruction_handlers/jump.rs index 19469be..7bbb1c9 100644 --- a/crates/vm2/src/instruction_handlers/jump.rs +++ b/crates/vm2/src/instruction_handlers/jump.rs @@ -13,11 +13,11 @@ use crate::{ VirtualMachine, }; -fn jump(vm: &mut VirtualMachine, world: &mut W, tracer: &mut T) -> ExecutionStatus -where - T: Tracer, - In: Source, -{ +fn jump( + vm: &mut VirtualMachine, + world: &mut W, + tracer: &mut T, +) -> ExecutionStatus { boilerplate::(vm, world, tracer, |vm, args| { #[allow(clippy::cast_possible_truncation)] // intentional let target = In::get(args, &mut vm.state).low_u32() as u16; diff --git a/crates/vm2/src/instruction_handlers/pointer.rs b/crates/vm2/src/instruction_handlers/pointer.rs index 4869870..e224ec1 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -20,17 +20,11 @@ use crate::{ Instruction, VirtualMachine, }; -fn ptr( +fn ptr( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, -) -> ExecutionStatus -where - T: Tracer, - Op: PtrOp, - In1: Source, - Out: Destination, -{ +) -> ExecutionStatus { boilerplate::(vm, world, tracer, |vm, args| { let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { ( diff --git a/crates/vm2/src/stack.rs b/crates/vm2/src/stack.rs index d3d7a83..847056e 100644 --- a/crates/vm2/src/stack.rs +++ b/crates/vm2/src/stack.rs @@ -21,7 +21,7 @@ const DIRTY_AREA_SIZE: usize = (1 << 16) / NUMBER_OF_DIRTY_AREAS; impl Stack { #[allow(clippy::cast_ptr_alignment)] // aligned per `Stack` layout pub(crate) fn new() -> Box { - unsafe { Box::from_raw(alloc_zeroed(Layout::new::()).cast::()) } + unsafe { Box::from_raw(alloc_zeroed(Layout::new::()).cast()) } } #[inline(always)] From e523bce3962007aeddbc04d790d9ff92f3cb4496 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 18 Sep 2024 11:46:46 +0300 Subject: [PATCH 32/32] Move doc comment --- crates/vm2/src/single_instruction_test/into_zk_evm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/vm2/src/single_instruction_test/into_zk_evm.rs b/crates/vm2/src/single_instruction_test/into_zk_evm.rs index 0d26762..9829007 100644 --- a/crates/vm2/src/single_instruction_test/into_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/into_zk_evm.rs @@ -91,8 +91,6 @@ pub struct MockMemory { heap_write: Option, } -// One arbitrary heap value is not enough for zk_evm -// because it reads two U256s to read one U256. #[derive(Debug, Copy, Clone)] pub(crate) struct ExpectedHeapValue { heap: u32, @@ -102,6 +100,7 @@ pub(crate) struct ExpectedHeapValue { impl ExpectedHeapValue { /// Returns a new U256 that contains data from the heap value and zero elsewhere. + /// One arbitrary heap value is not enough for `zk_evm` because it reads two U256s to read one U256. fn partially_overlapping_u256(&self, start: u32) -> U256 { let mut read = [0; 32]; for i in 0..32 {