From df5bec3d04d64d434f9b0ccb285ba4681008f7b3 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 18 Oct 2024 11:29:15 +0100 Subject: [PATCH] feat: expose storage to tracers (#77) Tracers can now read storage slots. This is required for the validation tracer. --- crates/vm2-interface/src/lib.rs | 14 +- crates/vm2-interface/src/state_interface.rs | 333 +++++++++--------- crates/vm2-interface/src/tracer_interface.rs | 22 +- crates/vm2/src/callframe.rs | 4 +- crates/vm2/src/instruction_handlers/binop.rs | 1 + crates/vm2/src/instruction_handlers/common.rs | 21 +- .../vm2/src/instruction_handlers/context.rs | 14 +- crates/vm2/src/instruction_handlers/event.rs | 8 +- .../vm2/src/instruction_handlers/far_call.rs | 2 +- .../src/instruction_handlers/heap_access.rs | 10 +- crates/vm2/src/instruction_handlers/jump.rs | 6 +- .../vm2/src/instruction_handlers/near_call.rs | 6 +- crates/vm2/src/instruction_handlers/nop.rs | 6 +- .../vm2/src/instruction_handlers/pointer.rs | 6 +- .../src/instruction_handlers/precompiles.rs | 6 +- crates/vm2/src/instruction_handlers/ret.rs | 35 +- .../vm2/src/instruction_handlers/storage.rs | 4 +- .../src/single_instruction_test/callframe.rs | 2 +- .../print_mock_info.rs | 6 +- .../src/single_instruction_test/program.rs | 2 +- .../state_to_zk_evm.rs | 3 +- crates/vm2/src/state.rs | 3 +- crates/vm2/src/tracing.rs | 92 ++++- crates/vm2/src/world_diff.rs | 38 +- 24 files changed, 385 insertions(+), 259 deletions(-) diff --git a/crates/vm2-interface/src/lib.rs b/crates/vm2-interface/src/lib.rs index 5e1a8fb0..29461be9 100644 --- a/crates/vm2-interface/src/lib.rs +++ b/crates/vm2-interface/src/lib.rs @@ -27,13 +27,15 @@ //! ``` //! # use zksync_vm2_interface as zksync_vm2_interface_v1; //! use zksync_vm2_interface_v1::{ -//! StateInterface as StateInterfaceV1, Tracer as TracerV1, opcodes::NearCall, +//! StateInterface as StateInterfaceV1, GlobalStateInterface as GlobalStateInterfaceV1, Tracer as TracerV1, opcodes::NearCall, //! }; //! //! trait StateInterface: StateInterfaceV1 { //! fn get_some_new_field(&self) -> u32; //! } //! +//! trait GlobalStateInterface: StateInterface + GlobalStateInterfaceV1 {} +//! //! pub struct NewOpcode; //! //! #[derive(PartialEq, Eq)] @@ -57,12 +59,12 @@ //! } //! //! trait Tracer { -//! fn before_instruction(&mut self, _state: &mut S) {} -//! fn after_instruction(&mut self, _state: &mut S) {} +//! fn before_instruction(&mut self, state: &mut S) {} +//! fn after_instruction(&mut self, state: &mut S) {} //! } //! //! impl Tracer for T { -//! fn before_instruction(&mut self, state: &mut S) { +//! fn before_instruction(&mut self, state: &mut S) { //! match OP::VALUE { //! Opcode::NewOpcode => {} //! // Do this for every old opcode @@ -71,13 +73,13 @@ //! } //! } //! } -//! fn after_instruction(&mut self, _state: &mut S) {} +//! fn after_instruction(&mut self, state: &mut S) {} //! } //! //! // Now you can use the new features by implementing TracerV2 //! struct MyTracer; //! impl Tracer for MyTracer { -//! fn before_instruction(&mut self, state: &mut S) { +//! fn before_instruction(&mut self, state: &mut S) { //! if OP::VALUE == Opcode::NewOpcode { //! state.get_some_new_field(); //! } diff --git a/crates/vm2-interface/src/state_interface.rs b/crates/vm2-interface/src/state_interface.rs index a5bc7fee..3f755978 100644 --- a/crates/vm2-interface/src/state_interface.rs +++ b/crates/vm2-interface/src/state_interface.rs @@ -41,6 +41,7 @@ pub trait StateInterface { /// 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. @@ -59,6 +60,12 @@ pub trait StateInterface { fn set_pubdata(&mut self, value: i32); } +/// State interface with access to global state like storage. +pub trait GlobalStateInterface: StateInterface { + /// Gets value of the specified storage slot. + fn get_storage(&mut self, address: H160, slot: U256) -> U256; +} + /// VM execution flags. See the EraVM reference for more details. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Flags { @@ -209,215 +216,227 @@ pub struct L2ToL1Log { } #[cfg(test)] -#[derive(Debug)] -pub struct DummyState; +pub mod tests { + use primitive_types::{H160, U256}; -#[cfg(test)] -impl StateInterface for DummyState { - fn read_register(&self, _: u8) -> (U256, bool) { - unimplemented!() - } + use super::{ + CallframeInterface, Event, Flags, GlobalStateInterface, HeapId, L2ToL1Log, StateInterface, + }; - fn set_register(&mut self, _: u8, _: U256, _: bool) { - unimplemented!() - } + #[derive(Debug)] + pub struct DummyState; - fn current_frame(&mut self) -> impl CallframeInterface + '_ { - DummyState - } + impl StateInterface for DummyState { + fn read_register(&self, _: u8) -> (U256, bool) { + unimplemented!() + } - fn number_of_callframes(&self) -> usize { - unimplemented!() - } + fn set_register(&mut self, _: u8, _: U256, _: bool) { + unimplemented!() + } - fn callframe(&mut self, _: usize) -> impl CallframeInterface + '_ { - DummyState - } + fn current_frame(&mut self) -> impl CallframeInterface + '_ { + DummyState + } - fn read_heap_byte(&self, _: HeapId, _: u32) -> u8 { - unimplemented!() - } + fn number_of_callframes(&self) -> usize { + unimplemented!() + } - fn read_heap_u256(&self, _: HeapId, _: u32) -> U256 { - unimplemented!() - } + fn callframe(&mut self, _: usize) -> impl CallframeInterface + '_ { + DummyState + } - fn write_heap_u256(&mut self, _: HeapId, _: u32, _: U256) { - unimplemented!() - } + fn read_heap_byte(&self, _: HeapId, _: u32) -> u8 { + unimplemented!() + } - fn flags(&self) -> Flags { - unimplemented!() - } + fn read_heap_u256(&self, _: HeapId, _: u32) -> U256 { + unimplemented!() + } - fn set_flags(&mut self, _: Flags) { - unimplemented!() - } + fn write_heap_u256(&mut self, _: HeapId, _: u32, _: U256) { + unimplemented!() + } - fn transaction_number(&self) -> u16 { - unimplemented!() - } + fn flags(&self) -> Flags { + unimplemented!() + } - fn set_transaction_number(&mut self, _: u16) { - unimplemented!() - } + fn set_flags(&mut self, _: Flags) { + unimplemented!() + } - fn context_u128_register(&self) -> u128 { - unimplemented!() - } + fn transaction_number(&self) -> u16 { + unimplemented!() + } - fn set_context_u128_register(&mut self, _: u128) { - unimplemented!() - } + fn set_transaction_number(&mut self, _: u16) { + unimplemented!() + } - fn get_storage_state(&self) -> impl Iterator { - std::iter::empty() - } + fn context_u128_register(&self) -> u128 { + unimplemented!() + } - fn get_transient_storage_state(&self) -> impl Iterator { - std::iter::empty() - } + fn set_context_u128_register(&mut self, _: u128) { + unimplemented!() + } - fn get_transient_storage(&self, _: H160, _: U256) -> U256 { - unimplemented!() - } + fn get_storage_state(&self) -> impl Iterator { + std::iter::empty() + } - fn write_transient_storage(&mut self, _: H160, _: U256, _: U256) { - unimplemented!() - } + fn get_transient_storage_state(&self) -> impl Iterator { + std::iter::empty() + } - fn events(&self) -> impl Iterator { - std::iter::empty() - } + fn get_transient_storage(&self, _: H160, _: U256) -> U256 { + unimplemented!() + } - fn l2_to_l1_logs(&self) -> impl Iterator { - std::iter::empty() - } + fn write_transient_storage(&mut self, _: H160, _: U256, _: U256) { + unimplemented!() + } - fn pubdata(&self) -> i32 { - unimplemented!() - } + fn events(&self) -> impl Iterator { + std::iter::empty() + } - fn set_pubdata(&mut self, _: i32) { - unimplemented!() - } -} + fn l2_to_l1_logs(&self) -> impl Iterator { + std::iter::empty() + } -#[cfg(test)] -impl CallframeInterface for DummyState { - fn address(&self) -> H160 { - unimplemented!() - } + fn pubdata(&self) -> i32 { + unimplemented!() + } - fn set_address(&mut self, _: H160) { - unimplemented!() + fn set_pubdata(&mut self, _: i32) { + unimplemented!() + } } - fn code_address(&self) -> H160 { - unimplemented!() + impl GlobalStateInterface for DummyState { + fn get_storage(&mut self, _: H160, _: U256) -> U256 { + unimplemented!() + } } - fn set_code_address(&mut self, _: H160) { - unimplemented!() - } + impl CallframeInterface for DummyState { + fn address(&self) -> H160 { + unimplemented!() + } - fn caller(&self) -> H160 { - unimplemented!() - } + fn set_address(&mut self, _: H160) { + unimplemented!() + } - fn set_caller(&mut self, _: H160) { - unimplemented!() - } + fn code_address(&self) -> H160 { + unimplemented!() + } - fn program_counter(&self) -> Option { - unimplemented!() - } + fn set_code_address(&mut self, _: H160) { + unimplemented!() + } - fn set_program_counter(&mut self, _: u16) { - unimplemented!() - } + fn caller(&self) -> H160 { + unimplemented!() + } - fn exception_handler(&self) -> u16 { - unimplemented!() - } + fn set_caller(&mut self, _: H160) { + unimplemented!() + } - fn set_exception_handler(&mut self, _: u16) { - unimplemented!() - } + fn program_counter(&self) -> Option { + unimplemented!() + } - fn is_static(&self) -> bool { - unimplemented!() - } + fn set_program_counter(&mut self, _: u16) { + unimplemented!() + } - fn is_kernel(&self) -> bool { - unimplemented!() - } + fn exception_handler(&self) -> u16 { + unimplemented!() + } - fn gas(&self) -> u32 { - unimplemented!() - } + fn set_exception_handler(&mut self, _: u16) { + unimplemented!() + } - fn set_gas(&mut self, _: u32) { - unimplemented!() - } + fn is_static(&self) -> bool { + unimplemented!() + } - fn stipend(&self) -> u32 { - unimplemented!() - } + fn is_kernel(&self) -> bool { + unimplemented!() + } - fn context_u128(&self) -> u128 { - unimplemented!() - } + fn gas(&self) -> u32 { + unimplemented!() + } - fn set_context_u128(&mut self, _: u128) { - unimplemented!() - } + fn set_gas(&mut self, _: u32) { + unimplemented!() + } - fn is_near_call(&self) -> bool { - unimplemented!() - } + fn stipend(&self) -> u32 { + unimplemented!() + } - fn read_stack(&self, _: u16) -> (U256, bool) { - unimplemented!() - } + fn context_u128(&self) -> u128 { + unimplemented!() + } - fn write_stack(&mut self, _: u16, _: U256, _: bool) { - unimplemented!() - } + fn set_context_u128(&mut self, _: u128) { + unimplemented!() + } - fn stack_pointer(&self) -> u16 { - unimplemented!() - } + fn is_near_call(&self) -> bool { + unimplemented!() + } - fn set_stack_pointer(&mut self, _: u16) { - unimplemented!() - } + fn read_stack(&self, _: u16) -> (U256, bool) { + unimplemented!() + } - fn heap(&self) -> HeapId { - unimplemented!() - } + fn write_stack(&mut self, _: u16, _: U256, _: bool) { + unimplemented!() + } - fn heap_bound(&self) -> u32 { - unimplemented!() - } + fn stack_pointer(&self) -> u16 { + unimplemented!() + } - fn set_heap_bound(&mut self, _: u32) { - unimplemented!() - } + fn set_stack_pointer(&mut self, _: u16) { + unimplemented!() + } - fn aux_heap(&self) -> HeapId { - unimplemented!() - } + fn heap(&self) -> HeapId { + unimplemented!() + } - fn aux_heap_bound(&self) -> u32 { - unimplemented!() - } + fn heap_bound(&self) -> u32 { + unimplemented!() + } - fn set_aux_heap_bound(&mut self, _: u32) { - unimplemented!() - } + fn set_heap_bound(&mut self, _: u32) { + unimplemented!() + } + + fn aux_heap(&self) -> HeapId { + unimplemented!() + } + + fn aux_heap_bound(&self) -> u32 { + unimplemented!() + } + + fn set_aux_heap_bound(&mut self, _: u32) { + unimplemented!() + } - fn read_contract_code(&self, _: u16) -> U256 { - unimplemented!() + fn read_contract_code(&self, _: u16) -> U256 { + unimplemented!() + } } } diff --git a/crates/vm2-interface/src/tracer_interface.rs b/crates/vm2-interface/src/tracer_interface.rs index 22a8b44b..ab1abfba 100644 --- a/crates/vm2-interface/src/tracer_interface.rs +++ b/crates/vm2-interface/src/tracer_interface.rs @@ -1,4 +1,4 @@ -use crate::StateInterface; +use crate::GlobalStateInterface; macro_rules! forall_simple_opcodes { ($m:ident) => { @@ -241,11 +241,11 @@ impl OpcodeType for opcodes::Ret { /// Here `FarCallCounter` counts the number of far calls. /// /// ``` -/// # use zksync_vm2_interface::{Tracer, StateInterface, OpcodeType, Opcode}; +/// # use zksync_vm2_interface::{Tracer, GlobalStateInterface, OpcodeType, Opcode}; /// struct FarCallCounter(usize); /// /// impl Tracer for FarCallCounter { -/// fn before_instruction(&mut self, state: &mut S) { +/// fn before_instruction(&mut self, state: &mut S) { /// match OP::VALUE { /// Opcode::FarCall(_) => self.0 += 1, /// _ => {} @@ -257,11 +257,15 @@ pub trait Tracer { /// Executes logic before an instruction handler. /// /// The default implementation does nothing. - fn before_instruction(&mut self, _state: &mut S) {} + fn before_instruction(&mut self, state: &mut S) { + let _ = state; + } /// Executes logic after an instruction handler. /// /// The default implementation does nothing. - fn after_instruction(&mut self, _state: &mut S) {} + fn after_instruction(&mut self, state: &mut S) { + let _ = state; + } /// Provides cycle statistics for "complex" instructions from the prover perspective (mostly precompile calls). /// @@ -293,12 +297,12 @@ impl Tracer for () {} // Multiple tracers can be combined by building a linked list out of tuples. impl Tracer for (A, B) { - fn before_instruction(&mut self, state: &mut S) { + fn before_instruction(&mut self, state: &mut S) { self.0.before_instruction::(state); self.1.before_instruction::(state); } - fn after_instruction(&mut self, state: &mut S) { + fn after_instruction(&mut self, state: &mut S) { self.0.after_instruction::(state); self.1.after_instruction::(state); } @@ -312,12 +316,12 @@ impl Tracer for (A, B) { #[cfg(test)] mod tests { use super::{CallingMode, OpcodeType}; - use crate::{opcodes, DummyState, Tracer}; + use crate::{opcodes, tests::DummyState, GlobalStateInterface, Tracer}; struct FarCallCounter(usize); impl Tracer for FarCallCounter { - fn before_instruction(&mut self, _: &mut S) { + fn before_instruction(&mut self, _: &mut S) { if let super::Opcode::FarCall(CallingMode::Normal) = OP::VALUE { self.0 += 1; } diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index b29376e6..4a989280 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -10,7 +10,7 @@ use crate::{ program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, - Instruction, + Instruction, World, }; #[derive(Debug)] @@ -107,7 +107,7 @@ impl Callframe { } } -impl Callframe { +impl> Callframe { pub(crate) fn push_near_call( &mut self, gas_to_call: u32, diff --git a/crates/vm2/src/instruction_handlers/binop.rs b/crates/vm2/src/instruction_handlers/binop.rs index a1ec08ae..37a7e0ff 100644 --- a/crates/vm2/src/instruction_handlers/binop.rs +++ b/crates/vm2/src/instruction_handlers/binop.rs @@ -28,6 +28,7 @@ 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 4f865113..c46ef476 100644 --- a/crates/vm2/src/instruction_handlers/common.rs +++ b/crates/vm2/src/instruction_handlers/common.rs @@ -1,10 +1,13 @@ 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, tracing::VmAndWorld, VirtualMachine, + World, +}; #[inline(always)] -pub(crate) fn boilerplate( +pub(crate) fn boilerplate>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -17,7 +20,7 @@ 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, @@ -30,7 +33,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, @@ -49,19 +52,19 @@ pub(crate) fn full_boilerplate( vm.state.current_frame.is_static, ) { - return free_panic(vm, tracer); + return free_panic(vm, world, tracer); } if args.predicate().satisfied(&vm.state.flags) { - tracer.before_instruction::(vm); + tracer.before_instruction::(&mut VmAndWorld { vm, world }); vm.state.current_frame.pc = unsafe { vm.state.current_frame.pc.add(1) }; let result = business_logic(vm, args, world, tracer); - tracer.after_instruction::(vm); + tracer.after_instruction::(&mut VmAndWorld { vm, world }); result } else { - tracer.before_instruction::(vm); + tracer.before_instruction::(&mut VmAndWorld { vm, world }); vm.state.current_frame.pc = unsafe { vm.state.current_frame.pc.add(1) }; - tracer.after_instruction::(vm); + tracer.after_instruction::(&mut VmAndWorld { vm, world }); ExecutionStatus::Running } } diff --git a/crates/vm2/src/instruction_handlers/context.rs b/crates/vm2/src/instruction_handlers/context.rs index 53461104..26073ead 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, + Instruction, VirtualMachine, World, }; pub(crate) fn address_into_u256(address: H160) -> U256 { @@ -19,7 +19,7 @@ pub(crate) fn address_into_u256(address: H160) -> U256 { U256::from_big_endian(&buffer) } -fn context( +fn context, Op>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -74,7 +74,7 @@ impl ContextOp for SP { } } -fn context_meta( +fn context_meta>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -102,7 +102,7 @@ fn context_meta( }) } -fn set_context_u128( +fn set_context_u128>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -113,7 +113,7 @@ fn set_context_u128( }) } -fn increment_tx_number( +fn increment_tx_number>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -123,7 +123,7 @@ fn increment_tx_number( }) } -fn aux_mutating( +fn aux_mutating>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -134,7 +134,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 dc3d44e3..a12e054b 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, + Instruction, VirtualMachine, World, }; -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 1fc80552..2c000b19 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -109,7 +109,7 @@ where let Some((calldata, program, is_evm_interpreter)) = failing_part else { vm.state.current_frame.gas += new_frame_gas.saturating_sub(RETURN_COST); - panic_from_failed_far_call(vm, tracer, exception_handler); + panic_from_failed_far_call(vm, world, tracer, exception_handler); return; }; diff --git a/crates/vm2/src/instruction_handlers/heap_access.rs b/crates/vm2/src/instruction_handlers/heap_access.rs index 7218fc95..725eb9ec 100644 --- a/crates/vm2/src/instruction_handlers/heap_access.rs +++ b/crates/vm2/src/instruction_handlers/heap_access.rs @@ -14,7 +14,7 @@ use crate::{ fat_pointer::FatPointer, instruction::ExecutionStatus, state::State, - ExecutionEnd, Instruction, VirtualMachine, + ExecutionEnd, Instruction, VirtualMachine, World, }; pub(crate) trait HeapFromState { @@ -64,7 +64,7 @@ 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, H: HeapFromState, In: Source, const INCREMENT: bool>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -100,7 +100,7 @@ fn load( }) } -fn store( +fn store, H, In, const INCREMENT: bool, const HOOKING_ENABLED: bool>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -163,7 +163,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, @@ -197,7 +197,7 @@ fn load_pointer( }) } -impl Instruction { +impl> Instruction { /// Creates a [`HeapRead`](opcodes::HeapRead) instruction with the provided params. pub fn from_heap_read( src: RegisterOrImmediate, diff --git a/crates/vm2/src/instruction_handlers/jump.rs b/crates/vm2/src/instruction_handlers/jump.rs index 7bbb1c93..ff2ec7d9 100644 --- a/crates/vm2/src/instruction_handlers/jump.rs +++ b/crates/vm2/src/instruction_handlers/jump.rs @@ -10,10 +10,10 @@ use crate::{ Immediate1, Register1, RelativeStack, Source, }, instruction::{ExecutionStatus, Instruction}, - VirtualMachine, + VirtualMachine, World, }; -fn jump( +fn jump, In: Source>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -29,7 +29,7 @@ fn jump( }) } -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 ac4a6378..83d3b06c 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, @@ -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 d1ec55be..c1c0711e 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 { /// 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 3e0ade85..f5a03018 100644 --- a/crates/vm2/src/instruction_handlers/pointer.rs +++ b/crates/vm2/src/instruction_handlers/pointer.rs @@ -18,10 +18,10 @@ use crate::{ }, fat_pointer::FatPointer, instruction::ExecutionStatus, - Instruction, VirtualMachine, + Instruction, VirtualMachine, World, }; -fn ptr( +fn ptr, Op: PtrOp, In1: Source, Out: Destination, const SWAP: bool>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -124,7 +124,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 cb364878..7e6c4bdf 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, + Instruction, VirtualMachine, World, }; -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 71d51b55..382949d3 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -15,10 +15,11 @@ use crate::{ instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, predication::Flags, - Instruction, Predicate, VirtualMachine, + tracing::VmAndWorld, + Instruction, Predicate, VirtualMachine, World, }; -fn naked_ret( +fn naked_ret, RT: TypeLevelReturnType, const TO_LABEL: bool>( vm: &mut VirtualMachine, args: &Arguments, ) -> ExecutionStatus { @@ -119,7 +120,7 @@ fn naked_ret( ExecutionStatus::Running } -fn ret( +fn ret, RT: TypeLevelReturnType, const TO_LABEL: bool>( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, @@ -138,28 +139,30 @@ 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, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - tracer.before_instruction::, _>(vm); + tracer.before_instruction::, _>(&mut VmAndWorld { vm, world }); // args aren't used for panics unless TO_LABEL let result = naked_ret::( vm, &Arguments::new(Predicate::Always, 0, ModeRequirements::none()), ); - tracer.after_instruction::, _>(vm); + tracer.after_instruction::, _>(&mut VmAndWorld { vm, world }); result } /// 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, + world: &mut W, tracer: &mut T, exception_handler: u16, ) { - tracer.before_instruction::, _>(vm); + tracer.before_instruction::, _>(&mut VmAndWorld { vm, world }); // Gas is already subtracted in the far call code. // No need to roll back, as no changes are made in this "frame". @@ -169,16 +172,16 @@ pub(crate) fn panic_from_failed_far_call( vm.state.flags = Flags::new(true, false, false); vm.state.current_frame.set_pc_from_u16(exception_handler); - tracer.after_instruction::, _>(vm); + tracer.after_instruction::, _>(&mut VmAndWorld { vm, world }); } -fn invalid( +fn invalid>( vm: &mut VirtualMachine, - _: &mut W, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { vm.state.current_frame.gas = 0; - free_panic(vm, tracer) + free_panic(vm, world, tracer) } trait GenericStatics { @@ -186,7 +189,7 @@ trait GenericStatics { const INVALID: Instruction; } -impl GenericStatics for () { +impl> GenericStatics for () { const PANIC: Instruction = Instruction { handler: ret::, arguments: Arguments::new(Predicate::Always, RETURN_COST, ModeRequirements::none()), @@ -198,19 +201,19 @@ impl GenericStatics for () { // They aren't marked as such because returning any lifetime is more ergonomic. /// Point the program counter at this instruction when a panic occurs during the logic of and instruction. -pub(crate) fn spontaneous_panic<'a, T: Tracer, W>() -> &'a Instruction { +pub(crate) fn spontaneous_panic<'a, T: Tracer, W: World>() -> &'a Instruction { &<()>::PANIC } /// Panics, burning all available gas. -pub(crate) fn invalid_instruction<'a, T: Tracer, W>() -> &'a Instruction { +pub(crate) fn invalid_instruction<'a, T: Tracer, W: World>() -> &'a Instruction { &<()>::INVALID } 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 07eaeb62..5a28cff1 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/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index e8caccb8..4c478bed 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -65,7 +65,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { } } -impl Callframe { +impl> Callframe { pub(crate) fn dummy() -> Self { Self { address: H160::zero(), 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 7b45fd3d..fc2021bf 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,8 @@ 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 +14,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 d7679cb0..87f88313 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 32e08fe7..1ff8f5fb 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 @@ -12,9 +12,10 @@ use crate::{ callframe::{Callframe, NearCallFrame}, instruction_handlers::spontaneous_panic, state::State, + World, }; -pub(crate) fn vm2_state_to_zk_evm_state( +pub(crate) fn vm2_state_to_zk_evm_state>( state: &State, ) -> VmLocalState<8, EncodingModeProduction> { // zk_evm requires an unused bottom frame diff --git a/crates/vm2/src/state.rs b/crates/vm2/src/state.rs index 9efdc387..4ba5550a 100644 --- a/crates/vm2/src/state.rs +++ b/crates/vm2/src/state.rs @@ -10,6 +10,7 @@ use crate::{ program::Program, stack::Stack, world_diff::Snapshot, + World, }; /// State of a [`VirtualMachine`](crate::VirtualMachine). @@ -95,7 +96,7 @@ impl State { } } -impl State { +impl> State { /// Returns the total unspent gas in the VM, including stipends. pub(crate) fn total_unspent_gas(&self) -> u32 { self.current_frame.gas diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 727c6f95..021893e7 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -2,17 +2,18 @@ use std::cmp::Ordering; use primitive_types::{H160, U256}; use zksync_vm2_interface::{ - CallframeInterface, Event, Flags, HeapId, L2ToL1Log, StateInterface, Tracer, + CallframeInterface, Event, Flags, GlobalStateInterface, HeapId, L2ToL1Log, StateInterface, + Tracer, }; 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], @@ -159,7 +160,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 } @@ -346,6 +347,89 @@ impl CallframeWrapper<'_, T, W> { } } +pub(crate) struct VmAndWorld<'a, T, W> { + pub vm: &'a mut VirtualMachine, + pub world: &'a mut W, +} + +impl> GlobalStateInterface for VmAndWorld<'_, T, W> { + fn get_storage(&mut self, address: H160, slot: U256) -> U256 { + self.vm + .world_diff + .just_read_storage(self.world, address, slot) + } +} + +// This impl just forwards all calls to the VM part of VmAndWorld +impl> StateInterface for VmAndWorld<'_, T, W> { + fn read_register(&self, register: u8) -> (U256, bool) { + self.vm.read_register(register) + } + fn set_register(&mut self, register: u8, value: U256, is_pointer: bool) { + self.vm.set_register(register, value, is_pointer); + } + fn current_frame(&mut self) -> impl CallframeInterface + '_ { + self.vm.current_frame() + } + fn number_of_callframes(&self) -> usize { + self.vm.number_of_callframes() + } + fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_ { + self.vm.callframe(n) + } + fn read_heap_byte(&self, heap: HeapId, offset: u32) -> u8 { + self.vm.read_heap_byte(heap, offset) + } + fn read_heap_u256(&self, heap: HeapId, offset: u32) -> U256 { + self.vm.read_heap_u256(heap, offset) + } + fn write_heap_u256(&mut self, heap: HeapId, offset: u32, value: U256) { + self.vm.write_heap_u256(heap, offset, value); + } + fn flags(&self) -> Flags { + self.vm.flags() + } + fn set_flags(&mut self, flags: Flags) { + self.vm.set_flags(flags); + } + fn transaction_number(&self) -> u16 { + self.vm.transaction_number() + } + fn set_transaction_number(&mut self, value: u16) { + self.vm.set_transaction_number(value); + } + fn context_u128_register(&self) -> u128 { + self.vm.context_u128_register() + } + fn set_context_u128_register(&mut self, value: u128) { + self.vm.set_context_u128_register(value); + } + fn get_storage_state(&self) -> impl Iterator { + self.vm.get_storage_state() + } + fn get_transient_storage_state(&self) -> impl Iterator { + self.vm.get_transient_storage_state() + } + fn get_transient_storage(&self, address: H160, slot: U256) -> U256 { + self.vm.get_transient_storage(address, slot) + } + fn write_transient_storage(&mut self, address: H160, slot: U256, value: U256) { + self.vm.write_transient_storage(address, slot, value); + } + fn events(&self) -> impl Iterator { + self.vm.events() + } + fn l2_to_l1_logs(&self) -> impl Iterator { + self.vm.l2_to_l1_logs() + } + fn pubdata(&self) -> i32 { + self.vm.pubdata() + } + fn set_pubdata(&mut self, value: i32) { + self.vm.set_pubdata(value); + } +} + #[cfg(all(test, not(feature = "single_instruction_test")))] mod test { use primitive_types::H160; diff --git a/crates/vm2/src/world_diff.rs b/crates/vm2/src/world_diff.rs index 42f21b87..07666740 100644 --- a/crates/vm2/src/world_diff.rs +++ b/crates/vm2/src/world_diff.rs @@ -57,7 +57,12 @@ impl WorldDiff { contract: H160, key: U256, ) -> (U256, u32) { - let (value, refund) = self.read_storage_inner(world, tracer, contract, key); + let (value, newly_added) = self.read_storage_inner(world, tracer, contract, key); + let refund = if !newly_added || world.is_free_storage_slot(&contract, &key) { + WARM_READ_REFUND + } else { + 0 + }; self.storage_refunds.push(refund); (value, refund) } @@ -81,26 +86,29 @@ impl WorldDiff { tracer: &mut impl Tracer, contract: H160, key: U256, - ) -> (U256, u32) { - let value = self - .storage_changes - .as_ref() - .get(&(contract, key)) - .copied() - .unwrap_or_else(|| world.read_storage_value(contract, key)); - + ) -> (U256, bool) { let newly_added = self.read_storage_slots.add((contract, key)); if newly_added { tracer.on_extra_prover_cycles(CycleStats::StorageRead); } - let refund = if !newly_added || world.is_free_storage_slot(&contract, &key) { - WARM_READ_REFUND - } else { - 0 - }; self.pubdata_costs.push(0); - (value, refund) + (self.just_read_storage(world, contract, key), newly_added) + } + + /// Reads the value of a storage slot without any extra bookkeeping. + /// Should only be used for tracers. + pub(crate) fn just_read_storage( + &self, + world: &mut impl StorageInterface, + contract: H160, + key: U256, + ) -> U256 { + self.storage_changes + .as_ref() + .get(&(contract, key)) + .copied() + .unwrap_or_else(|| world.read_storage_value(contract, key)) } /// Returns the refund based the hot/cold status of the storage slot and the change in pubdata.