diff --git a/afl-fuzz/src/check_input_size.rs b/afl-fuzz/src/check_input_size.rs index 81bd8ef8..72bcedca 100644 --- a/afl-fuzz/src/check_input_size.rs +++ b/afl-fuzz/src/check_input_size.rs @@ -6,6 +6,6 @@ fn main() { const BYTES_GIVEN: usize = 10000; let data = [0xFF; BYTES_GIVEN]; let mut u = arbitrary::Unstructured::new(&data); - let _: VmAndWorld = u.arbitrary().unwrap(); + let _: VmAndWorld<()> = u.arbitrary().unwrap(); println!("{:?}", BYTES_GIVEN - u.len()); } diff --git a/afl-fuzz/src/lib.rs b/afl-fuzz/src/lib.rs index debeaa03..b363193a 100644 --- a/afl-fuzz/src/lib.rs +++ b/afl-fuzz/src/lib.rs @@ -2,7 +2,7 @@ use arbitrary::Arbitrary; use vm2::{single_instruction_test::MockWorld, VirtualMachine}; #[derive(Arbitrary, Debug)] -pub struct VmAndWorld { - pub vm: VirtualMachine, +pub struct VmAndWorld { + pub vm: VirtualMachine, pub world: MockWorld, } diff --git a/afl-fuzz/src/main.rs b/afl-fuzz/src/main.rs index aef36033..a48730a8 100644 --- a/afl-fuzz/src/main.rs +++ b/afl-fuzz/src/main.rs @@ -11,7 +11,7 @@ fn main() { let mut zk_evm = vm2_to_zk_evm(&vm, world.clone()); - vm.run_single_instruction(&mut world); + vm.run_single_instruction(&mut world, &mut ()); assert!(vm.is_in_valid_state()); add_heap_to_zk_evm(&mut zk_evm, &vm); diff --git a/afl-fuzz/src/show_testcase.rs b/afl-fuzz/src/show_testcase.rs index ee76b085..04f2e521 100644 --- a/afl-fuzz/src/show_testcase.rs +++ b/afl-fuzz/src/show_testcase.rs @@ -27,7 +27,7 @@ fn main() { vm.state.current_frame.raw_first_instruction(), ); println!("{}", parsed); - vm.run_single_instruction(&mut world); + vm.run_single_instruction(&mut world, &mut ()); println!("Mocks that have been touched:"); vm.print_mock_info(); diff --git a/benches/nested_near_call.rs b/benches/nested_near_call.rs index c919b77a..5f5fbb24 100644 --- a/benches/nested_near_call.rs +++ b/benches/nested_near_call.rs @@ -40,7 +40,7 @@ fn nested_near_call(bencher: Bencher) { }, ); - vm.run(black_box(&mut world)); + vm.run(black_box(&mut world), &mut ()); }); } @@ -87,7 +87,7 @@ fn nested_near_call_with_storage_write(bencher: Bencher) { }, ); - vm.run(black_box(&mut world)); + vm.run(black_box(&mut world), &mut ()); }); } diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index 9a471fa9..36e563c4 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -1,136 +1,4 @@ -use primitive_types::{H160, U256}; - -pub trait StateInterface { - fn read_register(&self, register: u8) -> (U256, bool); - fn set_register(&mut self, register: u8, value: U256, is_pointer: bool); - - fn number_of_callframes(&self) -> usize; - /// 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; - fn write_heap_byte(&mut self, heap: HeapId, index: u32, byte: u8); - - fn flags(&self) -> Flags; - fn set_flags(&mut self, flags: Flags); - - fn transaction_number(&self) -> u16; - fn set_transaction_number(&mut self, value: u16); - - fn context_u128_register(&self) -> u128; - fn set_context_u128_register(&mut self, value: u128); - - fn get_storage_state(&self) -> impl Iterator; - /// Returns the new value and the amount of pubdata paid for it. - fn get_storage(&self, address: H160, slot: U256) -> Option<(U256, u32)>; - fn get_storage_initial_value(&self, address: H160, slot: U256) -> U256; - fn write_storage(&mut self, address: H160, slot: U256, value: U256); - - fn get_transient_storage_state(&self) -> impl Iterator; - fn get_transient_storage(&self, address: H160, slot: U256) -> U256; - fn write_transient_storage(&mut self, address: H160, slot: U256, value: U256); - - fn events(&self) -> impl Iterator; - fn l2_to_l1_logs(&self) -> impl Iterator; - - fn pubdata(&self) -> i32; - fn set_pubdata(&mut self, value: i32); - - /// it is run in kernel mode - fn run_arbitrary_code(code: &[u64]); - - fn static_heap(&self) -> HeapId; -} - -pub struct Flags { - pub less_than: bool, - pub equal: bool, - pub greater: bool, -} - -pub trait CallframeInterface { - fn address(&self) -> H160; - fn set_address(&mut self, address: H160); - fn code_address(&self) -> H160; - fn set_code_address(&mut self, address: H160); - fn caller(&self) -> H160; - fn set_caller(&mut self, address: H160); - - /// During panic and arbitrary code execution this returns None. - fn program_counter(&self) -> Option; - - /// The VM will execute an invalid instruction if you jump out of the program. - fn set_program_counter(&mut self, value: u16); - - fn exception_handler(&self) -> u16; - - fn is_static(&self) -> bool; - fn is_kernel(&self) -> bool { - todo!() - } - - fn gas(&self) -> u32; - fn set_gas(&mut self, new_gas: u32); - fn stipend(&self) -> u32; - - fn context_u128(&self) -> u128; - fn set_context_u128(&mut self, value: u128); - - fn is_near_call(&self) -> bool; - - fn read_stack(&self, index: u16) -> (U256, bool); - fn write_stack(&mut self, index: u16, value: U256, is_pointer: bool); - - fn stack_pointer(&self) -> u16; - fn set_stack_pointer(&mut self, value: u16); - - fn heap(&self) -> HeapId; - fn heap_bound(&self) -> u32; - fn set_heap_bound(&mut self, value: u32); - - fn aux_heap(&self) -> HeapId; - fn aux_heap_bound(&self) -> u32; - fn set_aux_heap_bound(&mut self, value: u32); - - fn read_code_page(&self, slot: u16) -> U256; -} - -pub trait Tracer { - fn before_instruction(&mut self, state: &mut S); - fn after_instruction(&mut self, state: &mut S); -} - -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct HeapId(u32); - -impl HeapId { - /// Only for dealing with external data structures, never use internally. - pub const fn from_u32_unchecked(value: u32) -> Self { - Self(value) - } - - pub const fn to_u32(self) -> u32 { - self.0 - } -} - -/// 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, -} +mod state_interface; +mod tracer_interface; +pub use state_interface::*; +pub use tracer_interface::*; diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs new file mode 100644 index 00000000..dd3f1caa --- /dev/null +++ b/eravm-stable-interface/src/state_interface.rs @@ -0,0 +1,131 @@ +use primitive_types::{H160, U256}; + +pub trait StateInterface { + fn read_register(&self, register: u8) -> (U256, bool); + fn set_register(&mut self, register: u8, value: U256, is_pointer: bool); + + fn number_of_callframes(&self) -> usize; + /// 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; + fn write_heap_byte(&mut self, heap: HeapId, index: u32, byte: u8); + + fn flags(&self) -> Flags; + fn set_flags(&mut self, flags: Flags); + + fn transaction_number(&self) -> u16; + fn set_transaction_number(&mut self, value: u16); + + fn context_u128_register(&self) -> u128; + fn set_context_u128_register(&mut self, value: u128); + + fn get_storage_state(&self) -> impl Iterator; + /// Returns the new value and the amount of pubdata paid for it. + fn get_storage(&self, address: H160, slot: U256) -> Option<(U256, u32)>; + fn get_storage_initial_value(&self, address: H160, slot: U256) -> U256; + fn write_storage(&mut self, address: H160, slot: U256, value: U256); + + fn get_transient_storage_state(&self) -> impl Iterator; + fn get_transient_storage(&self, address: H160, slot: U256) -> U256; + fn write_transient_storage(&mut self, address: H160, slot: U256, value: U256); + + fn events(&self) -> impl Iterator; + fn l2_to_l1_logs(&self) -> impl Iterator; + + fn pubdata(&self) -> i32; + fn set_pubdata(&mut self, value: i32); + + /// it is run in kernel mode + fn run_arbitrary_code(code: &[u64]); + + fn static_heap(&self) -> HeapId; +} + +pub struct Flags { + pub less_than: bool, + pub equal: bool, + pub greater: bool, +} + +pub trait CallframeInterface { + fn address(&self) -> H160; + fn set_address(&mut self, address: H160); + fn code_address(&self) -> H160; + fn set_code_address(&mut self, address: H160); + fn caller(&self) -> H160; + fn set_caller(&mut self, address: H160); + + /// During panic and arbitrary code execution this returns None. + fn program_counter(&self) -> Option; + + /// The VM will execute an invalid instruction if you jump out of the program. + fn set_program_counter(&mut self, value: u16); + + fn exception_handler(&self) -> u16; + + fn is_static(&self) -> bool; + fn is_kernel(&self) -> bool { + todo!() + } + + fn gas(&self) -> u32; + fn set_gas(&mut self, new_gas: u32); + fn stipend(&self) -> u32; + + fn context_u128(&self) -> u128; + fn set_context_u128(&mut self, value: u128); + + fn is_near_call(&self) -> bool; + + fn read_stack(&self, index: u16) -> (U256, bool); + fn write_stack(&mut self, index: u16, value: U256, is_pointer: bool); + + fn stack_pointer(&self) -> u16; + fn set_stack_pointer(&mut self, value: u16); + + fn heap(&self) -> HeapId; + fn heap_bound(&self) -> u32; + fn set_heap_bound(&mut self, value: u32); + + fn aux_heap(&self) -> HeapId; + fn aux_heap_bound(&self) -> u32; + fn set_aux_heap_bound(&mut self, value: u32); + + fn read_code_page(&self, slot: u16) -> U256; +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct HeapId(u32); + +impl HeapId { + /// Only for dealing with external data structures, never use internally. + pub const fn from_u32_unchecked(value: u32) -> Self { + Self(value) + } + + pub const fn to_u32(self) -> u32 { + self.0 + } +} + +/// 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, +} diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs new file mode 100644 index 00000000..4f39a42a --- /dev/null +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -0,0 +1,38 @@ +use crate::StateInterface; + +pub mod opcodes { + pub struct Nop; + pub struct NearCall; + pub struct FarCall; + pub struct Ret; + pub struct Jump; + pub struct Event; + pub struct L2ToL1Message; + pub struct Decommit; + pub struct ContextMeta; + pub struct SetContextU128; + pub struct IncrementTxNumber; + pub struct AuxMutating0; + pub struct PrecompileCall; + pub struct HeapRead; + pub struct HeapWrite; + pub struct PointerRead; + pub struct StorageRead; + pub struct StorageWrite; + pub struct TransientStorageRead; + pub struct TransientStorageWrite; + pub struct StaticMemoryRead; + pub struct StaticMemoryWrite; +} + +pub trait Tracer { + #[inline(always)] + fn before_instruction(&mut self, _state: &mut S) {} + + #[inline(always)] + fn after_instruction(&mut self, _state: &mut S) {} +} + +// The &mut is a workaround for the lack of specialization in stable Rust. +// Trait resolution will choose an implementation on U over the implementation on `&mut U`. +impl Tracer for &mut U {} diff --git a/src/callframe.rs b/src/callframe.rs index fac278c2..6532eb38 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -1,6 +1,6 @@ use crate::{ decommit::is_kernel, - instruction_handlers::INVALID_INSTRUCTION, + instruction_handlers::invalid_instruction, program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, @@ -10,8 +10,8 @@ use eravm_stable_interface::HeapId; use u256::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; -#[derive(Clone, PartialEq, Debug)] -pub struct Callframe { +#[derive(PartialEq, Debug)] +pub struct Callframe { pub address: H160, pub code_address: H160, pub caller: H160, @@ -29,8 +29,8 @@ pub struct Callframe { pub(crate) near_calls: Vec, - pub pc: *const Instruction, - pub(crate) program: Program, + pub pc: *const Instruction, + pub(crate) program: Program, pub heap: HeapId, pub aux_heap: HeapId, @@ -59,17 +59,17 @@ pub(crate) struct NearCallFrame { pub(crate) exception_handler: u16, pub(crate) previous_frame_sp: u16, pub(crate) previous_frame_gas: u32, - pub(crate) previous_frame_pc: *const Instruction, + pub(crate) previous_frame_pc: u16, world_before_this_frame: Snapshot, } -impl Callframe { +impl Callframe { #[allow(clippy::too_many_arguments)] pub(crate) fn new( address: H160, code_address: H160, caller: H160, - program: Program, + program: Program, stack: Box, heap: HeapId, aux_heap: HeapId, @@ -123,7 +123,7 @@ impl Callframe { exception_handler, previous_frame_sp: self.sp, previous_frame_gas: self.gas - gas_to_call, - previous_frame_pc: self.pc, + previous_frame_pc: self.get_pc_as_u16(), world_before_this_frame, }); self.gas = gas_to_call; @@ -133,7 +133,7 @@ impl Callframe { self.near_calls.pop().map(|f| { self.sp = f.previous_frame_sp; self.gas = f.previous_frame_gas; - self.pc = f.previous_frame_pc; + self.set_pc_from_u16(f.previous_frame_pc); FrameRemnant { exception_handler: f.exception_handler, @@ -152,7 +152,7 @@ impl Callframe { self.pc = self .program .instruction(index) - .unwrap_or(&INVALID_INSTRUCTION) + .unwrap_or(invalid_instruction()) } /// The total amount of gas in this frame, including gas currently inaccessible because of a near call. @@ -227,3 +227,31 @@ pub(crate) struct CallframeSnapshot { aux_heap_size: u32, heaps_i_was_keeping_alive: usize, } + +impl Clone for Callframe { + fn clone(&self) -> Self { + Self { + address: self.address, + code_address: self.code_address, + caller: self.caller, + exception_handler: self.exception_handler, + context_u128: self.context_u128, + is_static: self.is_static, + is_kernel: self.is_kernel, + stack: self.stack.clone(), + sp: self.sp, + gas: self.gas, + stipend: self.stipend, + near_calls: self.near_calls.clone(), + pc: self.pc, + program: self.program.clone(), + heap: self.heap, + aux_heap: self.aux_heap, + heap_size: self.heap_size, + aux_heap_size: self.aux_heap_size, + calldata_heap: self.calldata_heap, + heaps_i_am_keeping_alive: self.heaps_i_am_keeping_alive.clone(), + world_before_this_frame: self.world_before_this_frame.clone(), + } + } +} diff --git a/src/decode.rs b/src/decode.rs index 3e3a893c..2de1c9b1 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -22,7 +22,7 @@ use zkevm_opcode_defs::{ SWAP_OPERANDS_FLAG_IDX_FOR_PTR_OPCODE, UMA_INCREMENT_FLAG_IDX, }; -pub fn decode_program(raw: &[u64], is_bootloader: bool) -> Vec { +pub fn decode_program(raw: &[u64], is_bootloader: bool) -> Vec> { raw.iter() .take(1 << 16) .map(|i| decode(*i, is_bootloader)) @@ -34,7 +34,7 @@ pub fn decode_program(raw: &[u64], is_bootloader: bool) -> Vec { .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); @@ -43,7 +43,11 @@ fn unimplemented_instruction(variant: Opcode) -> Instruction { arguments, } } -fn unimplemented_handler(vm: &mut VirtualMachine, _: &mut dyn World) -> InstructionResult { +fn unimplemented_handler( + vm: &mut VirtualMachine, + _: &mut dyn World, + _: &mut T, +) -> InstructionResult { let variant: Opcode = unsafe { std::mem::transmute( Immediate1::get(&(*vm.state.current_frame.pc).arguments, &mut vm.state).low_u32() @@ -54,7 +58,7 @@ fn unimplemented_handler(vm: &mut VirtualMachine, _: &mut dyn World) -> Instruct Some(ExecutionEnd::Panicked) } -pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { +pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { let (parsed, _) = EncodingModeProduction::parse_preliminary_variant_and_absolute_number(raw); let predicate = match parsed.condition { diff --git a/src/decommit.rs b/src/decommit.rs index d1a36191..5e9f16aa 100644 --- a/src/decommit.rs +++ b/src/decommit.rs @@ -5,9 +5,9 @@ use zkevm_opcode_defs::{ }; impl WorldDiff { - pub(crate) fn decommit( + pub(crate) fn decommit( &mut self, - world: &mut dyn World, + world: &mut dyn World, address: U256, default_aa_code_hash: [u8; 32], evm_interpreter_code_hash: [u8; 32], @@ -80,17 +80,21 @@ impl WorldDiff { /// Returns the decommitted contract code and a flag set to `true` if this is a fresh decommit (i.e., /// the code wasn't decommitted previously in the same VM run). #[doc(hidden)] // should be used for testing purposes only; can break VM operation otherwise - pub fn decommit_opcode(&mut self, world: &mut dyn World, code_hash: U256) -> (Vec, bool) { + pub fn decommit_opcode( + &mut self, + world: &mut dyn World, + code_hash: U256, + ) -> (Vec, bool) { let was_decommitted = self.decommitted_hashes.insert(code_hash, ()).is_some(); (world.decommit_code(code_hash), !was_decommitted) } - pub(crate) fn pay_for_decommit( + pub(crate) fn pay_for_decommit( &mut self, - world: &mut dyn World, + world: &mut dyn World, decommit: UnpaidDecommit, gas: &mut u32, - ) -> Option { + ) -> Option> { if decommit.cost > *gas { // Unlike all other gas costs, this one is not paid if low on gas. return None; @@ -109,7 +113,7 @@ pub(crate) struct UnpaidDecommit { /// 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. -pub fn initial_decommit(world: &mut impl World, address: H160) -> Program { +pub fn initial_decommit(world: &mut impl World, 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/src/instruction.rs b/src/instruction.rs index 840f1d55..501d0ff0 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -5,12 +5,12 @@ use crate::{ Predicate, World, }; -pub struct Instruction { - pub(crate) handler: Handler, +pub struct Instruction { + pub(crate) handler: Handler, pub(crate) arguments: Arguments, } -impl fmt::Debug for Instruction { +impl fmt::Debug for Instruction { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("Instruction") @@ -19,7 +19,8 @@ impl fmt::Debug for Instruction { } } -pub(crate) type Handler = fn(&mut VirtualMachine, &mut dyn World) -> InstructionResult; +pub(crate) type Handler = + fn(&mut VirtualMachine, &mut dyn World, &mut T) -> InstructionResult; pub(crate) type InstructionResult = Option; #[derive(Debug, PartialEq)] @@ -32,13 +33,17 @@ pub enum ExecutionEnd { SuspendedOnHook(u32), } -pub fn jump_to_beginning() -> Instruction { +pub 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 dyn World) -> InstructionResult { +fn jump_to_beginning_handler( + vm: &mut VirtualMachine, + _: &mut dyn World, + _: &mut T, +) -> InstructionResult { let first_instruction = vm.state.current_frame.program.instruction(0).unwrap(); vm.state.current_frame.pc = first_instruction; None diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index ffb51ef3..f2237ddb 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -11,11 +11,12 @@ use crate::{ }; use u256::U256; -fn binop( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn binop( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let a = In1::get(args, &mut vm.state); let b = Register2::get(args, &mut vm.state); let (a, b) = if SWAP { (b, a) } else { (a, b) }; @@ -201,7 +202,7 @@ impl Binop for Div { use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_binop( src1: AnySource, @@ -213,7 +214,7 @@ impl Instruction { set_flags: bool, ) -> Self { Self { - handler: monomorphize!(binop [Op] match_source src1 match_destination out match_boolean swap match_boolean set_flags), + handler: monomorphize!(binop [T Op] match_source src1 match_destination out match_boolean swap match_boolean set_flags), arguments: arguments .write_source(&src1) .write_source(&src2) diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 7f01ae1c..a2a337b2 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,29 +1,45 @@ use crate::{addressing_modes::Arguments, instruction::InstructionResult, VirtualMachine, World}; +use eravm_stable_interface::Tracer; #[inline(always)] -pub(crate) fn instruction_boilerplate( - vm: &mut VirtualMachine, - world: &mut dyn World, - business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), +pub(crate) fn instruction_boilerplate( + vm: &mut VirtualMachine, + world: &mut dyn World, + mut tracer: &mut T, + business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), ) -> InstructionResult { - unsafe { + Tracer::::before_instruction(&mut tracer, vm); + let result = unsafe { let instruction = vm.state.current_frame.pc; vm.state.current_frame.pc = instruction.add(1); business_logic(vm, &(*instruction).arguments, world); None - } + }; + Tracer::::after_instruction(&mut tracer, vm); + + result } #[inline(always)] -pub(crate) fn instruction_boilerplate_ext( - vm: &mut VirtualMachine, - world: &mut dyn World, - business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World) -> InstructionResult, +pub(crate) fn instruction_boilerplate_ext( + vm: &mut VirtualMachine, + world: &mut dyn World, + mut tracer: &mut T, + business_logic: impl FnOnce( + &mut VirtualMachine, + &Arguments, + &mut dyn World, + ) -> InstructionResult, ) -> InstructionResult { - unsafe { + Tracer::::before_instruction(&mut tracer, vm); + + let result = unsafe { let instruction = vm.state.current_frame.pc; vm.state.current_frame.pc = instruction.add(1); business_logic(vm, &(*instruction).arguments, world) - } + }; + Tracer::::after_instruction(&mut tracer, vm); + + result } diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index d6d90acd..3796fc94 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -6,64 +6,73 @@ use crate::{ state::State, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; use u256::U256; use zkevm_opcode_defs::VmMetaParameters; -fn context(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn context( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let result = Op::get(&vm.state); Register1::set(args, &mut vm.state, result) }) } trait ContextOp { - fn get(state: &State) -> U256; + fn get(state: &State) -> U256; } struct This; impl ContextOp for This { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.address) } } struct Caller; impl ContextOp for Caller { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.caller) } } struct CodeAddress; impl ContextOp for CodeAddress { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.code_address) } } struct ErgsLeft; impl ContextOp for ErgsLeft { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { U256([state.current_frame.gas as u64, 0, 0, 0]) } } struct U128; impl ContextOp for U128 { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { state.get_context_u128().into() } } struct SP; impl ContextOp for SP { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { state.current_frame.sp.into() } } -fn context_meta(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn context_meta( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let result = VmMetaParameters { heap_size: vm.state.current_frame.heap_size, aux_heap_size: vm.state.current_frame.aux_heap_size, @@ -83,29 +92,41 @@ fn context_meta(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionRe }) } -fn set_context_u128(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn set_context_u128( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let value = Register1::get(args, &mut vm.state).low_u128(); vm.state.set_context_u128(value); }) } -fn increment_tx_number(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, _, _| { +fn increment_tx_number( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, _, _| { vm.start_new_tx(); }) } -fn aux_mutating(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |_, _, _| { +fn aux_mutating( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |_, _, _| { // This instruction just crashes or nops }) } -impl Instruction { +impl Instruction { fn from_context(out: Register1, arguments: Arguments) -> Self { Self { - handler: context::, + handler: context::, arguments: arguments.write_destination(&out), } } diff --git a/src/instruction_handlers/decommit.rs b/src/instruction_handlers/decommit.rs index 2fec27f2..92b88af1 100644 --- a/src/instruction_handlers/decommit.rs +++ b/src/instruction_handlers/decommit.rs @@ -1,17 +1,20 @@ -use u256::U256; -use zkevm_opcode_defs::{BlobSha256Format, ContractCodeSha256Format, VersionedHashLen32}; - +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, fat_pointer::FatPointer, instruction::InstructionResult, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; +use u256::U256; +use zkevm_opcode_defs::{BlobSha256Format, ContractCodeSha256Format, VersionedHashLen32}; -use super::common::instruction_boilerplate; - -fn decommit(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, world| { +fn decommit( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let code_hash = Register1::get(args, &mut vm.state); let extra_cost = Register2::get(args, &mut vm.state).low_u32(); @@ -48,7 +51,7 @@ fn decommit(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult }) } -impl Instruction { +impl Instruction { pub fn from_decommit( abi: Register1, burn: Register2, diff --git a/src/instruction_handlers/event.rs b/src/instruction_handlers/event.rs index 78c0db3c..9d2ebe20 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -5,11 +5,16 @@ use crate::{ world_diff::{Event, L2ToL1Log}, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; use u256::H160; use zkevm_opcode_defs::ADDRESS_EVENT_WRITER; -fn event(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn event( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { if vm.state.current_frame.address == H160::from_low_u64_be(ADDRESS_EVENT_WRITER as u64) { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); @@ -26,8 +31,12 @@ fn event(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { }) } -fn l2_to_l1(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn l2_to_l1( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); let is_service = Immediate1::get(args, &mut vm.state).low_u32() == 1; @@ -42,7 +51,7 @@ fn l2_to_l1(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult }) } -impl Instruction { +impl Instruction { pub fn from_event( key: Register1, value: Register2, diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 8b733ab9..91f13eec 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -12,6 +12,7 @@ use crate::{ predication::Flags, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; use u256::U256; use zkevm_opcode_defs::{ system_params::{EVM_SIMULATOR_STIPEND, MSG_VALUE_SIMULATOR_ADDITIVE_COST}, @@ -35,11 +36,12 @@ pub enum CallingMode { /// /// 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( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn far_call( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, world| { + instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); let address_mask: U256 = U256::MAX >> (256 - 160); @@ -175,10 +177,10 @@ 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, + vm: &mut VirtualMachine, already_failed: bool, ) -> Option { let mut pointer = FatPointer::from(raw_abi); @@ -198,11 +200,11 @@ pub(crate) fn get_far_call_calldata( } match target { ToHeap => { - grow_heap::(&mut vm.state, bound).ok()?; + grow_heap::<_, Heap>(&mut vm.state, bound).ok()?; pointer.memory_page = vm.state.current_frame.heap; } ToAuxHeap => { - grow_heap::(&mut vm.state, bound).ok()?; + grow_heap::<_, AuxHeap>(&mut vm.state, bound).ok()?; pointer.memory_page = vm.state.current_frame.aux_heap; } } @@ -212,10 +214,10 @@ pub(crate) fn get_far_call_calldata( let bound = u32::MAX; match target { ToHeap => { - grow_heap::(&mut vm.state, bound).ok()?; + grow_heap::<_, Heap>(&mut vm.state, bound).ok()?; } ToAuxHeap => { - grow_heap::(&mut vm.state, bound).ok()?; + grow_heap::<_, AuxHeap>(&mut vm.state, bound).ok()?; } } return None; @@ -257,7 +259,7 @@ impl FatPointer { use super::monomorphization::*; -impl Instruction { +impl Instruction { pub fn from_far_call( src1: Register1, src2: Register2, @@ -267,7 +269,7 @@ impl Instruction { arguments: Arguments, ) -> Self { Self { - handler: monomorphize!(far_call [MODE] match_boolean is_static match_boolean is_shard), + handler: monomorphize!(far_call [T MODE] match_boolean is_static match_boolean is_shard), arguments: arguments .write_source(&src1) .write_source(&src2) diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index b442df0b..59eaea47 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -1,7 +1,4 @@ -use super::{ - common::{instruction_boilerplate, instruction_boilerplate_ext}, - PANIC, -}; +use super::common::{instruction_boilerplate, instruction_boilerplate_ext}; use crate::{ addressing_modes::{ Arguments, Destination, DestinationWriter, Immediate1, Register1, Register2, @@ -12,6 +9,7 @@ use crate::{ state::State, ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; use std::ops::Range; use u256::U256; @@ -22,26 +20,26 @@ pub trait HeapInterface { } pub trait HeapFromState { - fn get_heap(state: &State) -> HeapId; - fn get_heap_size(state: &mut State) -> &mut u32; + fn get_heap(state: &State) -> HeapId; + fn get_heap_size(state: &mut State) -> &mut u32; } pub struct Heap; impl HeapFromState for Heap { - fn get_heap(state: &State) -> HeapId { + fn get_heap(state: &State) -> HeapId { state.current_frame.heap } - fn get_heap_size(state: &mut State) -> &mut u32 { + fn get_heap_size(state: &mut State) -> &mut u32 { &mut state.current_frame.heap_size } } pub struct AuxHeap; impl HeapFromState for AuxHeap { - fn get_heap(state: &State) -> HeapId { + fn get_heap(state: &State) -> HeapId { state.current_frame.aux_heap } - fn get_heap_size(state: &mut State) -> &mut u32 { + fn get_heap_size(state: &mut State) -> &mut u32 { &mut state.current_frame.aux_heap_size } } @@ -49,11 +47,12 @@ impl HeapFromState for AuxHeap { /// The last address to which 32 can be added without overflow. const LAST_ADDRESS: u32 = u32::MAX - 32; -fn load( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn load( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { + instruction_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. let (pointer, _) = In::get_with_pointer_flag(args, &mut vm.state); @@ -61,8 +60,8 @@ fn load( let address = pointer.low_u32(); let new_bound = address.wrapping_add(32); - if grow_heap::(&mut vm.state, new_bound).is_err() { - vm.state.current_frame.pc = &PANIC; + if grow_heap::<_, H>(&mut vm.state, new_bound).is_err() { + vm.state.current_frame.pc = &*vm.panic; return; }; @@ -70,7 +69,7 @@ fn load( // TODO PLA-974 revert to not growing the heap on failure as soon as zk_evm is fixed if pointer > LAST_ADDRESS.into() { let _ = vm.state.use_gas(u32::MAX); - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return; } @@ -84,11 +83,12 @@ fn load( }) } -fn store( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn store( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate_ext(vm, world, |vm, args, _| { + instruction_boilerplate_ext::(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. let (pointer, _) = In::get_with_pointer_flag(args, &mut vm.state); @@ -97,8 +97,8 @@ fn store(&mut vm.state, new_bound).is_err() { - vm.state.current_frame.pc = &PANIC; + if grow_heap::<_, H>(&mut vm.state, new_bound).is_err() { + vm.state.current_frame.pc = &*vm.panic; return None; } @@ -106,7 +106,7 @@ fn store LAST_ADDRESS.into() { let _ = vm.state.use_gas(u32::MAX); - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return None; } @@ -127,7 +127,7 @@ fn store(state: &mut State, new_bound: u32) -> Result<(), ()> { +pub 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; @@ -138,14 +138,15 @@ pub fn grow_heap(state: &mut State, new_bound: u32) -> Result< Ok(()) } -fn load_pointer( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn load_pointer( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let (input, input_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); if !input_is_pointer { - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return; } let pointer = FatPointer::from(input); @@ -154,7 +155,7 @@ fn load_pointer( // but if offset + 32 is not representable, we panic, even if we could've read some bytes. // This is not a bug, this is how it must work to be backwards compatible. if pointer.offset > LAST_ADDRESS { - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return; }; @@ -173,7 +174,7 @@ fn load_pointer( use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_load( src: RegisterOrImmediate, @@ -189,7 +190,7 @@ impl Instruction { } Self { - handler: monomorphize!(load [H] match_reg_imm src match_boolean increment), + handler: monomorphize!(load [T H] match_reg_imm src match_boolean increment), arguments, } } @@ -204,7 +205,7 @@ impl Instruction { ) -> Self { let increment = incremented_out.is_some(); Self { - handler: monomorphize!(store [H] match_reg_imm src1 match_boolean increment match_boolean should_hook), + handler: monomorphize!(store [T H] match_reg_imm src1 match_boolean increment match_boolean should_hook), arguments: arguments .write_source(&src1) .write_source(&src2) @@ -221,7 +222,7 @@ impl Instruction { ) -> Self { let increment = incremented_out.is_some(); Self { - handler: monomorphize!(load_pointer match_boolean increment), + handler: monomorphize!(load_pointer [T] match_boolean increment), arguments: arguments .write_source(&src) .write_destination(&out) diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index 3e2ea259..a91b1bc4 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -7,9 +7,14 @@ use crate::{ instruction::{Instruction, InstructionResult}, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; -fn jump(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn jump( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let target = In::get(args, &mut vm.state).low_u32() as u16; let next_instruction = vm.state.current_frame.get_pc_as_u16(); @@ -21,10 +26,10 @@ fn jump(vm: &mut VirtualMachine, world: &mut dyn World) -> Instructi use super::monomorphization::*; -impl Instruction { +impl Instruction { pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { - handler: monomorphize!(jump match_source source), + handler: monomorphize!(jump [T] match_source source), arguments: arguments .write_source(&source) .write_destination(&destination), diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index ba78a31f..7862c8d4 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -2,7 +2,7 @@ pub use binop::{Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, Shif pub use far_call::CallingMode; pub use heap_access::{AuxHeap, Heap, HeapInterface}; pub use pointer::{PtrAdd, PtrPack, PtrShrink, PtrSub}; -pub(crate) use ret::{free_panic, INVALID_INSTRUCTION, PANIC}; +pub(crate) use ret::{free_panic, invalid_instruction, RETURN_COST}; mod binop; mod common; diff --git a/src/instruction_handlers/near_call.rs b/src/instruction_handlers/near_call.rs index ec8e0761..a184c6da 100644 --- a/src/instruction_handlers/near_call.rs +++ b/src/instruction_handlers/near_call.rs @@ -5,9 +5,14 @@ use crate::{ predication::Flags, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; -fn near_call(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn near_call( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_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); @@ -31,7 +36,7 @@ fn near_call(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResul }) } -impl Instruction { +impl Instruction { pub fn from_near_call( gas: Register1, destination: Immediate1, diff --git a/src/instruction_handlers/nop.rs b/src/instruction_handlers/nop.rs index c780b5c8..09c93519 100644 --- a/src/instruction_handlers/nop.rs +++ b/src/instruction_handlers/nop.rs @@ -4,9 +4,14 @@ use crate::{ instruction::InstructionResult, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; -fn nop(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn nop( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { // nop's addressing modes can move the stack pointer! AdvanceStackPointer::get(args, &mut vm.state); vm.state.current_frame.sp = vm @@ -17,7 +22,7 @@ fn nop(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { }) } -impl Instruction { +impl Instruction { pub fn from_nop( pop: AdvanceStackPointer, push: AdvanceStackPointer, diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index 36b77fff..4018502f 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -1,4 +1,4 @@ -use super::{common::instruction_boilerplate, PANIC}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, @@ -10,11 +10,12 @@ use crate::{ }; use u256::U256; -fn ptr( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn ptr( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { ( Register2::get_with_pointer_flag(args, &mut vm.state), @@ -28,12 +29,12 @@ fn ptr( }; if !a_is_pointer || b_is_pointer { - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return; } let Some(result) = Op::perform(a, b) else { - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return; }; @@ -90,7 +91,7 @@ impl PtrOp for PtrShrink { use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_ptr( src1: AnySource, @@ -100,7 +101,7 @@ impl Instruction { swap: bool, ) -> Self { Self { - handler: monomorphize!(ptr [Op] match_source src1 match_destination out match_boolean swap), + handler: monomorphize!(ptr [T Op] match_source src1 match_destination out match_boolean swap), arguments: arguments .write_source(&src1) .write_source(&src2) diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index 6ecc8cdb..da77d755 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -1,11 +1,11 @@ -use super::{common::instruction_boilerplate, HeapInterface, PANIC}; +use super::{common::instruction_boilerplate, HeapInterface}; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, instruction::InstructionResult, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::HeapId; +use eravm_stable_interface::{opcodes, HeapId}; use zk_evm_abstractions::{ aux::Timestamp, precompiles::{ @@ -23,13 +23,17 @@ use zkevm_opcode_defs::{ PrecompileAuxData, PrecompileCallABI, }; -fn precompile_call(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn precompile_call( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { // The user gets to decide how much gas to burn // This is safe because system contracts are trusted let aux_data = PrecompileAuxData::from_u256(Register2::get(args, &mut vm.state)); let Ok(()) = vm.state.use_gas(aux_data.extra_ergs_cost) else { - vm.state.current_frame.pc = &PANIC; + vm.state.current_frame.pc = &*vm.panic; return; }; vm.world_diff.pubdata.0 += aux_data.extra_pubdata_cost as i32; @@ -117,7 +121,7 @@ impl Memory for Heaps { } } -impl Instruction { +impl Instruction { pub fn from_precompile_call( abi: Register1, burn: Register2, diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index e1485aa1..8f1e69b8 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -7,6 +7,7 @@ use crate::{ predication::Flags, Instruction, Predicate, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; use u256::U256; #[repr(u8)] @@ -32,11 +33,12 @@ impl ReturnType { } } -fn ret( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn ret( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, ) -> InstructionResult { - instruction_boilerplate_ext(vm, world, |vm, args, _| { + instruction_boilerplate_ext::(vm, world, tracer, |vm, args, _| { let mut return_type = ReturnType::from_u8(RETURN_TYPE); let near_call_leftover_gas = vm.state.current_frame.gas; @@ -135,7 +137,7 @@ fn ret( /// 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. /// TODO: when tracers are implemented, this function should count as a separate instruction! -pub(crate) fn panic_from_failed_far_call(vm: &mut VirtualMachine, exception_handler: u16) { +pub(crate) fn panic_from_failed_far_call(vm: &mut VirtualMachine, exception_handler: u16) { // Gas is already subtracted in the far call code. // No need to roll back, as no changes are made in this "frame". @@ -150,20 +152,14 @@ pub(crate) fn panic_from_failed_far_call(vm: &mut VirtualMachine, exception_hand } /// Panics, burning all available gas. -pub const INVALID_INSTRUCTION: Instruction = Instruction { - handler: ret::<{ ReturnType::Panic as u8 }, false>, - arguments: Arguments::new( - Predicate::Always, - INVALID_INSTRUCTION_COST, - ModeRequirements::none(), - ), -}; +static INVALID_INSTRUCTION: Instruction<()> = Instruction::from_invalid(); + +pub fn invalid_instruction<'a, T>() -> &'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; -pub static PANIC: Instruction = Instruction { - handler: ret::<{ ReturnType::Panic as u8 }, false>, - arguments: Arguments::new(Predicate::Always, RETURN_COST, ModeRequirements::none()), -}; /// Turn the current instruction into a panic at no extra cost. (Great value, I know.) /// @@ -174,18 +170,22 @@ pub static PANIC: Instruction = Instruction { /// - the far call stack overflows /// /// For all other panics, point the instruction pointer at [PANIC] instead. -pub(crate) fn free_panic(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - ret::<{ ReturnType::Panic as u8 }, false>(vm, world) +pub(crate) fn free_panic( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + ret::(vm, world, tracer) } use super::monomorphization::*; -impl Instruction { +impl Instruction { pub fn from_ret(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); const RETURN_TYPE: u8 = ReturnType::Normal as u8; Self { - handler: monomorphize!(ret [RETURN_TYPE] match_boolean to_label), + handler: monomorphize!(ret [T RETURN_TYPE] match_boolean to_label), arguments: arguments.write_source(&src1).write_source(&label), } } @@ -193,7 +193,7 @@ impl Instruction { let to_label = label.is_some(); const RETURN_TYPE: u8 = ReturnType::Revert as u8; Self { - handler: monomorphize!(ret [RETURN_TYPE] match_boolean to_label), + handler: monomorphize!(ret [T RETURN_TYPE] match_boolean to_label), arguments: arguments.write_source(&src1).write_source(&label), } } @@ -201,12 +201,20 @@ impl Instruction { let to_label = label.is_some(); const RETURN_TYPE: u8 = ReturnType::Panic as u8; Self { - handler: monomorphize!(ret [RETURN_TYPE] match_boolean to_label), + handler: monomorphize!(ret [T RETURN_TYPE] match_boolean to_label), arguments: arguments.write_source(&label), } } - pub fn from_invalid() -> Self { - INVALID_INSTRUCTION + pub const fn from_invalid() -> Self { + Self { + // This field is never read because the instruction fails at the gas cost stage. + handler: ret::, + arguments: Arguments::new( + Predicate::Always, + INVALID_INSTRUCTION_COST, + ModeRequirements::none(), + ), + } } } diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index 6b36e0b6..723e33fe 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -6,9 +6,14 @@ use crate::{ instruction::InstructionResult, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes; -fn sstore(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, world| { +fn sstore( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); @@ -21,18 +26,31 @@ fn sstore(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { }) } -fn sstore_transient(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { - let key = Register1::get(args, &mut vm.state); - let value = Register2::get(args, &mut vm.state); +fn sstore_transient( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::( + vm, + world, + tracer, + |vm, args, _| { + let key = Register1::get(args, &mut vm.state); + let value = Register2::get(args, &mut vm.state); - vm.world_diff - .write_transient_storage(vm.state.current_frame.address, key, value); - }) + vm.world_diff + .write_transient_storage(vm.state.current_frame.address, key, value); + }, + ) } -fn sload(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, world| { +fn sload( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let (value, refund) = vm.world_diff @@ -45,8 +63,12 @@ fn sload(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { }) } -fn sload_transient(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { - instruction_boilerplate(vm, world, |vm, args, _| { +fn sload_transient( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> InstructionResult { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let key = Register1::get(args, &mut vm.state); let value = vm .world_diff @@ -56,7 +78,7 @@ fn sload_transient(vm: &mut VirtualMachine, world: &mut dyn World) -> Instructio }) } -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_sstore(src1: Register1, src2: Register2, arguments: Arguments) -> Self { Self { @@ -64,9 +86,7 @@ impl Instruction { arguments: arguments.write_source(&src1).write_source(&src2), } } -} -impl Instruction { #[inline(always)] pub fn from_sstore_transient(src1: Register1, src2: Register2, arguments: Arguments) -> Self { Self { @@ -74,9 +94,7 @@ impl Instruction { arguments: arguments.write_source(&src1).write_source(&src2), } } -} -impl Instruction { #[inline(always)] pub fn from_sload(src: Register1, dst: Register1, arguments: Arguments) -> Self { Self { @@ -84,9 +102,7 @@ impl Instruction { arguments: arguments.write_source(&src).write_destination(&dst), } } -} -impl Instruction { #[inline(always)] pub fn from_sload_transient(src: Register1, dst: Register1, arguments: Arguments) -> Self { Self { diff --git a/src/lib.rs b/src/lib.rs index f24eebc1..3b86b764 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,6 @@ mod rollback; mod stack; mod state; pub mod testworld; -#[cfg(not(feature = "single_instruction_test"))] mod tracing; mod vm; mod world_diff; @@ -49,10 +48,10 @@ use single_instruction_test::stack; #[cfg(feature = "single_instruction_test")] pub use zkevm_opcode_defs; -pub trait World { +pub trait World { /// 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(&mut self, hash: U256) -> Program; fn decommit_code(&mut self, hash: U256) -> Vec; diff --git a/src/program.rs b/src/program.rs index db841772..5d217bbe 100644 --- a/src/program.rs +++ b/src/program.rs @@ -7,13 +7,21 @@ use u256::U256; // enable changing the internals later. /// Cloning this is cheap. It is a handle to memory similar to [`Arc`]. -#[derive(Clone)] -pub struct Program { +pub struct Program { code_page: Arc<[U256]>, - instructions: Arc<[Instruction]>, + instructions: Arc<[Instruction]>, } -impl fmt::Debug for Program { +impl Clone for Program { + fn clone(&self) -> Self { + Self { + code_page: self.code_page.clone(), + instructions: self.instructions.clone(), + } + } +} + +impl fmt::Debug for Program { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { const DEBUGGED_ITEMS: usize = 16; @@ -36,15 +44,15 @@ impl fmt::Debug for Program { } } -impl Program { - pub fn new(instructions: Vec, code_page: Vec) -> Self { +impl Program { + pub fn new(instructions: Vec>, code_page: Vec) -> Self { Self { code_page: code_page.into(), instructions: instructions.into(), } } - pub fn instruction(&self, n: u16) -> Option<&Instruction> { + pub fn instruction(&self, n: u16) -> Option<&Instruction> { self.instructions.get::(n.into()) } @@ -58,7 +66,7 @@ impl Program { // 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. -impl PartialEq for Program { +impl PartialEq for Program { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.code_page, &other.code_page) && Arc::ptr_eq(&self.instructions, &other.instructions) diff --git a/src/single_instruction_test/callframe.rs b/src/single_instruction_test/callframe.rs index 02017860..101cca3e 100644 --- a/src/single_instruction_test/callframe.rs +++ b/src/single_instruction_test/callframe.rs @@ -15,7 +15,7 @@ impl<'a> Arbitrary<'a> for Flags { } } -impl<'a> Arbitrary<'a> for Callframe { +impl<'a, T> Arbitrary<'a> for Callframe { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let address: H160 = u.arbitrary()?; @@ -27,7 +27,7 @@ impl<'a> Arbitrary<'a> for Callframe { // but the calldata heap must be different from the heap and aux heap let calldata_heap = HeapId::from_u32_unchecked(u.int_in_range(0..=base_page - 1)?); - let program: Program = u.arbitrary()?; + let program: Program = u.arbitrary()?; let mut me = Self { address, @@ -64,7 +64,7 @@ impl<'a> Arbitrary<'a> for Callframe { } } -impl Callframe { +impl Callframe { pub fn raw_first_instruction(&self) -> u64 { self.program.raw_first_instruction } @@ -83,7 +83,7 @@ impl Callframe { gas: 0, stipend: 0, near_calls: vec![], - pc: &crate::instruction_handlers::INVALID_INSTRUCTION, + pc: std::ptr::null(), program: Program::for_decommit(), heap: FIRST_AUX_HEAP, aux_heap: FIRST_AUX_HEAP, diff --git a/src/single_instruction_test/heap.rs b/src/single_instruction_test/heap.rs index 400914e4..ec036a24 100644 --- a/src/single_instruction_test/heap.rs +++ b/src/single_instruction_test/heap.rs @@ -16,6 +16,10 @@ impl Heap { assert!(self.write.is_none()); self.write = Some((start_address, value)); } + + pub(crate) fn read_byte(&self, _: u32) -> u8 { + unimplemented!() + } } impl HeapInterface for Heap { @@ -102,6 +106,10 @@ impl Heaps { pub(crate) fn delete_history(&mut self) { unimplemented!() } + + pub(crate) fn write_byte(&mut self, _: HeapId, _: u32, _: u8) { + unimplemented!() + } } impl Index for Heaps { diff --git a/src/single_instruction_test/into_zk_evm.rs b/src/single_instruction_test/into_zk_evm.rs index e44cbbcf..0515ef9f 100644 --- a/src/single_instruction_test/into_zk_evm.rs +++ b/src/single_instruction_test/into_zk_evm.rs @@ -26,12 +26,12 @@ 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)); VmState { - local_state: vm2_state_to_zk_evm_state(&vm.state), + local_state: vm2_state_to_zk_evm_state(&vm.state, &*vm.panic), block_properties: BlockProperties { default_aa_code_hash: U256::from_big_endian(&vm.settings.default_aa_code_hash), evm_simulator_code_hash: U256::from_big_endian(&vm.settings.evm_interpreter_code_hash), @@ -51,7 +51,7 @@ pub fn vm2_to_zk_evm(vm: &VirtualMachine, world: MockWorld) -> ZkEvmState { } } -pub fn add_heap_to_zk_evm(zk_evm: &mut ZkEvmState, vm_after_execution: &VirtualMachine) { +pub fn add_heap_to_zk_evm(zk_evm: &mut ZkEvmState, vm_after_execution: &VirtualMachine) { if let Some((heapid, heap)) = vm_after_execution.state.heaps.read.read_that_happened() { if let Some((start_index, mut value)) = heap.read.read_that_happened() { value.reverse(); @@ -201,8 +201,7 @@ impl Storage for MockWorldWrapper { query.read_value = if query.aux_byte == TRANSIENT_STORAGE_AUX_BYTE { U256::zero() } else { - self.0 - .read_storage(query.address, query.key) + >::read_storage(&mut self.0, query.address, query.key) .unwrap_or_default() }; (query, PubdataCost(0)) diff --git a/src/single_instruction_test/print_mock_info.rs b/src/single_instruction_test/print_mock_info.rs index 486f1014..e357dcd8 100644 --- a/src/single_instruction_test/print_mock_info.rs +++ b/src/single_instruction_test/print_mock_info.rs @@ -1,6 +1,6 @@ use crate::{callframe::Callframe, State, VirtualMachine}; -impl VirtualMachine { +impl VirtualMachine { pub fn print_mock_info(&self) { self.state.print_mock_info(); println!("Events: {:?}", self.world_diff.events()); @@ -12,7 +12,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); @@ -34,7 +34,7 @@ impl State { } } -impl Callframe { +impl Callframe { pub 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/src/single_instruction_test/program.rs b/src/single_instruction_test/program.rs index 9164ad48..cc6c7c53 100644 --- a/src/single_instruction_test/program.rs +++ b/src/single_instruction_test/program.rs @@ -5,18 +5,29 @@ use u256::U256; use super::mock_array::MockRead; -#[derive(Clone, Debug)] -pub struct Program { +#[derive(Debug)] +pub struct Program { pub raw_first_instruction: u64, // Need a two-instruction array so that incrementing the program counter is safe - first_instruction: MockRead>, - other_instruction: MockRead>>, + first_instruction: MockRead; 2]>>, + other_instruction: MockRead; 2]>>>, code_page: Arc<[U256]>, } -impl<'a> Arbitrary<'a> for Program { +impl Clone for Program { + fn clone(&self) -> Self { + Self { + raw_first_instruction: self.raw_first_instruction.clone(), + first_instruction: self.first_instruction.clone(), + other_instruction: self.other_instruction.clone(), + code_page: self.code_page.clone(), + } + } +} + +impl<'a, T> Arbitrary<'a> for Program { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let raw_first_instruction = u.arbitrary()?; @@ -35,8 +46,8 @@ impl<'a> Arbitrary<'a> for Program { } } -impl Program { - pub fn instruction(&self, n: u16) -> Option<&Instruction> { +impl Program { + pub fn instruction(&self, n: u16) -> Option<&Instruction> { if n == 0 { Some(&self.first_instruction.get(n).as_ref()[0]) } else { @@ -68,7 +79,7 @@ impl Program { } } -impl PartialEq for Program { +impl PartialEq for Program { fn eq(&self, _: &Self) -> bool { false } diff --git a/src/single_instruction_test/state_to_zk_evm.rs b/src/single_instruction_test/state_to_zk_evm.rs index aa7e2ead..157d6440 100644 --- a/src/single_instruction_test/state_to_zk_evm.rs +++ b/src/single_instruction_test/state_to_zk_evm.rs @@ -1,7 +1,4 @@ -use crate::{ - callframe::{Callframe, NearCallFrame}, - instruction_handlers::PANIC, -}; +use crate::callframe::{Callframe, NearCallFrame}; use std::iter; use u256::U256; use zk_evm::{ @@ -10,8 +7,9 @@ use zk_evm::{ }; use zkevm_opcode_defs::decoding::EncodingModeProduction; -pub(crate) fn vm2_state_to_zk_evm_state( - state: &crate::State, +pub(crate) fn vm2_state_to_zk_evm_state( + state: &crate::State, + panic: &crate::Instruction, ) -> VmLocalState<8, EncodingModeProduction> { // zk_evm requires an unused bottom frame let mut callframes: Vec<_> = iter::once(CallStackEntry::empty_context()) @@ -47,7 +45,7 @@ pub(crate) fn vm2_state_to_zk_evm_state( memory_page_counter: 3000, absolute_execution_step: 0, tx_number_in_block: state.transaction_number, - pending_exception: state.current_frame.pc == &PANIC, + pending_exception: state.current_frame.pc == panic, previous_super_pc: 0, // Same as current pc so the instruction is read from previous_code_word context_u128_register: state.context_u128, callstack: Callstack { @@ -59,7 +57,7 @@ pub(crate) fn vm2_state_to_zk_evm_state( } } -fn vm2_frame_to_zk_evm_frames(frame: Callframe) -> impl Iterator { +fn vm2_frame_to_zk_evm_frames(frame: Callframe) -> impl Iterator { let far_frame = CallStackEntry { this_address: frame.address, msg_sender: frame.caller, @@ -92,8 +90,7 @@ fn vm2_frame_to_zk_evm_frames(frame: Callframe) -> impl Iterator bool { } } -impl State { +impl State { pub(crate) fn is_valid(&self) -> bool { self.current_frame.is_valid() && self.previous_frames.iter().all(|frame| frame.is_valid()) @@ -23,7 +23,7 @@ impl State { } } -impl Callframe { +impl Callframe { pub(crate) fn is_valid(&self) -> bool { self.stack.is_valid() } diff --git a/src/single_instruction_test/vm.rs b/src/single_instruction_test/vm.rs index 804de514..82cd4c80 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -1,14 +1,19 @@ use super::{heap::Heaps, stack::StackPool}; use crate::{ - callframe::Callframe, fat_pointer::FatPointer, instruction::InstructionResult, - instruction_handlers::free_panic, HeapId, Settings, State, VirtualMachine, World, + addressing_modes::Arguments, callframe::Callframe, fat_pointer::FatPointer, + instruction::InstructionResult, instruction_handlers::free_panic, HeapId, Instruction, + ModeRequirements, Predicate, Settings, State, VirtualMachine, World, }; use arbitrary::Arbitrary; use std::fmt::Debug; use u256::U256; -impl VirtualMachine { - pub fn run_single_instruction(&mut self, world: &mut dyn World) -> InstructionResult { +impl VirtualMachine { + pub fn run_single_instruction( + &mut self, + world: &mut dyn World, + tracer: &mut T, + ) -> InstructionResult { unsafe { let args = &(*self.state.current_frame.pc).arguments; @@ -18,11 +23,11 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - return free_panic(self, world); + return free_panic(self, world, tracer); } if args.predicate().satisfied(&self.state.flags) { - ((*self.state.current_frame.pc).handler)(self, world) + ((*self.state.current_frame.pc).handler)(self, world, tracer) } else { self.state.current_frame.pc = self.state.current_frame.pc.add(1); None @@ -54,9 +59,9 @@ impl VirtualMachine { } } -impl<'a> Arbitrary<'a> for VirtualMachine { +impl<'a, T> Arbitrary<'a> for VirtualMachine { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let current_frame: Callframe = u.arbitrary()?; + let current_frame: Callframe = u.arbitrary()?; let mut registers = [U256::zero(); 16]; let mut register_pointer_flags = 0; @@ -89,6 +94,10 @@ impl<'a> Arbitrary<'a> for VirtualMachine { settings: u.arbitrary()?, world_diff: Default::default(), stack_pool: StackPool {}, + panic: Box::new(Instruction::from_panic( + None, + Arguments::new(Predicate::Always, 5, ModeRequirements::none()), + )), }) } } @@ -144,7 +153,7 @@ impl<'a> Arbitrary<'a> for Settings { } } -impl Debug for VirtualMachine { +impl Debug for VirtualMachine { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "print useful debugging information here!") } diff --git a/src/single_instruction_test/world.rs b/src/single_instruction_test/world.rs index 511a68cc..966282a4 100644 --- a/src/single_instruction_test/world.rs +++ b/src/single_instruction_test/world.rs @@ -8,8 +8,8 @@ pub struct MockWorld { storage_slot: MockRead<(H160, U256), Option>, } -impl World for MockWorld { - fn decommit(&mut self, _hash: U256) -> Program { +impl World for MockWorld { + fn decommit(&mut self, _hash: U256) -> Program { Program::for_decommit() } diff --git a/src/state.rs b/src/state.rs index 0fb37324..783b991d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,17 +11,17 @@ use crate::{ use u256::{H160, U256}; #[derive(Clone, PartialEq, Debug)] -pub struct State { +pub struct State { pub registers: [U256; 16], pub(crate) register_pointer_flags: u16, pub flags: Flags, - pub current_frame: Callframe, + pub current_frame: Callframe, /// Contains indices to the far call instructions currently being executed. /// They are needed to continue execution from the correct spot upon return. - pub previous_frames: Vec, + pub previous_frames: Vec>, pub heaps: Heaps, @@ -30,13 +30,13 @@ pub struct State { pub(crate) context_u128: u128, } -impl State { +impl State { pub(crate) fn new( address: H160, caller: H160, calldata: Vec, gas: u32, - program: Program, + program: Program, world_before_this_frame: Snapshot, stack: Box, ) -> Self { @@ -146,7 +146,7 @@ impl State { } } -impl Addressable for State { +impl Addressable for State { fn registers(&mut self) -> &mut [U256; 16] { &mut self.registers } diff --git a/src/testworld.rs b/src/testworld.rs index 705b2e46..435bcfbb 100644 --- a/src/testworld.rs +++ b/src/testworld.rs @@ -8,13 +8,13 @@ use zkevm_opcode_defs::{ ethereum_types::Address, system_params::DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW, }; -pub struct TestWorld { +pub struct TestWorld { pub address_to_hash: BTreeMap, - pub hash_to_contract: BTreeMap, + pub hash_to_contract: BTreeMap>, } -impl TestWorld { - pub fn new(contracts: &[(Address, Program)]) -> Self { +impl TestWorld { + pub fn new(contracts: &[(Address, Program)]) -> Self { let mut address_to_hash = BTreeMap::new(); let mut hash_to_contract = BTreeMap::new(); for (i, (address, code)) in contracts.iter().enumerate() { @@ -39,8 +39,8 @@ impl TestWorld { } } -impl World for TestWorld { - fn decommit(&mut self, hash: u256::U256) -> Program { +impl World for TestWorld { + fn decommit(&mut self, hash: u256::U256) -> Program { if let Some(program) = self.hash_to_contract.get(&hash) { program.clone() } else { diff --git a/src/tracing.rs b/src/tracing.rs index 7905b638..94b34e22 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -7,7 +7,7 @@ use crate::{ use eravm_stable_interface::*; use std::cmp::Ordering; -impl StateInterface for VirtualMachine { +impl StateInterface for VirtualMachine { fn read_register(&self, register: u8) -> (u256::U256, bool) { ( self.state.registers[register as usize], @@ -175,12 +175,12 @@ impl StateInterface for VirtualMachine { } } -struct CallframeWrapper<'a> { - frame: &'a mut Callframe, +struct CallframeWrapper<'a, T> { + frame: &'a mut Callframe, near_call: Option, } -impl CallframeInterface for CallframeWrapper<'_> { +impl CallframeInterface for CallframeWrapper<'_, T> { fn address(&self) -> u256::H160 { self.frame.address } @@ -305,20 +305,22 @@ impl CallframeInterface for CallframeWrapper<'_> { } fn program_counter(&self) -> Option { - let pointer = if let Some(call) = self.near_call_on_top() { - call.previous_frame_pc - } else { - self.frame.pc - }; - - let offset = unsafe { pointer.offset_from(self.frame.program.instruction(0).unwrap()) }; - if offset < 0 - || offset > u16::MAX as isize - || self.frame.program.instruction(offset as u16).is_none() - { - None + if let Some(call) = self.near_call_on_top() { + Some(call.previous_frame_pc) } else { - Some(offset as u16) + let offset = unsafe { + self.frame + .pc + .offset_from(self.frame.program.instruction(0).unwrap()) + }; + if offset < 0 + || offset > u16::MAX as isize + || self.frame.program.instruction(offset as u16).is_none() + { + None + } else { + Some(offset as u16) + } } } @@ -335,7 +337,7 @@ impl CallframeInterface for CallframeWrapper<'_> { } } -impl CallframeWrapper<'_> { +impl CallframeWrapper<'_, T> { fn near_call_on_top(&self) -> Option<&NearCallFrame> { if self.frame.near_calls.is_empty() || self.near_call == Some(0) { None diff --git a/src/vm.rs b/src/vm.rs index d8ab99d4..4386ceb4 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,3 +1,5 @@ +use crate::addressing_modes::Arguments; +use crate::instruction_handlers::RETURN_COST; use crate::state::StateSnapshot; use crate::world_diff::ExternalSnapshot; use crate::{ @@ -9,6 +11,7 @@ use crate::{ world_diff::{Snapshot, WorldDiff}, ExecutionEnd, Program, World, }; +use crate::{Instruction, ModeRequirements, Predicate}; use eravm_stable_interface::HeapId; use u256::H160; @@ -21,22 +24,25 @@ pub struct Settings { pub hook_address: u32, } -pub struct VirtualMachine { +pub struct VirtualMachine { pub world_diff: WorldDiff, /// Storing the state in a separate struct is not just cosmetic. /// The state couldn't be passed to the world if it was inlined. - pub state: State, + pub state: State, pub(crate) settings: Settings, pub(crate) stack_pool: StackPool, + + // Instructions that are jumped to when things go wrong. + pub(crate) panic: Box>, } -impl VirtualMachine { +impl VirtualMachine { pub fn new( address: H160, - program: Program, + program: Program, caller: H160, calldata: Vec, gas: u32, @@ -59,10 +65,14 @@ impl VirtualMachine { ), settings, stack_pool, + panic: Box::new(Instruction::from_panic( + None, + Arguments::new(Predicate::Always, RETURN_COST, ModeRequirements::none()), + )), } } - pub fn run(&mut self, world: &mut dyn World) -> ExecutionEnd { + pub fn run(&mut self, world: &mut dyn World, tracer: &mut T) -> ExecutionEnd { unsafe { loop { let args = &(*self.state.current_frame.pc).arguments; @@ -73,7 +83,7 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - if let Some(end) = free_panic(self, world) { + if let Some(end) = free_panic(self, world, tracer) { return end; }; continue; @@ -83,7 +93,8 @@ impl VirtualMachine { self.print_instruction(self.state.current_frame.pc); if args.predicate().satisfied(&self.state.flags) { - if let Some(end) = ((*self.state.current_frame.pc).handler)(self, world) { + if let Some(end) = ((*self.state.current_frame.pc).handler)(self, world, tracer) + { return end; }; } else { @@ -101,7 +112,8 @@ impl VirtualMachine { /// depending on remaining gas. pub fn resume_with_additional_gas_limit( &mut self, - world: &mut dyn World, + world: &mut dyn World, + tracer: &mut T, gas_limit: u32, ) -> Option<(u32, ExecutionEnd)> { let minimum_gas = self.state.total_unspent_gas().saturating_sub(gas_limit); @@ -116,7 +128,7 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - if let Some(e) = free_panic(self, world) { + if let Some(e) = free_panic(self, world, tracer) { break e; }; continue; @@ -126,7 +138,8 @@ impl VirtualMachine { self.print_instruction(self.state.current_frame.pc); if args.predicate().satisfied(&self.state.flags) { - if let Some(end) = ((*self.state.current_frame.pc).handler)(self, world) { + if let Some(end) = ((*self.state.current_frame.pc).handler)(self, world, tracer) + { break end; }; } else { @@ -187,7 +200,7 @@ impl VirtualMachine { pub(crate) fn push_frame( &mut self, code_address: H160, - program: Program, + program: Program, gas: u32, stipend: u32, exception_handler: u16, diff --git a/src/world_diff.rs b/src/world_diff.rs index f7b0c671..da18a1f9 100644 --- a/src/world_diff.rs +++ b/src/world_diff.rs @@ -66,9 +66,9 @@ pub struct L2ToL1Log { impl WorldDiff { /// Returns the storage slot's value and a refund based on its hot/cold status. - pub(crate) fn read_storage( + pub(crate) fn read_storage( &mut self, - world: &mut dyn World, + world: &mut dyn World, contract: H160, key: U256, ) -> (U256, u32) { @@ -80,18 +80,18 @@ impl WorldDiff { /// Same as [`Self::read_storage()`], but without recording the refund value (which is important /// because the storage is read not only from the `sload` op handler, but also from the `farcall` op handler; /// the latter must not record a refund as per previous VM versions). - pub(crate) fn read_storage_without_refund( + pub(crate) fn read_storage_without_refund( &mut self, - world: &mut dyn World, + world: &mut dyn World, contract: H160, key: U256, ) -> U256 { self.read_storage_inner(world, contract, key).0 } - fn read_storage_inner( + fn read_storage_inner( &mut self, - world: &mut dyn World, + world: &mut dyn World, contract: H160, key: U256, ) -> (U256, u32) { @@ -115,9 +115,9 @@ impl WorldDiff { } /// Returns the refund based the hot/cold status of the storage slot and the change in pubdata. - pub(crate) fn write_storage( + pub(crate) fn write_storage( &mut self, - world: &mut dyn World, + world: &mut dyn World, contract: H160, key: U256, value: U256, @@ -385,7 +385,7 @@ mod tests { let checkpoint1 = world_diff.snapshot(); for (key, value) in &first_changes { - world_diff.write_storage(&mut NoWorld, key.0, key.1, *value); + world_diff.write_storage::<()>(&mut NoWorld, key.0, key.1, *value); } assert_eq!( world_diff @@ -406,7 +406,7 @@ mod tests { let checkpoint2 = world_diff.snapshot(); for (key, value) in &second_changes { - world_diff.write_storage(&mut NoWorld, key.0, key.1, *value); + world_diff.write_storage::<()>(&mut NoWorld, key.0, key.1, *value); } assert_eq!( world_diff @@ -456,8 +456,8 @@ mod tests { } struct NoWorld; - impl World for NoWorld { - fn decommit(&mut self, _: U256) -> crate::Program { + impl World for NoWorld { + fn decommit(&mut self, _: U256) -> crate::Program { unimplemented!() } diff --git a/tests/bytecode_behaviour.rs b/tests/bytecode_behaviour.rs index a187b9dd..39da1398 100644 --- a/tests/bytecode_behaviour.rs +++ b/tests/bytecode_behaviour.rs @@ -7,7 +7,7 @@ use vm2::{ }; use zkevm_opcode_defs::ethereum_types::Address; -fn program_from_file(filename: &str) -> Program { +fn program_from_file(filename: &str) -> Program { let blob = std::fs::read(filename).unwrap(); Program::new( decode_program( @@ -45,6 +45,9 @@ fn call_to_invalid_address() { hook_address: 0, }, ); - assert!(matches!(vm.run(&mut world), ExecutionEnd::Panicked)); + assert!(matches!( + vm.run(&mut world, &mut ()), + ExecutionEnd::Panicked + )); assert_eq!(vm.state.current_frame.gas, 0); } diff --git a/tests/panic.rs b/tests/panic.rs index 5f09eeeb..c6721c83 100644 --- a/tests/panic.rs +++ b/tests/panic.rs @@ -48,7 +48,7 @@ proptest! { }, ); - assert_eq!(vm.run(&mut world), + assert_eq!(vm.run(&mut world, &mut ()), if 1 < label && label < 100 { ExecutionEnd::ProgramFinished(vec![]) } else { diff --git a/tests/stipend.rs b/tests/stipend.rs index 3c97350f..c7d2fc09 100644 --- a/tests/stipend.rs +++ b/tests/stipend.rs @@ -119,7 +119,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { }, ); - let result = vm.run(&mut world); + let result = vm.run(&mut world, &mut ()); (result, vm.state.current_frame.gas) }