From 17a9d331b5ab74a21f15b1e8f783ddf3793149ad Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 25 Jun 2024 20:24:07 +0200 Subject: [PATCH 01/61] interface draft --- Cargo.toml | 2 +- eravm-stable-interface/Cargo.toml | 7 ++ eravm-stable-interface/src/lib.rs | 132 ++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 eravm-stable-interface/Cargo.toml create mode 100644 eravm-stable-interface/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index d41089b1..3956c7b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,4 @@ trace = [] single_instruction_test = ["arbitrary", "u256/arbitrary", "zk_evm", "anyhow"] [workspace] -members = [".", "afl-fuzz"] +members = [".", "afl-fuzz", "eravm-stable-interface"] diff --git a/eravm-stable-interface/Cargo.toml b/eravm-stable-interface/Cargo.toml new file mode 100644 index 00000000..b3edff54 --- /dev/null +++ b/eravm-stable-interface/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "eravm-stable-interface" +version = "0.1.0" +edition = "2021" + +[dependencies] +u256 = { package = "primitive-types", version = "0.12.1" } \ No newline at end of file diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs new file mode 100644 index 00000000..69d38590 --- /dev/null +++ b/eravm-stable-interface/src/lib.rs @@ -0,0 +1,132 @@ +use u256::{H160, U256}; + +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) -> &mut impl CallframeInterface; + + fn read_heap_byte(&self, heap: HeapId, index: u32) -> u8; + fn write_heap_byte(&mut self, heap: HeapId, index: u32, byte: u8); + + // Do you need to be able to allocate a new heap + // Related to if we can create new callframes. + // Possibly want to pass fat pointer to a newly created heap. + + 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); + + /// Returns the new value and the amount of pubdata paid for it. + fn get_storage_state(&self) -> impl Iterator; + 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; + // Why would someone need to add an event? They aren't read by the VM. + + 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; + + // Do we want to expose the data collected for cold/warm pricing? +} + +pub struct Flags { + pub less_than: bool, + pub equal: bool, + pub greater: bool, +} + +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, register: u16) -> (U256, bool); + fn write_stack(&mut self, register: 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; +} + +trait Tracer { + fn before_instruction(&mut self, state: &mut S); + fn after_instruction(&mut self, state: &mut S); +} + +pub struct HeapId(u32); +// methods elided there is a method to make it from u32 + +/// 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, +} From 6711c486fb5c97ccd9b4a8dcda31d67c47e48d03 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 26 Jul 2024 14:43:28 +0200 Subject: [PATCH 02/61] depend on tracer interface --- Cargo.lock | 8 ++++++ Cargo.toml | 1 + eravm-stable-interface/src/lib.rs | 20 +++++++++------ src/callframe.rs | 2 +- src/fat_pointer.rs | 2 +- src/heap.rs | 33 ++++++++----------------- src/instruction_handlers/precompiles.rs | 3 ++- src/lib.rs | 3 ++- src/single_instruction_test/heap.rs | 21 +++------------- src/vm.rs | 2 +- 10 files changed, 42 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8a36638..082d4538 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,6 +336,13 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "eravm-stable-interface" +version = "0.1.0" +dependencies = [ + "primitive-types", +] + [[package]] name = "errno" version = "0.3.9" @@ -1207,6 +1214,7 @@ dependencies = [ "arbitrary", "divan", "enum_dispatch", + "eravm-stable-interface", "primitive-types", "proptest", "zk_evm", diff --git a/Cargo.toml b/Cargo.toml index 3956c7b5..1c7be0ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT OR Apache-2.0" authors = ["The Matter Labs Team "] [dependencies] +eravm-stable-interface = { path = "./eravm-stable-interface" } zkevm_opcode_defs = "0.150.0" zk_evm_abstractions = "0.150.0" u256 = { package = "primitive-types", version = "0.12.1" } diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index 69d38590..b9d190dc 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -11,10 +11,6 @@ trait StateInterface { fn read_heap_byte(&self, heap: HeapId, index: u32) -> u8; fn write_heap_byte(&mut self, heap: HeapId, index: u32, byte: u8); - // Do you need to be able to allocate a new heap - // Related to if we can create new callframes. - // Possibly want to pass fat pointer to a newly created heap. - fn flags(&self) -> Flags; fn set_flags(&mut self, flags: Flags); @@ -36,7 +32,6 @@ trait StateInterface { fn events(&self) -> impl Iterator; fn l2_to_l1_logs(&self) -> impl Iterator; - // Why would someone need to add an event? They aren't read by the VM. fn pubdata(&self) -> i32; fn set_pubdata(&mut self, value: i32); @@ -45,8 +40,6 @@ trait StateInterface { fn run_arbitrary_code(code: &[u64]); fn static_heap(&self) -> HeapId; - - // Do we want to expose the data collected for cold/warm pricing? } pub struct Flags { @@ -107,8 +100,19 @@ trait Tracer { fn after_instruction(&mut self, state: &mut S); } +#[derive(Copy, Clone, PartialEq, Debug)] pub struct HeapId(u32); -// methods elided there is a method to make it from 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. diff --git a/src/callframe.rs b/src/callframe.rs index a6dce3ae..7b5b5c3d 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -1,11 +1,11 @@ use crate::{ decommit::is_kernel, - heap::HeapId, program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, Instruction, }; +use eravm_stable_interface::HeapId; use u256::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; diff --git a/src/fat_pointer.rs b/src/fat_pointer.rs index 88c15df3..c0012aaa 100644 --- a/src/fat_pointer.rs +++ b/src/fat_pointer.rs @@ -1,4 +1,4 @@ -use crate::heap::HeapId; +use eravm_stable_interface::HeapId; use u256::U256; #[derive(Debug)] diff --git a/src/heap.rs b/src/heap.rs index 2ef4eede..0880eb37 100644 --- a/src/heap.rs +++ b/src/heap.rs @@ -1,4 +1,5 @@ use crate::{hash_for_debugging, instruction_handlers::HeapInterface}; +use eravm_stable_interface::HeapId; use std::{ fmt, mem, ops::{Index, Range}, @@ -6,20 +7,6 @@ use std::{ use u256::U256; use zkevm_opcode_defs::system_params::NEW_FRAME_MEMORY_STIPEND; -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct HeapId(u32); - -impl HeapId { - /// Only for dealing with external data structures, never use internally. - pub fn from_u32_unchecked(value: u32) -> Self { - Self(value) - } - - pub fn to_u32(self) -> u32 { - self.0 - } -} - #[derive(Clone)] pub struct Heap(Vec); @@ -97,9 +84,9 @@ pub struct Heaps { bootloader_aux_rollback_info: Vec<(u32, U256)>, } -pub(crate) const CALLDATA_HEAP: HeapId = HeapId(1); -pub const FIRST_HEAP: HeapId = HeapId(2); -pub(crate) const FIRST_AUX_HEAP: HeapId = HeapId(3); +pub(crate) const CALLDATA_HEAP: HeapId = HeapId::from_u32_unchecked(1); +pub const FIRST_HEAP: HeapId = HeapId::from_u32_unchecked(2); +pub(crate) const FIRST_AUX_HEAP: HeapId = HeapId::from_u32_unchecked(3); impl Heaps { pub(crate) fn new(calldata: Vec) -> Self { @@ -121,13 +108,13 @@ impl Heaps { } fn allocate_inner(&mut self, memory: Vec) -> HeapId { - let id = HeapId(self.heaps.len() as u32); + let id = HeapId::from_u32_unchecked(self.heaps.len() as u32); self.heaps.push(Heap(memory)); id } pub(crate) fn deallocate(&mut self, heap: HeapId) { - self.heaps[heap.0 as usize].0 = vec![]; + self.heaps[heap.to_u32() as usize].0 = vec![]; } pub fn write_u256(&mut self, heap: HeapId, start_address: u32, value: U256) { @@ -138,7 +125,7 @@ impl Heaps { self.bootloader_aux_rollback_info .push((start_address, self[heap].read_u256(start_address))); } - self.heaps[heap.0 as usize].write_u256(start_address, value); + self.heaps[heap.to_u32() as usize].write_u256(start_address, value); } pub(crate) fn snapshot(&self) -> (usize, usize) { @@ -150,10 +137,10 @@ impl Heaps { pub(crate) fn rollback(&mut self, (heap_snap, aux_snap): (usize, usize)) { for (address, value) in self.bootloader_heap_rollback_info.drain(heap_snap..).rev() { - self.heaps[FIRST_HEAP.0 as usize].write_u256(address, value); + self.heaps[FIRST_HEAP.to_u32() as usize].write_u256(address, value); } for (address, value) in self.bootloader_aux_rollback_info.drain(aux_snap..).rev() { - self.heaps[FIRST_AUX_HEAP.0 as usize].write_u256(address, value); + self.heaps[FIRST_AUX_HEAP.to_u32() as usize].write_u256(address, value); } } @@ -167,7 +154,7 @@ impl Index for Heaps { type Output = Heap; fn index(&self, index: HeapId) -> &Self::Output { - &self.heaps[index.0 as usize] + &self.heaps[index.to_u32() as usize] } } diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index d11531ad..035a8f93 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -1,10 +1,11 @@ use super::{common::instruction_boilerplate_with_panic, HeapInterface, PANIC}; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, - heap::{HeapId, Heaps}, + heap::Heaps, instruction::InstructionResult, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::HeapId; use zk_evm_abstractions::{ aux::Timestamp, precompiles::{ diff --git a/src/lib.rs b/src/lib.rs index 25b72ba5..0ec4f094 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,8 @@ use u256::{H160, U256}; pub use decommit::address_into_u256; pub use decommit::initial_decommit; -pub use heap::{HeapId, FIRST_HEAP}; +pub use eravm_stable_interface::HeapId; +pub use heap::FIRST_HEAP; pub use instruction::{jump_to_beginning, ExecutionEnd, Instruction}; pub use mode_requirements::ModeRequirements; pub use predication::Predicate; diff --git a/src/single_instruction_test/heap.rs b/src/single_instruction_test/heap.rs index e6b574b6..400914e4 100644 --- a/src/single_instruction_test/heap.rs +++ b/src/single_instruction_test/heap.rs @@ -1,6 +1,7 @@ use super::mock_array::MockRead; use crate::instruction_handlers::HeapInterface; use arbitrary::Arbitrary; +use eravm_stable_interface::HeapId; use std::ops::Index; use u256::U256; @@ -53,9 +54,9 @@ pub struct Heaps { pub(crate) read: MockRead, } -pub(crate) const CALLDATA_HEAP: HeapId = HeapId(1); -pub const FIRST_HEAP: HeapId = HeapId(2); -pub(crate) const FIRST_AUX_HEAP: HeapId = HeapId(3); +pub(crate) const CALLDATA_HEAP: HeapId = HeapId::from_u32_unchecked(1); +pub const FIRST_HEAP: HeapId = HeapId::from_u32_unchecked(2); +pub(crate) const FIRST_AUX_HEAP: HeapId = HeapId::from_u32_unchecked(3); impl Heaps { pub(crate) fn new(_: Vec) -> Self { @@ -116,17 +117,3 @@ impl PartialEq for Heaps { false } } - -#[derive(Copy, Clone, PartialEq, Debug, Arbitrary)] -pub struct HeapId(u32); - -impl HeapId { - /// Only for dealing with external data structures, never use internally. - pub fn from_u32_unchecked(value: u32) -> Self { - Self(value) - } - - pub fn to_u32(self) -> u32 { - self.0 - } -} diff --git a/src/vm.rs b/src/vm.rs index 4b2f0e1c..6382f9ef 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,3 @@ -use crate::heap::HeapId; use crate::state::StateSnapshot; use crate::world_diff::ExternalSnapshot; use crate::{ @@ -10,6 +9,7 @@ use crate::{ world_diff::{Snapshot, WorldDiff}, ExecutionEnd, Instruction, Program, World, }; +use eravm_stable_interface::HeapId; use u256::H160; #[derive(Debug)] From f9c3c45ce9cb941212ceaa91a6871e3e7485d34e Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 30 Jul 2024 16:48:17 +0200 Subject: [PATCH 03/61] WIP --- eravm-stable-interface/src/lib.rs | 6 +- src/lib.rs | 1 + src/tracing.rs | 250 ++++++++++++++++++++++++++++++ src/world_diff.rs | 4 + 4 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 src/tracing.rs diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index b9d190dc..8c5db85c 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -1,6 +1,6 @@ use u256::{H160, U256}; -trait StateInterface { +pub trait StateInterface { fn read_register(&self, register: u8) -> (U256, bool); fn set_register(&mut self, register: u8, value: U256, is_pointer: bool); @@ -48,7 +48,7 @@ pub struct Flags { pub greater: bool, } -trait CallframeInterface { +pub trait CallframeInterface { fn address(&self) -> H160; fn set_address(&mut self, address: H160); fn code_address(&self) -> H160; @@ -95,7 +95,7 @@ trait CallframeInterface { fn read_code_page(&self, slot: u16) -> U256; } -trait Tracer { +pub trait Tracer { fn before_instruction(&mut self, state: &mut S); fn after_instruction(&mut self, state: &mut S); } diff --git a/src/lib.rs b/src/lib.rs index 0ec4f094..385ab08a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ mod rollback; mod stack; mod state; pub mod testworld; +mod tracing; mod vm; mod world_diff; diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 00000000..64966fd1 --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,250 @@ +use crate::{callframe::Callframe, instruction_handlers::HeapInterface, VirtualMachine}; +use eravm_stable_interface::*; + +impl StateInterface for VirtualMachine { + fn read_register(&self, register: u8) -> (u256::U256, bool) { + ( + self.state.registers[register as usize], + self.state.register_pointer_flags & (1 << register) != 0, + ) + } + + fn set_register(&mut self, register: u8, value: u256::U256, is_pointer: bool) { + self.state.registers[register as usize] = value; + + self.state.register_pointer_flags &= !(1 << register); + self.state.register_pointer_flags |= u16::from(is_pointer) << register; + } + + fn number_of_callframes(&self) -> usize { + self.state.previous_frames.len() + 1 + } + + fn callframe(&mut self, n: usize) -> &mut impl CallframeInterface { + if n == 0 { + &mut self.state.current_frame + } else { + &mut self.state.previous_frames[n - 1].1 + } + } + + fn read_heap_byte(&self, heap: HeapId, index: u32) -> u8 { + self.state.heaps[heap] + } + + fn write_heap_byte(&mut self, heap: HeapId, index: u32, byte: u8) { + todo!() + } + + fn flags(&self) -> Flags { + todo!() + } + + fn set_flags(&mut self, flags: Flags) { + todo!() + } + + fn transaction_number(&self) -> u16 { + todo!() + } + + fn set_transaction_number(&mut self, value: u16) { + todo!() + } + + fn context_u128_register(&self) -> u128 { + todo!() + } + + fn set_context_u128_register(&mut self, value: u128) { + todo!() + } + + fn get_storage_state(&self) -> impl Iterator { + self.world_diff + .get_storage_state() + .iter() + .map(|(key, value)| (*key, *value)) + } + + fn get_storage(&self, address: u256::H160, slot: u256::U256) -> Option<(u256::U256, u32)> { + todo!() + } + + fn get_storage_initial_value(&self, address: u256::H160, slot: u256::U256) -> u256::U256 { + todo!() + } + + fn write_storage(&mut self, address: u256::H160, slot: u256::U256, value: u256::U256) { + todo!() + } + + fn get_transient_storage_state( + &self, + ) -> impl Iterator { + self.world_diff + .get_transient_storage_state() + .iter() + .map(|(key, value)| (*key, *value)) + } + + fn get_transient_storage(&self, address: u256::H160, slot: u256::U256) -> u256::U256 { + todo!() + } + + fn write_transient_storage( + &mut self, + address: u256::H160, + slot: u256::U256, + value: u256::U256, + ) { + todo!() + } + + fn events(&self) -> impl Iterator { + self.world_diff.events().iter().map(|event| Event { + key: event.key, + value: event.value, + is_first: event.is_first, + shard_id: event.shard_id, + tx_number: event.tx_number, + }) + } + + fn l2_to_l1_logs(&self) -> impl Iterator { + self.world_diff.l2_to_l1_logs().iter().map(|log| L2ToL1Log { + address: log.address, + key: log.key, + value: log.value, + is_service: log.is_service, + shard_id: log.shard_id, + tx_number: log.tx_number, + }) + } + + fn pubdata(&self) -> i32 { + todo!() + } + + fn set_pubdata(&mut self, value: i32) { + todo!() + } + + fn run_arbitrary_code(code: &[u64]) { + todo!() + } + + fn static_heap(&self) -> HeapId { + todo!() + } +} + +impl CallframeInterface for Callframe { + fn address(&self) -> u256::H160 { + todo!() + } + + fn set_address(&mut self, address: u256::H160) { + todo!() + } + + fn code_address(&self) -> u256::H160 { + todo!() + } + + fn set_code_address(&mut self, address: u256::H160) { + todo!() + } + + fn caller(&self) -> u256::H160 { + todo!() + } + + fn set_caller(&mut self, address: u256::H160) { + todo!() + } + + fn program_counter(&self) -> Option { + todo!() + } + + fn set_program_counter(&mut self, value: u16) { + todo!() + } + + fn exception_handler(&self) -> u16 { + todo!() + } + + fn is_static(&self) -> bool { + todo!() + } + + fn gas(&self) -> u32 { + todo!() + } + + fn set_gas(&mut self, new_gas: u32) { + todo!() + } + + fn stipend(&self) -> u32 { + todo!() + } + + fn context_u128(&self) -> u128 { + todo!() + } + + fn set_context_u128(&mut self, value: u128) { + todo!() + } + + fn is_near_call(&self) -> bool { + todo!() + } + + fn read_stack(&self, register: u16) -> (u256::U256, bool) { + todo!() + } + + fn write_stack(&mut self, register: u16, value: u256::U256, is_pointer: bool) { + todo!() + } + + fn stack_pointer(&self) -> u16 { + todo!() + } + + fn set_stack_pointer(&mut self, value: u16) { + todo!() + } + + fn heap(&self) -> HeapId { + todo!() + } + + fn heap_bound(&self) -> u32 { + todo!() + } + + fn set_heap_bound(&mut self, value: u32) { + todo!() + } + + fn aux_heap(&self) -> HeapId { + todo!() + } + + fn aux_heap_bound(&self) -> u32 { + todo!() + } + + fn set_aux_heap_bound(&mut self, value: u32) { + todo!() + } + + fn read_code_page(&self, slot: u16) -> u256::U256 { + todo!() + } +} diff --git a/src/world_diff.rs b/src/world_diff.rs index 016125fa..f7b0c671 100644 --- a/src/world_diff.rs +++ b/src/world_diff.rs @@ -231,6 +231,10 @@ impl WorldDiff { .insert((contract, key), value); } + pub fn get_transient_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { + self.transient_storage_changes.as_ref() + } + pub(crate) fn record_event(&mut self, event: Event) { self.events.push(event); } From fab16a5c6e9e10006cd3144283bcc292b55fc5a3 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 30 Jul 2024 20:03:22 +0200 Subject: [PATCH 04/61] the easy part of tracers --- eravm-stable-interface/src/lib.rs | 6 +- src/heap.rs | 10 ++ src/lib.rs | 1 + src/tracing.rs | 242 +++++++++++++++++++++--------- 4 files changed, 186 insertions(+), 73 deletions(-) diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index 8c5db85c..79f4dc7e 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -6,7 +6,7 @@ pub trait StateInterface { fn number_of_callframes(&self) -> usize; /// zero is the current frame, one is the frame before that etc. - fn callframe(&mut self, n: usize) -> &mut impl CallframeInterface; + 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); @@ -78,8 +78,8 @@ pub trait CallframeInterface { fn is_near_call(&self) -> bool; - fn read_stack(&self, register: u16) -> (U256, bool); - fn write_stack(&mut self, register: u16, value: U256, is_pointer: 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); diff --git a/src/heap.rs b/src/heap.rs index 0880eb37..6e2cd918 100644 --- a/src/heap.rs +++ b/src/heap.rs @@ -52,6 +52,11 @@ impl Heap { value.to_big_endian(&mut self.0[start_address as usize..end]); } + + /// Needed only by tracers + pub(crate) fn read_byte(&self, address: u32) -> u8 { + self.0[address as usize] + } } impl HeapInterface for Heap { @@ -128,6 +133,11 @@ impl Heaps { self.heaps[heap.to_u32() as usize].write_u256(start_address, value); } + /// Needed only by tracers + pub(crate) fn write_byte(&mut self, heap: HeapId, address: u32, value: u8) { + self.heaps[heap.to_u32() as usize].0[address as usize] = value; + } + pub(crate) fn snapshot(&self) -> (usize, usize) { ( self.bootloader_heap_rollback_info.len(), diff --git a/src/lib.rs b/src/lib.rs index 385ab08a..f24eebc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ mod rollback; mod stack; mod state; pub mod testworld; +#[cfg(not(feature = "single_instruction_test"))] mod tracing; mod vm; mod world_diff; diff --git a/src/tracing.rs b/src/tracing.rs index 64966fd1..76aa9587 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -1,4 +1,9 @@ -use crate::{callframe::Callframe, instruction_handlers::HeapInterface, VirtualMachine}; +use crate::{ + callframe::{Callframe, NearCallFrame}, + decommit::is_kernel, + predication::{self, Predicate}, + VirtualMachine, +}; use eravm_stable_interface::*; impl StateInterface for VirtualMachine { @@ -17,47 +22,72 @@ impl StateInterface for VirtualMachine { } fn number_of_callframes(&self) -> usize { - self.state.previous_frames.len() + 1 - } - - fn callframe(&mut self, n: usize) -> &mut impl CallframeInterface { - if n == 0 { - &mut self.state.current_frame - } else { - &mut self.state.previous_frames[n - 1].1 + self.state + .previous_frames + .iter() + .map(|(_, frame)| frame.near_calls.len() + 1) + .sum::() + + self.state.current_frame.near_calls.len() + + 1 + } + + fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_ { + for far_frame in std::iter::once(&mut self.state.current_frame).chain( + self.state + .previous_frames + .iter_mut() + .map(|(_, frame)| frame), + ) { + if n < far_frame.near_calls.len() { + return CallframeWrapper { + frame: far_frame, + near_call: Some(n), + }; + } else if n == far_frame.near_calls.len() { + return CallframeWrapper { + frame: far_frame, + near_call: None, + }; + } } + panic!("Callframe index out of bounds") } fn read_heap_byte(&self, heap: HeapId, index: u32) -> u8 { - self.state.heaps[heap] + self.state.heaps[heap].read_byte(index) } fn write_heap_byte(&mut self, heap: HeapId, index: u32, byte: u8) { - todo!() + self.state.heaps.write_byte(heap, index, byte); } fn flags(&self) -> Flags { - todo!() + let flags = &self.state.flags; + Flags { + less_than: Predicate::IfLT.satisfied(flags), + greater: Predicate::IfGT.satisfied(flags), + equal: Predicate::IfEQ.satisfied(flags), + } } fn set_flags(&mut self, flags: Flags) { - todo!() + self.state.flags = predication::Flags::new(flags.less_than, flags.equal, flags.greater); } fn transaction_number(&self) -> u16 { - todo!() + self.state.transaction_number } fn set_transaction_number(&mut self, value: u16) { - todo!() + self.state.transaction_number = value; } fn context_u128_register(&self) -> u128 { - todo!() + self.state.context_u128 } fn set_context_u128_register(&mut self, value: u128) { - todo!() + self.state.context_u128 = value; } fn get_storage_state(&self) -> impl Iterator { @@ -68,11 +98,11 @@ impl StateInterface for VirtualMachine { } fn get_storage(&self, address: u256::H160, slot: u256::U256) -> Option<(u256::U256, u32)> { - todo!() + todo!() // Do we really want to expose the pubdata? } fn get_storage_initial_value(&self, address: u256::H160, slot: u256::U256) -> u256::U256 { - todo!() + todo!() // Do we really want to expose the caching? } fn write_storage(&mut self, address: u256::H160, slot: u256::U256, value: u256::U256) { @@ -89,7 +119,11 @@ impl StateInterface for VirtualMachine { } fn get_transient_storage(&self, address: u256::H160, slot: u256::U256) -> u256::U256 { - todo!() + self.world_diff + .get_transient_storage_state() + .get(&(address, slot)) + .copied() + .unwrap_or_default() } fn write_transient_storage( @@ -123,11 +157,11 @@ impl StateInterface for VirtualMachine { } fn pubdata(&self) -> i32 { - todo!() + self.world_diff.pubdata() } fn set_pubdata(&mut self, value: i32) { - todo!() + self.world_diff.pubdata.0 = value; } fn run_arbitrary_code(code: &[u64]) { @@ -139,112 +173,180 @@ impl StateInterface for VirtualMachine { } } -impl CallframeInterface for Callframe { +struct CallframeWrapper<'a> { + frame: &'a mut Callframe, + near_call: Option, +} + +impl CallframeInterface for CallframeWrapper<'_> { fn address(&self) -> u256::H160 { - todo!() + self.frame.address } fn set_address(&mut self, address: u256::H160) { - todo!() + self.frame.address = address; + self.frame.is_kernel = is_kernel(address); } fn code_address(&self) -> u256::H160 { - todo!() + self.frame.code_address } fn set_code_address(&mut self, address: u256::H160) { - todo!() + self.frame.code_address = address; } fn caller(&self) -> u256::H160 { - todo!() + self.frame.caller } fn set_caller(&mut self, address: u256::H160) { - todo!() + self.frame.caller = address; } - fn program_counter(&self) -> Option { - todo!() + fn is_static(&self) -> bool { + self.frame.is_static } - fn set_program_counter(&mut self, value: u16) { - todo!() + fn stipend(&self) -> u32 { + self.frame.stipend } - fn exception_handler(&self) -> u16 { - todo!() + fn context_u128(&self) -> u128 { + self.frame.context_u128 } - fn is_static(&self) -> bool { - todo!() + fn set_context_u128(&mut self, value: u128) { + self.frame.context_u128 = value; } - fn gas(&self) -> u32 { - todo!() + fn read_stack(&self, index: u16) -> (u256::U256, bool) { + ( + self.frame.stack.get(index), + self.frame.stack.get_pointer_flag(index), + ) } - fn set_gas(&mut self, new_gas: u32) { - todo!() + fn write_stack(&mut self, index: u16, value: u256::U256, is_pointer: bool) { + self.frame.stack.set(index, value); + if is_pointer { + self.frame.stack.set_pointer_flag(index); + } else { + self.frame.stack.clear_pointer_flag(index); + } } - fn stipend(&self) -> u32 { - todo!() + fn heap(&self) -> HeapId { + self.frame.heap } - fn context_u128(&self) -> u128 { - todo!() + fn heap_bound(&self) -> u32 { + self.frame.heap_size } - fn set_context_u128(&mut self, value: u128) { - todo!() + fn set_heap_bound(&mut self, value: u32) { + self.frame.heap_size = value; } - fn is_near_call(&self) -> bool { - todo!() + fn aux_heap(&self) -> HeapId { + self.frame.aux_heap } - fn read_stack(&self, register: u16) -> (u256::U256, bool) { - todo!() + fn aux_heap_bound(&self) -> u32 { + self.frame.aux_heap_size } - fn write_stack(&mut self, register: u16, value: u256::U256, is_pointer: bool) { - todo!() + fn set_aux_heap_bound(&mut self, value: u32) { + self.frame.aux_heap_size = value; } - fn stack_pointer(&self) -> u16 { - todo!() + fn read_code_page(&self, slot: u16) -> u256::U256 { + self.frame.program.code_page()[slot as usize] } - fn set_stack_pointer(&mut self, value: u16) { - todo!() + // The following methods are affected by near calls + + fn is_near_call(&self) -> bool { + self.near_call.is_some() } - fn heap(&self) -> HeapId { - todo!() + fn gas(&self) -> u32 { + if let Some(call) = self.near_call_on_top() { + call.previous_frame_gas + } else { + self.frame.gas + } } - fn heap_bound(&self) -> u32 { - todo!() + fn set_gas(&mut self, new_gas: u32) { + if let Some(call) = self.near_call_on_top_mut() { + call.previous_frame_gas = new_gas; + } else { + self.frame.gas = new_gas; + } } - fn set_heap_bound(&mut self, value: u32) { - todo!() + fn stack_pointer(&self) -> u16 { + if let Some(call) = self.near_call_on_top() { + call.previous_frame_sp + } else { + self.frame.sp + } } - fn aux_heap(&self) -> HeapId { - todo!() + fn set_stack_pointer(&mut self, value: u16) { + if let Some(call) = self.near_call_on_top_mut() { + call.previous_frame_sp = value; + } else { + self.frame.sp = value; + } } - fn aux_heap_bound(&self) -> u32 { - todo!() + fn program_counter(&self) -> Option { + if let Some(call) = self.near_call_on_top() { + Some(call.call_instruction) + } else { + todo!() + } } - fn set_aux_heap_bound(&mut self, value: u32) { + fn set_program_counter(&mut self, value: u16) { todo!() } - fn read_code_page(&self, slot: u16) -> u256::U256 { - todo!() + fn exception_handler(&self) -> u16 { + if let Some(i) = self.near_call { + self.frame.near_calls[i].exception_handler + } else { + self.frame.exception_handler + } + } +} + +impl CallframeWrapper<'_> { + fn near_call_on_top(&self) -> Option<&NearCallFrame> { + if self.frame.near_calls.is_empty() || self.near_call == Some(0) { + None + } else { + let index = if let Some(i) = self.near_call { + i - 1 + } else { + self.frame.near_calls.len() - 1 + }; + Some(&self.frame.near_calls[index]) + } + } + + fn near_call_on_top_mut(&mut self) -> Option<&mut NearCallFrame> { + if self.frame.near_calls.is_empty() || self.near_call == Some(0) { + None + } else { + let index = if let Some(i) = self.near_call { + i - 1 + } else { + self.frame.near_calls.len() - 1 + }; + Some(&mut self.frame.near_calls[index]) + } } } From 33921605abefbd59f59041f3604f73e3da444739 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 31 Jul 2024 19:08:12 +0200 Subject: [PATCH 05/61] store program counter inside vm This way there is no need for the integration to juggle it when the VM is suspended. It also enables tracers to manipulate it. This also changes the pc storage format of previous frames and near calls from u16 to *const Instruction, which increases memory use and may hurt performance, so may need to be changed. --- afl-fuzz/src/main.rs | 10 +- afl-fuzz/src/show_testcase.rs | 10 +- src/callframe.rs | 24 ++- src/decode.rs | 11 +- src/instruction.rs | 19 +- src/instruction_handlers/binop.rs | 3 +- src/instruction_handlers/common.rs | 26 ++- src/instruction_handlers/context.rs | 40 +--- src/instruction_handlers/decommit.rs | 8 +- src/instruction_handlers/event.rs | 16 +- src/instruction_handlers/far_call.rs | 202 +++++++++--------- src/instruction_handlers/heap_access.rs | 47 ++-- src/instruction_handlers/jump.rs | 21 +- src/instruction_handlers/mod.rs | 2 +- src/instruction_handlers/near_call.rs | 50 ++--- src/instruction_handlers/nop.rs | 8 +- src/instruction_handlers/pointer.rs | 13 +- src/instruction_handlers/precompiles.rs | 15 +- src/instruction_handlers/ret.rs | 188 ++++++++-------- src/instruction_handlers/storage.rs | 32 +-- src/single_instruction_test/callframe.rs | 7 +- src/single_instruction_test/into_zk_evm.rs | 12 +- .../print_mock_info.rs | 4 +- .../state_to_zk_evm.rs | 11 +- src/single_instruction_test/validation.rs | 5 +- src/single_instruction_test/vm.rs | 17 +- src/state.rs | 4 +- src/tracing.rs | 68 +++--- src/vm.rs | 123 ++++------- 29 files changed, 426 insertions(+), 570 deletions(-) diff --git a/afl-fuzz/src/main.rs b/afl-fuzz/src/main.rs index b65a8274..aef36033 100644 --- a/afl-fuzz/src/main.rs +++ b/afl-fuzz/src/main.rs @@ -9,13 +9,9 @@ fn main() { // Tests that running one instruction and converting to zk_evm produces the same result as // first converting to zk_evm and then running one instruction. - let mut zk_evm = vm2_to_zk_evm( - &vm, - world.clone(), - vm.state.current_frame.pc_from_u16(0).unwrap(), - ); + let mut zk_evm = vm2_to_zk_evm(&vm, world.clone()); - let pc = vm.run_single_instruction(&mut world).unwrap(); + vm.run_single_instruction(&mut world); assert!(vm.is_in_valid_state()); add_heap_to_zk_evm(&mut zk_evm, &vm); @@ -29,7 +25,7 @@ fn main() { assert_eq!( UniversalVmState::from(zk_evm), - vm2_to_zk_evm(&vm, world.clone(), pc).into() + vm2_to_zk_evm(&vm, world.clone()).into() ); } } diff --git a/afl-fuzz/src/show_testcase.rs b/afl-fuzz/src/show_testcase.rs index 2f119cec..ee76b085 100644 --- a/afl-fuzz/src/show_testcase.rs +++ b/afl-fuzz/src/show_testcase.rs @@ -21,17 +21,13 @@ fn main() { println!("{:?}", vm.state); assert!(vm.is_in_valid_state()); - let mut zk_evm = vm2_to_zk_evm( - &vm, - world.clone(), - vm.state.current_frame.pc_from_u16(0).unwrap(), - ); + let mut zk_evm = vm2_to_zk_evm(&vm, world.clone()); let (parsed, _) = EncodingModeProduction::parse_preliminary_variant_and_absolute_number( vm.state.current_frame.raw_first_instruction(), ); println!("{}", parsed); - let pc = vm.run_single_instruction(&mut world).unwrap(); + vm.run_single_instruction(&mut world); println!("Mocks that have been touched:"); vm.print_mock_info(); @@ -49,6 +45,6 @@ fn main() { assert_eq!( UniversalVmState::from(zk_evm), - vm2_to_zk_evm(&vm, world.clone(), pc).into() + vm2_to_zk_evm(&vm, world.clone()).into() ); } diff --git a/src/callframe.rs b/src/callframe.rs index 7b5b5c3d..fac278c2 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -1,5 +1,6 @@ use crate::{ decommit::is_kernel, + instruction_handlers::INVALID_INSTRUCTION, program::Program, stack::{Stack, StackSnapshot}, world_diff::Snapshot, @@ -28,6 +29,7 @@ pub struct Callframe { pub(crate) near_calls: Vec, + pub pc: *const Instruction, pub(crate) program: Program, pub heap: HeapId, @@ -54,10 +56,10 @@ pub struct Callframe { #[derive(Clone, PartialEq, Debug)] pub(crate) struct NearCallFrame { - pub(crate) call_instruction: u16, pub(crate) exception_handler: u16, pub(crate) previous_frame_sp: u16, pub(crate) previous_frame_gas: u32, + pub(crate) previous_frame_pc: *const Instruction, world_before_this_frame: Snapshot, } @@ -90,6 +92,7 @@ impl Callframe { address, code_address, caller, + pc: program.instruction(0).unwrap(), program, context_u128, is_static, @@ -113,15 +116,14 @@ impl Callframe { pub(crate) fn push_near_call( &mut self, gas_to_call: u32, - old_pc: *const Instruction, exception_handler: u16, world_before_this_frame: Snapshot, ) { self.near_calls.push(NearCallFrame { - call_instruction: self.pc_to_u16(old_pc), exception_handler, previous_frame_sp: self.sp, previous_frame_gas: self.gas - gas_to_call, + previous_frame_pc: self.pc, world_before_this_frame, }); self.gas = gas_to_call; @@ -131,23 +133,26 @@ 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; FrameRemnant { - program_counter: f.call_instruction, exception_handler: f.exception_handler, snapshot: f.world_before_this_frame, } }) } - pub(crate) fn pc_to_u16(&self, pc: *const Instruction) -> u16 { - unsafe { pc.offset_from(self.program.instruction(0).unwrap()) as u16 } + pub(crate) fn get_pc_as_u16(&self) -> u16 { + unsafe { self.pc.offset_from(self.program.instruction(0).unwrap()) as u16 } } - pub fn pc_from_u16(&self, index: u16) -> Option<*const Instruction> { - self.program + /// Sets the next instruction to execute to the instruction at the given index. + /// If the index is out of bounds, the invalid instruction is used. + pub fn set_pc_from_u16(&mut self, index: u16) { + self.pc = self + .program .instruction(index) - .map(|p| p as *const Instruction) + .unwrap_or(&INVALID_INSTRUCTION) } /// The total amount of gas in this frame, including gas currently inaccessible because of a near call. @@ -206,7 +211,6 @@ impl Callframe { } pub(crate) struct FrameRemnant { - pub(crate) program_counter: u16, pub(crate) exception_handler: u16, pub(crate) snapshot: Snapshot, } diff --git a/src/decode.rs b/src/decode.rs index b952904f..3e3a893c 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -43,18 +43,15 @@ fn unimplemented_instruction(variant: Opcode) -> Instruction { arguments, } } -fn unimplemented_handler( - vm: &mut VirtualMachine, - instruction: *const Instruction, - _: &mut dyn World, -) -> InstructionResult { +fn unimplemented_handler(vm: &mut VirtualMachine, _: &mut dyn World) -> InstructionResult { let variant: Opcode = unsafe { std::mem::transmute( - Immediate1::get(&(*instruction).arguments, &mut vm.state).low_u32() as u16, + Immediate1::get(&(*vm.state.current_frame.pc).arguments, &mut vm.state).low_u32() + as u16, ) }; eprintln!("Unimplemented instruction: {:?}!", variant); - Err(ExecutionEnd::Panicked) + Some(ExecutionEnd::Panicked) } pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { diff --git a/src/instruction.rs b/src/instruction.rs index 9a629123..840f1d55 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -19,9 +19,8 @@ impl fmt::Debug for Instruction { } } -pub(crate) type Handler = - fn(&mut VirtualMachine, *const Instruction, &mut dyn World) -> InstructionResult; -pub(crate) type InstructionResult = Result<*const Instruction, ExecutionEnd>; +pub(crate) type Handler = fn(&mut VirtualMachine, &mut dyn World) -> InstructionResult; +pub(crate) type InstructionResult = Option; #[derive(Debug, PartialEq)] pub enum ExecutionEnd { @@ -30,10 +29,7 @@ pub enum ExecutionEnd { Panicked, /// Returned when the bootloader writes to the heap location [crate::Settings::hook_address] - SuspendedOnHook { - hook: u32, - pc_to_resume_from: u16, - }, + SuspendedOnHook(u32), } pub fn jump_to_beginning() -> Instruction { @@ -42,11 +38,8 @@ pub fn jump_to_beginning() -> Instruction { arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()), } } -fn jump_to_beginning_handler( - vm: &mut VirtualMachine, - _: *const Instruction, - _: &mut dyn World, -) -> InstructionResult { +fn jump_to_beginning_handler(vm: &mut VirtualMachine, _: &mut dyn World) -> InstructionResult { let first_instruction = vm.state.current_frame.program.instruction(0).unwrap(); - Ok(first_instruction) + vm.state.current_frame.pc = first_instruction; + None } diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index d9d422aa..ffb51ef3 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -13,10 +13,9 @@ use u256::U256; fn binop( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { + instruction_boilerplate(vm, world, |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) }; diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index ffa6a846..7f01ae1c 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,31 +1,29 @@ -use crate::{ - addressing_modes::Arguments, instruction::InstructionResult, Instruction, VirtualMachine, World, -}; +use crate::{addressing_modes::Arguments, instruction::InstructionResult, VirtualMachine, World}; #[inline(always)] pub(crate) fn instruction_boilerplate( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), ) -> InstructionResult { unsafe { + let instruction = vm.state.current_frame.pc; + vm.state.current_frame.pc = instruction.add(1); business_logic(vm, &(*instruction).arguments, world); - Ok(instruction.add(1)) + None } } #[inline(always)] -pub(crate) fn instruction_boilerplate_with_panic( +pub(crate) fn instruction_boilerplate_ext( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, - business_logic: impl FnOnce( - &mut VirtualMachine, - &Arguments, - &mut dyn World, - InstructionResult, - ) -> InstructionResult, + business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World) -> InstructionResult, ) -> InstructionResult { - unsafe { business_logic(vm, &(*instruction).arguments, world, Ok(instruction.add(1))) } + unsafe { + let instruction = vm.state.current_frame.pc; + vm.state.current_frame.pc = instruction.add(1); + + business_logic(vm, &(*instruction).arguments, world) + } } diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index f0fc054c..d6d90acd 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -9,12 +9,8 @@ use crate::{ use u256::U256; use zkevm_opcode_defs::VmMetaParameters; -fn context( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn context(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, _| { let result = Op::get(&vm.state); Register1::set(args, &mut vm.state, result) }) @@ -66,12 +62,8 @@ impl ContextOp for SP { } } -fn context_meta( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn context_meta(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, _| { let result = VmMetaParameters { heap_size: vm.state.current_frame.heap_size, aux_heap_size: vm.state.current_frame.aux_heap_size, @@ -91,33 +83,21 @@ fn context_meta( }) } -fn set_context_u128( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn set_context_u128(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |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, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, _, _| { +fn increment_tx_number(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, _, _| { vm.start_new_tx(); }) } -fn aux_mutating( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |_, _, _| { +fn aux_mutating(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |_, _, _| { // This instruction just crashes or nops }) } diff --git a/src/instruction_handlers/decommit.rs b/src/instruction_handlers/decommit.rs index dfa3952a..2fec27f2 100644 --- a/src/instruction_handlers/decommit.rs +++ b/src/instruction_handlers/decommit.rs @@ -10,12 +10,8 @@ use crate::{ use super::common::instruction_boilerplate; -fn decommit( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, world| { +fn decommit(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, world| { let code_hash = Register1::get(args, &mut vm.state); let extra_cost = Register2::get(args, &mut vm.state).low_u32(); diff --git a/src/instruction_handlers/event.rs b/src/instruction_handlers/event.rs index 28edd194..78c0db3c 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -8,12 +8,8 @@ use crate::{ use u256::H160; use zkevm_opcode_defs::ADDRESS_EVENT_WRITER; -fn event( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn event(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |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); @@ -30,12 +26,8 @@ fn event( }) } -fn l2_to_l1( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn l2_to_l1(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); let is_service = Immediate1::get(args, &mut vm.state).low_u32() == 1; diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 17e88b23..8b733ab9 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -1,4 +1,5 @@ use super::{ + common::instruction_boilerplate, heap_access::grow_heap, ret::{panic_from_failed_far_call, RETURN_COST}, AuxHeap, Heap, @@ -36,119 +37,118 @@ pub enum CallingMode { /// not in the caller! fn far_call( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - let args = unsafe { &(*instruction).arguments }; - - let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); - - let address_mask: U256 = U256::MAX >> (256 - 160); - let destination_address = Register2::get(args, &mut vm.state) & address_mask; - let exception_handler = Immediate1::get(args, &mut vm.state).low_u32() as u16; - - let mut abi = get_far_call_arguments(raw_abi); - abi.is_constructor_call = abi.is_constructor_call && vm.state.current_frame.is_kernel; - abi.is_system_call = abi.is_system_call && is_kernel(u256_into_address(destination_address)); - - let mut mandated_gas = if abi.is_system_call && destination_address == ADDRESS_MSG_VALUE.into() - { - MSG_VALUE_SIMULATOR_ADDITIVE_COST - } else { - 0 - }; - - let failing_part = (|| { - let decommit_result = vm.world_diff.decommit( - world, - destination_address, - vm.settings.default_aa_code_hash, - vm.settings.evm_interpreter_code_hash, - abi.is_constructor_call, - ); + instruction_boilerplate(vm, world, |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); + let destination_address = Register2::get(args, &mut vm.state) & address_mask; + let exception_handler = Immediate1::get(args, &mut vm.state).low_u32() as u16; - // calldata has to be constructed even if we already know we will panic because - // overflowing start + length makes the heap resize even when already panicking. - let already_failed = decommit_result.is_none() || IS_SHARD && abi.shard_id != 0; + let mut abi = get_far_call_arguments(raw_abi); + abi.is_constructor_call = abi.is_constructor_call && vm.state.current_frame.is_kernel; + abi.is_system_call = + abi.is_system_call && is_kernel(u256_into_address(destination_address)); - let maybe_calldata = get_far_call_calldata(raw_abi, raw_abi_is_pointer, vm, already_failed); + let mut mandated_gas = + if abi.is_system_call && destination_address == ADDRESS_MSG_VALUE.into() { + MSG_VALUE_SIMULATOR_ADDITIVE_COST + } else { + 0 + }; + + let failing_part = (|| { + let decommit_result = vm.world_diff.decommit( + world, + destination_address, + vm.settings.default_aa_code_hash, + vm.settings.evm_interpreter_code_hash, + abi.is_constructor_call, + ); + + // calldata has to be constructed even if we already know we will panic because + // overflowing start + length makes the heap resize even when already panicking. + let already_failed = decommit_result.is_none() || IS_SHARD && abi.shard_id != 0; + + let maybe_calldata = + get_far_call_calldata(raw_abi, raw_abi_is_pointer, vm, already_failed); + + // mandated gas is passed even if it means transferring more than the 63/64 rule allows + if let Some(gas_left) = vm.state.current_frame.gas.checked_sub(mandated_gas) { + vm.state.current_frame.gas = gas_left; + } else { + // If the gas is insufficient, the rest is burned + vm.state.current_frame.gas = 0; + mandated_gas = 0; + return None; + }; + + let calldata = maybe_calldata?; + let (unpaid_decommit, is_evm) = decommit_result?; + let program = vm.world_diff.pay_for_decommit( + world, + unpaid_decommit, + &mut vm.state.current_frame.gas, + )?; + + Some((calldata, program, is_evm)) + })(); + + let maximum_gas = vm.state.current_frame.gas / 64 * 63; + let normally_passed_gas = abi.gas_to_pass.min(maximum_gas); + vm.state.current_frame.gas -= normally_passed_gas; + let new_frame_gas = normally_passed_gas + mandated_gas; + + 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, exception_handler); + return; + }; - // mandated gas is passed even if it means transferring more than the 63/64 rule allows - if let Some(gas_left) = vm.state.current_frame.gas.checked_sub(mandated_gas) { - vm.state.current_frame.gas = gas_left; + let stipend = if is_evm_interpreter { + EVM_SIMULATOR_STIPEND } else { - // If the gas is insufficient, the rest is burned - vm.state.current_frame.gas = 0; - mandated_gas = 0; - return None; + 0 }; + let new_frame_gas = new_frame_gas + .checked_add(stipend) + .expect("stipend must not cause overflow"); + + let new_frame_is_static = IS_STATIC || vm.state.current_frame.is_static; + vm.push_frame::( + u256_into_address(destination_address), + program, + new_frame_gas, + stipend, + exception_handler, + new_frame_is_static && !is_evm_interpreter, + calldata.memory_page, + vm.world_diff.snapshot(), + ); - let calldata = maybe_calldata?; - let (unpaid_decommit, is_evm) = decommit_result?; - let program = vm.world_diff.pay_for_decommit( - world, - unpaid_decommit, - &mut vm.state.current_frame.gas, - )?; - - Some((calldata, program, is_evm)) - })(); - - let maximum_gas = vm.state.current_frame.gas / 64 * 63; - let normally_passed_gas = abi.gas_to_pass.min(maximum_gas); - vm.state.current_frame.gas -= normally_passed_gas; - let new_frame_gas = normally_passed_gas + mandated_gas; - - let Some((calldata, program, is_evm_interpreter)) = failing_part else { - vm.state.current_frame.gas += new_frame_gas.saturating_sub(RETURN_COST); - return panic_from_failed_far_call(vm, exception_handler); - }; - - let stipend = if is_evm_interpreter { - EVM_SIMULATOR_STIPEND - } else { - 0 - }; - let new_frame_gas = new_frame_gas - .checked_add(stipend) - .expect("stipend must not cause overflow"); - - let new_frame_is_static = IS_STATIC || vm.state.current_frame.is_static; - vm.push_frame::( - instruction, - u256_into_address(destination_address), - program, - new_frame_gas, - stipend, - exception_handler, - new_frame_is_static && !is_evm_interpreter, - calldata.memory_page, - vm.world_diff.snapshot(), - ); - - vm.state.flags = Flags::new(false, false, false); - - if abi.is_system_call { - // r3 to r12 are kept but they lose their pointer flags - vm.state.registers[13] = U256::zero(); - vm.state.registers[14] = U256::zero(); - vm.state.registers[15] = U256::zero(); - } else { - vm.state.registers = [U256::zero(); 16]; - } + vm.state.flags = Flags::new(false, false, false); - // Only r1 is a pointer - vm.state.register_pointer_flags = 2; - vm.state.registers[1] = calldata.into_u256(); + if abi.is_system_call { + // r3 to r12 are kept but they lose their pointer flags + vm.state.registers[13] = U256::zero(); + vm.state.registers[14] = U256::zero(); + vm.state.registers[15] = U256::zero(); + } else { + vm.state.registers = [U256::zero(); 16]; + } - let is_static_call_to_evm_interpreter = new_frame_is_static && is_evm_interpreter; - let call_type = (u8::from(is_static_call_to_evm_interpreter) << 2) - | (u8::from(abi.is_system_call) << 1) - | u8::from(abi.is_constructor_call); + // Only r1 is a pointer + vm.state.register_pointer_flags = 2; + vm.state.registers[1] = calldata.into_u256(); - vm.state.registers[2] = call_type.into(); + let is_static_call_to_evm_interpreter = new_frame_is_static && is_evm_interpreter; + let call_type = (u8::from(is_static_call_to_evm_interpreter) << 2) + | (u8::from(abi.is_system_call) << 1) + | u8::from(abi.is_constructor_call); - Ok(vm.state.current_frame.program.instruction(0).unwrap()) + vm.state.registers[2] = call_type.into(); + }) } pub(crate) struct FarCallABI { diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index b4d2d47c..b442df0b 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -1,4 +1,7 @@ -use super::{common::instruction_boilerplate_with_panic, PANIC}; +use super::{ + common::{instruction_boilerplate, instruction_boilerplate_ext}, + PANIC, +}; use crate::{ addressing_modes::{ Arguments, Destination, DestinationWriter, Immediate1, Register1, Register2, @@ -48,10 +51,9 @@ const LAST_ADDRESS: u32 = u32::MAX - 32; fn load( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { + instruction_boilerplate(vm, world, |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); @@ -60,14 +62,16 @@ fn load( let new_bound = address.wrapping_add(32); if grow_heap::(&mut vm.state, new_bound).is_err() { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; }; // The heap is always grown even when the index nonsensical. // 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); - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; } let heap = H::get_heap(&vm.state); @@ -77,17 +81,14 @@ fn load( if INCREMENT { Register2::set(args, &mut vm.state, pointer + 32) } - - continue_normally }) } fn store( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { + instruction_boilerplate_ext(vm, world, |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,14 +98,16 @@ fn store(&mut vm.state, new_bound).is_err() { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return None; } // The heap is always grown even when the index nonsensical. // 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); - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return None; } let heap = H::get_heap(&vm.state); @@ -115,16 +118,9 @@ fn store(state: &mut State, new_bound: u32) -> Result< fn load_pointer( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { + instruction_boilerplate(vm, world, |vm, args, _| { let (input, input_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); if !input_is_pointer { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; } let pointer = FatPointer::from(input); @@ -158,7 +154,8 @@ 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 { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; }; let start = pointer.start + pointer.offset.min(pointer.length); @@ -171,8 +168,6 @@ fn load_pointer( // This addition does not overflow because we checked that the offset is small enough above. Register2::set_fat_ptr(args, &mut vm.state, input + 32) } - - continue_normally }) } diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index 6606d6a2..3c743edd 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -1,4 +1,4 @@ -use super::ret::INVALID_INSTRUCTION; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, @@ -8,24 +8,15 @@ use crate::{ VirtualMachine, World, }; -fn jump( - vm: &mut VirtualMachine, - instruction: *const Instruction, - _: &mut dyn World, -) -> InstructionResult { - unsafe { - let args = &(*instruction).arguments; +fn jump(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, _| { let target = In::get(args, &mut vm.state).low_u32() as u16; - let next_instruction = vm.state.current_frame.pc_to_u16(instruction) + 1; + let next_instruction = vm.state.current_frame.get_pc_as_u16() + 1; Register1::set(args, &mut vm.state, next_instruction.into()); - if let Some(instruction) = vm.state.current_frame.program.instruction(target) { - Ok(instruction) - } else { - Ok(&INVALID_INSTRUCTION) - } - } + vm.state.current_frame.set_pc_from_u16(target); + }) } use super::monomorphization::*; diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index 220566d7..ba78a31f 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, PANIC}; +pub(crate) use ret::{free_panic, INVALID_INSTRUCTION, PANIC}; mod binop; mod common; diff --git a/src/instruction_handlers/near_call.rs b/src/instruction_handlers/near_call.rs index 5041a159..ec8e0761 100644 --- a/src/instruction_handlers/near_call.rs +++ b/src/instruction_handlers/near_call.rs @@ -1,4 +1,4 @@ -use super::ret::INVALID_INSTRUCTION; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register1, Source}, instruction::InstructionResult, @@ -6,37 +6,29 @@ use crate::{ Instruction, VirtualMachine, World, }; -fn near_call( - vm: &mut VirtualMachine, - instruction: *const Instruction, - _: &mut dyn World, -) -> InstructionResult { - let args = unsafe { &(*instruction).arguments }; +fn near_call(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, _| { + let gas_to_pass = Register1::get(args, &mut vm.state).0[0] as u32; + let destination = Immediate1::get(args, &mut vm.state); + let error_handler = Immediate2::get(args, &mut vm.state); - let gas_to_pass = Register1::get(args, &mut vm.state).0[0] as u32; - let destination = Immediate1::get(args, &mut vm.state); - let error_handler = Immediate2::get(args, &mut vm.state); + let new_frame_gas = if gas_to_pass == 0 { + vm.state.current_frame.gas + } else { + gas_to_pass.min(vm.state.current_frame.gas) + }; + vm.state.current_frame.push_near_call( + new_frame_gas, + error_handler.low_u32() as u16, + vm.world_diff.snapshot(), + ); - let new_frame_gas = if gas_to_pass == 0 { - vm.state.current_frame.gas - } else { - gas_to_pass.min(vm.state.current_frame.gas) - }; - vm.state.current_frame.push_near_call( - new_frame_gas, - instruction, - error_handler.low_u32() as u16, - vm.world_diff.snapshot(), - ); + vm.state.flags = Flags::new(false, false, false); - vm.state.flags = Flags::new(false, false, false); - - Ok(vm - .state - .current_frame - .program - .instruction(destination.low_u32() as u16) - .unwrap_or(&INVALID_INSTRUCTION)) + vm.state + .current_frame + .set_pc_from_u16(destination.low_u32() as u16); + }) } impl Instruction { diff --git a/src/instruction_handlers/nop.rs b/src/instruction_handlers/nop.rs index aa7b6987..c780b5c8 100644 --- a/src/instruction_handlers/nop.rs +++ b/src/instruction_handlers/nop.rs @@ -5,12 +5,8 @@ use crate::{ Instruction, VirtualMachine, World, }; -fn nop( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn nop(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, _| { // nop's addressing modes can move the stack pointer! AdvanceStackPointer::get(args, &mut vm.state); vm.state.current_frame.sp = vm diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index 9fcc62bc..36b77fff 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -1,4 +1,4 @@ -use super::{common::instruction_boilerplate_with_panic, PANIC}; +use super::{common::instruction_boilerplate, PANIC}; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, @@ -12,10 +12,9 @@ use u256::U256; fn ptr( vm: &mut VirtualMachine, - instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { + instruction_boilerplate(vm, world, |vm, args, _| { let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { ( Register2::get_with_pointer_flag(args, &mut vm.state), @@ -29,16 +28,16 @@ fn ptr( }; if !a_is_pointer || b_is_pointer { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; } let Some(result) = Op::perform(a, b) else { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; }; Out::set_fat_ptr(args, &mut vm.state, result); - - continue_normally }) } diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index 035a8f93..6ecc8cdb 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -1,4 +1,4 @@ -use super::{common::instruction_boilerplate_with_panic, HeapInterface, PANIC}; +use super::{common::instruction_boilerplate, HeapInterface, PANIC}; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, @@ -23,17 +23,14 @@ use zkevm_opcode_defs::{ PrecompileAuxData, PrecompileCallABI, }; -fn precompile_call( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { +fn precompile_call(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |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 { - return Ok(&PANIC); + vm.state.current_frame.pc = &PANIC; + return; }; vm.world_diff.pubdata.0 += aux_data.extra_pubdata_cost as i32; @@ -82,8 +79,6 @@ fn precompile_call( } Register1::set(args, &mut vm.state, 1.into()); - - continue_normally }) } diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index fc087fd3..e1485aa1 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -1,4 +1,4 @@ -use super::{far_call::get_far_call_calldata, HeapInterface}; +use super::{common::instruction_boilerplate_ext, far_call::get_far_call_calldata, HeapInterface}; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Source, INVALID_INSTRUCTION_COST}, callframe::FrameRemnant, @@ -34,123 +34,108 @@ impl ReturnType { fn ret( vm: &mut VirtualMachine, - instruction: *const Instruction, - _: &mut dyn World, + world: &mut dyn World, ) -> InstructionResult { - let args = unsafe { &(*instruction).arguments }; - - let mut return_type = ReturnType::from_u8(RETURN_TYPE); - let near_call_leftover_gas = vm.state.current_frame.gas; - - let (pc, snapshot, leftover_gas) = if let Some(FrameRemnant { - program_counter, - exception_handler, - snapshot, - }) = vm.state.current_frame.pop_near_call() - { - ( + instruction_boilerplate_ext(vm, world, |vm, args, _| { + let mut return_type = ReturnType::from_u8(RETURN_TYPE); + let near_call_leftover_gas = vm.state.current_frame.gas; + + let (snapshot, leftover_gas) = if let Some(FrameRemnant { + exception_handler, + snapshot, + }) = vm.state.current_frame.pop_near_call() + { if TO_LABEL { - Immediate1::get(args, &mut vm.state).low_u32() as u16 + let pc = Immediate1::get(args, &mut vm.state).low_u32() as u16; + vm.state.current_frame.set_pc_from_u16(pc); } else if return_type.is_failure() { - exception_handler - } else { - program_counter.wrapping_add(1) - }, - snapshot, - near_call_leftover_gas, - ) - } else { - let return_value_or_panic = if return_type == ReturnType::Panic { - None - } else { - let (raw_abi, is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); - let result = get_far_call_calldata(raw_abi, is_pointer, vm, false).filter(|pointer| { - vm.state.current_frame.is_kernel - || pointer.memory_page != vm.state.current_frame.calldata_heap - }); - - if result.is_none() { - return_type = ReturnType::Panic; + vm.state.current_frame.set_pc_from_u16(exception_handler) } - result - }; - let leftover_gas = vm - .state - .current_frame - .gas - .saturating_sub(vm.state.current_frame.stipend); + (snapshot, near_call_leftover_gas) + } else { + let return_value_or_panic = if return_type == ReturnType::Panic { + None + } else { + let (raw_abi, is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); + let result = + get_far_call_calldata(raw_abi, is_pointer, vm, false).filter(|pointer| { + vm.state.current_frame.is_kernel + || pointer.memory_page != vm.state.current_frame.calldata_heap + }); + + if result.is_none() { + return_type = ReturnType::Panic; + } + result + }; - let Some(FrameRemnant { - program_counter, - exception_handler, - snapshot, - }) = vm.pop_frame( - return_value_or_panic - .as_ref() - .map(|pointer| pointer.memory_page), - ) - else { - // The initial frame is not rolled back, even if it fails. - // It is the caller's job to clean up when the execution as a whole fails because - // the caller may take external snapshots while the VM is in the initial frame and - // these would break were the initial frame to be rolled back. - - return if let Some(return_value) = return_value_or_panic { - let output = vm.state.heaps[return_value.memory_page] - .read_range_big_endian( - return_value.start..return_value.start + return_value.length, - ) - .to_vec(); - if return_type == ReturnType::Revert { - Err(ExecutionEnd::Reverted(output)) + let leftover_gas = vm + .state + .current_frame + .gas + .saturating_sub(vm.state.current_frame.stipend); + + let Some(FrameRemnant { + exception_handler, + snapshot, + }) = vm.pop_frame( + return_value_or_panic + .as_ref() + .map(|pointer| pointer.memory_page), + ) + else { + // The initial frame is not rolled back, even if it fails. + // It is the caller's job to clean up when the execution as a whole fails because + // the caller may take external snapshots while the VM is in the initial frame and + // these would break were the initial frame to be rolled back. + + return if let Some(return_value) = return_value_or_panic { + let output = vm.state.heaps[return_value.memory_page] + .read_range_big_endian( + return_value.start..return_value.start + return_value.length, + ) + .to_vec(); + if return_type == ReturnType::Revert { + Some(ExecutionEnd::Reverted(output)) + } else { + Some(ExecutionEnd::ProgramFinished(output)) + } } else { - Err(ExecutionEnd::ProgramFinished(output)) - } - } else { - Err(ExecutionEnd::Panicked) + Some(ExecutionEnd::Panicked) + }; }; - }; - vm.state.set_context_u128(0); - vm.state.registers = [U256::zero(); 16]; + vm.state.set_context_u128(0); + vm.state.registers = [U256::zero(); 16]; - if let Some(return_value) = return_value_or_panic { - vm.state.registers[1] = return_value.into_u256(); - } - vm.state.register_pointer_flags = 2; + if let Some(return_value) = return_value_or_panic { + vm.state.registers[1] = return_value.into_u256(); + } + vm.state.register_pointer_flags = 2; - ( if return_type.is_failure() { - exception_handler - } else { - program_counter.wrapping_add(1) - }, - snapshot, - leftover_gas, - ) - }; + vm.state.current_frame.set_pc_from_u16(exception_handler) + } - if return_type.is_failure() { - vm.world_diff.rollback(snapshot); - } + (snapshot, leftover_gas) + }; + + if return_type.is_failure() { + vm.world_diff.rollback(snapshot); + } - vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); - vm.state.current_frame.gas += leftover_gas; + vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); + vm.state.current_frame.gas += leftover_gas; - match vm.state.current_frame.pc_from_u16(pc) { - Some(i) => Ok(i), - None => Ok(&INVALID_INSTRUCTION), - } + None + }) } /// 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, -) -> InstructionResult { +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". @@ -161,10 +146,7 @@ pub(crate) fn panic_from_failed_far_call( vm.state.flags = Flags::new(true, false, false); - match vm.state.current_frame.pc_from_u16(exception_handler) { - Some(i) => Ok(i), - None => Ok(&INVALID_INSTRUCTION), - } + vm.state.current_frame.set_pc_from_u16(exception_handler); } /// Panics, burning all available gas. @@ -193,7 +175,7 @@ pub static PANIC: Instruction = Instruction { /// /// 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, &PANIC, world) + ret::<{ ReturnType::Panic as u8 }, false>(vm, world) } use super::monomorphization::*; diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index b8027d18..6b36e0b6 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -7,12 +7,8 @@ use crate::{ Instruction, VirtualMachine, World, }; -fn sstore( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, world| { +fn sstore(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); @@ -25,12 +21,8 @@ fn sstore( }) } -fn sstore_transient( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +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); @@ -39,12 +31,8 @@ fn sstore_transient( }) } -fn sload( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, world| { +fn sload(vm: &mut VirtualMachine, world: &mut dyn World) -> InstructionResult { + instruction_boilerplate(vm, world, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let (value, refund) = vm.world_diff @@ -57,12 +45,8 @@ fn sload( }) } -fn sload_transient( - vm: &mut VirtualMachine, - instruction: *const Instruction, - world: &mut dyn World, -) -> InstructionResult { - instruction_boilerplate(vm, instruction, world, |vm, args, _| { +fn sload_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 = vm .world_diff diff --git a/src/single_instruction_test/callframe.rs b/src/single_instruction_test/callframe.rs index 2e460495..8865593d 100644 --- a/src/single_instruction_test/callframe.rs +++ b/src/single_instruction_test/callframe.rs @@ -27,6 +27,8 @@ 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 mut me = Self { address, code_address: u.arbitrary()?, @@ -41,7 +43,8 @@ impl<'a> Arbitrary<'a> for Callframe { gas: u.int_in_range(0..=u32::MAX - EVM_SIMULATOR_STIPEND)?, stipend: u.arbitrary()?, near_calls: vec![], - program: u.arbitrary()?, + pc: program.instruction(0).unwrap(), + program, heap: HeapId::from_u32_unchecked(base_page + 2), aux_heap: HeapId::from_u32_unchecked(base_page + 3), heap_size: u.arbitrary()?, @@ -53,7 +56,6 @@ impl<'a> Arbitrary<'a> for Callframe { if u.arbitrary()? { me.push_near_call( u.arbitrary::()?.min(me.gas), - me.program.instruction(0).unwrap(), u.arbitrary()?, WorldDiff::default().snapshot(), ); @@ -81,6 +83,7 @@ impl Callframe { gas: 0, stipend: 0, near_calls: vec![], + pc: &crate::instruction_handlers::PANIC, program: Program::for_decommit(), heap: FIRST_AUX_HEAP, aux_heap: FIRST_AUX_HEAP, diff --git a/src/single_instruction_test/into_zk_evm.rs b/src/single_instruction_test/into_zk_evm.rs index 2f864380..e44cbbcf 100644 --- a/src/single_instruction_test/into_zk_evm.rs +++ b/src/single_instruction_test/into_zk_evm.rs @@ -1,9 +1,7 @@ use std::sync::Arc; use super::{stack::Stack, state_to_zk_evm::vm2_state_to_zk_evm_state, MockWorld}; -use crate::{ - zkevm_opcode_defs::decoding::EncodingModeProduction, Instruction, VirtualMachine, World, -}; +use crate::{zkevm_opcode_defs::decoding::EncodingModeProduction, VirtualMachine, World}; use u256::U256; use zk_evm::{ abstractions::{DecommittmentProcessor, Memory, MemoryType, PrecompilesProcessor, Storage}, @@ -28,16 +26,12 @@ type ZkEvmState = VmState< EncodingModeProduction, >; -pub fn vm2_to_zk_evm( - vm: &VirtualMachine, - world: MockWorld, - program_counter: *const Instruction, -) -> 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, program_counter), + local_state: vm2_state_to_zk_evm_state(&vm.state), 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), diff --git a/src/single_instruction_test/print_mock_info.rs b/src/single_instruction_test/print_mock_info.rs index 9de60515..486f1014 100644 --- a/src/single_instruction_test/print_mock_info.rs +++ b/src/single_instruction_test/print_mock_info.rs @@ -27,8 +27,8 @@ impl State { println!("Current frame:"); self.current_frame.print_mock_info(); - if let Some((pc, previous)) = self.previous_frames.first() { - println!("Previous frame (pc at {pc}):"); + if let Some(previous) = self.previous_frames.first() { + println!("Previous frame (pc at {}):", previous.get_pc_as_u16()); previous.print_mock_info(); } } diff --git a/src/single_instruction_test/state_to_zk_evm.rs b/src/single_instruction_test/state_to_zk_evm.rs index dd64eb5a..aa7e2ead 100644 --- a/src/single_instruction_test/state_to_zk_evm.rs +++ b/src/single_instruction_test/state_to_zk_evm.rs @@ -1,7 +1,6 @@ use crate::{ callframe::{Callframe, NearCallFrame}, instruction_handlers::PANIC, - Instruction, }; use std::iter; use u256::U256; @@ -13,7 +12,6 @@ use zkevm_opcode_defs::decoding::EncodingModeProduction; pub(crate) fn vm2_state_to_zk_evm_state( state: &crate::State, - program_counter: *const Instruction, ) -> VmLocalState<8, EncodingModeProduction> { // zk_evm requires an unused bottom frame let mut callframes: Vec<_> = iter::once(CallStackEntry::empty_context()) @@ -21,7 +19,7 @@ pub(crate) fn vm2_state_to_zk_evm_state( state .previous_frames .iter() - .map(|x| x.1.clone()) + .cloned() .chain(iter::once(state.current_frame.clone())) .flat_map(vm2_frame_to_zk_evm_frames), ) @@ -49,7 +47,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: program_counter == &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 { @@ -86,15 +84,16 @@ fn vm2_frame_to_zk_evm_frames(frame: Callframe) -> impl Iterator bool { impl State { pub(crate) fn is_valid(&self) -> bool { self.current_frame.is_valid() - && self - .previous_frames - .iter() - .all(|(_, frame)| frame.is_valid()) + && self.previous_frames.iter().all(|frame| frame.is_valid()) && (0..16).all(|i| { is_valid_tagged_value(( self.registers[i as usize], diff --git a/src/single_instruction_test/vm.rs b/src/single_instruction_test/vm.rs index ecf6203e..804de514 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -1,22 +1,16 @@ use super::{heap::Heaps, stack::StackPool}; use crate::{ callframe::Callframe, fat_pointer::FatPointer, instruction::InstructionResult, - instruction_handlers::free_panic, HeapId, Instruction, Settings, State, VirtualMachine, World, + instruction_handlers::free_panic, HeapId, Settings, State, VirtualMachine, World, }; use arbitrary::Arbitrary; use std::fmt::Debug; use u256::U256; impl VirtualMachine { - fn get_first_instruction(&self) -> *const Instruction { - self.state.current_frame.program.instruction(0).unwrap() - } - pub fn run_single_instruction(&mut self, world: &mut dyn World) -> InstructionResult { - let instruction = self.get_first_instruction(); - unsafe { - let args = &(*instruction).arguments; + let args = &(*self.state.current_frame.pc).arguments; if self.state.use_gas(args.get_static_gas_cost()).is_err() || !args.mode_requirements().met( @@ -28,9 +22,10 @@ impl VirtualMachine { } if args.predicate().satisfied(&self.state.flags) { - ((*instruction).handler)(self, instruction, world) + ((*self.state.current_frame.pc).handler)(self, world) } else { - Ok(instruction.add(1)) + self.state.current_frame.pc = self.state.current_frame.pc.add(1); + None } } } @@ -86,7 +81,7 @@ impl<'a> Arbitrary<'a> for VirtualMachine { current_frame, // Exiting the final frame is different in vm2 on purpose, // so always generate two frames to avoid that. - previous_frames: vec![(0, Callframe::dummy())], + previous_frames: vec![Callframe::dummy()], heaps, transaction_number: u.arbitrary()?, context_u128: u.arbitrary()?, diff --git a/src/state.rs b/src/state.rs index 3e7c546e..0fb37324 100644 --- a/src/state.rs +++ b/src/state.rs @@ -21,7 +21,7 @@ pub struct State { /// 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<(u16, Callframe)>, + pub previous_frames: Vec, pub heaps: Heaps, @@ -95,7 +95,7 @@ impl State { + self .previous_frames .iter() - .map(|(_, frame)| frame.contained_gas()) + .map(|frame| frame.contained_gas()) .sum::() } diff --git a/src/tracing.rs b/src/tracing.rs index 76aa9587..7905b638 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -5,6 +5,7 @@ use crate::{ VirtualMachine, }; use eravm_stable_interface::*; +use std::cmp::Ordering; impl StateInterface for VirtualMachine { fn read_register(&self, register: u8) -> (u256::U256, bool) { @@ -25,29 +26,30 @@ impl StateInterface for VirtualMachine { self.state .previous_frames .iter() - .map(|(_, frame)| frame.near_calls.len() + 1) + .map(|frame| frame.near_calls.len() + 1) .sum::() + self.state.current_frame.near_calls.len() + 1 } fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_ { - for far_frame in std::iter::once(&mut self.state.current_frame).chain( - self.state - .previous_frames - .iter_mut() - .map(|(_, frame)| frame), - ) { - if n < far_frame.near_calls.len() { - return CallframeWrapper { - frame: far_frame, - near_call: Some(n), - }; - } else if n == far_frame.near_calls.len() { - return CallframeWrapper { - frame: far_frame, - near_call: None, - }; + for far_frame in std::iter::once(&mut self.state.current_frame) + .chain(self.state.previous_frames.iter_mut()) + { + match n.cmp(&far_frame.near_calls.len()) { + Ordering::Less => { + return CallframeWrapper { + frame: far_frame, + near_call: Some(n), + } + } + Ordering::Equal => { + return CallframeWrapper { + frame: far_frame, + near_call: None, + } + } + _ => {} } } panic!("Callframe index out of bounds") @@ -97,15 +99,15 @@ impl StateInterface for VirtualMachine { .map(|(key, value)| (*key, *value)) } - fn get_storage(&self, address: u256::H160, slot: u256::U256) -> Option<(u256::U256, u32)> { + fn get_storage(&self, _address: u256::H160, _slot: u256::U256) -> Option<(u256::U256, u32)> { todo!() // Do we really want to expose the pubdata? } - fn get_storage_initial_value(&self, address: u256::H160, slot: u256::U256) -> u256::U256 { + fn get_storage_initial_value(&self, _address: u256::H160, _slot: u256::U256) -> u256::U256 { todo!() // Do we really want to expose the caching? } - fn write_storage(&mut self, address: u256::H160, slot: u256::U256, value: u256::U256) { + fn write_storage(&mut self, _address: u256::H160, _slot: u256::U256, _value: u256::U256) { todo!() } @@ -128,9 +130,9 @@ impl StateInterface for VirtualMachine { fn write_transient_storage( &mut self, - address: u256::H160, - slot: u256::U256, - value: u256::U256, + _address: u256::H160, + _slot: u256::U256, + _value: u256::U256, ) { todo!() } @@ -164,7 +166,7 @@ impl StateInterface for VirtualMachine { self.world_diff.pubdata.0 = value; } - fn run_arbitrary_code(code: &[u64]) { + fn run_arbitrary_code(_code: &[u64]) { todo!() } @@ -303,15 +305,25 @@ impl CallframeInterface for CallframeWrapper<'_> { } fn program_counter(&self) -> Option { - if let Some(call) = self.near_call_on_top() { - Some(call.call_instruction) + 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 } else { - todo!() + Some(offset as u16) } } fn set_program_counter(&mut self, value: u16) { - todo!() + self.frame.set_pc_from_u16(value); } fn exception_handler(&self) -> u16 { diff --git a/src/vm.rs b/src/vm.rs index 6382f9ef..e0cbb962 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -7,7 +7,7 @@ use crate::{ stack::StackPool, state::State, world_diff::{Snapshot, WorldDiff}, - ExecutionEnd, Instruction, Program, World, + ExecutionEnd, Program, World, }; use eravm_stable_interface::HeapId; use u256::H160; @@ -63,20 +63,9 @@ impl VirtualMachine { } pub fn run(&mut self, world: &mut dyn World) -> ExecutionEnd { - self.resume_from(0, world) - } - - pub fn resume_from(&mut self, instruction_number: u16, world: &mut dyn World) -> ExecutionEnd { - let mut instruction: *const Instruction = self - .state - .current_frame - .program - .instruction(instruction_number) - .unwrap(); - unsafe { loop { - let args = &(*instruction).arguments; + let args = &(*self.state.current_frame.pc).arguments; if self.state.use_gas(args.get_static_gas_cost()).is_err() || !args.mode_requirements().met( @@ -84,23 +73,21 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - instruction = match free_panic(self, world) { - Ok(i) => i, - Err(e) => return e, + if let Some(end) = free_panic(self, world) { + return end; }; continue; } #[cfg(feature = "trace")] - self.print_instruction(instruction); + self.print_instruction(self.state.current_frame.instruction); if args.predicate().satisfied(&self.state.flags) { - instruction = match ((*instruction).handler)(self, instruction, world) { - Ok(n) => n, - Err(e) => return e, + if let Some(end) = ((*self.state.current_frame.pc).handler)(self, world) { + return end; }; } else { - instruction = instruction.add(1); + self.state.current_frame.pc = self.state.current_frame.pc.add(1); } } } @@ -114,22 +101,14 @@ impl VirtualMachine { /// depending on remaining gas. pub fn resume_with_additional_gas_limit( &mut self, - instruction_number: u16, world: &mut dyn World, gas_limit: u32, ) -> Option<(u32, ExecutionEnd)> { let minimum_gas = self.state.total_unspent_gas().saturating_sub(gas_limit); - let mut instruction: *const Instruction = self - .state - .current_frame - .program - .instruction(instruction_number) - .unwrap(); - let end = unsafe { loop { - let args = &(*instruction).arguments; + let args = &(*self.state.current_frame.pc).arguments; if self.state.use_gas(args.get_static_gas_cost()).is_err() || !args.mode_requirements().met( @@ -137,23 +116,21 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - instruction = match free_panic(self, world) { - Ok(i) => i, - Err(end) => break end, + if let Some(e) = free_panic(self, world) { + break e; }; continue; } #[cfg(feature = "trace")] - self.print_instruction(instruction); + self.print_instruction(self.state.current_frame.pc); if args.predicate().satisfied(&self.state.flags) { - instruction = match ((*instruction).handler)(self, instruction, world) { - Ok(n) => n, - Err(end) => break end, + if let Some(end) = ((*self.state.current_frame.pc).handler)(self, world) { + break end; }; } else { - instruction = instruction.add(1); + self.state.current_frame.pc = self.state.current_frame.pc.add(1); } if self.state.total_unspent_gas() < minimum_gas { @@ -209,7 +186,6 @@ impl VirtualMachine { #[allow(clippy::too_many_arguments)] pub(crate) fn push_frame( &mut self, - instruction_pointer: *const Instruction, code_address: H160, program: Program, gas: u32, @@ -252,49 +228,44 @@ impl VirtualMachine { ); self.state.context_u128 = 0; - let old_pc = self.state.current_frame.pc_to_u16(instruction_pointer); std::mem::swap(&mut new_frame, &mut self.state.current_frame); - self.state.previous_frames.push((old_pc, new_frame)); + self.state.previous_frames.push(new_frame); } pub(crate) fn pop_frame(&mut self, heap_to_keep: Option) -> Option { - self.state - .previous_frames - .pop() - .map(|(program_counter, mut frame)| { - for &heap in [ - self.state.current_frame.heap, - self.state.current_frame.aux_heap, - ] - .iter() - .chain(&self.state.current_frame.heaps_i_am_keeping_alive) - { - if Some(heap) != heap_to_keep { - self.state.heaps.deallocate(heap); - } + self.state.previous_frames.pop().map(|mut frame| { + for &heap in [ + self.state.current_frame.heap, + self.state.current_frame.aux_heap, + ] + .iter() + .chain(&self.state.current_frame.heaps_i_am_keeping_alive) + { + if Some(heap) != heap_to_keep { + self.state.heaps.deallocate(heap); } + } - std::mem::swap(&mut self.state.current_frame, &mut frame); - let Callframe { - exception_handler, - world_before_this_frame, - stack, - .. - } = frame; - - self.stack_pool.recycle(stack); - - self.state - .current_frame - .heaps_i_am_keeping_alive - .extend(heap_to_keep); - - FrameRemnant { - program_counter, - exception_handler, - snapshot: world_before_this_frame, - } - }) + std::mem::swap(&mut self.state.current_frame, &mut frame); + let Callframe { + exception_handler, + world_before_this_frame, + stack, + .. + } = frame; + + self.stack_pool.recycle(stack); + + self.state + .current_frame + .heaps_i_am_keeping_alive + .extend(heap_to_keep); + + FrameRemnant { + exception_handler, + snapshot: world_before_this_frame, + } + }) } #[cfg(feature = "trace")] From 1fddab96c7b25487be82177ed1f12d3101c5d971 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 31 Jul 2024 19:19:11 +0200 Subject: [PATCH 06/61] add license --- eravm-stable-interface/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eravm-stable-interface/Cargo.toml b/eravm-stable-interface/Cargo.toml index b3edff54..320d0f46 100644 --- a/eravm-stable-interface/Cargo.toml +++ b/eravm-stable-interface/Cargo.toml @@ -2,6 +2,8 @@ name = "eravm-stable-interface" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" +authors = ["The Matter Labs Team "] [dependencies] u256 = { package = "primitive-types", version = "0.12.1" } \ No newline at end of file From 048526f4db80023efa4564ec70d7cd6efdcb53ed Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 1 Aug 2024 12:24:58 +0200 Subject: [PATCH 07/61] make the previous frame not look like a panic --- src/single_instruction_test/callframe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/single_instruction_test/callframe.rs b/src/single_instruction_test/callframe.rs index 8865593d..02017860 100644 --- a/src/single_instruction_test/callframe.rs +++ b/src/single_instruction_test/callframe.rs @@ -83,7 +83,7 @@ impl Callframe { gas: 0, stipend: 0, near_calls: vec![], - pc: &crate::instruction_handlers::PANIC, + pc: &crate::instruction_handlers::INVALID_INSTRUCTION, program: Program::for_decommit(), heap: FIRST_AUX_HEAP, aux_heap: FIRST_AUX_HEAP, From 76d0b2b7839a36d7b672dae43be3002a139c60c8 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 1 Aug 2024 12:28:55 +0200 Subject: [PATCH 08/61] jump returns next instruction, not the one after that --- src/instruction_handlers/jump.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index 3c743edd..3e2ea259 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -12,7 +12,7 @@ fn jump(vm: &mut VirtualMachine, world: &mut dyn World) -> Instructi instruction_boilerplate(vm, world, |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() + 1; + let next_instruction = vm.state.current_frame.get_pc_as_u16(); Register1::set(args, &mut vm.state, next_instruction.into()); vm.state.current_frame.set_pc_from_u16(target); From 21686b9a265dd8411eb00071674194d07cc3e1f8 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 1 Aug 2024 12:59:56 +0200 Subject: [PATCH 09/61] fix trace compilation --- src/vm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index e0cbb962..d8ab99d4 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -80,7 +80,7 @@ impl VirtualMachine { } #[cfg(feature = "trace")] - self.print_instruction(self.state.current_frame.instruction); + 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) { @@ -269,7 +269,7 @@ impl VirtualMachine { } #[cfg(feature = "trace")] - fn print_instruction(&self, instruction: *const Instruction) { + fn print_instruction(&self, instruction: *const crate::instruction::Instruction) { print!("{:?}: ", unsafe { instruction.offset_from(self.state.current_frame.program.instruction(0).unwrap()) }); From 8cbcc2236444d28d78bfd19194a19e0b77e18603 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 5 Aug 2024 14:16:52 +0200 Subject: [PATCH 10/61] move comment to right place --- eravm-stable-interface/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index 79f4dc7e..da590b53 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -20,8 +20,8 @@ pub trait StateInterface { fn context_u128_register(&self) -> u128; fn set_context_u128_register(&mut self, value: u128); - /// Returns the new value and the amount of pubdata paid for it. 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); From 331253d1102a2401ac42c5d81d613446a24b3036 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 5 Aug 2024 14:21:14 +0200 Subject: [PATCH 11/61] unrename u256 to primitive types --- eravm-stable-interface/Cargo.toml | 2 +- eravm-stable-interface/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eravm-stable-interface/Cargo.toml b/eravm-stable-interface/Cargo.toml index 320d0f46..490a6f86 100644 --- a/eravm-stable-interface/Cargo.toml +++ b/eravm-stable-interface/Cargo.toml @@ -6,4 +6,4 @@ license = "MIT OR Apache-2.0" authors = ["The Matter Labs Team "] [dependencies] -u256 = { package = "primitive-types", version = "0.12.1" } \ No newline at end of file +primitive-types = { version = "0.12.1" } \ No newline at end of file diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index da590b53..9a471fa9 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -1,4 +1,4 @@ -use u256::{H160, U256}; +use primitive_types::{H160, U256}; pub trait StateInterface { fn read_register(&self, register: u8) -> (U256, bool); From 23a782bcaef557fc5078be8146230557fff44dc5 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 5 Aug 2024 20:10:32 +0200 Subject: [PATCH 12/61] zero-cost tracers Tracers add code to instructions directly, so the performance impact is minimized. Introduces a lot of ugliness that needs to be addressed: - World is now World because otherwise it isn't object safe - &PANIC is now &*vm.panic because panic's code depends on the tracer - UNIMPLEMENTED_INSTRUCTION isn't quite as bad because of questionable unsafe code --- afl-fuzz/src/check_input_size.rs | 2 +- afl-fuzz/src/lib.rs | 4 +- afl-fuzz/src/main.rs | 2 +- afl-fuzz/src/show_testcase.rs | 2 +- benches/nested_near_call.rs | 4 +- eravm-stable-interface/src/lib.rs | 140 +----------------- eravm-stable-interface/src/state_interface.rs | 131 ++++++++++++++++ .../src/tracer_interface.rs | 38 +++++ src/callframe.rs | 50 +++++-- src/decode.rs | 12 +- src/decommit.rs | 18 ++- src/instruction.rs | 17 ++- src/instruction_handlers/binop.rs | 13 +- src/instruction_handlers/common.rs | 40 +++-- src/instruction_handlers/context.rs | 59 +++++--- src/instruction_handlers/decommit.rs | 19 ++- src/instruction_handlers/event.rs | 19 ++- src/instruction_handlers/far_call.rs | 26 ++-- src/instruction_handlers/heap_access.rs | 71 ++++----- src/instruction_handlers/jump.rs | 13 +- src/instruction_handlers/mod.rs | 2 +- src/instruction_handlers/near_call.rs | 11 +- src/instruction_handlers/nop.rs | 11 +- src/instruction_handlers/pointer.rs | 19 +-- src/instruction_handlers/precompiles.rs | 16 +- src/instruction_handlers/ret.rs | 58 ++++---- src/instruction_handlers/storage.rs | 56 ++++--- src/lib.rs | 5 +- src/program.rs | 24 ++- src/single_instruction_test/callframe.rs | 8 +- src/single_instruction_test/heap.rs | 8 + src/single_instruction_test/into_zk_evm.rs | 9 +- .../print_mock_info.rs | 6 +- src/single_instruction_test/program.rs | 27 +++- .../state_to_zk_evm.rs | 17 +-- src/single_instruction_test/validation.rs | 4 +- src/single_instruction_test/vm.rs | 27 ++-- src/single_instruction_test/world.rs | 4 +- src/state.rs | 12 +- src/testworld.rs | 12 +- src/tracing.rs | 38 ++--- src/vm.rs | 35 +++-- src/world_diff.rs | 24 +-- tests/bytecode_behaviour.rs | 7 +- tests/panic.rs | 2 +- tests/stipend.rs | 2 +- 46 files changed, 674 insertions(+), 450 deletions(-) create mode 100644 eravm-stable-interface/src/state_interface.rs create mode 100644 eravm-stable-interface/src/tracer_interface.rs 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) } From a03ac29e4ed31acac6b85dfbfa285441990a60c7 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 5 Aug 2024 20:18:05 +0200 Subject: [PATCH 13/61] fix traces --- src/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm.rs b/src/vm.rs index 4386ceb4..0b59089d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -282,7 +282,7 @@ impl VirtualMachine { } #[cfg(feature = "trace")] - fn print_instruction(&self, instruction: *const crate::instruction::Instruction) { + fn print_instruction(&self, instruction: *const crate::instruction::Instruction) { print!("{:?}: ", unsafe { instruction.offset_from(self.state.current_frame.program.instruction(0).unwrap()) }); From b77f09f1464585ae050c646f0ead3c41826893c3 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 6 Aug 2024 13:25:22 +0200 Subject: [PATCH 14/61] remove clone --- src/single_instruction_test/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/single_instruction_test/program.rs b/src/single_instruction_test/program.rs index cc6c7c53..35d9ce8b 100644 --- a/src/single_instruction_test/program.rs +++ b/src/single_instruction_test/program.rs @@ -19,7 +19,7 @@ pub struct Program { impl Clone for Program { fn clone(&self) -> Self { Self { - raw_first_instruction: self.raw_first_instruction.clone(), + raw_first_instruction: self.raw_first_instruction, first_instruction: self.first_instruction.clone(), other_instruction: self.other_instruction.clone(), code_page: self.code_page.clone(), From 7bfce119e9fffeb4b91997431ba641f0d4add340 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 6 Aug 2024 15:45:55 +0200 Subject: [PATCH 15/61] make all opcodes part of the tracer interface --- .../src/tracer_interface.rs | 48 ++++++++++++++----- src/decode.rs | 12 ++--- src/instruction_handlers/binop.rs | 14 ++---- src/instruction_handlers/context.rs | 8 +--- src/instruction_handlers/mod.rs | 6 ++- src/instruction_handlers/pointer.rs | 47 ++++++++++-------- 6 files changed, 76 insertions(+), 59 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 4f39a42a..cf466eaa 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -1,7 +1,33 @@ use crate::StateInterface; +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 {} + +// These are all the opcodes the VM currently supports. +// Old tracers will keep working even if new opcodes are added. +// The Tracer trait itself never needs to be updated. pub mod opcodes { pub struct Nop; + pub struct Add; + pub struct Sub; + pub struct And; + pub struct Or; + pub struct Xor; + pub struct ShiftLeft; + pub struct ShiftRight; + pub struct RotateLeft; + pub struct RotateRight; + pub struct Mul; + pub struct Div; pub struct NearCall; pub struct FarCall; pub struct Ret; @@ -9,6 +35,12 @@ pub mod opcodes { pub struct Event; pub struct L2ToL1Message; pub struct Decommit; + pub struct This; + pub struct Caller; + pub struct CodeAddress; + pub struct ErgsLeft; + pub struct U128; + pub struct SP; pub struct ContextMeta; pub struct SetContextU128; pub struct IncrementTxNumber; @@ -17,6 +49,10 @@ pub mod opcodes { pub struct HeapRead; pub struct HeapWrite; pub struct PointerRead; + pub struct PointerAdd; + pub struct PointerSub; + pub struct PointerPack; + pub struct PointerShrink; pub struct StorageRead; pub struct StorageWrite; pub struct TransientStorageRead; @@ -24,15 +60,3 @@ pub mod opcodes { 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/decode.rs b/src/decode.rs index 2de1c9b1..1bc22ccf 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -6,8 +6,8 @@ use crate::{ }, instruction::{ExecutionEnd, InstructionResult}, instruction_handlers::{ - Add, And, AuxHeap, CallingMode, Div, Heap, Mul, Or, PtrAdd, PtrPack, PtrShrink, PtrSub, - RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, + Add, And, AuxHeap, CallingMode, Div, Heap, Mul, Or, PointerAdd, PointerPack, PointerShrink, + PointerSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, }, jump_to_beginning, mode_requirements::ModeRequirements, @@ -195,10 +195,10 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { } }, zkevm_opcode_defs::Opcode::Ptr(x) => match x { - zkevm_opcode_defs::PtrOpcode::Add => ptr!(PtrAdd), - zkevm_opcode_defs::PtrOpcode::Sub => ptr!(PtrSub), - zkevm_opcode_defs::PtrOpcode::Pack => ptr!(PtrPack), - zkevm_opcode_defs::PtrOpcode::Shrink => ptr!(PtrShrink), + zkevm_opcode_defs::PtrOpcode::Add => ptr!(PointerAdd), + zkevm_opcode_defs::PtrOpcode::Sub => ptr!(PointerSub), + zkevm_opcode_defs::PtrOpcode::Pack => ptr!(PointerPack), + zkevm_opcode_defs::PtrOpcode::Shrink => ptr!(PointerShrink), }, zkevm_opcode_defs::Opcode::NearCall(_) => Instruction::from_near_call( Register1(Register::new(parsed.src0_reg_idx)), diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index f2237ddb..496189bc 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -9,6 +9,9 @@ use crate::{ predication::Flags, VirtualMachine, World, }; +use eravm_stable_interface::opcodes::{ + Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, +}; use u256::U256; fn binop( @@ -35,7 +38,6 @@ pub trait Binop { fn perform(a: &U256, b: &U256) -> (U256, Self::Out2, Flags); } -pub struct Add; impl Binop for Add { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -49,7 +51,6 @@ impl Binop for Add { type Out2 = (); } -pub struct Sub; impl Binop for Sub { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -63,7 +64,6 @@ impl Binop for Sub { type Out2 = (); } -pub struct And; impl Binop for And { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -73,7 +73,6 @@ impl Binop for And { type Out2 = (); } -pub struct Or; impl Binop for Or { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -83,7 +82,6 @@ impl Binop for Or { type Out2 = (); } -pub struct Xor; impl Binop for Xor { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -93,7 +91,6 @@ impl Binop for Xor { type Out2 = (); } -pub struct ShiftLeft; impl Binop for ShiftLeft { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -103,7 +100,6 @@ impl Binop for ShiftLeft { type Out2 = (); } -pub struct ShiftRight; impl Binop for ShiftRight { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -113,7 +109,6 @@ impl Binop for ShiftRight { type Out2 = (); } -pub struct RotateLeft; impl Binop for RotateLeft { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -124,7 +119,6 @@ impl Binop for RotateLeft { type Out2 = (); } -pub struct RotateRight; impl Binop for RotateRight { #[inline(always)] fn perform(a: &U256, b: &U256) -> (U256, (), Flags) { @@ -156,7 +150,6 @@ impl SecondOutput for U256 { } } -pub struct Mul; impl Binop for Mul { fn perform(a: &U256, b: &U256) -> (U256, Self::Out2, Flags) { let res = a.full_mul(*b); @@ -183,7 +176,6 @@ impl Binop for Mul { type Out2 = U256; } -pub struct Div; impl Binop for Div { fn perform(a: &U256, b: &U256) -> (U256, Self::Out2, Flags) { if *b != U256::zero() { diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index 3796fc94..25752ae5 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -6,7 +6,7 @@ use crate::{ state::State, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::opcodes::{self, Caller, CodeAddress, ErgsLeft, This, SP, U128}; use u256::U256; use zkevm_opcode_defs::VmMetaParameters; @@ -25,42 +25,36 @@ trait ContextOp { fn get(state: &State) -> U256; } -struct This; impl ContextOp for This { fn get(state: &State) -> U256 { address_into_u256(state.current_frame.address) } } -struct Caller; impl ContextOp for Caller { fn get(state: &State) -> U256 { address_into_u256(state.current_frame.caller) } } -struct CodeAddress; impl ContextOp for CodeAddress { fn get(state: &State) -> U256 { address_into_u256(state.current_frame.code_address) } } -struct ErgsLeft; impl ContextOp for ErgsLeft { 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 { state.get_context_u128().into() } } -struct SP; impl ContextOp for SP { fn get(state: &State) -> U256 { state.current_frame.sp.into() diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index 7862c8d4..4a842216 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -1,7 +1,9 @@ -pub use binop::{Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor}; +pub use eravm_stable_interface::opcodes::{ + Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, + RotateRight, ShiftLeft, ShiftRight, Sub, Xor, +}; 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, RETURN_COST}; mod binop; diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index 4018502f..e8aa3c6e 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -8,6 +8,7 @@ use crate::{ instruction::InstructionResult, Instruction, VirtualMachine, World, }; +use eravm_stable_interface::opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}; use u256::U256; fn ptr( @@ -46,31 +47,36 @@ pub trait PtrOp { fn perform(in1: U256, in2: U256) -> Option; } -pub struct PtrAddSub; -pub type PtrAdd = PtrAddSub; -pub type PtrSub = PtrAddSub; +impl PtrOp for PointerAdd { + fn perform(in1: U256, in2: U256) -> Option { + ptr_add_sub::(in1, in2) + } +} -impl PtrOp for PtrAddSub { - fn perform(mut in1: U256, in2: U256) -> Option { - if in2 > u32::MAX.into() { - return None; - } - let pointer: &mut FatPointer = (&mut in1).into(); +impl PtrOp for PointerSub { + fn perform(in1: U256, in2: U256) -> Option { + ptr_add_sub::(in1, in2) + } +} - let new_offset = if IS_ADD { - pointer.offset.checked_add(in2.low_u32()) - } else { - pointer.offset.checked_sub(in2.low_u32()) - }?; +fn ptr_add_sub(mut in1: U256, in2: U256) -> Option { + if in2 > u32::MAX.into() { + return None; + } + let pointer: &mut FatPointer = (&mut in1).into(); - pointer.offset = new_offset; + let new_offset = if IS_ADD { + pointer.offset.checked_add(in2.low_u32()) + } else { + pointer.offset.checked_sub(in2.low_u32()) + }?; - Some(in1) - } + pointer.offset = new_offset; + + Some(in1) } -pub struct PtrPack; -impl PtrOp for PtrPack { +impl PtrOp for PointerPack { fn perform(in1: U256, in2: U256) -> Option { if in2.low_u128() != 0 { None @@ -80,8 +86,7 @@ impl PtrOp for PtrPack { } } -pub struct PtrShrink; -impl PtrOp for PtrShrink { +impl PtrOp for PointerShrink { fn perform(mut in1: U256, in2: U256) -> Option { let pointer: &mut FatPointer = (&mut in1).into(); pointer.length = pointer.length.checked_sub(in2.low_u32())?; From abf0aede682f8bc8bdf11e2f654406fe4139f116 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 6 Aug 2024 15:57:10 +0200 Subject: [PATCH 16/61] clearer naming for InstructionResult --- src/decode.rs | 6 +++--- src/instruction.rs | 11 +++++++---- src/instruction_handlers/binop.rs | 4 ++-- src/instruction_handlers/common.rs | 13 ++++++------- src/instruction_handlers/context.rs | 12 ++++++------ src/instruction_handlers/decommit.rs | 4 ++-- src/instruction_handlers/event.rs | 6 +++--- src/instruction_handlers/far_call.rs | 4 ++-- src/instruction_handlers/heap_access.rs | 16 ++++++++-------- src/instruction_handlers/jump.rs | 4 ++-- src/instruction_handlers/near_call.rs | 4 ++-- src/instruction_handlers/nop.rs | 8 ++------ src/instruction_handlers/pointer.rs | 4 ++-- src/instruction_handlers/precompiles.rs | 4 ++-- src/instruction_handlers/ret.rs | 14 +++++++------- src/instruction_handlers/storage.rs | 10 +++++----- src/single_instruction_test/vm.rs | 6 +++--- src/vm.rs | 11 +++++++---- 18 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/decode.rs b/src/decode.rs index 1bc22ccf..29f7bfcc 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -4,7 +4,7 @@ use crate::{ Immediate1, Immediate2, Register, Register1, Register2, RegisterAndImmediate, RelativeStack, Source, SourceWriter, }, - instruction::{ExecutionEnd, InstructionResult}, + instruction::{ExecutionEnd, ExecutionStatus}, instruction_handlers::{ Add, And, AuxHeap, CallingMode, Div, Heap, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, @@ -47,7 +47,7 @@ fn unimplemented_handler( vm: &mut VirtualMachine, _: &mut dyn World, _: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { let variant: Opcode = unsafe { std::mem::transmute( Immediate1::get(&(*vm.state.current_frame.pc).arguments, &mut vm.state).low_u32() @@ -55,7 +55,7 @@ fn unimplemented_handler( ) }; eprintln!("Unimplemented instruction: {:?}!", variant); - Some(ExecutionEnd::Panicked) + ExecutionStatus::Stopped(ExecutionEnd::Panicked) } pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { diff --git a/src/instruction.rs b/src/instruction.rs index 501d0ff0..3d7167cd 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -20,8 +20,11 @@ impl fmt::Debug for Instruction { } pub(crate) type Handler = - fn(&mut VirtualMachine, &mut dyn World, &mut T) -> InstructionResult; -pub(crate) type InstructionResult = Option; + fn(&mut VirtualMachine, &mut dyn World, &mut T) -> ExecutionStatus; +pub enum ExecutionStatus { + Running, + Stopped(ExecutionEnd), +} #[derive(Debug, PartialEq)] pub enum ExecutionEnd { @@ -43,8 +46,8 @@ fn jump_to_beginning_handler( vm: &mut VirtualMachine, _: &mut dyn World, _: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { let first_instruction = vm.state.current_frame.program.instruction(0).unwrap(); vm.state.current_frame.pc = first_instruction; - None + ExecutionStatus::Running } diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index 496189bc..f43c0dd9 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -5,7 +5,7 @@ use crate::{ CodePage, Destination, DestinationWriter, Immediate1, Register1, Register2, RelativeStack, Source, }, - instruction::{Instruction, InstructionResult}, + instruction::{ExecutionStatus, Instruction}, predication::Flags, VirtualMachine, World, }; @@ -18,7 +18,7 @@ fn binop, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let a = In1::get(args, &mut vm.state); let b = Register2::get(args, &mut vm.state); diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index a2a337b2..7bac1851 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,4 +1,4 @@ -use crate::{addressing_modes::Arguments, instruction::InstructionResult, VirtualMachine, World}; +use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; use eravm_stable_interface::Tracer; #[inline(always)] @@ -7,17 +7,16 @@ pub(crate) fn instruction_boilerplate( world: &mut dyn World, mut tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), -) -> InstructionResult { +) -> ExecutionStatus { Tracer::::before_instruction(&mut tracer, vm); - let result = unsafe { + 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 + ExecutionStatus::Running } #[inline(always)] @@ -29,8 +28,8 @@ pub(crate) fn instruction_boilerplate_ext( &mut VirtualMachine, &Arguments, &mut dyn World, - ) -> InstructionResult, -) -> InstructionResult { + ) -> ExecutionStatus, +) -> ExecutionStatus { Tracer::::before_instruction(&mut tracer, vm); let result = unsafe { diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index 25752ae5..3303272f 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -2,7 +2,7 @@ use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, decommit::address_into_u256, - instruction::InstructionResult, + instruction::ExecutionStatus, state::State, Instruction, VirtualMachine, World, }; @@ -14,7 +14,7 @@ fn context( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let result = Op::get(&vm.state); Register1::set(args, &mut vm.state, result) @@ -65,7 +65,7 @@ fn context_meta( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let result = VmMetaParameters { heap_size: vm.state.current_frame.heap_size, @@ -90,7 +90,7 @@ fn set_context_u128( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let value = Register1::get(args, &mut vm.state).low_u128(); vm.state.set_context_u128(value); @@ -101,7 +101,7 @@ fn increment_tx_number( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, _, _| { vm.start_new_tx(); }) @@ -111,7 +111,7 @@ fn aux_mutating( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |_, _, _| { // This instruction just crashes or nops }) diff --git a/src/instruction_handlers/decommit.rs b/src/instruction_handlers/decommit.rs index 92b88af1..20d800f5 100644 --- a/src/instruction_handlers/decommit.rs +++ b/src/instruction_handlers/decommit.rs @@ -2,7 +2,7 @@ use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, fat_pointer::FatPointer, - instruction::InstructionResult, + instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; use eravm_stable_interface::opcodes; @@ -13,7 +13,7 @@ fn decommit( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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(); diff --git a/src/instruction_handlers/event.rs b/src/instruction_handlers/event.rs index 9d2ebe20..953cfbcd 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -1,7 +1,7 @@ use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, - instruction::InstructionResult, + instruction::ExecutionStatus, world_diff::{Event, L2ToL1Log}, Instruction, VirtualMachine, World, }; @@ -13,7 +13,7 @@ fn event( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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); @@ -35,7 +35,7 @@ fn l2_to_l1( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 91f13eec..0c6dc539 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -8,7 +8,7 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, decommit::{is_kernel, u256_into_address}, fat_pointer::FatPointer, - instruction::InstructionResult, + instruction::ExecutionStatus, predication::Flags, Instruction, VirtualMachine, World, }; @@ -40,7 +40,7 @@ fn far_call, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index 59eaea47..256c0ced 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -5,7 +5,7 @@ use crate::{ RegisterOrImmediate, Source, }, fat_pointer::FatPointer, - instruction::InstructionResult, + instruction::ExecutionStatus, state::State, ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; @@ -51,7 +51,7 @@ fn load( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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. @@ -87,7 +87,7 @@ fn store, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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. @@ -99,7 +99,7 @@ fn store(&mut vm.state, new_bound).is_err() { vm.state.current_frame.pc = &*vm.panic; - return None; + return ExecutionStatus::Running; } // The heap is always grown even when the index nonsensical. @@ -107,7 +107,7 @@ fn store LAST_ADDRESS.into() { let _ = vm.state.use_gas(u32::MAX); vm.state.current_frame.pc = &*vm.panic; - return None; + return ExecutionStatus::Running; } let heap = H::get_heap(&vm.state); @@ -118,9 +118,9 @@ fn store( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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 { diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index a91b1bc4..d0b2bb0c 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -4,7 +4,7 @@ use crate::{ AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, Immediate1, Register1, RelativeStack, Source, }, - instruction::{Instruction, InstructionResult}, + instruction::{Instruction, ExecutionStatus}, VirtualMachine, World, }; use eravm_stable_interface::opcodes; @@ -13,7 +13,7 @@ fn jump( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let target = In::get(args, &mut vm.state).low_u32() as u16; diff --git a/src/instruction_handlers/near_call.rs b/src/instruction_handlers/near_call.rs index a184c6da..4a2ce974 100644 --- a/src/instruction_handlers/near_call.rs +++ b/src/instruction_handlers/near_call.rs @@ -1,7 +1,7 @@ use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register1, Source}, - instruction::InstructionResult, + instruction::ExecutionStatus, predication::Flags, Instruction, VirtualMachine, World, }; @@ -11,7 +11,7 @@ fn near_call( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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); diff --git a/src/instruction_handlers/nop.rs b/src/instruction_handlers/nop.rs index 09c93519..733f8ffd 100644 --- a/src/instruction_handlers/nop.rs +++ b/src/instruction_handlers/nop.rs @@ -1,16 +1,12 @@ use super::common::instruction_boilerplate; use crate::{ addressing_modes::{destination_stack_address, AdvanceStackPointer, Arguments, Source}, - instruction::InstructionResult, + instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; use eravm_stable_interface::opcodes; -fn nop( - vm: &mut VirtualMachine, - world: &mut dyn World, - tracer: &mut T, -) -> InstructionResult { +fn nop(vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { // nop's addressing modes can move the stack pointer! AdvanceStackPointer::get(args, &mut vm.state); diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index e8aa3c6e..61641d7e 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -5,7 +5,7 @@ use crate::{ Destination, Immediate1, Register1, Register2, RelativeStack, Source, }, fat_pointer::FatPointer, - instruction::InstructionResult, + instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; use eravm_stable_interface::opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}; @@ -15,7 +15,7 @@ fn ptr( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { ( diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index da77d755..ad166001 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -2,7 +2,7 @@ use super::{common::instruction_boilerplate, HeapInterface}; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, - instruction::InstructionResult, + instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; use eravm_stable_interface::{opcodes, HeapId}; @@ -27,7 +27,7 @@ fn precompile_call( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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 diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index 8f1e69b8..5697d616 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -2,7 +2,7 @@ use super::{common::instruction_boilerplate_ext, far_call::get_far_call_calldata use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Source, INVALID_INSTRUCTION_COST}, callframe::FrameRemnant, - instruction::{ExecutionEnd, InstructionResult}, + instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, predication::Flags, Instruction, Predicate, VirtualMachine, World, @@ -37,7 +37,7 @@ fn ret( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { 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; @@ -99,12 +99,12 @@ fn ret( ) .to_vec(); if return_type == ReturnType::Revert { - Some(ExecutionEnd::Reverted(output)) + ExecutionStatus::Stopped(ExecutionEnd::Reverted(output)) } else { - Some(ExecutionEnd::ProgramFinished(output)) + ExecutionStatus::Stopped(ExecutionEnd::ProgramFinished(output)) } } else { - Some(ExecutionEnd::Panicked) + ExecutionStatus::Stopped(ExecutionEnd::Panicked) }; }; @@ -130,7 +130,7 @@ fn ret( vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); vm.state.current_frame.gas += leftover_gas; - None + ExecutionStatus::Running }) } @@ -174,7 +174,7 @@ pub(crate) fn free_panic( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { ret::(vm, world, tracer) } diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index 723e33fe..bfe3539e 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -3,7 +3,7 @@ use crate::{ addressing_modes::{ Arguments, Destination, Register1, Register2, Source, SLOAD_COST, SSTORE_COST, }, - instruction::InstructionResult, + instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; use eravm_stable_interface::opcodes; @@ -12,7 +12,7 @@ fn sstore( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); @@ -30,7 +30,7 @@ fn sstore_transient( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::( vm, world, @@ -49,7 +49,7 @@ fn sload( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let (value, refund) = @@ -67,7 +67,7 @@ fn sload_transient( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, -) -> InstructionResult { +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let key = Register1::get(args, &mut vm.state); let value = vm diff --git a/src/single_instruction_test/vm.rs b/src/single_instruction_test/vm.rs index 82cd4c80..5a9ca03d 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -1,7 +1,7 @@ use super::{heap::Heaps, stack::StackPool}; use crate::{ addressing_modes::Arguments, callframe::Callframe, fat_pointer::FatPointer, - instruction::InstructionResult, instruction_handlers::free_panic, HeapId, Instruction, + instruction::ExecutionStatus, instruction_handlers::free_panic, HeapId, Instruction, ModeRequirements, Predicate, Settings, State, VirtualMachine, World, }; use arbitrary::Arbitrary; @@ -13,7 +13,7 @@ impl VirtualMachine { &mut self, world: &mut dyn World, tracer: &mut T, - ) -> InstructionResult { + ) -> ExecutionStatus { unsafe { let args = &(*self.state.current_frame.pc).arguments; @@ -30,7 +30,7 @@ impl VirtualMachine { ((*self.state.current_frame.pc).handler)(self, world, tracer) } else { self.state.current_frame.pc = self.state.current_frame.pc.add(1); - None + ExecutionStatus::Running } } } diff --git a/src/vm.rs b/src/vm.rs index 0b59089d..201bf918 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,5 @@ use crate::addressing_modes::Arguments; +use crate::instruction::ExecutionStatus; use crate::instruction_handlers::RETURN_COST; use crate::state::StateSnapshot; use crate::world_diff::ExternalSnapshot; @@ -83,7 +84,7 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - if let Some(end) = free_panic(self, world, tracer) { + if let ExecutionStatus::Stopped(end) = free_panic(self, world, tracer) { return end; }; continue; @@ -93,7 +94,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, tracer) + if let ExecutionStatus::Stopped(end) = + ((*self.state.current_frame.pc).handler)(self, world, tracer) { return end; }; @@ -128,7 +130,7 @@ impl VirtualMachine { self.state.current_frame.is_static, ) { - if let Some(e) = free_panic(self, world, tracer) { + if let ExecutionStatus::Stopped(e) = free_panic(self, world, tracer) { break e; }; continue; @@ -138,7 +140,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, tracer) + if let ExecutionStatus::Stopped(end) = + ((*self.state.current_frame.pc).handler)(self, world, tracer) { break end; }; From ee2c5036cb31cdb52cdf13fe65bc37ee0a057039 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 6 Aug 2024 17:25:54 +0200 Subject: [PATCH 17/61] cargo fmt --- src/instruction_handlers/jump.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index d0b2bb0c..f15bbf5a 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -4,7 +4,7 @@ use crate::{ AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, Immediate1, Register1, RelativeStack, Source, }, - instruction::{Instruction, ExecutionStatus}, + instruction::{ExecutionStatus, Instruction}, VirtualMachine, World, }; use eravm_stable_interface::opcodes; From d01513744b10f2ac296dc9cfa6f69ec014ae9901 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 7 Aug 2024 20:55:28 +0200 Subject: [PATCH 18/61] fix tracers so they actually work; add tests --- eravm-stable-interface/src/state_interface.rs | 253 ++++++++++++++++++ .../src/tracer_interface.rs | 128 ++++++++- src/instruction_handlers/common.rs | 15 +- 3 files changed, 385 insertions(+), 11 deletions(-) diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index dd3f1caa..38815ba9 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -129,3 +129,256 @@ pub struct L2ToL1Log { pub shard_id: u8, pub tx_number: u16, } + +#[cfg(test)] +pub struct DummyState; + +#[cfg(test)] +impl StateInterface for DummyState { + fn read_register(&self, _: u8) -> (primitive_types::U256, bool) { + todo!() + } + + fn set_register(&mut self, _: u8, _: primitive_types::U256, _: bool) { + todo!() + } + + fn number_of_callframes(&self) -> usize { + todo!() + } + + fn callframe(&mut self, _: usize) -> impl crate::CallframeInterface + '_ { + DummyState + } + + fn read_heap_byte(&self, _: crate::HeapId, _: u32) -> u8 { + todo!() + } + + fn write_heap_byte(&mut self, _: crate::HeapId, _: u32, _: u8) { + todo!() + } + + fn flags(&self) -> crate::Flags { + todo!() + } + + fn set_flags(&mut self, _: crate::Flags) { + todo!() + } + + fn transaction_number(&self) -> u16 { + todo!() + } + + fn set_transaction_number(&mut self, _: u16) { + todo!() + } + + fn context_u128_register(&self) -> u128 { + todo!() + } + + fn set_context_u128_register(&mut self, _: u128) { + todo!() + } + + fn get_storage_state( + &self, + ) -> impl Iterator< + Item = ( + (primitive_types::H160, primitive_types::U256), + primitive_types::U256, + ), + > { + std::iter::empty() + } + + fn get_storage( + &self, + _: primitive_types::H160, + _: primitive_types::U256, + ) -> Option<(primitive_types::U256, u32)> { + todo!() + } + + fn get_storage_initial_value( + &self, + _: primitive_types::H160, + _: primitive_types::U256, + ) -> primitive_types::U256 { + todo!() + } + + fn write_storage( + &mut self, + _: primitive_types::H160, + _: primitive_types::U256, + _: primitive_types::U256, + ) { + todo!() + } + + fn get_transient_storage_state( + &self, + ) -> impl Iterator< + Item = ( + (primitive_types::H160, primitive_types::U256), + primitive_types::U256, + ), + > { + std::iter::empty() + } + + fn get_transient_storage( + &self, + _: primitive_types::H160, + _: primitive_types::U256, + ) -> primitive_types::U256 { + todo!() + } + + fn write_transient_storage( + &mut self, + _: primitive_types::H160, + _: primitive_types::U256, + _: primitive_types::U256, + ) { + todo!() + } + + fn events(&self) -> impl Iterator { + std::iter::empty() + } + + fn l2_to_l1_logs(&self) -> impl Iterator { + std::iter::empty() + } + + fn pubdata(&self) -> i32 { + todo!() + } + + fn set_pubdata(&mut self, _: i32) { + todo!() + } + + fn run_arbitrary_code(_: &[u64]) { + todo!() + } + + fn static_heap(&self) -> crate::HeapId { + todo!() + } +} + +#[cfg(test)] +impl CallframeInterface for DummyState { + fn address(&self) -> primitive_types::H160 { + todo!() + } + + fn set_address(&mut self, _: primitive_types::H160) { + todo!() + } + + fn code_address(&self) -> primitive_types::H160 { + todo!() + } + + fn set_code_address(&mut self, _: primitive_types::H160) { + todo!() + } + + fn caller(&self) -> primitive_types::H160 { + todo!() + } + + fn set_caller(&mut self, _: primitive_types::H160) { + todo!() + } + + fn program_counter(&self) -> Option { + todo!() + } + + fn set_program_counter(&mut self, _: u16) { + todo!() + } + + fn exception_handler(&self) -> u16 { + todo!() + } + + fn is_static(&self) -> bool { + todo!() + } + + fn gas(&self) -> u32 { + todo!() + } + + fn set_gas(&mut self, _: u32) { + todo!() + } + + fn stipend(&self) -> u32 { + todo!() + } + + fn context_u128(&self) -> u128 { + todo!() + } + + fn set_context_u128(&mut self, _: u128) { + todo!() + } + + fn is_near_call(&self) -> bool { + todo!() + } + + fn read_stack(&self, _: u16) -> (primitive_types::U256, bool) { + todo!() + } + + fn write_stack(&mut self, _: u16, _: primitive_types::U256, _: bool) { + todo!() + } + + fn stack_pointer(&self) -> u16 { + todo!() + } + + fn set_stack_pointer(&mut self, _: u16) { + todo!() + } + + fn heap(&self) -> crate::HeapId { + todo!() + } + + fn heap_bound(&self) -> u32 { + todo!() + } + + fn set_heap_bound(&mut self, _: u32) { + todo!() + } + + fn aux_heap(&self) -> crate::HeapId { + todo!() + } + + fn aux_heap_bound(&self) -> u32 { + todo!() + } + + fn set_aux_heap_bound(&mut self, _: u32) { + todo!() + } + + fn read_code_page(&self, _: u16) -> primitive_types::U256 { + todo!() + } +} diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index cf466eaa..38e6e456 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -1,5 +1,17 @@ use crate::StateInterface; +use std::marker::PhantomData; +/// For example, if you want `FarCallCounter` to trace far calls, +/// you need to implement [Tracer] for `FarCallCounter`. +/// ``` +/// use eravm_stable_interface::{Tracer, opcodes, StateInterface}; +/// struct FarCallCounter(usize); +/// impl Tracer for FarCallCounter { +/// fn before_instruction(&mut self, state: &mut S) { +/// self.0 += 1; +/// } +/// } +/// ``` pub trait Tracer { #[inline(always)] fn before_instruction(&mut self, _state: &mut S) {} @@ -8,9 +20,72 @@ pub trait Tracer { 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 {} +/// This trait is a workaround for the lack of specialization in stable Rust. +/// Trait resolution will choose an implementation on U over the implementation on `&U`. +/// +/// Useful for VM implementers only. +/// If you import this trait and [OpcodeSelect], you can notify a tracer like this: +/// `tracer.opcode::().before_instruction(&mut state)` +pub trait TracerDispatch { + fn before_instruction(self, state: &mut S); + fn after_instruction(self, state: &mut S); +} + +pub struct TraceCase { + tracer: T, + _opcode: PhantomData, +} + +impl TracerDispatch for TraceCase<&mut T, I> +where + T: Tracer, +{ + fn before_instruction(self, state: &mut S) { + self.tracer.before_instruction(state); + } + + fn after_instruction(self, state: &mut S) { + self.tracer.after_instruction(state); + } +} + +impl TracerDispatch for &TraceCase<&mut T, I> { + #[inline(always)] + fn before_instruction(self, _: &mut S) {} + #[inline(always)] + fn after_instruction(self, _: &mut S) {} +} + +/// To be used with [TracerDispatch]. +pub trait OpcodeSelect { + fn opcode(&mut self) -> TraceCase<&mut Self, I>; +} + +impl OpcodeSelect for T { + fn opcode(&mut self) -> TraceCase<&mut Self, I> { + TraceCase { + tracer: self, + _opcode: PhantomData::, + } + } +} + +// Multiple tracers can be combined by building a linked list out of tuples. +impl Tracer for (A, B) +where + A: Tracer, + B: Tracer, +{ + 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) { + self.0.after_instruction(state); + self.1.after_instruction(state); + } +} // These are all the opcodes the VM currently supports. // Old tracers will keep working even if new opcodes are added. @@ -60,3 +135,50 @@ pub mod opcodes { pub struct StaticMemoryRead; pub struct StaticMemoryWrite; } + +#[cfg(test)] +mod tests { + use crate::{opcodes, DummyState, OpcodeSelect, Tracer, TracerDispatch}; + + struct FarCallCounter(usize); + + impl Tracer for FarCallCounter { + fn before_instruction(&mut self, _: &mut S) { + self.0 += 1; + } + } + + #[test] + fn test_tracer() { + let mut tracer = FarCallCounter(0); + + tracer + .opcode::() + .before_instruction(&mut DummyState); + assert_eq!(tracer.0, 0); + + tracer + .opcode::() + .before_instruction(&mut DummyState); + assert_eq!(tracer.0, 1); + } + + #[test] + fn test_aggregate_tracer() { + let mut tracer = (FarCallCounter(0), (FarCallCounter(0), FarCallCounter(0))); + + tracer + .opcode::() + .before_instruction(&mut DummyState); + assert_eq!(tracer.0 .0, 0); + assert_eq!(tracer.1 .0 .0, 0); + assert_eq!(tracer.1 .1 .0, 0); + + tracer + .opcode::() + .before_instruction(&mut DummyState); + assert_eq!(tracer.0 .0, 1); + assert_eq!(tracer.1 .0 .0, 1); + assert_eq!(tracer.1 .1 .0, 1); + } +} diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 7bac1851..867bb6c0 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,20 +1,20 @@ use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; -use eravm_stable_interface::Tracer; +use eravm_stable_interface::{OpcodeSelect, TracerDispatch}; #[inline(always)] pub(crate) fn instruction_boilerplate( vm: &mut VirtualMachine, world: &mut dyn World, - mut tracer: &mut T, + tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), ) -> ExecutionStatus { - Tracer::::before_instruction(&mut tracer, vm); + tracer.opcode::().before_instruction(vm); 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); + tracer.opcode::().after_instruction(vm); ExecutionStatus::Running } @@ -23,22 +23,21 @@ pub(crate) fn instruction_boilerplate( pub(crate) fn instruction_boilerplate_ext( vm: &mut VirtualMachine, world: &mut dyn World, - mut tracer: &mut T, + tracer: &mut T, business_logic: impl FnOnce( &mut VirtualMachine, &Arguments, &mut dyn World, ) -> ExecutionStatus, ) -> ExecutionStatus { - Tracer::::before_instruction(&mut tracer, vm); - + tracer.opcode::().before_instruction(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); + tracer.opcode::().after_instruction(vm); result } From 3eea9393885d7dc4b0b42f3529c9d75f6f571f37 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 8 Aug 2024 15:29:11 +0200 Subject: [PATCH 19/61] document the strategy for extending the interface --- eravm-stable-interface/src/lib.rs | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index 36e563c4..a7499d8b 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -1,3 +1,73 @@ +//! # EraVM Stable Interface +//! +//! This crate defines an interface for tracers that will never change but may be extended. +//! To be precise, a tracer using this interface will work in any VM written against that +//! version or a newer one. Updating the tracer to depend on a newer interface version is +//! not necessary. In fact, tracers should depend on the oldest version that has the required +//! features. +//! +//! A struct implementing [Tracer] may read and mutate the VM's state via [StateInterface] +//! when particular opcodes are executed. +//! +//! ## Why is extreme backwards compatibility required here? +//! +//! Suppose VM1 uses stable interface version 1 and VM2 uses stable interface version 2. +//! With any sane design it would be trivial to take a tracer written for version 1 and +//! update it to work with version 2. However, then it can no longer be used with VM1. +//! +//! This exact thing caused us a lot of trouble when we put many versions of zk_evm in multivm. +//! +//! ## How do I add a new feature to the interface? +//! +//! Do not change the existing traits. In fact, you should delete all existing code in the new +//! version that you publish and import it from the previous version instead. +//! +//! This is how you would add a new method to StateInterface and a new opcode. +//! ``` +//! # trait StateInterface {} +//! # trait Tracer { +//! # #[inline(always)] +//! # fn before_old_opcode(&mut self, _state: &mut S) {} +//! # #[inline(always)] +//! # fn after_old_opcode(&mut self, _state: &mut S) {} +//! # } +//! +//! trait StateInterfaceV2: StateInterface { +//! fn get_some_new_field(&self) -> u32; +//! } +//! +//! trait TracerV2 { +//! // redefine all existing opcodes +//! #[inline(always)] +//! fn before_old_opcode(&mut self, _state: &mut S) {} +//! #[inline(always)] +//! fn after_old_opcode(&mut self, _state: &mut S) {} +//! +//! #[inline(always)] +//! fn before_new_opcode(&mut self, _state: &mut S) {} +//! #[inline(always)] +//! fn after_new_opcode(&mut self, _state: &mut S) {} +//! } +//! +//! impl TracerV2 for T { +//! // repeat this for all existing opcodes +//! fn before_old_opcode(&mut self, state: &mut S) { +//! ::before_old_opcode(self, state); +//! } +//! fn after_old_opcode(&mut self, state: &mut S) { +//! ::after_old_opcode(self, state); +//! } +//! } +//! +//! // Now you can use the new features by implementing TracerV2 +//! struct MyTracer; +//! impl TracerV2 for MyTracer { +//! fn before_new_opcode(&mut self, state: &mut S) { +//! state.get_some_new_field(); +//! } +//! } +//! ``` + mod state_interface; mod tracer_interface; pub use state_interface::*; From 5adab735ab10a4fa47a719fe50edae6e7ac9b3b3 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 8 Aug 2024 19:22:30 +0200 Subject: [PATCH 20/61] make tracers *actually* work --- Cargo.lock | 1 + afl-fuzz/Cargo.toml | 3 + afl-fuzz/src/lib.rs | 3 +- eravm-stable-interface/src/lib.rs | 2 +- .../src/tracer_interface.rs | 214 +++++++++++------- src/decode.rs | 5 +- src/instruction_handlers/binop.rs | 18 +- src/instruction_handlers/common.rs | 14 +- src/instruction_handlers/context.rs | 19 +- src/instruction_handlers/decommit.rs | 6 +- src/instruction_handlers/event.rs | 8 +- src/instruction_handlers/far_call.rs | 8 +- src/instruction_handlers/heap_access.rs | 21 +- src/instruction_handlers/jump.rs | 6 +- src/instruction_handlers/near_call.rs | 6 +- src/instruction_handlers/nop.rs | 10 +- src/instruction_handlers/pointer.rs | 11 +- src/instruction_handlers/precompiles.rs | 6 +- src/instruction_handlers/ret.rs | 13 +- src/instruction_handlers/storage.rs | 12 +- src/single_instruction_test/callframe.rs | 5 +- src/single_instruction_test/into_zk_evm.rs | 7 +- src/single_instruction_test/program.rs | 5 +- .../state_to_zk_evm.rs | 3 +- src/single_instruction_test/vm.rs | 5 +- src/single_instruction_test/world.rs | 3 +- src/vm.rs | 4 +- 27 files changed, 254 insertions(+), 164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 082d4538..3ca30e20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,6 +243,7 @@ version = "0.1.0" dependencies = [ "afl", "arbitrary", + "eravm-stable-interface", "pretty_assertions", "vm2", ] diff --git a/afl-fuzz/Cargo.toml b/afl-fuzz/Cargo.toml index ffb36249..844abac9 100644 --- a/afl-fuzz/Cargo.toml +++ b/afl-fuzz/Cargo.toml @@ -13,6 +13,9 @@ pretty_assertions = "1.4.0" path = ".." features = ["single_instruction_test"] +[dependencies.eravm-stable-interface] +path = "../eravm-stable-interface" + [[bin]] name = "show_testcase" path = "src/show_testcase.rs" diff --git a/afl-fuzz/src/lib.rs b/afl-fuzz/src/lib.rs index b363193a..166084c2 100644 --- a/afl-fuzz/src/lib.rs +++ b/afl-fuzz/src/lib.rs @@ -1,8 +1,9 @@ use arbitrary::Arbitrary; +use eravm_stable_interface::Tracer; use vm2::{single_instruction_test::MockWorld, VirtualMachine}; #[derive(Arbitrary, Debug)] -pub struct VmAndWorld { +pub struct VmAndWorld { pub vm: VirtualMachine, pub world: MockWorld, } diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index a7499d8b..1a868040 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -19,7 +19,7 @@ //! //! ## How do I add a new feature to the interface? //! -//! Do not change the existing traits. In fact, you should delete all existing code in the new +//! Do not change the existing traits. In fact, you should delete existing code in the new //! version that you publish and import it from the previous version instead. //! //! This is how you would add a new method to StateInterface and a new opcode. diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 38e6e456..8b5e3e31 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -1,95 +1,148 @@ use crate::StateInterface; -use std::marker::PhantomData; -/// For example, if you want `FarCallCounter` to trace far calls, -/// you need to implement [Tracer] for `FarCallCounter`. +macro_rules! forall_opcodes { + ($m: ident) => { + $m!(Nop, before_nop, after_nop); + $m!(Add, before_add, after_add); + $m!(Sub, before_sub, after_sub); + $m!(And, before_and, after_and); + $m!(Or, before_or, after_or); + $m!(Xor, before_xor, after_xor); + $m!(ShiftLeft, before_shift_left, after_shift_left); + $m!(ShiftRight, before_shift_right, after_shift_right); + $m!(RotateLeft, before_rotate_left, after_rotate_left); + $m!(RotateRight, before_rotate_right, after_rotate_right); + $m!(Mul, before_mul, after_mul); + $m!(Div, before_div, after_div); + $m!(NearCall, before_near_call, after_near_call); + $m!(FarCall, before_far_call, after_far_call); + $m!(Ret, before_ret, after_ret); + $m!(Jump, before_jump, after_jump); + $m!(Event, before_event, after_event); + $m!(L2ToL1Message, before_l1_message, after_l1_message); + $m!(Decommit, before_decommit, after_decommit); + $m!(This, before_this, after_this); + $m!(Caller, before_caller, after_caller); + $m!(CodeAddress, before_code_address, after_code_address); + $m!(ErgsLeft, before_ergs_left, after_ergs_left); + $m!(U128, before_u128, after_u128); + $m!(SP, before_sp, after_sp); + $m!(ContextMeta, before_context_meta, after_context_meta); + $m!( + SetContextU128, + before_set_context_u128, + after_set_context_u128 + ); + $m!( + IncrementTxNumber, + before_increment_tx_number, + after_increment_tx_number + ); + $m!(AuxMutating0, before_aux_mutating0, after_aux_mutating0); + $m!( + PrecompileCall, + before_precompile_call, + after_precompile_call + ); + $m!(HeapRead, before_heap_read, after_heap_read); + $m!(HeapWrite, before_heap_write, after_heap_write); + $m!(PointerRead, before_pointer_read, after_pointer_read); + $m!(PointerAdd, before_pointer_add, after_pointer_add); + $m!(PointerSub, before_pointer_sub, after_pointer_sub); + $m!(PointerPack, before_pointer_pack, after_pointer_pack); + $m!(PointerShrink, before_pointer_shrink, after_pointer_shrink); + $m!(StorageRead, before_storage_read, after_storage_read); + $m!(StorageWrite, before_storage_write, after_storage_write); + $m!( + TransientStorageRead, + before_transient_storage_read, + after_transient_storage_read + ); + $m!( + TransientStorageWrite, + before_transient_storage_write, + after_transient_storage_write + ); + $m!( + StaticMemoryRead, + before_static_memory_read, + after_static_memory_read + ); + $m!( + StaticMemoryWrite, + before_static_memory_write, + after_static_memory_write + ); + }; +} + +macro_rules! into_default_method_implementations { + ($op:ident, $before_method:ident, $after_method:ident) => { + #[inline(always)] + fn $before_method(&mut self, _state: &mut S) {} + #[inline(always)] + fn $after_method(&mut self, _state: &mut S) {} + }; +} + +/// For example, here `FarCallCounter` counts the number of far calls. /// ``` /// use eravm_stable_interface::{Tracer, opcodes, StateInterface}; /// struct FarCallCounter(usize); -/// impl Tracer for FarCallCounter { -/// fn before_instruction(&mut self, state: &mut S) { +/// impl Tracer for FarCallCounter { +/// fn before_far_call(&mut self, state: &mut S) { /// self.0 += 1; /// } /// } /// ``` -pub trait Tracer { - #[inline(always)] - fn before_instruction(&mut self, _state: &mut S) {} - - #[inline(always)] - fn after_instruction(&mut self, _state: &mut S) {} +pub trait Tracer { + forall_opcodes! { + into_default_method_implementations + } } -/// This trait is a workaround for the lack of specialization in stable Rust. -/// Trait resolution will choose an implementation on U over the implementation on `&U`. -/// -/// Useful for VM implementers only. -/// If you import this trait and [OpcodeSelect], you can notify a tracer like this: -/// `tracer.opcode::().before_instruction(&mut state)` -pub trait TracerDispatch { - fn before_instruction(self, state: &mut S); - fn after_instruction(self, state: &mut S); -} +impl Tracer for () {} -pub struct TraceCase { - tracer: T, - _opcode: PhantomData, +macro_rules! dispatch_to_tracer_tuple { + ($op:ident, $before_method:ident, $after_method:ident) => { + fn $before_method(&mut self, state: &mut S) { + self.0.$before_method(state); + self.1.$before_method(state); + } + fn $after_method(&mut self, state: &mut S) { + self.0.$after_method(state); + self.1.$after_method(state); + } + }; } -impl TracerDispatch for TraceCase<&mut T, I> -where - T: Tracer, -{ - fn before_instruction(self, state: &mut S) { - self.tracer.before_instruction(state); - } - - fn after_instruction(self, state: &mut S) { - self.tracer.after_instruction(state); - } +// Multiple tracers can be combined by building a linked list out of tuples. +impl Tracer for (A, B) { + forall_opcodes!(dispatch_to_tracer_tuple); } -impl TracerDispatch for &TraceCase<&mut T, I> { - #[inline(always)] - fn before_instruction(self, _: &mut S) {} - #[inline(always)] - fn after_instruction(self, _: &mut S) {} +/// Call the before/after_{opcode} method of the tracer. +pub trait NotifyTracer { + fn before(tracer: &mut T, state: &mut S); + fn after(tracer: &mut T, state: &mut S); } -/// To be used with [TracerDispatch]. -pub trait OpcodeSelect { - fn opcode(&mut self) -> TraceCase<&mut Self, I>; -} +macro_rules! implement_notify_tracer { + ($opcode:ident, $before_method:ident, $after_method:ident) => { + impl NotifyTracer for $opcode { + fn before(tracer: &mut T, state: &mut S) { + tracer.$before_method(state) + } -impl OpcodeSelect for T { - fn opcode(&mut self) -> TraceCase<&mut Self, I> { - TraceCase { - tracer: self, - _opcode: PhantomData::, + fn after(tracer: &mut T, state: &mut S) { + tracer.$after_method(state) + } } - } + }; } -// Multiple tracers can be combined by building a linked list out of tuples. -impl Tracer for (A, B) -where - A: Tracer, - B: Tracer, -{ - 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) { - self.0.after_instruction(state); - self.1.after_instruction(state); - } -} +forall_opcodes!(implement_notify_tracer); -// These are all the opcodes the VM currently supports. -// Old tracers will keep working even if new opcodes are added. -// The Tracer trait itself never needs to be updated. pub mod opcodes { pub struct Nop; pub struct Add; @@ -135,15 +188,16 @@ pub mod opcodes { pub struct StaticMemoryRead; pub struct StaticMemoryWrite; } +use opcodes::*; #[cfg(test)] mod tests { - use crate::{opcodes, DummyState, OpcodeSelect, Tracer, TracerDispatch}; + use crate::{DummyState, Tracer}; struct FarCallCounter(usize); - impl Tracer for FarCallCounter { - fn before_instruction(&mut self, _: &mut S) { + impl Tracer for FarCallCounter { + fn before_far_call(&mut self, _: &mut S) { self.0 += 1; } } @@ -152,14 +206,10 @@ mod tests { fn test_tracer() { let mut tracer = FarCallCounter(0); - tracer - .opcode::() - .before_instruction(&mut DummyState); + tracer.before_nop(&mut DummyState); assert_eq!(tracer.0, 0); - tracer - .opcode::() - .before_instruction(&mut DummyState); + tracer.before_far_call(&mut DummyState); assert_eq!(tracer.0, 1); } @@ -167,16 +217,12 @@ mod tests { fn test_aggregate_tracer() { let mut tracer = (FarCallCounter(0), (FarCallCounter(0), FarCallCounter(0))); - tracer - .opcode::() - .before_instruction(&mut DummyState); + tracer.before_nop(&mut DummyState); assert_eq!(tracer.0 .0, 0); assert_eq!(tracer.1 .0 .0, 0); assert_eq!(tracer.1 .1 .0, 0); - tracer - .opcode::() - .before_instruction(&mut DummyState); + tracer.before_far_call(&mut DummyState); assert_eq!(tracer.0 .0, 1); assert_eq!(tracer.1 .0 .0, 1); assert_eq!(tracer.1 .1 .0, 1); diff --git a/src/decode.rs b/src/decode.rs index 29f7bfcc..c5ea95f8 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -13,6 +13,7 @@ use crate::{ mode_requirements::ModeRequirements, Instruction, Predicate, VirtualMachine, World, }; +use eravm_stable_interface::Tracer; use zkevm_opcode_defs::{ decoding::{EncodingModeProduction, VmEncodingMode}, ImmMemHandlerFlags, Opcode, @@ -22,7 +23,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)) @@ -58,7 +59,7 @@ fn unimplemented_handler( ExecutionStatus::Stopped(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/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index f43c0dd9..e817c921 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -9,12 +9,20 @@ use crate::{ predication::Flags, VirtualMachine, World, }; -use eravm_stable_interface::opcodes::{ - Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, +use eravm_stable_interface::{ + opcodes::{Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor}, + NotifyTracer, Tracer, }; use u256::U256; -fn binop( +fn binop< + T: Tracer, + Op: Binop, + In1: Source, + Out: Destination, + const SWAP: bool, + const SET_FLAGS: bool, +>( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -33,7 +41,7 @@ fn binop (U256, Self::Out2, Flags); } @@ -194,7 +202,7 @@ impl Binop for Div { use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_binop( src1: AnySource, diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 867bb6c0..29fe491a 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,26 +1,26 @@ use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; -use eravm_stable_interface::{OpcodeSelect, TracerDispatch}; +use eravm_stable_interface::{NotifyTracer, Tracer}; #[inline(always)] -pub(crate) fn instruction_boilerplate( +pub(crate) fn instruction_boilerplate( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), ) -> ExecutionStatus { - tracer.opcode::().before_instruction(vm); + Opcode::before(tracer, vm); unsafe { let instruction = vm.state.current_frame.pc; vm.state.current_frame.pc = instruction.add(1); business_logic(vm, &(*instruction).arguments, world); }; - tracer.opcode::().after_instruction(vm); + Opcode::after(tracer, vm); ExecutionStatus::Running } #[inline(always)] -pub(crate) fn instruction_boilerplate_ext( +pub(crate) fn instruction_boilerplate_ext( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -30,14 +30,14 @@ pub(crate) fn instruction_boilerplate_ext( &mut dyn World, ) -> ExecutionStatus, ) -> ExecutionStatus { - tracer.opcode::().before_instruction(vm); + Opcode::before(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.opcode::().after_instruction(vm); + Opcode::after(tracer, vm); result } diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index 3303272f..edeeeb5d 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -6,11 +6,14 @@ use crate::{ state::State, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes::{self, Caller, CodeAddress, ErgsLeft, This, SP, U128}; +use eravm_stable_interface::{ + opcodes::{self, Caller, CodeAddress, ErgsLeft, This, SP, U128}, + NotifyTracer, Tracer, +}; use u256::U256; use zkevm_opcode_defs::VmMetaParameters; -fn context( +fn context( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -21,7 +24,7 @@ fn context( }) } -trait ContextOp { +trait ContextOp: NotifyTracer { fn get(state: &State) -> U256; } @@ -61,7 +64,7 @@ impl ContextOp for SP { } } -fn context_meta( +fn context_meta( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -86,7 +89,7 @@ fn context_meta( }) } -fn set_context_u128( +fn set_context_u128( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -97,7 +100,7 @@ fn set_context_u128( }) } -fn increment_tx_number( +fn increment_tx_number( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -107,7 +110,7 @@ fn increment_tx_number( }) } -fn aux_mutating( +fn aux_mutating( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -117,7 +120,7 @@ fn aux_mutating( }) } -impl Instruction { +impl Instruction { fn from_context(out: Register1, arguments: Arguments) -> Self { Self { handler: context::, diff --git a/src/instruction_handlers/decommit.rs b/src/instruction_handlers/decommit.rs index 20d800f5..dd6c1e6c 100644 --- a/src/instruction_handlers/decommit.rs +++ b/src/instruction_handlers/decommit.rs @@ -5,11 +5,11 @@ use crate::{ instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; use u256::U256; use zkevm_opcode_defs::{BlobSha256Format, ContractCodeSha256Format, VersionedHashLen32}; -fn decommit( +fn decommit( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -51,7 +51,7 @@ fn decommit( }) } -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 953cfbcd..56410d6a 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -5,11 +5,11 @@ use crate::{ world_diff::{Event, L2ToL1Log}, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; use u256::H160; use zkevm_opcode_defs::ADDRESS_EVENT_WRITER; -fn event( +fn event( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -31,7 +31,7 @@ fn event( }) } -fn l2_to_l1( +fn l2_to_l1( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -51,7 +51,7 @@ fn l2_to_l1( }) } -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 0c6dc539..0eaa534a 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -12,7 +12,7 @@ use crate::{ predication::Flags, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; use u256::U256; use zkevm_opcode_defs::{ system_params::{EVM_SIMULATOR_STIPEND, MSG_VALUE_SIMULATOR_ADDITIVE_COST}, @@ -36,7 +36,7 @@ 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( +fn far_call( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -177,7 +177,7 @@ pub(crate) fn get_far_call_arguments(abi: U256) -> FarCallABI { /// /// This function needs to be called even if we already know we will panic because /// overflowing start + length makes the heap resize even when already panicking. -pub(crate) fn get_far_call_calldata( +pub(crate) fn get_far_call_calldata( raw_abi: U256, is_pointer: bool, vm: &mut VirtualMachine, @@ -259,7 +259,7 @@ impl FatPointer { use super::monomorphization::*; -impl Instruction { +impl Instruction { pub fn from_far_call( src1: Register1, src2: Register2, diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index 256c0ced..4a805239 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -9,7 +9,7 @@ use crate::{ state::State, ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; use std::ops::Range; use u256::U256; @@ -47,7 +47,7 @@ impl HeapFromState for AuxHeap { /// The last address to which 32 can be added without overflow. const LAST_ADDRESS: u32 = u32::MAX - 32; -fn load( +fn load( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -83,7 +83,13 @@ fn load( }) } -fn store( +fn store< + T: Tracer, + H: HeapFromState, + In: Source, + const INCREMENT: bool, + const HOOKING_ENABLED: bool, +>( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -127,7 +133,10 @@ 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,7 +147,7 @@ pub fn grow_heap(state: &mut State, new_bound: u32) -> R Ok(()) } -fn load_pointer( +fn load_pointer( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -174,7 +183,7 @@ fn load_pointer( use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_load( src: RegisterOrImmediate, diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index f15bbf5a..96ba6b20 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -7,9 +7,9 @@ use crate::{ instruction::{ExecutionStatus, Instruction}, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; -fn jump( +fn jump( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -26,7 +26,7 @@ fn jump( use super::monomorphization::*; -impl Instruction { +impl Instruction { pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { handler: monomorphize!(jump [T] match_source source), diff --git a/src/instruction_handlers/near_call.rs b/src/instruction_handlers/near_call.rs index 4a2ce974..91086fb0 100644 --- a/src/instruction_handlers/near_call.rs +++ b/src/instruction_handlers/near_call.rs @@ -5,9 +5,9 @@ use crate::{ predication::Flags, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; -fn near_call( +fn near_call( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -36,7 +36,7 @@ fn near_call( }) } -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 733f8ffd..f8849fee 100644 --- a/src/instruction_handlers/nop.rs +++ b/src/instruction_handlers/nop.rs @@ -4,9 +4,13 @@ use crate::{ instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; -fn nop(vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T) -> ExecutionStatus { +fn nop( + vm: &mut VirtualMachine, + world: &mut dyn World, + tracer: &mut T, +) -> ExecutionStatus { instruction_boilerplate::(vm, world, tracer, |vm, args, _| { // nop's addressing modes can move the stack pointer! AdvanceStackPointer::get(args, &mut vm.state); @@ -18,7 +22,7 @@ fn nop(vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T) }) } -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 61641d7e..5d270f94 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -8,10 +8,13 @@ use crate::{ instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}; +use eravm_stable_interface::{ + opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}, + NotifyTracer, Tracer, +}; use u256::U256; -fn ptr( +fn ptr( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -43,7 +46,7 @@ fn ptr( }) } -pub trait PtrOp { +pub trait PtrOp: NotifyTracer { fn perform(in1: U256, in2: U256) -> Option; } @@ -96,7 +99,7 @@ impl PtrOp for PointerShrink { use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_ptr( src1: AnySource, diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index ad166001..8edf740e 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -5,7 +5,7 @@ use crate::{ instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, HeapId}; +use eravm_stable_interface::{opcodes, HeapId, Tracer}; use zk_evm_abstractions::{ aux::Timestamp, precompiles::{ @@ -23,7 +23,7 @@ use zkevm_opcode_defs::{ PrecompileAuxData, PrecompileCallABI, }; -fn precompile_call( +fn precompile_call( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -121,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 5697d616..b20b13c1 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -7,7 +7,7 @@ use crate::{ predication::Flags, Instruction, Predicate, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; use u256::U256; #[repr(u8)] @@ -33,7 +33,7 @@ impl ReturnType { } } -fn ret( +fn ret( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -137,7 +137,10 @@ 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". @@ -170,7 +173,7 @@ pub(crate) const RETURN_COST: u32 = 5; /// - 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 dyn World, tracer: &mut T, @@ -180,7 +183,7 @@ pub(crate) fn free_panic( 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; diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index bfe3539e..0d519e48 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -6,9 +6,9 @@ use crate::{ instruction::ExecutionStatus, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::opcodes; +use eravm_stable_interface::{opcodes, Tracer}; -fn sstore( +fn sstore( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -26,7 +26,7 @@ fn sstore( }) } -fn sstore_transient( +fn sstore_transient( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -45,7 +45,7 @@ fn sstore_transient( ) } -fn sload( +fn sload( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -63,7 +63,7 @@ fn sload( }) } -fn sload_transient( +fn sload_transient( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -78,7 +78,7 @@ fn sload_transient( }) } -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_sstore(src1: Register1, src2: Register2, arguments: Arguments) -> Self { Self { diff --git a/src/single_instruction_test/callframe.rs b/src/single_instruction_test/callframe.rs index 101cca3e..a589fde7 100644 --- a/src/single_instruction_test/callframe.rs +++ b/src/single_instruction_test/callframe.rs @@ -6,6 +6,7 @@ use crate::{ callframe::Callframe, decommit::is_kernel, predication::Flags, HeapId, Program, WorldDiff, }; use arbitrary::Arbitrary; +use eravm_stable_interface::Tracer; use u256::H160; use zkevm_opcode_defs::EVM_SIMULATOR_STIPEND; @@ -15,7 +16,7 @@ impl<'a> Arbitrary<'a> for Flags { } } -impl<'a, T> Arbitrary<'a> for Callframe { +impl<'a, T: Tracer> Arbitrary<'a> for Callframe { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let address: H160 = u.arbitrary()?; @@ -64,7 +65,7 @@ impl<'a, T> Arbitrary<'a> for Callframe { } } -impl Callframe { +impl Callframe { pub fn raw_first_instruction(&self) -> u64 { self.program.raw_first_instruction } diff --git a/src/single_instruction_test/into_zk_evm.rs b/src/single_instruction_test/into_zk_evm.rs index 0515ef9f..9ca68652 100644 --- a/src/single_instruction_test/into_zk_evm.rs +++ b/src/single_instruction_test/into_zk_evm.rs @@ -2,13 +2,14 @@ use std::sync::Arc; use super::{stack::Stack, state_to_zk_evm::vm2_state_to_zk_evm_state, MockWorld}; use crate::{zkevm_opcode_defs::decoding::EncodingModeProduction, VirtualMachine, World}; +use eravm_stable_interface::Tracer; use u256::U256; use zk_evm::{ abstractions::{DecommittmentProcessor, Memory, MemoryType, PrecompilesProcessor, Storage}, aux_structures::PubdataCost, block_properties::BlockProperties, reference_impls::event_sink::InMemoryEventSink, - tracing::Tracer, + tracing, vm_state::VmState, witness_trace::VmWitnessTracer, }; @@ -26,7 +27,7 @@ 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)); @@ -241,7 +242,7 @@ impl DecommittmentProcessor for MockDecommitter { #[derive(Debug)] pub struct NoTracer; -impl Tracer for NoTracer { +impl tracing::Tracer for NoTracer { type SupportedMemory = MockMemory; fn before_decoding( diff --git a/src/single_instruction_test/program.rs b/src/single_instruction_test/program.rs index 35d9ce8b..02d4cf5c 100644 --- a/src/single_instruction_test/program.rs +++ b/src/single_instruction_test/program.rs @@ -1,5 +1,6 @@ use crate::{decode::decode, Instruction}; use arbitrary::Arbitrary; +use eravm_stable_interface::Tracer; use std::{rc::Rc, sync::Arc}; use u256::U256; @@ -27,7 +28,7 @@ impl Clone for Program { } } -impl<'a, T> Arbitrary<'a> for Program { +impl<'a, T: Tracer> Arbitrary<'a> for Program { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let raw_first_instruction = u.arbitrary()?; @@ -62,7 +63,9 @@ impl Program { pub fn code_page(&self) -> &Arc<[U256]> { &self.code_page } +} +impl Program { pub fn for_decommit() -> Self { Self { raw_first_instruction: 0, diff --git a/src/single_instruction_test/state_to_zk_evm.rs b/src/single_instruction_test/state_to_zk_evm.rs index 157d6440..063850e7 100644 --- a/src/single_instruction_test/state_to_zk_evm.rs +++ b/src/single_instruction_test/state_to_zk_evm.rs @@ -1,4 +1,5 @@ use crate::callframe::{Callframe, NearCallFrame}; +use eravm_stable_interface::Tracer; use std::iter; use u256::U256; use zk_evm::{ @@ -7,7 +8,7 @@ use zk_evm::{ }; use zkevm_opcode_defs::decoding::EncodingModeProduction; -pub(crate) fn vm2_state_to_zk_evm_state( +pub(crate) fn vm2_state_to_zk_evm_state( state: &crate::State, panic: &crate::Instruction, ) -> VmLocalState<8, EncodingModeProduction> { diff --git a/src/single_instruction_test/vm.rs b/src/single_instruction_test/vm.rs index 5a9ca03d..80d5c1e4 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -5,10 +5,11 @@ use crate::{ ModeRequirements, Predicate, Settings, State, VirtualMachine, World, }; use arbitrary::Arbitrary; +use eravm_stable_interface::Tracer; use std::fmt::Debug; use u256::U256; -impl VirtualMachine { +impl VirtualMachine { pub fn run_single_instruction( &mut self, world: &mut dyn World, @@ -59,7 +60,7 @@ impl VirtualMachine { } } -impl<'a, T> Arbitrary<'a> for VirtualMachine { +impl<'a, T: Tracer> Arbitrary<'a> for VirtualMachine { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let current_frame: Callframe = u.arbitrary()?; diff --git a/src/single_instruction_test/world.rs b/src/single_instruction_test/world.rs index 966282a4..e756129a 100644 --- a/src/single_instruction_test/world.rs +++ b/src/single_instruction_test/world.rs @@ -1,6 +1,7 @@ use super::mock_array::MockRead; use crate::{Program, World}; use arbitrary::Arbitrary; +use eravm_stable_interface::Tracer; use u256::{H160, U256}; #[derive(Debug, Arbitrary, Clone)] @@ -8,7 +9,7 @@ pub struct MockWorld { storage_slot: MockRead<(H160, U256), Option>, } -impl World for MockWorld { +impl World for MockWorld { fn decommit(&mut self, _hash: U256) -> Program { Program::for_decommit() } diff --git a/src/vm.rs b/src/vm.rs index 201bf918..a2789c5d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -13,7 +13,7 @@ use crate::{ ExecutionEnd, Program, World, }; use crate::{Instruction, ModeRequirements, Predicate}; -use eravm_stable_interface::HeapId; +use eravm_stable_interface::{HeapId, Tracer}; use u256::H160; #[derive(Debug)] @@ -40,7 +40,7 @@ pub struct VirtualMachine { pub(crate) panic: Box>, } -impl VirtualMachine { +impl VirtualMachine { pub fn new( address: H160, program: Program, From 63c708ce6c73590ee8319952419973adfaf89128 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 9 Aug 2024 13:41:25 +0200 Subject: [PATCH 21/61] fix test compilation --- tests/bytecode_behaviour.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bytecode_behaviour.rs b/tests/bytecode_behaviour.rs index 39da1398..7b02ce0c 100644 --- a/tests/bytecode_behaviour.rs +++ b/tests/bytecode_behaviour.rs @@ -1,5 +1,6 @@ #![cfg(not(feature = "single_instruction_test"))] +use eravm_stable_interface::Tracer; use u256::U256; use vm2::{ decode::decode_program, initial_decommit, testworld::TestWorld, ExecutionEnd, Program, @@ -7,7 +8,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( From b3c834c00767cb0dc4b18c3e498999ed1955faad Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 9 Aug 2024 13:54:26 +0200 Subject: [PATCH 22/61] simplify Cargo.toml --- eravm-stable-interface/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eravm-stable-interface/Cargo.toml b/eravm-stable-interface/Cargo.toml index 490a6f86..36f46668 100644 --- a/eravm-stable-interface/Cargo.toml +++ b/eravm-stable-interface/Cargo.toml @@ -6,4 +6,4 @@ license = "MIT OR Apache-2.0" authors = ["The Matter Labs Team "] [dependencies] -primitive-types = { version = "0.12.1" } \ No newline at end of file +primitive-types = "0.12.1" From 33ffc4ab909f428f2aad31d4889ff2cef1f71d3b Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 9 Aug 2024 13:58:16 +0200 Subject: [PATCH 23/61] change todo to unimplemented --- eravm-stable-interface/src/state_interface.rs | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index 38815ba9..c4b41966 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -136,15 +136,15 @@ pub struct DummyState; #[cfg(test)] impl StateInterface for DummyState { fn read_register(&self, _: u8) -> (primitive_types::U256, bool) { - todo!() + unimplemented!() } fn set_register(&mut self, _: u8, _: primitive_types::U256, _: bool) { - todo!() + unimplemented!() } fn number_of_callframes(&self) -> usize { - todo!() + unimplemented!() } fn callframe(&mut self, _: usize) -> impl crate::CallframeInterface + '_ { @@ -152,35 +152,35 @@ impl StateInterface for DummyState { } fn read_heap_byte(&self, _: crate::HeapId, _: u32) -> u8 { - todo!() + unimplemented!() } fn write_heap_byte(&mut self, _: crate::HeapId, _: u32, _: u8) { - todo!() + unimplemented!() } fn flags(&self) -> crate::Flags { - todo!() + unimplemented!() } fn set_flags(&mut self, _: crate::Flags) { - todo!() + unimplemented!() } fn transaction_number(&self) -> u16 { - todo!() + unimplemented!() } fn set_transaction_number(&mut self, _: u16) { - todo!() + unimplemented!() } fn context_u128_register(&self) -> u128 { - todo!() + unimplemented!() } fn set_context_u128_register(&mut self, _: u128) { - todo!() + unimplemented!() } fn get_storage_state( @@ -199,7 +199,7 @@ impl StateInterface for DummyState { _: primitive_types::H160, _: primitive_types::U256, ) -> Option<(primitive_types::U256, u32)> { - todo!() + unimplemented!() } fn get_storage_initial_value( @@ -207,7 +207,7 @@ impl StateInterface for DummyState { _: primitive_types::H160, _: primitive_types::U256, ) -> primitive_types::U256 { - todo!() + unimplemented!() } fn write_storage( @@ -216,7 +216,7 @@ impl StateInterface for DummyState { _: primitive_types::U256, _: primitive_types::U256, ) { - todo!() + unimplemented!() } fn get_transient_storage_state( @@ -235,7 +235,7 @@ impl StateInterface for DummyState { _: primitive_types::H160, _: primitive_types::U256, ) -> primitive_types::U256 { - todo!() + unimplemented!() } fn write_transient_storage( @@ -244,7 +244,7 @@ impl StateInterface for DummyState { _: primitive_types::U256, _: primitive_types::U256, ) { - todo!() + unimplemented!() } fn events(&self) -> impl Iterator { @@ -256,129 +256,129 @@ impl StateInterface for DummyState { } fn pubdata(&self) -> i32 { - todo!() + unimplemented!() } fn set_pubdata(&mut self, _: i32) { - todo!() + unimplemented!() } fn run_arbitrary_code(_: &[u64]) { - todo!() + unimplemented!() } fn static_heap(&self) -> crate::HeapId { - todo!() + unimplemented!() } } #[cfg(test)] impl CallframeInterface for DummyState { fn address(&self) -> primitive_types::H160 { - todo!() + unimplemented!() } fn set_address(&mut self, _: primitive_types::H160) { - todo!() + unimplemented!() } fn code_address(&self) -> primitive_types::H160 { - todo!() + unimplemented!() } fn set_code_address(&mut self, _: primitive_types::H160) { - todo!() + unimplemented!() } fn caller(&self) -> primitive_types::H160 { - todo!() + unimplemented!() } fn set_caller(&mut self, _: primitive_types::H160) { - todo!() + unimplemented!() } fn program_counter(&self) -> Option { - todo!() + unimplemented!() } fn set_program_counter(&mut self, _: u16) { - todo!() + unimplemented!() } fn exception_handler(&self) -> u16 { - todo!() + unimplemented!() } fn is_static(&self) -> bool { - todo!() + unimplemented!() } fn gas(&self) -> u32 { - todo!() + unimplemented!() } fn set_gas(&mut self, _: u32) { - todo!() + unimplemented!() } fn stipend(&self) -> u32 { - todo!() + unimplemented!() } fn context_u128(&self) -> u128 { - todo!() + unimplemented!() } fn set_context_u128(&mut self, _: u128) { - todo!() + unimplemented!() } fn is_near_call(&self) -> bool { - todo!() + unimplemented!() } fn read_stack(&self, _: u16) -> (primitive_types::U256, bool) { - todo!() + unimplemented!() } fn write_stack(&mut self, _: u16, _: primitive_types::U256, _: bool) { - todo!() + unimplemented!() } fn stack_pointer(&self) -> u16 { - todo!() + unimplemented!() } fn set_stack_pointer(&mut self, _: u16) { - todo!() + unimplemented!() } fn heap(&self) -> crate::HeapId { - todo!() + unimplemented!() } fn heap_bound(&self) -> u32 { - todo!() + unimplemented!() } fn set_heap_bound(&mut self, _: u32) { - todo!() + unimplemented!() } fn aux_heap(&self) -> crate::HeapId { - todo!() + unimplemented!() } fn aux_heap_bound(&self) -> u32 { - todo!() + unimplemented!() } fn set_aux_heap_bound(&mut self, _: u32) { - todo!() + unimplemented!() } fn read_code_page(&self, _: u16) -> primitive_types::U256 { - todo!() + unimplemented!() } } From 43fd7dd173724406958e26f5d75fd3bafe35b3cc Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 9 Aug 2024 14:09:06 +0200 Subject: [PATCH 24/61] remove default is_kernel impl --- eravm-stable-interface/src/state_interface.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index c4b41966..1f8201d9 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -65,9 +65,7 @@ pub trait CallframeInterface { fn exception_handler(&self) -> u16; fn is_static(&self) -> bool; - fn is_kernel(&self) -> bool { - todo!() - } + fn is_kernel(&self) -> bool; fn gas(&self) -> u32; fn set_gas(&mut self, new_gas: u32); @@ -314,6 +312,10 @@ impl CallframeInterface for DummyState { unimplemented!() } + fn is_kernel(&self) -> bool { + unimplemented!() + } + fn gas(&self) -> u32 { unimplemented!() } From 2daea310fb55c8481238e39fc25dd8260698344e Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 9 Aug 2024 15:30:48 +0200 Subject: [PATCH 25/61] add missing method --- src/tracing.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tracing.rs b/src/tracing.rs index 94b34e22..f7c52359 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -210,6 +210,10 @@ impl CallframeInterface for CallframeWrapper<'_, T> { self.frame.is_static } + fn is_kernel(&self) -> bool { + self.frame.is_kernel + } + fn stipend(&self) -> u32 { self.frame.stipend } From ce4ba4bf9ca8c43a284d90967a0042e625da25a2 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 14 Aug 2024 18:49:07 +0200 Subject: [PATCH 26/61] separate methods for aux heap --- eravm-stable-interface/src/tracer_interface.rs | 4 ++++ src/instruction_handlers/heap_access.rs | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 8b5e3e31..2e8d9e84 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -46,6 +46,8 @@ macro_rules! forall_opcodes { ); $m!(HeapRead, before_heap_read, after_heap_read); $m!(HeapWrite, before_heap_write, after_heap_write); + $m!(AuxHeapRead, before_aux_heap_read, after_aux_heap_read); + $m!(AuxHeapWrite, before_aux_heap_write, after_aux_heap_write); $m!(PointerRead, before_pointer_read, after_pointer_read); $m!(PointerAdd, before_pointer_add, after_pointer_add); $m!(PointerSub, before_pointer_sub, after_pointer_sub); @@ -176,6 +178,8 @@ pub mod opcodes { pub struct PrecompileCall; pub struct HeapRead; pub struct HeapWrite; + pub struct AuxHeapRead; + pub struct AuxHeapWrite; pub struct PointerRead; pub struct PointerAdd; pub struct PointerSub; diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index 4a805239..0ccf89a7 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -9,7 +9,7 @@ use crate::{ state::State, ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, Tracer}; +use eravm_stable_interface::{opcodes, NotifyTracer, Tracer}; use std::ops::Range; use u256::U256; @@ -22,6 +22,8 @@ pub trait HeapInterface { pub trait HeapFromState { fn get_heap(state: &State) -> HeapId; fn get_heap_size(state: &mut State) -> &mut u32; + type Read: NotifyTracer; + type Write: NotifyTracer; } pub struct Heap; @@ -32,6 +34,8 @@ impl HeapFromState for Heap { fn get_heap_size(state: &mut State) -> &mut u32 { &mut state.current_frame.heap_size } + type Read = opcodes::HeapRead; + type Write = opcodes::HeapWrite; } pub struct AuxHeap; @@ -42,6 +46,8 @@ impl HeapFromState for AuxHeap { fn get_heap_size(state: &mut State) -> &mut u32 { &mut state.current_frame.aux_heap_size } + type Read = opcodes::AuxHeapRead; + type Write = opcodes::AuxHeapWrite; } /// The last address to which 32 can be added without overflow. @@ -52,7 +58,7 @@ fn load( world: &mut dyn World, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |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); @@ -94,7 +100,7 @@ fn store< world: &mut dyn World, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::(vm, world, tracer, |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); From f0113e0718a678935bc4ec5e75e23748514172db Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 14 Aug 2024 19:02:56 +0200 Subject: [PATCH 27/61] NotifyTracer to vm; expose forall_opcodes --- .../src/tracer_interface.rs | 24 +---------------- src/instruction_handlers/binop.rs | 4 +-- src/instruction_handlers/common.rs | 26 ++++++++++++++++++- src/instruction_handlers/context.rs | 4 +-- src/instruction_handlers/heap_access.rs | 4 +-- src/instruction_handlers/pointer.rs | 4 +-- 6 files changed, 34 insertions(+), 32 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 2e8d9e84..dc41d83d 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -1,5 +1,6 @@ use crate::StateInterface; +#[macro_export] macro_rules! forall_opcodes { ($m: ident) => { $m!(Nop, before_nop, after_nop); @@ -123,28 +124,6 @@ impl Tracer for (A, B) { forall_opcodes!(dispatch_to_tracer_tuple); } -/// Call the before/after_{opcode} method of the tracer. -pub trait NotifyTracer { - fn before(tracer: &mut T, state: &mut S); - fn after(tracer: &mut T, state: &mut S); -} - -macro_rules! implement_notify_tracer { - ($opcode:ident, $before_method:ident, $after_method:ident) => { - impl NotifyTracer for $opcode { - fn before(tracer: &mut T, state: &mut S) { - tracer.$before_method(state) - } - - fn after(tracer: &mut T, state: &mut S) { - tracer.$after_method(state) - } - } - }; -} - -forall_opcodes!(implement_notify_tracer); - pub mod opcodes { pub struct Nop; pub struct Add; @@ -192,7 +171,6 @@ pub mod opcodes { pub struct StaticMemoryRead; pub struct StaticMemoryWrite; } -use opcodes::*; #[cfg(test)] mod tests { diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index e817c921..037a21e0 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::{instruction_boilerplate, NotifyTracer}; use crate::{ addressing_modes::{ AbsoluteStack, Addressable, AdvanceStackPointer, AnyDestination, AnySource, Arguments, @@ -11,7 +11,7 @@ use crate::{ }; use eravm_stable_interface::{ opcodes::{Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor}, - NotifyTracer, Tracer, + Tracer, }; use u256::U256; diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 29fe491a..eb2b488f 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,5 +1,5 @@ use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; -use eravm_stable_interface::{NotifyTracer, Tracer}; +use eravm_stable_interface::{forall_opcodes, StateInterface, Tracer}; #[inline(always)] pub(crate) fn instruction_boilerplate( @@ -41,3 +41,27 @@ pub(crate) fn instruction_boilerplate_ext( result } + +/// Call the before/after_{opcode} method of the tracer. +/// Implemented for all ZSTs representing opcodes. +pub trait NotifyTracer { + fn before(tracer: &mut T, state: &mut S); + fn after(tracer: &mut T, state: &mut S); +} + +macro_rules! implement_notify_tracer { + ($opcode:ident, $before_method:ident, $after_method:ident) => { + impl NotifyTracer for $opcode { + fn before(tracer: &mut T, state: &mut S) { + tracer.$before_method(state) + } + + fn after(tracer: &mut T, state: &mut S) { + tracer.$after_method(state) + } + } + }; +} + +use eravm_stable_interface::opcodes::*; +forall_opcodes!(implement_notify_tracer); diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index edeeeb5d..ab687e10 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::{instruction_boilerplate, NotifyTracer}; use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, decommit::address_into_u256, @@ -8,7 +8,7 @@ use crate::{ }; use eravm_stable_interface::{ opcodes::{self, Caller, CodeAddress, ErgsLeft, This, SP, U128}, - NotifyTracer, Tracer, + Tracer, }; use u256::U256; use zkevm_opcode_defs::VmMetaParameters; diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index 0ccf89a7..50d3b22f 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -1,4 +1,4 @@ -use super::common::{instruction_boilerplate, instruction_boilerplate_ext}; +use super::common::{instruction_boilerplate, instruction_boilerplate_ext, NotifyTracer}; use crate::{ addressing_modes::{ Arguments, Destination, DestinationWriter, Immediate1, Register1, Register2, @@ -9,7 +9,7 @@ use crate::{ state::State, ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, NotifyTracer, Tracer}; +use eravm_stable_interface::{opcodes, Tracer}; use std::ops::Range; use u256::U256; diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index 5d270f94..997bc894 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::{instruction_boilerplate, NotifyTracer}; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, @@ -10,7 +10,7 @@ use crate::{ }; use eravm_stable_interface::{ opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}, - NotifyTracer, Tracer, + Tracer, }; use u256::U256; From 01d77c5079e2d6273b8f44b0cc630fff0c3c9004 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 14 Aug 2024 20:30:55 +0200 Subject: [PATCH 28/61] write test for callframe picking and fix bugs --- src/tracing.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/src/tracing.rs b/src/tracing.rs index f7c52359..14d36c35 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -32,9 +32,9 @@ impl StateInterface for VirtualMachine { + 1 } - fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_ { + fn callframe(&mut self, mut n: usize) -> impl CallframeInterface + '_ { for far_frame in std::iter::once(&mut self.state.current_frame) - .chain(self.state.previous_frames.iter_mut()) + .chain(self.state.previous_frames.iter_mut().rev()) { match n.cmp(&far_frame.near_calls.len()) { Ordering::Less => { @@ -49,7 +49,7 @@ impl StateInterface for VirtualMachine { near_call: None, } } - _ => {} + Ordering::Greater => n -= far_frame.near_calls.len() + 1, } } panic!("Callframe index out of bounds") @@ -334,7 +334,7 @@ impl CallframeInterface for CallframeWrapper<'_, T> { fn exception_handler(&self) -> u16 { if let Some(i) = self.near_call { - self.frame.near_calls[i].exception_handler + self.frame.near_calls[self.frame.near_calls.len() - i - 1].exception_handler } else { self.frame.exception_handler } @@ -368,3 +368,71 @@ impl CallframeWrapper<'_, T> { } } } + +#[cfg(all(test, not(feature = "single_instruction_test")))] +mod test { + use super::*; + use crate::{initial_decommit, testworld::TestWorld, Instruction, Program, VirtualMachine}; + use eravm_stable_interface::HeapId; + use u256::H160; + use zkevm_opcode_defs::ethereum_types::Address; + + #[test] + fn callframe_picking() { + let program = Program::new(vec![Instruction::from_invalid()], vec![]); + + let address = Address::from_low_u64_be(0x1234567890abcdef); + let mut world = TestWorld::new(&[(address, program)]); + let program = initial_decommit(&mut world, address); + + let mut vm = VirtualMachine::<()>::new( + address, + program.clone(), + Address::zero(), + vec![], + 1000, + crate::Settings { + default_aa_code_hash: [0; 32], + evm_interpreter_code_hash: [0; 32], + hook_address: 0, + }, + ); + + let mut frame_count = 1; + + let add_far_frame = |vm: &mut VirtualMachine<()>, counter: &mut u16| { + vm.push_frame::<0>( + H160::from_low_u64_be(1), + program.clone(), + 0, + 0, + *counter, + false, + HeapId::from_u32_unchecked(5), + vm.world_diff.snapshot(), + ); + *counter += 1; + }; + + let add_near_frame = |vm: &mut VirtualMachine<()>, counter: &mut u16| { + vm.state + .current_frame + .push_near_call(0, *counter, vm.world_diff.snapshot()); + *counter += 1; + }; + + add_far_frame(&mut vm, &mut frame_count); + add_near_frame(&mut vm, &mut frame_count); + add_far_frame(&mut vm, &mut frame_count); + add_far_frame(&mut vm, &mut frame_count); + add_near_frame(&mut vm, &mut frame_count); + add_near_frame(&mut vm, &mut frame_count); + + for i in 0..frame_count { + assert_eq!( + vm.callframe(i as usize).exception_handler(), + frame_count - i - 1 + ); + } + } +} From ee09fd2a7bb57d47115439447c8e03dfbaac1465 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 15 Aug 2024 13:51:06 +0200 Subject: [PATCH 29/61] better name for context_u128 --- eravm-stable-interface/src/tracer_interface.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index dc41d83d..9d648463 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -26,7 +26,7 @@ macro_rules! forall_opcodes { $m!(Caller, before_caller, after_caller); $m!(CodeAddress, before_code_address, after_code_address); $m!(ErgsLeft, before_ergs_left, after_ergs_left); - $m!(U128, before_u128, after_u128); + $m!(ContextU128, before_context_u128, after_context_u128); $m!(SP, before_sp, after_sp); $m!(ContextMeta, before_context_meta, after_context_meta); $m!( @@ -148,9 +148,9 @@ pub mod opcodes { pub struct Caller; pub struct CodeAddress; pub struct ErgsLeft; - pub struct U128; pub struct SP; pub struct ContextMeta; + pub struct ContextU128; pub struct SetContextU128; pub struct IncrementTxNumber; pub struct AuxMutating0; From 481ce54b20f94e65ddea17e347c300885f657555 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 15 Aug 2024 14:06:36 +0200 Subject: [PATCH 30/61] fix previous --- src/instruction_handlers/context.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index ab687e10..939b2830 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -7,7 +7,7 @@ use crate::{ Instruction, VirtualMachine, World, }; use eravm_stable_interface::{ - opcodes::{self, Caller, CodeAddress, ErgsLeft, This, SP, U128}, + opcodes::{self, Caller, CodeAddress, ContextU128, ErgsLeft, This, SP}, Tracer, }; use u256::U256; @@ -52,7 +52,7 @@ impl ContextOp for ErgsLeft { } } -impl ContextOp for U128 { +impl ContextOp for ContextU128 { fn get(state: &State) -> U256 { state.get_context_u128().into() } @@ -141,7 +141,7 @@ impl Instruction { Self::from_context::(out, arguments) } pub fn from_context_u128(out: Register1, arguments: Arguments) -> Self { - Self::from_context::(out, arguments) + Self::from_context::(out, arguments) } pub fn from_context_sp(out: Register1, arguments: Arguments) -> Self { Self::from_context::(out, arguments) From 5757076ddc89eafe6257f8801d91b4c0ff4b37a1 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 15 Aug 2024 14:51:56 +0200 Subject: [PATCH 31/61] trace Nop when running a disabled instruction --- src/instruction_handlers/mod.rs | 1 + src/vm.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index 4a842216..6011fce4 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -1,3 +1,4 @@ +pub(crate) use common::NotifyTracer; pub use eravm_stable_interface::opcodes::{ Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, diff --git a/src/vm.rs b/src/vm.rs index a2789c5d..65945d0e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,14 +6,14 @@ use crate::world_diff::ExternalSnapshot; use crate::{ callframe::{Callframe, FrameRemnant}, decommit::u256_into_address, - instruction_handlers::{free_panic, CallingMode}, + instruction_handlers::{free_panic, CallingMode, NotifyTracer}, stack::StackPool, state::State, world_diff::{Snapshot, WorldDiff}, ExecutionEnd, Program, World, }; use crate::{Instruction, ModeRequirements, Predicate}; -use eravm_stable_interface::{HeapId, Tracer}; +use eravm_stable_interface::{opcodes, HeapId, Tracer}; use u256::H160; #[derive(Debug)] @@ -100,7 +100,9 @@ impl VirtualMachine { return end; }; } else { + opcodes::Nop::before(tracer, self); self.state.current_frame.pc = self.state.current_frame.pc.add(1); + opcodes::Nop::after(tracer, self); } } } From 16d8603d886d8b74556b87ea3572f7c11489b8ba Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 13:18:27 +0200 Subject: [PATCH 32/61] more ergornomic tracer interface --- .../src/tracer_interface.rs | 299 +++++++++--------- src/instruction_handlers/binop.rs | 6 +- src/instruction_handlers/common.rs | 38 +-- src/instruction_handlers/context.rs | 6 +- src/instruction_handlers/heap_access.rs | 8 +- src/instruction_handlers/mod.rs | 1 - src/instruction_handlers/pointer.rs | 6 +- src/vm.rs | 6 +- 8 files changed, 165 insertions(+), 205 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 9d648463..1ac9e948 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -1,186 +1,171 @@ use crate::StateInterface; -#[macro_export] macro_rules! forall_opcodes { - ($m: ident) => { - $m!(Nop, before_nop, after_nop); - $m!(Add, before_add, after_add); - $m!(Sub, before_sub, after_sub); - $m!(And, before_and, after_and); - $m!(Or, before_or, after_or); - $m!(Xor, before_xor, after_xor); - $m!(ShiftLeft, before_shift_left, after_shift_left); - $m!(ShiftRight, before_shift_right, after_shift_right); - $m!(RotateLeft, before_rotate_left, after_rotate_left); - $m!(RotateRight, before_rotate_right, after_rotate_right); - $m!(Mul, before_mul, after_mul); - $m!(Div, before_div, after_div); - $m!(NearCall, before_near_call, after_near_call); - $m!(FarCall, before_far_call, after_far_call); - $m!(Ret, before_ret, after_ret); - $m!(Jump, before_jump, after_jump); - $m!(Event, before_event, after_event); - $m!(L2ToL1Message, before_l1_message, after_l1_message); - $m!(Decommit, before_decommit, after_decommit); - $m!(This, before_this, after_this); - $m!(Caller, before_caller, after_caller); - $m!(CodeAddress, before_code_address, after_code_address); - $m!(ErgsLeft, before_ergs_left, after_ergs_left); - $m!(ContextU128, before_context_u128, after_context_u128); - $m!(SP, before_sp, after_sp); - $m!(ContextMeta, before_context_meta, after_context_meta); - $m!( - SetContextU128, - before_set_context_u128, - after_set_context_u128 - ); - $m!( - IncrementTxNumber, - before_increment_tx_number, - after_increment_tx_number - ); - $m!(AuxMutating0, before_aux_mutating0, after_aux_mutating0); - $m!( - PrecompileCall, - before_precompile_call, - after_precompile_call - ); - $m!(HeapRead, before_heap_read, after_heap_read); - $m!(HeapWrite, before_heap_write, after_heap_write); - $m!(AuxHeapRead, before_aux_heap_read, after_aux_heap_read); - $m!(AuxHeapWrite, before_aux_heap_write, after_aux_heap_write); - $m!(PointerRead, before_pointer_read, after_pointer_read); - $m!(PointerAdd, before_pointer_add, after_pointer_add); - $m!(PointerSub, before_pointer_sub, after_pointer_sub); - $m!(PointerPack, before_pointer_pack, after_pointer_pack); - $m!(PointerShrink, before_pointer_shrink, after_pointer_shrink); - $m!(StorageRead, before_storage_read, after_storage_read); - $m!(StorageWrite, before_storage_write, after_storage_write); - $m!( - TransientStorageRead, - before_transient_storage_read, - after_transient_storage_read - ); - $m!( - TransientStorageWrite, - before_transient_storage_write, - after_transient_storage_write - ); - $m!( - StaticMemoryRead, - before_static_memory_read, - after_static_memory_read - ); - $m!( - StaticMemoryWrite, - before_static_memory_write, - after_static_memory_write - ); + ($m:ident) => { + $m!(Nop); + $m!(Add); + $m!(Sub); + $m!(And); + $m!(Or); + $m!(Xor); + $m!(ShiftLeft); + $m!(ShiftRight); + $m!(RotateLeft); + $m!(RotateRight); + $m!(Mul); + $m!(Div); + $m!(NearCall); + $m!(FarCall); + $m!(Ret); + $m!(Jump); + $m!(Event); + $m!(L2ToL1Message); + $m!(Decommit); + $m!(This); + $m!(Caller); + $m!(CodeAddress); + $m!(ErgsLeft); + $m!(SP); + $m!(ContextMeta); + $m!(ContextU128); + $m!(SetContextU128); + $m!(IncrementTxNumber); + $m!(AuxMutating0); + $m!(PrecompileCall); + $m!(HeapRead); + $m!(HeapWrite); + $m!(AuxHeapRead); + $m!(AuxHeapWrite); + $m!(PointerRead); + $m!(PointerAdd); + $m!(PointerSub); + $m!(PointerPack); + $m!(PointerShrink); + $m!(StorageRead); + $m!(StorageWrite); + $m!(TransientStorageRead); + $m!(TransientStorageWrite); }; } -macro_rules! into_default_method_implementations { - ($op:ident, $before_method:ident, $after_method:ident) => { - #[inline(always)] - fn $before_method(&mut self, _state: &mut S) {} - #[inline(always)] - fn $after_method(&mut self, _state: &mut S) {} +macro_rules! pub_struct { + ($x:ident) => { + pub struct $x; }; } +pub mod opcodes { + forall_opcodes!(pub_struct); +} + +#[derive(PartialEq, Eq)] +pub enum Opcode { + Nop, + Add, + Sub, + And, + Or, + Xor, + ShiftLeft, + ShiftRight, + RotateLeft, + RotateRight, + Mul, + Div, + NearCall, + FarCall, + Ret, + Jump, + Event, + L2ToL1Message, + Decommit, + This, + Caller, + CodeAddress, + ErgsLeft, + SP, + ContextMeta, + ContextU128, + SetContextU128, + IncrementTxNumber, + AuxMutating0, + PrecompileCall, + HeapRead, + HeapWrite, + AuxHeapRead, + AuxHeapWrite, + PointerRead, + PointerAdd, + PointerSub, + PointerPack, + PointerShrink, + StorageRead, + StorageWrite, + TransientStorageRead, + TransientStorageWrite, +} + +pub trait OpcodeType { + const VALUE: Opcode; +} + +macro_rules! impl_opcode { + ($x:ident) => { + impl OpcodeType for opcodes::$x { + const VALUE: Opcode = Opcode::$x; + } + }; +} + +forall_opcodes!(impl_opcode); + +pub trait Tracer { + fn before_instruction(&mut self, _state: &mut S) {} + fn after_instruction(&mut self, _state: &mut S) {} +} + /// For example, here `FarCallCounter` counts the number of far calls. /// ``` -/// use eravm_stable_interface::{Tracer, opcodes, StateInterface}; +/// use eravm_stable_interface::{Tracer, StateInterface, OpcodeType, Opcode}; /// struct FarCallCounter(usize); /// impl Tracer for FarCallCounter { -/// fn before_far_call(&mut self, state: &mut S) { -/// self.0 += 1; +/// fn before_instruction(&mut self, state: &mut S) { +/// match OP::VALUE { +/// Opcode::FarCall => self.0 += 1, +/// _ => {} +/// } /// } /// } /// ``` -pub trait Tracer { - forall_opcodes! { - into_default_method_implementations - } -} impl Tracer for () {} -macro_rules! dispatch_to_tracer_tuple { - ($op:ident, $before_method:ident, $after_method:ident) => { - fn $before_method(&mut self, state: &mut S) { - self.0.$before_method(state); - self.1.$before_method(state); - } - fn $after_method(&mut self, state: &mut S) { - self.0.$after_method(state); - self.1.$after_method(state); - } - }; -} - // Multiple tracers can be combined by building a linked list out of tuples. impl Tracer for (A, B) { - forall_opcodes!(dispatch_to_tracer_tuple); -} + fn before_instruction(&mut self, state: &mut S) { + self.0.before_instruction::(state); + self.1.before_instruction::(state); + } -pub mod opcodes { - pub struct Nop; - pub struct Add; - pub struct Sub; - pub struct And; - pub struct Or; - pub struct Xor; - pub struct ShiftLeft; - pub struct ShiftRight; - pub struct RotateLeft; - pub struct RotateRight; - pub struct Mul; - pub struct Div; - pub struct NearCall; - pub struct FarCall; - pub struct Ret; - pub struct Jump; - pub struct Event; - pub struct L2ToL1Message; - pub struct Decommit; - pub struct This; - pub struct Caller; - pub struct CodeAddress; - pub struct ErgsLeft; - pub struct SP; - pub struct ContextMeta; - pub struct ContextU128; - pub struct SetContextU128; - pub struct IncrementTxNumber; - pub struct AuxMutating0; - pub struct PrecompileCall; - pub struct HeapRead; - pub struct HeapWrite; - pub struct AuxHeapRead; - pub struct AuxHeapWrite; - pub struct PointerRead; - pub struct PointerAdd; - pub struct PointerSub; - pub struct PointerPack; - pub struct PointerShrink; - pub struct StorageRead; - pub struct StorageWrite; - pub struct TransientStorageRead; - pub struct TransientStorageWrite; - pub struct StaticMemoryRead; - pub struct StaticMemoryWrite; + fn after_instruction(&mut self, state: &mut S) { + self.0.after_instruction::(state); + self.1.after_instruction::(state); + } } #[cfg(test)] mod tests { - use crate::{DummyState, Tracer}; + use crate::{opcodes, DummyState, Tracer}; + + use super::OpcodeType; struct FarCallCounter(usize); impl Tracer for FarCallCounter { - fn before_far_call(&mut self, _: &mut S) { - self.0 += 1; + fn before_instruction(&mut self, _: &mut S) { + if OP::VALUE == super::Opcode::FarCall { + self.0 += 1; + } } } @@ -188,10 +173,10 @@ mod tests { fn test_tracer() { let mut tracer = FarCallCounter(0); - tracer.before_nop(&mut DummyState); + tracer.before_instruction::(&mut DummyState); assert_eq!(tracer.0, 0); - tracer.before_far_call(&mut DummyState); + tracer.before_instruction::(&mut DummyState); assert_eq!(tracer.0, 1); } @@ -199,12 +184,12 @@ mod tests { fn test_aggregate_tracer() { let mut tracer = (FarCallCounter(0), (FarCallCounter(0), FarCallCounter(0))); - tracer.before_nop(&mut DummyState); + tracer.before_instruction::(&mut DummyState); assert_eq!(tracer.0 .0, 0); assert_eq!(tracer.1 .0 .0, 0); assert_eq!(tracer.1 .1 .0, 0); - tracer.before_far_call(&mut DummyState); + tracer.before_instruction::(&mut DummyState); assert_eq!(tracer.0 .0, 1); assert_eq!(tracer.1 .0 .0, 1); assert_eq!(tracer.1 .1 .0, 1); diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index 037a21e0..b2f83c27 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -1,4 +1,4 @@ -use super::common::{instruction_boilerplate, NotifyTracer}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, Addressable, AdvanceStackPointer, AnyDestination, AnySource, Arguments, @@ -11,7 +11,7 @@ use crate::{ }; use eravm_stable_interface::{ opcodes::{Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor}, - Tracer, + OpcodeType, Tracer, }; use u256::U256; @@ -41,7 +41,7 @@ fn binop< }) } -pub trait Binop: NotifyTracer { +pub trait Binop: OpcodeType { type Out2: SecondOutput; fn perform(a: &U256, b: &U256) -> (U256, Self::Out2, Flags); } diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index eb2b488f..791f8586 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,26 +1,26 @@ use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; -use eravm_stable_interface::{forall_opcodes, StateInterface, Tracer}; +use eravm_stable_interface::{OpcodeType, Tracer}; #[inline(always)] -pub(crate) fn instruction_boilerplate( +pub(crate) fn instruction_boilerplate( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), ) -> ExecutionStatus { - Opcode::before(tracer, vm); + tracer.before_instruction::(vm); unsafe { let instruction = vm.state.current_frame.pc; vm.state.current_frame.pc = instruction.add(1); business_logic(vm, &(*instruction).arguments, world); }; - Opcode::after(tracer, vm); + tracer.after_instruction::(vm); ExecutionStatus::Running } #[inline(always)] -pub(crate) fn instruction_boilerplate_ext( +pub(crate) fn instruction_boilerplate_ext( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, @@ -30,38 +30,14 @@ pub(crate) fn instruction_boilerplate_ext( &mut dyn World, ) -> ExecutionStatus, ) -> ExecutionStatus { - Opcode::before(tracer, vm); + tracer.before_instruction::(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) }; - Opcode::after(tracer, vm); + tracer.after_instruction::(vm); result } - -/// Call the before/after_{opcode} method of the tracer. -/// Implemented for all ZSTs representing opcodes. -pub trait NotifyTracer { - fn before(tracer: &mut T, state: &mut S); - fn after(tracer: &mut T, state: &mut S); -} - -macro_rules! implement_notify_tracer { - ($opcode:ident, $before_method:ident, $after_method:ident) => { - impl NotifyTracer for $opcode { - fn before(tracer: &mut T, state: &mut S) { - tracer.$before_method(state) - } - - fn after(tracer: &mut T, state: &mut S) { - tracer.$after_method(state) - } - } - }; -} - -use eravm_stable_interface::opcodes::*; -forall_opcodes!(implement_notify_tracer); diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index 939b2830..93cf80a9 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -1,4 +1,4 @@ -use super::common::{instruction_boilerplate, NotifyTracer}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, decommit::address_into_u256, @@ -8,7 +8,7 @@ use crate::{ }; use eravm_stable_interface::{ opcodes::{self, Caller, CodeAddress, ContextU128, ErgsLeft, This, SP}, - Tracer, + OpcodeType, Tracer, }; use u256::U256; use zkevm_opcode_defs::VmMetaParameters; @@ -24,7 +24,7 @@ fn context( }) } -trait ContextOp: NotifyTracer { +trait ContextOp: OpcodeType { fn get(state: &State) -> U256; } diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index 50d3b22f..31d249c3 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -1,4 +1,4 @@ -use super::common::{instruction_boilerplate, instruction_boilerplate_ext, NotifyTracer}; +use super::common::{instruction_boilerplate, instruction_boilerplate_ext}; use crate::{ addressing_modes::{ Arguments, Destination, DestinationWriter, Immediate1, Register1, Register2, @@ -9,7 +9,7 @@ use crate::{ state::State, ExecutionEnd, HeapId, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, Tracer}; +use eravm_stable_interface::{opcodes, OpcodeType, Tracer}; use std::ops::Range; use u256::U256; @@ -22,8 +22,8 @@ pub trait HeapInterface { pub trait HeapFromState { fn get_heap(state: &State) -> HeapId; fn get_heap_size(state: &mut State) -> &mut u32; - type Read: NotifyTracer; - type Write: NotifyTracer; + type Read: OpcodeType; + type Write: OpcodeType; } pub struct Heap; diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index 6011fce4..4a842216 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -1,4 +1,3 @@ -pub(crate) use common::NotifyTracer; pub use eravm_stable_interface::opcodes::{ Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index 997bc894..bb903247 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -1,4 +1,4 @@ -use super::common::{instruction_boilerplate, NotifyTracer}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, @@ -10,7 +10,7 @@ use crate::{ }; use eravm_stable_interface::{ opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}, - Tracer, + OpcodeType, Tracer, }; use u256::U256; @@ -46,7 +46,7 @@ fn ptr( }) } -pub trait PtrOp: NotifyTracer { +pub trait PtrOp: OpcodeType { fn perform(in1: U256, in2: U256) -> Option; } diff --git a/src/vm.rs b/src/vm.rs index 65945d0e..bd7668a6 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,7 +6,7 @@ use crate::world_diff::ExternalSnapshot; use crate::{ callframe::{Callframe, FrameRemnant}, decommit::u256_into_address, - instruction_handlers::{free_panic, CallingMode, NotifyTracer}, + instruction_handlers::{free_panic, CallingMode}, stack::StackPool, state::State, world_diff::{Snapshot, WorldDiff}, @@ -100,9 +100,9 @@ impl VirtualMachine { return end; }; } else { - opcodes::Nop::before(tracer, self); + tracer.before_instruction::(self); self.state.current_frame.pc = self.state.current_frame.pc.add(1); - opcodes::Nop::after(tracer, self); + tracer.after_instruction::(self); } } } From bd17a320a35372392438ec827a6b921a3f7f63d1 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 13:46:45 +0200 Subject: [PATCH 33/61] update docs on how to extend the interface --- eravm-stable-interface/src/lib.rs | 74 ++++++++++++++++++------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/eravm-stable-interface/src/lib.rs b/eravm-stable-interface/src/lib.rs index 1a868040..17b3d0d7 100644 --- a/eravm-stable-interface/src/lib.rs +++ b/eravm-stable-interface/src/lib.rs @@ -24,46 +24,60 @@ //! //! This is how you would add a new method to StateInterface and a new opcode. //! ``` -//! # trait StateInterface {} -//! # trait Tracer { -//! # #[inline(always)] -//! # fn before_old_opcode(&mut self, _state: &mut S) {} -//! # #[inline(always)] -//! # fn after_old_opcode(&mut self, _state: &mut S) {} -//! # } -//! -//! trait StateInterfaceV2: StateInterface { +//! # use eravm_stable_interface as eravm_stable_interface_v1; +//! use eravm_stable_interface_v1::{StateInterface as StateInterfaceV1, Tracer as TracerV1, opcodes::NearCall}; +//! +//! trait StateInterface: StateInterfaceV1 { //! fn get_some_new_field(&self) -> u32; //! } //! -//! trait TracerV2 { -//! // redefine all existing opcodes -//! #[inline(always)] -//! fn before_old_opcode(&mut self, _state: &mut S) {} -//! #[inline(always)] -//! fn after_old_opcode(&mut self, _state: &mut S) {} -//! -//! #[inline(always)] -//! fn before_new_opcode(&mut self, _state: &mut S) {} -//! #[inline(always)] -//! fn after_new_opcode(&mut self, _state: &mut S) {} +//! pub struct NewOpcode; +//! +//! #[derive(PartialEq, Eq)] +//! enum Opcode { +//! NewOpcode, +//! NearCall, +//! // ... //! } //! -//! impl TracerV2 for T { -//! // repeat this for all existing opcodes -//! fn before_old_opcode(&mut self, state: &mut S) { -//! ::before_old_opcode(self, state); -//! } -//! fn after_old_opcode(&mut self, state: &mut S) { -//! ::after_old_opcode(self, state); +//! trait OpcodeType { +//! const VALUE: Opcode; +//! } +//! +//! impl OpcodeType for NewOpcode { +//! const VALUE: Opcode = Opcode::NewOpcode; +//! } +//! +//! // Do this for every old opcode +//! impl OpcodeType for NearCall { +//! const VALUE: Opcode = Opcode::NearCall; +//! } +//! +//! trait Tracer { +//! 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) { +//! match OP::VALUE { +//! Opcode::NewOpcode => {} +//! // Do this for every old opcode +//! Opcode::NearCall => { +//! ::before_instruction::(self, state) +//! } +//! } //! } +//! fn after_instruction(&mut self, _state: &mut S) {} //! } //! //! // Now you can use the new features by implementing TracerV2 //! struct MyTracer; -//! impl TracerV2 for MyTracer { -//! fn before_new_opcode(&mut self, state: &mut S) { -//! state.get_some_new_field(); +//! impl Tracer for MyTracer { +//! fn before_instruction(&mut self, state: &mut S) { +//! if OP::VALUE == Opcode::NewOpcode { +//! state.get_some_new_field(); +//! } //! } //! } //! ``` From 717120f2e806ceeb990af5d0e8946ed4de4b77c0 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 14:23:20 +0200 Subject: [PATCH 34/61] expose farcall and ret variants --- .../src/tracer_interface.rs | 87 ++++++++++++++++--- src/decode.rs | 6 +- src/instruction_handlers/far_call.rs | 11 +-- src/instruction_handlers/mod.rs | 1 - src/instruction_handlers/ret.rs | 27 +----- src/vm.rs | 4 +- tests/stipend.rs | 3 +- 7 files changed, 84 insertions(+), 55 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 1ac9e948..2a6d64c1 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -1,6 +1,6 @@ use crate::StateInterface; -macro_rules! forall_opcodes { +macro_rules! forall_simple_opcodes { ($m:ident) => { $m!(Nop); $m!(Add); @@ -15,8 +15,6 @@ macro_rules! forall_opcodes { $m!(Mul); $m!(Div); $m!(NearCall); - $m!(FarCall); - $m!(Ret); $m!(Jump); $m!(Event); $m!(L2ToL1Message); @@ -55,7 +53,9 @@ macro_rules! pub_struct { } pub mod opcodes { - forall_opcodes!(pub_struct); + forall_simple_opcodes!(pub_struct); + pub struct FarCall; + pub struct Ret; } #[derive(PartialEq, Eq)] @@ -73,8 +73,8 @@ pub enum Opcode { Mul, Div, NearCall, - FarCall, - Ret, + FarCall(CallingMode), + Ret(ReturnType), Jump, Event, L2ToL1Message, @@ -105,6 +105,48 @@ pub enum Opcode { TransientStorageWrite, } +#[derive(PartialEq, Eq)] +#[repr(u8)] +pub enum CallingMode { + Normal = 0, + Delegate, + Mimic, +} + +impl CallingMode { + pub const fn from_u8(value: u8) -> Self { + match value { + 0 => CallingMode::Normal, + 1 => CallingMode::Delegate, + 2 => CallingMode::Mimic, + _ => unreachable!(), + } + } +} + +#[repr(u8)] +#[derive(PartialEq, Eq)] +pub enum ReturnType { + Normal = 0, + Revert, + Panic, +} + +impl ReturnType { + pub fn is_failure(&self) -> bool { + *self != ReturnType::Normal + } + + pub const fn from_u8(value: u8) -> Self { + match value { + 0 => ReturnType::Normal, + 1 => ReturnType::Revert, + 2 => ReturnType::Panic, + _ => unreachable!(), + } + } +} + pub trait OpcodeType { const VALUE: Opcode; } @@ -117,7 +159,15 @@ macro_rules! impl_opcode { }; } -forall_opcodes!(impl_opcode); +forall_simple_opcodes!(impl_opcode); + +impl OpcodeType for opcodes::FarCall { + const VALUE: Opcode = Opcode::FarCall(CallingMode::from_u8(M)); +} + +impl OpcodeType for opcodes::Ret { + const VALUE: Opcode = Opcode::Ret(ReturnType::from_u8(M)); +} pub trait Tracer { fn before_instruction(&mut self, _state: &mut S) {} @@ -131,7 +181,7 @@ pub trait Tracer { /// impl Tracer for FarCallCounter { /// fn before_instruction(&mut self, state: &mut S) { /// match OP::VALUE { -/// Opcode::FarCall => self.0 += 1, +/// Opcode::FarCall(_) => self.0 += 1, /// _ => {} /// } /// } @@ -155,16 +205,16 @@ impl Tracer for (A, B) { #[cfg(test)] mod tests { + use super::{CallingMode, OpcodeType}; use crate::{opcodes, DummyState, Tracer}; - use super::OpcodeType; - struct FarCallCounter(usize); impl Tracer for FarCallCounter { fn before_instruction(&mut self, _: &mut S) { - if OP::VALUE == super::Opcode::FarCall { - self.0 += 1; + match OP::VALUE { + super::Opcode::FarCall(CallingMode::Normal) => self.0 += 1, + _ => {} } } } @@ -176,7 +226,14 @@ mod tests { tracer.before_instruction::(&mut DummyState); assert_eq!(tracer.0, 0); - tracer.before_instruction::(&mut DummyState); + tracer.before_instruction::, _>( + &mut DummyState, + ); + assert_eq!(tracer.0, 1); + + tracer.before_instruction::, _>( + &mut DummyState, + ); assert_eq!(tracer.0, 1); } @@ -189,7 +246,9 @@ mod tests { assert_eq!(tracer.1 .0 .0, 0); assert_eq!(tracer.1 .1 .0, 0); - tracer.before_instruction::(&mut DummyState); + tracer.before_instruction::, _>( + &mut DummyState, + ); assert_eq!(tracer.0 .0, 1); assert_eq!(tracer.1 .0 .0, 1); assert_eq!(tracer.1 .1 .0, 1); diff --git a/src/decode.rs b/src/decode.rs index c5ea95f8..937e2070 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -6,14 +6,14 @@ use crate::{ }, instruction::{ExecutionEnd, ExecutionStatus}, instruction_handlers::{ - Add, And, AuxHeap, CallingMode, Div, Heap, Mul, Or, PointerAdd, PointerPack, PointerShrink, - PointerSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, + Add, And, AuxHeap, Div, Heap, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, + RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, }, jump_to_beginning, mode_requirements::ModeRequirements, Instruction, Predicate, VirtualMachine, World, }; -use eravm_stable_interface::Tracer; +use eravm_stable_interface::{CallingMode, Tracer}; use zkevm_opcode_defs::{ decoding::{EncodingModeProduction, VmEncodingMode}, ImmMemHandlerFlags, Opcode, diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 0eaa534a..30e1f8a4 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -12,20 +12,13 @@ use crate::{ predication::Flags, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, Tracer}; +use eravm_stable_interface::{opcodes::FarCall, Tracer}; use u256::U256; use zkevm_opcode_defs::{ system_params::{EVM_SIMULATOR_STIPEND, MSG_VALUE_SIMULATOR_ADDITIVE_COST}, ADDRESS_MSG_VALUE, }; -#[repr(u8)] -pub enum CallingMode { - Normal, - Delegate, - Mimic, -} - /// A call to another contract. /// /// First, the code of the called contract is fetched and a fat pointer is created @@ -41,7 +34,7 @@ fn far_call, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |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); diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index 4a842216..67214e54 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -2,7 +2,6 @@ pub use eravm_stable_interface::opcodes::{ Add, And, Div, Mul, Or, PointerAdd, PointerPack, PointerShrink, PointerSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, }; -pub use far_call::CallingMode; pub use heap_access::{AuxHeap, Heap, HeapInterface}; pub(crate) use ret::{free_panic, invalid_instruction, RETURN_COST}; diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index b20b13c1..0e6af7b9 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -7,38 +7,15 @@ use crate::{ predication::Flags, Instruction, Predicate, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, Tracer}; +use eravm_stable_interface::{opcodes, ReturnType, Tracer}; use u256::U256; -#[repr(u8)] -#[derive(PartialEq)] -enum ReturnType { - Normal = 0, - Revert, - Panic, -} - -impl ReturnType { - fn is_failure(&self) -> bool { - *self != ReturnType::Normal - } - - fn from_u8(value: u8) -> Self { - match value { - 0 => ReturnType::Normal, - 1 => ReturnType::Revert, - 2 => ReturnType::Panic, - _ => unreachable!(), - } - } -} - fn ret( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::(vm, world, tracer, |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; diff --git a/src/vm.rs b/src/vm.rs index bd7668a6..e2e83710 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,14 +6,14 @@ use crate::world_diff::ExternalSnapshot; use crate::{ callframe::{Callframe, FrameRemnant}, decommit::u256_into_address, - instruction_handlers::{free_panic, CallingMode}, + instruction_handlers::free_panic, stack::StackPool, state::State, world_diff::{Snapshot, WorldDiff}, ExecutionEnd, Program, World, }; use crate::{Instruction, ModeRequirements, Predicate}; -use eravm_stable_interface::{opcodes, HeapId, Tracer}; +use eravm_stable_interface::{opcodes, CallingMode, HeapId, Tracer}; use u256::H160; #[derive(Debug)] diff --git a/tests/stipend.rs b/tests/stipend.rs index c7d2fc09..9fe6b391 100644 --- a/tests/stipend.rs +++ b/tests/stipend.rs @@ -1,5 +1,6 @@ #![cfg(not(feature = "single_instruction_test"))] +use eravm_stable_interface::CallingMode; use u256::U256; use vm2::{ address_into_u256, @@ -7,7 +8,7 @@ use vm2::{ Arguments, CodePage, Immediate1, Register, Register1, Register2, RegisterAndImmediate, }, initial_decommit, - instruction_handlers::{Add, CallingMode}, + instruction_handlers::Add, testworld::TestWorld, ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, VirtualMachine, }; From 373be9d049016a1d4fb60524865e0cea2f2d688b Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 14:28:50 +0200 Subject: [PATCH 35/61] remove static heap; nobody uses it --- eravm-stable-interface/src/state_interface.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index 1f8201d9..4c91e078 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -38,8 +38,6 @@ pub trait StateInterface { /// it is run in kernel mode fn run_arbitrary_code(code: &[u64]); - - fn static_heap(&self) -> HeapId; } pub struct Flags { @@ -264,10 +262,6 @@ impl StateInterface for DummyState { fn run_arbitrary_code(_: &[u64]) { unimplemented!() } - - fn static_heap(&self) -> crate::HeapId { - unimplemented!() - } } #[cfg(test)] From b4b0b720ab7478035cee171c3dc636616b9d1f7a Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 14:30:47 +0200 Subject: [PATCH 36/61] appease clippy --- eravm-stable-interface/src/tracer_interface.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 2a6d64c1..d82d0e84 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -212,9 +212,8 @@ mod tests { impl Tracer for FarCallCounter { fn before_instruction(&mut self, _: &mut S) { - match OP::VALUE { - super::Opcode::FarCall(CallingMode::Normal) => self.0 += 1, - _ => {} + if let super::Opcode::FarCall(CallingMode::Normal) = OP::VALUE { + self.0 += 1; } } } From 439338ec66bc51c7084acbad6773568928389b5e Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 14:38:54 +0200 Subject: [PATCH 37/61] remove problematic storage access for now --- eravm-stable-interface/src/state_interface.rs | 29 ------------------- src/tracing.rs | 25 ++++------------ 2 files changed, 5 insertions(+), 49 deletions(-) diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index 4c91e078..2bd52bb8 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -21,10 +21,6 @@ pub trait StateInterface { 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; @@ -190,31 +186,6 @@ impl StateInterface for DummyState { std::iter::empty() } - fn get_storage( - &self, - _: primitive_types::H160, - _: primitive_types::U256, - ) -> Option<(primitive_types::U256, u32)> { - unimplemented!() - } - - fn get_storage_initial_value( - &self, - _: primitive_types::H160, - _: primitive_types::U256, - ) -> primitive_types::U256 { - unimplemented!() - } - - fn write_storage( - &mut self, - _: primitive_types::H160, - _: primitive_types::U256, - _: primitive_types::U256, - ) { - unimplemented!() - } - fn get_transient_storage_state( &self, ) -> impl Iterator< diff --git a/src/tracing.rs b/src/tracing.rs index 14d36c35..c3867205 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -99,18 +99,6 @@ impl StateInterface for VirtualMachine { .map(|(key, value)| (*key, *value)) } - fn get_storage(&self, _address: u256::H160, _slot: u256::U256) -> Option<(u256::U256, u32)> { - todo!() // Do we really want to expose the pubdata? - } - - fn get_storage_initial_value(&self, _address: u256::H160, _slot: u256::U256) -> u256::U256 { - todo!() // Do we really want to expose the caching? - } - - fn write_storage(&mut self, _address: u256::H160, _slot: u256::U256, _value: u256::U256) { - todo!() - } - fn get_transient_storage_state( &self, ) -> impl Iterator { @@ -130,11 +118,12 @@ impl StateInterface for VirtualMachine { fn write_transient_storage( &mut self, - _address: u256::H160, - _slot: u256::U256, - _value: u256::U256, + address: u256::H160, + slot: u256::U256, + value: u256::U256, ) { - todo!() + self.world_diff + .write_transient_storage(address, slot, value) } fn events(&self) -> impl Iterator { @@ -169,10 +158,6 @@ impl StateInterface for VirtualMachine { fn run_arbitrary_code(_code: &[u64]) { todo!() } - - fn static_heap(&self) -> HeapId { - todo!() - } } struct CallframeWrapper<'a, T> { From 58961c645bed970beb38eccd0e1cf8d1234cf0eb Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 14:47:02 +0200 Subject: [PATCH 38/61] remove run_arbitrary_code --- eravm-stable-interface/src/state_interface.rs | 7 ------- src/tracing.rs | 4 ---- 2 files changed, 11 deletions(-) diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index 2bd52bb8..cfccce75 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -31,9 +31,6 @@ pub trait StateInterface { fn pubdata(&self) -> i32; fn set_pubdata(&mut self, value: i32); - - /// it is run in kernel mode - fn run_arbitrary_code(code: &[u64]); } pub struct Flags { @@ -229,10 +226,6 @@ impl StateInterface for DummyState { fn set_pubdata(&mut self, _: i32) { unimplemented!() } - - fn run_arbitrary_code(_: &[u64]) { - unimplemented!() - } } #[cfg(test)] diff --git a/src/tracing.rs b/src/tracing.rs index c3867205..3ea0868d 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -154,10 +154,6 @@ impl StateInterface for VirtualMachine { fn set_pubdata(&mut self, value: i32) { self.world_diff.pubdata.0 = value; } - - fn run_arbitrary_code(_code: &[u64]) { - todo!() - } } struct CallframeWrapper<'a, T> { From 609450b4092483973565aec9353f2718c69e5833 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 19:15:36 +0200 Subject: [PATCH 39/61] expose tracer interface via VM for convenience --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3b86b764..4692af4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,9 @@ use u256::{H160, U256}; pub use decommit::address_into_u256; pub use decommit::initial_decommit; -pub use eravm_stable_interface::HeapId; +pub use eravm_stable_interface::{ + CallframeInterface, HeapId, Opcode, OpcodeType, StateInterface, Tracer, +}; pub use heap::FIRST_HEAP; pub use instruction::{jump_to_beginning, ExecutionEnd, Instruction}; pub use mode_requirements::ModeRequirements; From bf5866ef5bb2b3546cf15b9e80dc979417d0ca30 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 20:24:55 +0200 Subject: [PATCH 40/61] better document Tracer --- .../src/tracer_interface.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index d82d0e84..54ef3b53 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -169,12 +169,15 @@ impl OpcodeType for opcodes::Ret { const VALUE: Opcode = Opcode::Ret(ReturnType::from_u8(M)); } -pub trait Tracer { - fn before_instruction(&mut self, _state: &mut S) {} - fn after_instruction(&mut self, _state: &mut S) {} -} - -/// For example, here `FarCallCounter` counts the number of far calls. +/// Implement this for a type that holds the state of your tracer. +/// +/// [Tracer::before_instruction] is called just before the actual instruction is executed. +/// If the instruction is skipped, `before_instruction` will be called with [Nop](opcodes::Nop). +/// [Tracer::after_instruction] is called once the instruction is executed and the program +/// counter has advanced. +/// +/// # Examples +/// Here `FarCallCounter` counts the number of far calls. /// ``` /// use eravm_stable_interface::{Tracer, StateInterface, OpcodeType, Opcode}; /// struct FarCallCounter(usize); @@ -187,6 +190,10 @@ pub trait Tracer { /// } /// } /// ``` +pub trait Tracer { + fn before_instruction(&mut self, _state: &mut S) {} + fn after_instruction(&mut self, _state: &mut S) {} +} impl Tracer for () {} From 139f48612b96c0c917e1c2f353e4419a39759b0c Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 19 Aug 2024 21:49:29 +0200 Subject: [PATCH 41/61] replace const u8 with meaningful trait --- .../src/tracer_interface.rs | 86 ++++++++++--------- src/decode.rs | 8 +- src/instruction_handlers/far_call.rs | 15 ++-- src/instruction_handlers/ret.rs | 25 +++--- src/vm.rs | 18 ++-- 5 files changed, 81 insertions(+), 71 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 54ef3b53..7b11d69a 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -54,8 +54,44 @@ macro_rules! pub_struct { pub mod opcodes { forall_simple_opcodes!(pub_struct); - pub struct FarCall; - pub struct Ret; + pub struct FarCall(M); + pub struct Ret(T); + + pub struct Normal; + pub struct Delegate; + pub struct Mimic; + pub struct Revert; + pub struct Panic; + + use super::{CallingMode, ReturnType}; + + pub trait TypeLevelCallingMode { + const VALUE: CallingMode; + } + + impl TypeLevelCallingMode for Normal { + const VALUE: CallingMode = CallingMode::Normal; + } + impl TypeLevelCallingMode for Delegate { + const VALUE: CallingMode = CallingMode::Delegate; + } + impl TypeLevelCallingMode for Mimic { + const VALUE: CallingMode = CallingMode::Mimic; + } + + pub trait TypeLevelReturnType { + const VALUE: ReturnType; + } + + impl TypeLevelReturnType for Normal { + const VALUE: ReturnType = ReturnType::Normal; + } + impl TypeLevelReturnType for Revert { + const VALUE: ReturnType = ReturnType::Revert; + } + impl TypeLevelReturnType for Panic { + const VALUE: ReturnType = ReturnType::Panic; + } } #[derive(PartialEq, Eq)] @@ -106,28 +142,15 @@ pub enum Opcode { } #[derive(PartialEq, Eq)] -#[repr(u8)] pub enum CallingMode { - Normal = 0, + Normal, Delegate, Mimic, } -impl CallingMode { - pub const fn from_u8(value: u8) -> Self { - match value { - 0 => CallingMode::Normal, - 1 => CallingMode::Delegate, - 2 => CallingMode::Mimic, - _ => unreachable!(), - } - } -} - -#[repr(u8)] #[derive(PartialEq, Eq)] pub enum ReturnType { - Normal = 0, + Normal, Revert, Panic, } @@ -136,15 +159,6 @@ impl ReturnType { pub fn is_failure(&self) -> bool { *self != ReturnType::Normal } - - pub const fn from_u8(value: u8) -> Self { - match value { - 0 => ReturnType::Normal, - 1 => ReturnType::Revert, - 2 => ReturnType::Panic, - _ => unreachable!(), - } - } } pub trait OpcodeType { @@ -161,12 +175,12 @@ macro_rules! impl_opcode { forall_simple_opcodes!(impl_opcode); -impl OpcodeType for opcodes::FarCall { - const VALUE: Opcode = Opcode::FarCall(CallingMode::from_u8(M)); +impl OpcodeType for opcodes::FarCall { + const VALUE: Opcode = Opcode::FarCall(M::VALUE); } -impl OpcodeType for opcodes::Ret { - const VALUE: Opcode = Opcode::Ret(ReturnType::from_u8(M)); +impl OpcodeType for opcodes::Ret { + const VALUE: Opcode = Opcode::Ret(T::VALUE); } /// Implement this for a type that holds the state of your tracer. @@ -232,14 +246,10 @@ mod tests { tracer.before_instruction::(&mut DummyState); assert_eq!(tracer.0, 0); - tracer.before_instruction::, _>( - &mut DummyState, - ); + tracer.before_instruction::, _>(&mut DummyState); assert_eq!(tracer.0, 1); - tracer.before_instruction::, _>( - &mut DummyState, - ); + tracer.before_instruction::, _>(&mut DummyState); assert_eq!(tracer.0, 1); } @@ -252,9 +262,7 @@ mod tests { assert_eq!(tracer.1 .0 .0, 0); assert_eq!(tracer.1 .1 .0, 0); - tracer.before_instruction::, _>( - &mut DummyState, - ); + tracer.before_instruction::, _>(&mut DummyState); assert_eq!(tracer.0 .0, 1); assert_eq!(tracer.1 .0 .0, 1); assert_eq!(tracer.1 .1 .0, 1); diff --git a/src/decode.rs b/src/decode.rs index 937e2070..f891271b 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -13,7 +13,7 @@ use crate::{ mode_requirements::ModeRequirements, Instruction, Predicate, VirtualMachine, World, }; -use eravm_stable_interface::{CallingMode, Tracer}; +use eravm_stable_interface::{opcodes, Tracer}; use zkevm_opcode_defs::{ decoding::{EncodingModeProduction, VmEncodingMode}, ImmMemHandlerFlags, Opcode, @@ -210,13 +210,13 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction zkevm_opcode_defs::Opcode::FarCall(kind) => { let constructor = match kind { zkevm_opcode_defs::FarCallOpcode::Normal => { - Instruction::from_far_call::<{ CallingMode::Normal as u8 }> + Instruction::from_far_call:: } zkevm_opcode_defs::FarCallOpcode::Delegate => { - Instruction::from_far_call::<{ CallingMode::Delegate as u8 }> + Instruction::from_far_call:: } zkevm_opcode_defs::FarCallOpcode::Mimic => { - Instruction::from_far_call::<{ CallingMode::Mimic as u8 }> + Instruction::from_far_call:: } }; constructor( diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 30e1f8a4..b1678146 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -12,7 +12,10 @@ use crate::{ predication::Flags, Instruction, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes::FarCall, Tracer}; +use eravm_stable_interface::{ + opcodes::{FarCall, TypeLevelCallingMode}, + Tracer, +}; use u256::U256; use zkevm_opcode_defs::{ system_params::{EVM_SIMULATOR_STIPEND, MSG_VALUE_SIMULATOR_ADDITIVE_COST}, @@ -29,12 +32,12 @@ use zkevm_opcode_defs::{ /// /// 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( +fn far_call( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::, _>(vm, world, tracer, |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); @@ -111,7 +114,7 @@ fn far_call( + vm.push_frame::( u256_into_address(destination_address), program, new_frame_gas, @@ -253,7 +256,7 @@ impl FatPointer { use super::monomorphization::*; impl Instruction { - pub fn from_far_call( + pub fn from_far_call( src1: Register1, src2: Register2, error_handler: Immediate1, @@ -262,7 +265,7 @@ impl Instruction { arguments: Arguments, ) -> Self { Self { - handler: monomorphize!(far_call [T MODE] match_boolean is_static match_boolean is_shard), + handler: monomorphize!(far_call [T M] match_boolean is_static match_boolean is_shard), arguments: arguments .write_source(&src1) .write_source(&src2) diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index 0e6af7b9..df011f7d 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -7,16 +7,19 @@ use crate::{ predication::Flags, Instruction, Predicate, VirtualMachine, World, }; -use eravm_stable_interface::{opcodes, ReturnType, Tracer}; +use eravm_stable_interface::{ + opcodes::{self, TypeLevelReturnType}, + ReturnType, Tracer, +}; use u256::U256; -fn ret( +fn ret( vm: &mut VirtualMachine, world: &mut dyn World, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::, _>(vm, world, tracer, |vm, args, _| { - let mut return_type = ReturnType::from_u8(RETURN_TYPE); + instruction_boilerplate_ext::, _>(vm, world, tracer, |vm, args, _| { + let mut return_type = RT::VALUE; let near_call_leftover_gas = vm.state.current_frame.gas; let (snapshot, leftover_gas) = if let Some(FrameRemnant { @@ -155,33 +158,31 @@ pub(crate) fn free_panic( world: &mut dyn World, tracer: &mut T, ) -> ExecutionStatus { - ret::(vm, world, tracer) + ret::(vm, world, tracer) } use super::monomorphization::*; +use eravm_stable_interface::opcodes::{Normal, Panic, Revert}; 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 [T RETURN_TYPE] match_boolean to_label), + handler: monomorphize!(ret [T Normal] match_boolean to_label), arguments: arguments.write_source(&src1).write_source(&label), } } pub fn from_revert(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); - const RETURN_TYPE: u8 = ReturnType::Revert as u8; Self { - handler: monomorphize!(ret [T RETURN_TYPE] match_boolean to_label), + handler: monomorphize!(ret [T Revert] match_boolean to_label), arguments: arguments.write_source(&src1).write_source(&label), } } pub fn from_panic(label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); - const RETURN_TYPE: u8 = ReturnType::Panic as u8; Self { - handler: monomorphize!(ret [T RETURN_TYPE] match_boolean to_label), + handler: monomorphize!(ret [T Panic] match_boolean to_label), arguments: arguments.write_source(&label), } } @@ -189,7 +190,7 @@ impl Instruction { pub const fn from_invalid() -> Self { Self { // This field is never read because the instruction fails at the gas cost stage. - handler: ret::, + handler: ret::, arguments: Arguments::new( Predicate::Always, INVALID_INSTRUCTION_COST, diff --git a/src/vm.rs b/src/vm.rs index e2e83710..1e937a89 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -13,6 +13,7 @@ use crate::{ ExecutionEnd, Program, World, }; use crate::{Instruction, ModeRequirements, Predicate}; +use eravm_stable_interface::opcodes::TypeLevelCallingMode; use eravm_stable_interface::{opcodes, CallingMode, HeapId, Tracer}; use u256::H160; @@ -202,7 +203,7 @@ impl VirtualMachine { } #[allow(clippy::too_many_arguments)] - pub(crate) fn push_frame( + pub(crate) fn push_frame( &mut self, code_address: H160, program: Program, @@ -214,19 +215,16 @@ impl VirtualMachine { world_before_this_frame: Snapshot, ) { let mut new_frame = Callframe::new( - if CALLING_MODE == CallingMode::Delegate as u8 { + if M::VALUE == CallingMode::Delegate { self.state.current_frame.address } else { code_address }, code_address, - if CALLING_MODE == CallingMode::Normal as u8 { - self.state.current_frame.address - } else if CALLING_MODE == CallingMode::Delegate as u8 { - self.state.current_frame.caller - } else { - // Mimic call - u256_into_address(self.state.registers[15]) + match M::VALUE { + CallingMode::Normal => self.state.current_frame.address, + CallingMode::Delegate => self.state.current_frame.caller, + CallingMode::Mimic => u256_into_address(self.state.registers[15]), }, program, self.stack_pool.get(), @@ -236,7 +234,7 @@ impl VirtualMachine { gas, stipend, exception_handler, - if CALLING_MODE == CallingMode::Delegate as u8 { + if M::VALUE == CallingMode::Delegate { self.state.current_frame.context_u128 } else { self.state.context_u128 From 32fee21d22bbdb96f565cd512cd8b874871cc61b Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 20 Aug 2024 15:02:02 +0200 Subject: [PATCH 42/61] fix tests --- src/tracing.rs | 2 +- tests/stipend.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tracing.rs b/src/tracing.rs index 3ea0868d..885eba4c 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -382,7 +382,7 @@ mod test { let mut frame_count = 1; let add_far_frame = |vm: &mut VirtualMachine<()>, counter: &mut u16| { - vm.push_frame::<0>( + vm.push_frame::( H160::from_low_u64_be(1), program.clone(), 0, diff --git a/tests/stipend.rs b/tests/stipend.rs index 9fe6b391..4da1d56e 100644 --- a/tests/stipend.rs +++ b/tests/stipend.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "single_instruction_test"))] -use eravm_stable_interface::CallingMode; +use eravm_stable_interface::opcodes; use u256::U256; use vm2::{ address_into_u256, @@ -53,7 +53,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { false, false, ), - Instruction::from_far_call::<{ CallingMode::Normal as u8 }>( + Instruction::from_far_call::( Register1(r1), Register2(r2), // crash on error From 58e6352e5260ebf077ca0135154fb026ae5696e9 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 20 Aug 2024 16:28:17 +0200 Subject: [PATCH 43/61] hide pc --- src/callframe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callframe.rs b/src/callframe.rs index 6532eb38..6409f588 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -29,7 +29,7 @@ pub struct Callframe { pub(crate) near_calls: Vec, - pub pc: *const Instruction, + pub(crate) pc: *const Instruction, pub(crate) program: Program, pub heap: HeapId, From 0026891fd1d1d4655f970a5b2a6e6a08a793dcbc Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 20 Aug 2024 16:31:04 +0200 Subject: [PATCH 44/61] add derives to Opcode --- eravm-stable-interface/src/tracer_interface.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 7b11d69a..d79ce461 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -94,7 +94,7 @@ pub mod opcodes { } } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Opcode { Nop, Add, @@ -141,14 +141,14 @@ pub enum Opcode { TransientStorageWrite, } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum CallingMode { Normal, Delegate, Mimic, } -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum ReturnType { Normal, Revert, From 18e36717817d8a83d9f2894e8ca6ef2871387086 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 20 Aug 2024 19:46:02 +0200 Subject: [PATCH 45/61] prevent resuming after execution ends --- src/instruction_handlers/ret.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index df011f7d..30768c28 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -72,6 +72,10 @@ fn ret( // the caller may take external snapshots while the VM is in the initial frame and // these would break were the initial frame to be rolled back. + // But to continue execution would be nonsensical and can cause UB because there + // is no next instruction after a panic arising from some other instruction. + vm.state.current_frame.pc = invalid_instruction(); + return if let Some(return_value) = return_value_or_panic { let output = vm.state.heaps[return_value.memory_page] .read_range_big_endian( From 63b40ce503ddb2b134ce7117713b6b38a85783c9 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 20 Aug 2024 20:35:13 +0200 Subject: [PATCH 46/61] save pc in snapshots --- src/callframe.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/callframe.rs b/src/callframe.rs index 6409f588..b8af0ae5 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -171,6 +171,7 @@ impl Callframe { context_u128: self.context_u128, sp: self.sp, + pc: self.get_pc_as_u16(), gas: self.gas, near_calls: self.near_calls.clone(), heap_size: self.heap_size, @@ -189,6 +190,7 @@ impl Callframe { stack, context_u128, sp, + pc, gas, near_calls, heap_size, @@ -200,6 +202,7 @@ impl Callframe { self.context_u128 = context_u128; self.sp = sp; + self.set_pc_from_u16(pc); self.gas = gas; self.near_calls = near_calls; self.heap_size = heap_size; @@ -221,6 +224,7 @@ pub(crate) struct CallframeSnapshot { stack: StackSnapshot, context_u128: u128, sp: u16, + pc: u16, gas: u32, near_calls: Vec, heap_size: u32, From 36b66902431c1f8d788cf03c3e041980811b4bb0 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 21 Aug 2024 20:04:17 +0200 Subject: [PATCH 47/61] statically dispatch World --- afl-fuzz/src/lib.rs | 2 +- src/callframe.rs | 12 ++-- src/decode.rs | 15 +++-- src/decommit.rs | 12 ++-- src/instruction.rs | 20 +++---- src/instruction_handlers/binop.rs | 13 ++-- src/instruction_handlers/common.rs | 22 +++---- src/instruction_handlers/context.rs | 60 +++++++++---------- src/instruction_handlers/decommit.rs | 10 ++-- src/instruction_handlers/event.rs | 20 +++---- src/instruction_handlers/far_call.rs | 30 ++++++---- src/instruction_handlers/heap_access.rs | 53 ++++++++-------- src/instruction_handlers/jump.rs | 14 ++--- src/instruction_handlers/near_call.rs | 12 ++-- src/instruction_handlers/nop.rs | 12 ++-- src/instruction_handlers/pointer.rs | 14 ++--- src/instruction_handlers/precompiles.rs | 12 ++-- src/instruction_handlers/ret.rs | 38 ++++++------ src/instruction_handlers/storage.rs | 51 +++++++++------- src/lib.rs | 6 +- src/program.rs | 16 ++--- src/single_instruction_test/callframe.rs | 9 +-- src/single_instruction_test/into_zk_evm.rs | 14 +++-- .../print_mock_info.rs | 6 +- src/single_instruction_test/program.rs | 20 +++---- .../state_to_zk_evm.rs | 10 ++-- src/single_instruction_test/validation.rs | 4 +- src/single_instruction_test/vm.rs | 14 ++--- src/single_instruction_test/world.rs | 6 +- src/state.rs | 12 ++-- src/testworld.rs | 13 ++-- src/tracing.rs | 16 ++--- src/vm.rs | 18 +++--- src/world_diff.rs | 32 ++++------ tests/bytecode_behaviour.rs | 4 +- 35 files changed, 318 insertions(+), 304 deletions(-) diff --git a/afl-fuzz/src/lib.rs b/afl-fuzz/src/lib.rs index 166084c2..52f33d27 100644 --- a/afl-fuzz/src/lib.rs +++ b/afl-fuzz/src/lib.rs @@ -4,6 +4,6 @@ use vm2::{single_instruction_test::MockWorld, VirtualMachine}; #[derive(Arbitrary, Debug)] pub struct VmAndWorld { - pub vm: VirtualMachine, + pub vm: VirtualMachine, pub world: MockWorld, } diff --git a/src/callframe.rs b/src/callframe.rs index b8af0ae5..947bf938 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -11,7 +11,7 @@ use u256::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; #[derive(PartialEq, Debug)] -pub struct Callframe { +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(crate) pc: *const Instruction, - pub(crate) program: Program, + pub(crate) pc: *const Instruction, + pub(crate) program: Program, pub heap: HeapId, pub aux_heap: HeapId, @@ -63,13 +63,13 @@ pub(crate) struct NearCallFrame { 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, @@ -232,7 +232,7 @@ pub(crate) struct CallframeSnapshot { heaps_i_was_keeping_alive: usize, } -impl Clone for Callframe { +impl Clone for Callframe { fn clone(&self) -> Self { Self { address: self.address, diff --git a/src/decode.rs b/src/decode.rs index f891271b..6b971fca 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -23,7 +23,10 @@ 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)) @@ -35,7 +38,7 @@ pub fn decode_program(raw: &[u64], is_bootloader: bool) -> Vec(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); @@ -44,9 +47,9 @@ fn unimplemented_instruction(variant: Opcode) -> Instruction { arguments, } } -fn unimplemented_handler( - vm: &mut VirtualMachine, - _: &mut dyn World, +fn unimplemented_handler( + vm: &mut VirtualMachine, + _: &mut W, _: &mut T, ) -> ExecutionStatus { let variant: Opcode = unsafe { @@ -59,7 +62,7 @@ fn unimplemented_handler( ExecutionStatus::Stopped(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 5e9f16aa..d395be83 100644 --- a/src/decommit.rs +++ b/src/decommit.rs @@ -7,7 +7,7 @@ use zkevm_opcode_defs::{ impl WorldDiff { pub(crate) fn decommit( &mut self, - world: &mut dyn World, + world: &mut impl World, address: U256, default_aa_code_hash: [u8; 32], evm_interpreter_code_hash: [u8; 32], @@ -82,19 +82,19 @@ impl WorldDiff { #[doc(hidden)] // should be used for testing purposes only; can break VM operation otherwise pub fn decommit_opcode( &mut self, - world: &mut dyn World, + world: &mut impl 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 W, 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; @@ -113,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 W, address: H160) -> Program { let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); let code_info = world diff --git a/src/instruction.rs b/src/instruction.rs index 3d7167cd..2625f59d 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,16 +1,15 @@ use std::fmt; use crate::{ - addressing_modes::Arguments, mode_requirements::ModeRequirements, vm::VirtualMachine, - Predicate, World, + addressing_modes::Arguments, mode_requirements::ModeRequirements, vm::VirtualMachine, Predicate, }; -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,8 +18,7 @@ impl fmt::Debug for Instruction { } } -pub(crate) type Handler = - fn(&mut VirtualMachine, &mut dyn World, &mut T) -> ExecutionStatus; +pub(crate) type Handler = fn(&mut VirtualMachine, &mut W, &mut T) -> ExecutionStatus; pub enum ExecutionStatus { Running, Stopped(ExecutionEnd), @@ -36,15 +34,15 @@ 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, +fn jump_to_beginning_handler( + vm: &mut VirtualMachine, + _: &mut W, _: &mut T, ) -> ExecutionStatus { let first_instruction = vm.state.current_frame.program.instruction(0).unwrap(); diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index b2f83c27..b7b8429c 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -7,7 +7,7 @@ use crate::{ }, instruction::{ExecutionStatus, Instruction}, predication::Flags, - VirtualMachine, World, + VirtualMachine, }; use eravm_stable_interface::{ opcodes::{Add, And, Div, Mul, Or, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor}, @@ -17,17 +17,18 @@ use u256::U256; fn binop< T: Tracer, + W, Op: Binop, In1: Source, Out: Destination, const SWAP: bool, const SET_FLAGS: bool, >( - vm: &mut VirtualMachine, - world: &mut dyn World, + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |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) }; @@ -202,7 +203,7 @@ impl Binop for Div { use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_binop( src1: AnySource, @@ -214,7 +215,7 @@ impl Instruction { set_flags: bool, ) -> Self { Self { - handler: monomorphize!(binop [T Op] match_source src1 match_destination out match_boolean swap match_boolean set_flags), + handler: monomorphize!(binop [T W 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 791f8586..85a5cb4a 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,12 +1,12 @@ -use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine, World}; +use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine}; use eravm_stable_interface::{OpcodeType, Tracer}; #[inline(always)] -pub(crate) fn instruction_boilerplate( - vm: &mut VirtualMachine, - world: &mut dyn World, +pub(crate) fn instruction_boilerplate( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, - business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut dyn World), + business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W), ) -> ExecutionStatus { tracer.before_instruction::(vm); unsafe { @@ -20,15 +20,11 @@ pub(crate) fn instruction_boilerplate( } #[inline(always)] -pub(crate) fn instruction_boilerplate_ext( - vm: &mut VirtualMachine, - world: &mut dyn World, +pub(crate) fn instruction_boilerplate_ext( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, - business_logic: impl FnOnce( - &mut VirtualMachine, - &Arguments, - &mut dyn World, - ) -> ExecutionStatus, + business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W) -> ExecutionStatus, ) -> ExecutionStatus { tracer.before_instruction::(vm); let result = unsafe { diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index 93cf80a9..66a14956 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -4,7 +4,7 @@ use crate::{ decommit::address_into_u256, instruction::ExecutionStatus, state::State, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; use eravm_stable_interface::{ opcodes::{self, Caller, CodeAddress, ContextU128, ErgsLeft, This, SP}, @@ -13,63 +13,63 @@ use eravm_stable_interface::{ use u256::U256; use zkevm_opcode_defs::VmMetaParameters; -fn context( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn context( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + instruction_boilerplate::(vm, world, tracer, |vm, args, _| { let result = Op::get(&vm.state); Register1::set(args, &mut vm.state, result) }) } trait ContextOp: OpcodeType { - fn get(state: &State) -> U256; + fn get(state: &State) -> U256; } impl ContextOp for This { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.address) } } impl ContextOp for Caller { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.caller) } } impl ContextOp for CodeAddress { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { address_into_u256(state.current_frame.code_address) } } impl ContextOp for ErgsLeft { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { U256([state.current_frame.gas as u64, 0, 0, 0]) } } impl ContextOp for ContextU128 { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { state.get_context_u128().into() } } impl ContextOp for SP { - fn get(state: &State) -> U256 { + fn get(state: &State) -> U256 { state.current_frame.sp.into() } } -fn context_meta( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn context_meta( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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, @@ -89,41 +89,41 @@ fn context_meta( }) } -fn set_context_u128( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn set_context_u128( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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, +fn increment_tx_number( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, _, _| { + instruction_boilerplate::(vm, world, tracer, |vm, _, _| { vm.start_new_tx(); }) } -fn aux_mutating( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn aux_mutating( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |_, _, _| { + 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 dd6c1e6c..770cb180 100644 --- a/src/instruction_handlers/decommit.rs +++ b/src/instruction_handlers/decommit.rs @@ -9,12 +9,12 @@ use eravm_stable_interface::{opcodes, Tracer}; use u256::U256; use zkevm_opcode_defs::{BlobSha256Format, ContractCodeSha256Format, VersionedHashLen32}; -fn decommit( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn decommit>( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, world| { + 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(); @@ -51,7 +51,7 @@ fn decommit( }) } -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 56410d6a..d6174caa 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -3,18 +3,18 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, instruction::ExecutionStatus, world_diff::{Event, L2ToL1Log}, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; use eravm_stable_interface::{opcodes, Tracer}; use u256::H160; use zkevm_opcode_defs::ADDRESS_EVENT_WRITER; -fn event( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn event( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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); @@ -31,12 +31,12 @@ fn event( }) } -fn l2_to_l1( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn l2_to_l1( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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; @@ -51,7 +51,7 @@ fn l2_to_l1( }) } -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 b1678146..06105960 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -32,12 +32,18 @@ use zkevm_opcode_defs::{ /// /// 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< + T: Tracer, + W: World, + M: TypeLevelCallingMode, + const IS_STATIC: bool, + const IS_SHARD: bool, +>( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::, _>(vm, world, tracer, |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); @@ -173,10 +179,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); @@ -196,11 +202,11 @@ pub(crate) fn get_far_call_calldata( } match target { ToHeap => { - grow_heap::<_, 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::<_, AuxHeap>(&mut vm.state, bound).ok()?; + grow_heap::<_, _, AuxHeap>(&mut vm.state, bound).ok()?; pointer.memory_page = vm.state.current_frame.aux_heap; } } @@ -210,10 +216,10 @@ pub(crate) fn get_far_call_calldata( let bound = u32::MAX; match target { ToHeap => { - grow_heap::<_, Heap>(&mut vm.state, bound).ok()?; + grow_heap::<_, _, Heap>(&mut vm.state, bound).ok()?; } ToAuxHeap => { - grow_heap::<_, AuxHeap>(&mut vm.state, bound).ok()?; + grow_heap::<_, _, AuxHeap>(&mut vm.state, bound).ok()?; } } return None; @@ -255,7 +261,7 @@ impl FatPointer { use super::monomorphization::*; -impl Instruction { +impl> Instruction { pub fn from_far_call( src1: Register1, src2: Register2, @@ -265,7 +271,7 @@ impl Instruction { arguments: Arguments, ) -> Self { Self { - handler: monomorphize!(far_call [T M] match_boolean is_static match_boolean is_shard), + handler: monomorphize!(far_call [T W M] 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 31d249c3..93661129 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -7,7 +7,7 @@ use crate::{ fat_pointer::FatPointer, instruction::ExecutionStatus, state::State, - ExecutionEnd, HeapId, Instruction, VirtualMachine, World, + ExecutionEnd, HeapId, Instruction, VirtualMachine, }; use eravm_stable_interface::{opcodes, OpcodeType, Tracer}; use std::ops::Range; @@ -20,18 +20,18 @@ 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; type Read: OpcodeType; type Write: OpcodeType; } 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 } type Read = opcodes::HeapRead; @@ -40,10 +40,10 @@ impl HeapFromState for Heap { 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 } type Read = opcodes::AuxHeapRead; @@ -53,12 +53,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 W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |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); @@ -66,7 +66,7 @@ fn load( let address = pointer.low_u32(); let new_bound = address.wrapping_add(32); - if grow_heap::<_, H>(&mut vm.state, new_bound).is_err() { + if grow_heap::<_, _, H>(&mut vm.state, new_bound).is_err() { vm.state.current_frame.pc = &*vm.panic; return; }; @@ -91,16 +91,17 @@ fn load( fn store< T: Tracer, + W, H: HeapFromState, In: Source, const INCREMENT: bool, const HOOKING_ENABLED: bool, >( - vm: &mut VirtualMachine, - world: &mut dyn World, + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::(vm, world, tracer, |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); @@ -109,7 +110,7 @@ fn store< let value = Register2::get(args, &mut vm.state); let new_bound = address.wrapping_add(32); - if grow_heap::<_, H>(&mut vm.state, new_bound).is_err() { + if grow_heap::<_, _, H>(&mut vm.state, new_bound).is_err() { vm.state.current_frame.pc = &*vm.panic; return ExecutionStatus::Running; } @@ -139,8 +140,8 @@ fn store< /// Pays for more heap space. Doesn't acually grow the heap. /// That distinction is necessary because the bootloader gets u32::MAX heap for free. -pub fn grow_heap( - state: &mut State, +pub fn grow_heap( + state: &mut State, new_bound: u32, ) -> Result<(), ()> { let already_paid = H::get_heap_size(state); @@ -153,12 +154,12 @@ pub fn grow_heap( Ok(()) } -fn load_pointer( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn load_pointer( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |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 = &*vm.panic; @@ -189,7 +190,7 @@ fn load_pointer( use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_load( src: RegisterOrImmediate, @@ -205,7 +206,7 @@ impl Instruction { } Self { - handler: monomorphize!(load [T H] match_reg_imm src match_boolean increment), + handler: monomorphize!(load [T W H] match_reg_imm src match_boolean increment), arguments, } } @@ -220,7 +221,7 @@ impl Instruction { ) -> Self { let increment = incremented_out.is_some(); Self { - handler: monomorphize!(store [T H] match_reg_imm src1 match_boolean increment match_boolean should_hook), + handler: monomorphize!(store [T W H] match_reg_imm src1 match_boolean increment match_boolean should_hook), arguments: arguments .write_source(&src1) .write_source(&src2) @@ -237,7 +238,7 @@ impl Instruction { ) -> Self { let increment = incremented_out.is_some(); Self { - handler: monomorphize!(load_pointer [T] match_boolean increment), + handler: monomorphize!(load_pointer [T W] 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 96ba6b20..bcd801bf 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -5,16 +5,16 @@ use crate::{ Immediate1, Register1, RelativeStack, Source, }, instruction::{ExecutionStatus, Instruction}, - VirtualMachine, World, + VirtualMachine, }; use eravm_stable_interface::{opcodes, Tracer}; -fn jump( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn jump( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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(); @@ -26,10 +26,10 @@ fn jump( use super::monomorphization::*; -impl Instruction { +impl Instruction { pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { - handler: monomorphize!(jump [T] match_source source), + handler: monomorphize!(jump [T W] match_source source), arguments: arguments .write_source(&source) .write_destination(&destination), diff --git a/src/instruction_handlers/near_call.rs b/src/instruction_handlers/near_call.rs index 91086fb0..c73a6759 100644 --- a/src/instruction_handlers/near_call.rs +++ b/src/instruction_handlers/near_call.rs @@ -3,16 +3,16 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register1, Source}, instruction::ExecutionStatus, predication::Flags, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; use eravm_stable_interface::{opcodes, Tracer}; -fn near_call( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn near_call( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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); @@ -36,7 +36,7 @@ fn near_call( }) } -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 f8849fee..79aafa27 100644 --- a/src/instruction_handlers/nop.rs +++ b/src/instruction_handlers/nop.rs @@ -2,16 +2,16 @@ use super::common::instruction_boilerplate; use crate::{ addressing_modes::{destination_stack_address, AdvanceStackPointer, Arguments, Source}, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; use eravm_stable_interface::{opcodes, Tracer}; -fn nop( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn nop( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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 @@ -22,7 +22,7 @@ fn nop( }) } -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 bb903247..d9cb87fc 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -6,7 +6,7 @@ use crate::{ }, fat_pointer::FatPointer, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; use eravm_stable_interface::{ opcodes::{PointerAdd, PointerPack, PointerShrink, PointerSub}, @@ -14,12 +14,12 @@ use eravm_stable_interface::{ }; use u256::U256; -fn ptr( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn ptr( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |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), @@ -99,7 +99,7 @@ impl PtrOp for PointerShrink { use super::monomorphization::*; -impl Instruction { +impl Instruction { #[inline(always)] pub fn from_ptr( src1: AnySource, @@ -109,7 +109,7 @@ impl Instruction { swap: bool, ) -> Self { Self { - handler: monomorphize!(ptr [T Op] match_source src1 match_destination out match_boolean swap), + handler: monomorphize!(ptr [T W 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 8edf740e..8357c603 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -3,7 +3,7 @@ use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, instruction::ExecutionStatus, - Instruction, VirtualMachine, World, + Instruction, VirtualMachine, }; use eravm_stable_interface::{opcodes, HeapId, Tracer}; use zk_evm_abstractions::{ @@ -23,12 +23,12 @@ use zkevm_opcode_defs::{ PrecompileAuxData, PrecompileCallABI, }; -fn precompile_call( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn precompile_call( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + 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)); @@ -121,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 30768c28..30a6000f 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -5,7 +5,7 @@ use crate::{ instruction::{ExecutionEnd, ExecutionStatus}, mode_requirements::ModeRequirements, predication::Flags, - Instruction, Predicate, VirtualMachine, World, + Instruction, Predicate, VirtualMachine, }; use eravm_stable_interface::{ opcodes::{self, TypeLevelReturnType}, @@ -13,12 +13,12 @@ use eravm_stable_interface::{ }; use u256::U256; -fn ret( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn ret( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::, _>(vm, world, tracer, |vm, args, _| { + instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, _| { let mut return_type = RT::VALUE; let near_call_leftover_gas = vm.state.current_frame.gas; @@ -121,8 +121,8 @@ 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, +pub(crate) fn panic_from_failed_far_call( + vm: &mut VirtualMachine, exception_handler: u16, ) { // Gas is already subtracted in the far call code. @@ -139,11 +139,11 @@ pub(crate) fn panic_from_failed_far_call( } /// Panics, burning all available gas. -static INVALID_INSTRUCTION: Instruction<()> = Instruction::from_invalid(); +static INVALID_INSTRUCTION: Instruction<(), ()> = Instruction::from_invalid(); -pub fn invalid_instruction<'a, T>() -> &'a Instruction { +pub fn invalid_instruction<'a, T, W>() -> &'a Instruction { // Safety: the handler of an invalid instruction is never read. - unsafe { &*(&INVALID_INSTRUCTION as *const Instruction<()>).cast() } + unsafe { &*(&INVALID_INSTRUCTION as *const Instruction<(), ()>).cast() } } pub(crate) const RETURN_COST: u32 = 5; @@ -157,36 +157,36 @@ pub(crate) const RETURN_COST: u32 = 5; /// - 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, +pub(crate) fn free_panic( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - ret::(vm, world, tracer) + ret::(vm, world, tracer) } use super::monomorphization::*; use eravm_stable_interface::opcodes::{Normal, Panic, Revert}; -impl Instruction { +impl Instruction { pub fn from_ret(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { - handler: monomorphize!(ret [T Normal] match_boolean to_label), + handler: monomorphize!(ret [T W Normal] match_boolean to_label), arguments: arguments.write_source(&src1).write_source(&label), } } pub fn from_revert(src1: Register1, label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { - handler: monomorphize!(ret [T Revert] match_boolean to_label), + handler: monomorphize!(ret [T W Revert] match_boolean to_label), arguments: arguments.write_source(&src1).write_source(&label), } } pub fn from_panic(label: Option, arguments: Arguments) -> Self { let to_label = label.is_some(); Self { - handler: monomorphize!(ret [T Panic] match_boolean to_label), + handler: monomorphize!(ret [T W Panic] match_boolean to_label), arguments: arguments.write_source(&label), } } @@ -194,7 +194,7 @@ impl Instruction { pub const fn from_invalid() -> Self { Self { // This field is never read because the instruction fails at the gas cost stage. - handler: ret::, + handler: ret::, arguments: Arguments::new( Predicate::Always, INVALID_INSTRUCTION_COST, diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index 0d519e48..9a27e485 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -8,12 +8,12 @@ use crate::{ }; use eravm_stable_interface::{opcodes, Tracer}; -fn sstore( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn sstore>( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, world| { + instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); @@ -26,12 +26,12 @@ fn sstore( }) } -fn sstore_transient( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn sstore_transient( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::( + instruction_boilerplate::( vm, world, tracer, @@ -45,12 +45,12 @@ fn sstore_transient( ) } -fn sload( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn sload>( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, world| { + instruction_boilerplate::(vm, world, tracer, |vm, args, world| { let key = Register1::get(args, &mut vm.state); let (value, refund) = vm.world_diff @@ -63,22 +63,27 @@ fn sload( }) } -fn sload_transient( - vm: &mut VirtualMachine, - world: &mut dyn World, +fn sload_transient( + vm: &mut VirtualMachine, + world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { - let key = Register1::get(args, &mut vm.state); - let value = vm - .world_diff - .read_transient_storage(vm.state.current_frame.address, key); + instruction_boilerplate::( + vm, + world, + tracer, + |vm, args, _| { + let key = Register1::get(args, &mut vm.state); + let value = vm + .world_diff + .read_transient_storage(vm.state.current_frame.address, key); - Register1::set(args, &mut vm.state, value); - }) + Register1::set(args, &mut vm.state, value); + }, + ) } -impl Instruction { +impl> Instruction { #[inline(always)] pub fn from_sstore(src1: Register1, src2: Register2, arguments: Arguments) -> Self { Self { diff --git a/src/lib.rs b/src/lib.rs index 4692af4f..c50f148c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,13 +50,15 @@ use single_instruction_test::stack; #[cfg(feature = "single_instruction_test")] pub use zkevm_opcode_defs; -pub trait World { +pub trait World: StorageInterface + Sized { /// This will be called *every* time a contract is called. Caching and decoding is /// the world implementor's job. - fn decommit(&mut self, hash: U256) -> Program; + fn decommit(&mut self, hash: U256) -> Program; fn decommit_code(&mut self, hash: U256) -> Vec; +} +pub trait StorageInterface { /// There is no write_storage; [WorldDiff::get_storage_changes] gives a list of all storage changes. fn read_storage(&mut self, contract: H160, key: U256) -> Option; diff --git a/src/program.rs b/src/program.rs index 5d217bbe..401892b1 100644 --- a/src/program.rs +++ b/src/program.rs @@ -7,12 +7,12 @@ use u256::U256; // enable changing the internals later. /// Cloning this is cheap. It is a handle to memory similar to [`Arc`]. -pub struct Program { +pub struct Program { code_page: Arc<[U256]>, - instructions: Arc<[Instruction]>, + instructions: Arc<[Instruction]>, } -impl Clone for Program { +impl Clone for Program { fn clone(&self) -> Self { Self { code_page: self.code_page.clone(), @@ -21,7 +21,7 @@ impl Clone for Program { } } -impl fmt::Debug for Program { +impl fmt::Debug for Program { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { const DEBUGGED_ITEMS: usize = 16; @@ -44,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()) } @@ -66,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 a589fde7..f2ac3839 100644 --- a/src/single_instruction_test/callframe.rs +++ b/src/single_instruction_test/callframe.rs @@ -3,7 +3,8 @@ use super::{ stack::{Stack, StackPool}, }; use crate::{ - callframe::Callframe, decommit::is_kernel, predication::Flags, HeapId, Program, WorldDiff, + callframe::Callframe, decommit::is_kernel, predication::Flags, HeapId, Program, World, + WorldDiff, }; use arbitrary::Arbitrary; use eravm_stable_interface::Tracer; @@ -16,7 +17,7 @@ impl<'a> Arbitrary<'a> for Flags { } } -impl<'a, T: Tracer> Arbitrary<'a> for Callframe { +impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let address: H160 = u.arbitrary()?; @@ -28,7 +29,7 @@ impl<'a, T: Tracer> 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, @@ -65,7 +66,7 @@ impl<'a, T: Tracer> Arbitrary<'a> for Callframe { } } -impl Callframe { +impl Callframe { pub fn raw_first_instruction(&self) -> u64 { self.program.raw_first_instruction } diff --git a/src/single_instruction_test/into_zk_evm.rs b/src/single_instruction_test/into_zk_evm.rs index 9ca68652..13470752 100644 --- a/src/single_instruction_test/into_zk_evm.rs +++ b/src/single_instruction_test/into_zk_evm.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use super::{stack::Stack, state_to_zk_evm::vm2_state_to_zk_evm_state, MockWorld}; -use crate::{zkevm_opcode_defs::decoding::EncodingModeProduction, VirtualMachine, World}; +use crate::{ + zkevm_opcode_defs::decoding::EncodingModeProduction, StorageInterface, VirtualMachine, +}; use eravm_stable_interface::Tracer; use u256::U256; use zk_evm::{ @@ -27,7 +29,7 @@ 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)); @@ -52,7 +54,10 @@ pub fn vm2_to_zk_evm(vm: &VirtualMachine, world: MockWorld) -> ZkE } } -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(); @@ -202,7 +207,8 @@ impl Storage for MockWorldWrapper { query.read_value = if query.aux_byte == TRANSIENT_STORAGE_AUX_BYTE { U256::zero() } else { - >::read_storage(&mut self.0, query.address, query.key) + self.0 + .read_storage(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 e357dcd8..8048da4f 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 02d4cf5c..646d326c 100644 --- a/src/single_instruction_test/program.rs +++ b/src/single_instruction_test/program.rs @@ -1,4 +1,4 @@ -use crate::{decode::decode, Instruction}; +use crate::{decode::decode, Instruction, World}; use arbitrary::Arbitrary; use eravm_stable_interface::Tracer; use std::{rc::Rc, sync::Arc}; @@ -7,17 +7,17 @@ use u256::U256; use super::mock_array::MockRead; #[derive(Debug)] -pub struct Program { +pub struct Program { pub raw_first_instruction: u64, // Need a two-instruction array so that incrementing the program counter is safe - first_instruction: MockRead; 2]>>, - other_instruction: MockRead; 2]>>>, + first_instruction: MockRead; 2]>>, + other_instruction: MockRead; 2]>>>, code_page: Arc<[U256]>, } -impl Clone for Program { +impl Clone for Program { fn clone(&self) -> Self { Self { raw_first_instruction: self.raw_first_instruction, @@ -28,7 +28,7 @@ impl Clone for Program { } } -impl<'a, T: Tracer> Arbitrary<'a> for Program { +impl<'a, T: Tracer, W: World> Arbitrary<'a> for Program { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let raw_first_instruction = u.arbitrary()?; @@ -47,8 +47,8 @@ impl<'a, T: Tracer> 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 { @@ -65,7 +65,7 @@ impl Program { } } -impl Program { +impl Program { pub fn for_decommit() -> Self { Self { raw_first_instruction: 0, @@ -82,7 +82,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 063850e7..907b944b 100644 --- a/src/single_instruction_test/state_to_zk_evm.rs +++ b/src/single_instruction_test/state_to_zk_evm.rs @@ -8,9 +8,9 @@ use zk_evm::{ }; use zkevm_opcode_defs::decoding::EncodingModeProduction; -pub(crate) fn vm2_state_to_zk_evm_state( - state: &crate::State, - panic: &crate::Instruction, +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()) @@ -58,7 +58,9 @@ 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, diff --git a/src/single_instruction_test/validation.rs b/src/single_instruction_test/validation.rs index e24cc575..df4eed4d 100644 --- a/src/single_instruction_test/validation.rs +++ b/src/single_instruction_test/validation.rs @@ -10,7 +10,7 @@ pub(crate) fn is_valid_tagged_value((value, is_pointer): (U256, bool)) -> 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 80d5c1e4..f7682126 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -9,12 +9,8 @@ use eravm_stable_interface::Tracer; use std::fmt::Debug; use u256::U256; -impl VirtualMachine { - pub fn run_single_instruction( - &mut self, - world: &mut dyn World, - tracer: &mut T, - ) -> ExecutionStatus { +impl VirtualMachine { + pub fn run_single_instruction(&mut self, world: &mut W, tracer: &mut T) -> ExecutionStatus { unsafe { let args = &(*self.state.current_frame.pc).arguments; @@ -60,9 +56,9 @@ impl VirtualMachine { } } -impl<'a, T: Tracer> Arbitrary<'a> for VirtualMachine { +impl<'a, T: Tracer, W: World> 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; @@ -154,7 +150,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 e756129a..1dc7d6c9 100644 --- a/src/single_instruction_test/world.rs +++ b/src/single_instruction_test/world.rs @@ -1,5 +1,5 @@ use super::mock_array::MockRead; -use crate::{Program, World}; +use crate::{Program, StorageInterface, World}; use arbitrary::Arbitrary; use eravm_stable_interface::Tracer; use u256::{H160, U256}; @@ -10,14 +10,16 @@ pub struct MockWorld { } impl World for MockWorld { - fn decommit(&mut self, _hash: U256) -> Program { + fn decommit(&mut self, _hash: U256) -> Program { Program::for_decommit() } fn decommit_code(&mut self, _hash: U256) -> Vec { vec![0; 32] } +} +impl StorageInterface for MockWorld { fn read_storage(&mut self, contract: H160, key: U256) -> Option { *self.storage_slot.get((contract, key)) } diff --git a/src/state.rs b/src/state.rs index 783b991d..ece6ccbd 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 435bcfbb..dcfb0fca 100644 --- a/src/testworld.rs +++ b/src/testworld.rs @@ -1,4 +1,5 @@ -use crate::{address_into_u256, Program, World}; +use crate::{address_into_u256, Program, StorageInterface, World}; +use eravm_stable_interface::Tracer; use std::{ collections::{hash_map::DefaultHasher, BTreeMap}, hash::{Hash, Hasher}, @@ -10,11 +11,11 @@ use zkevm_opcode_defs::{ 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 { + 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 +40,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 { @@ -59,7 +60,9 @@ impl World for TestWorld { }) .collect() } +} +impl StorageInterface for TestWorld { fn read_storage(&mut self, contract: u256::H160, key: u256::U256) -> Option { let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); diff --git a/src/tracing.rs b/src/tracing.rs index 885eba4c..14afee25 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], @@ -156,12 +156,12 @@ impl StateInterface for VirtualMachine { } } -struct CallframeWrapper<'a, T> { - frame: &'a mut Callframe, +struct CallframeWrapper<'a, T, W> { + frame: &'a mut Callframe, near_call: Option, } -impl CallframeInterface for CallframeWrapper<'_, T> { +impl CallframeInterface for CallframeWrapper<'_, T, W> { fn address(&self) -> u256::H160 { self.frame.address } @@ -322,7 +322,7 @@ impl CallframeInterface for CallframeWrapper<'_, T> { } } -impl CallframeWrapper<'_, T> { +impl CallframeWrapper<'_, T, W> { fn near_call_on_top(&self) -> Option<&NearCallFrame> { if self.frame.near_calls.is_empty() || self.near_call == Some(0) { None @@ -366,7 +366,7 @@ mod test { let mut world = TestWorld::new(&[(address, program)]); let program = initial_decommit(&mut world, address); - let mut vm = VirtualMachine::<()>::new( + let mut vm = VirtualMachine::new( address, program.clone(), Address::zero(), @@ -381,7 +381,7 @@ mod test { let mut frame_count = 1; - let add_far_frame = |vm: &mut VirtualMachine<()>, counter: &mut u16| { + let add_far_frame = |vm: &mut VirtualMachine<(), TestWorld<()>>, counter: &mut u16| { vm.push_frame::( H160::from_low_u64_be(1), program.clone(), @@ -395,7 +395,7 @@ mod test { *counter += 1; }; - let add_near_frame = |vm: &mut VirtualMachine<()>, counter: &mut u16| { + let add_near_frame = |vm: &mut VirtualMachine<(), TestWorld<()>>, counter: &mut u16| { vm.state .current_frame .push_near_call(0, *counter, vm.world_diff.snapshot()); diff --git a/src/vm.rs b/src/vm.rs index 1e937a89..aea23b0e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -10,7 +10,7 @@ use crate::{ stack::StackPool, state::State, world_diff::{Snapshot, WorldDiff}, - ExecutionEnd, Program, World, + ExecutionEnd, Program, }; use crate::{Instruction, ModeRequirements, Predicate}; use eravm_stable_interface::opcodes::TypeLevelCallingMode; @@ -26,25 +26,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>, + pub(crate) panic: Box>, } -impl VirtualMachine { +impl VirtualMachine { pub fn new( address: H160, - program: Program, + program: Program, caller: H160, calldata: Vec, gas: u32, @@ -74,7 +74,7 @@ impl VirtualMachine { } } - pub fn run(&mut self, world: &mut dyn World, tracer: &mut T) -> ExecutionEnd { + pub fn run(&mut self, world: &mut W, tracer: &mut T) -> ExecutionEnd { unsafe { loop { let args = &(*self.state.current_frame.pc).arguments; @@ -117,7 +117,7 @@ impl VirtualMachine { /// depending on remaining gas. pub fn resume_with_additional_gas_limit( &mut self, - world: &mut dyn World, + world: &mut W, tracer: &mut T, gas_limit: u32, ) -> Option<(u32, ExecutionEnd)> { @@ -206,7 +206,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 da18a1f9..cc9d27e2 100644 --- a/src/world_diff.rs +++ b/src/world_diff.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use crate::{ rollback::{Rollback, RollbackableLog, RollbackableMap, RollbackablePod, RollbackableSet}, - World, + StorageInterface, }; use u256::{H160, U256}; use zkevm_opcode_defs::system_params::{ @@ -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 impl StorageInterface, 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 impl StorageInterface, 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 impl StorageInterface, 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 impl StorageInterface, 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,11 +456,7 @@ mod tests { } struct NoWorld; - impl World for NoWorld { - fn decommit(&mut self, _: U256) -> crate::Program { - unimplemented!() - } - + impl StorageInterface for NoWorld { fn read_storage(&mut self, _: H160, _: U256) -> Option { None } @@ -472,9 +468,5 @@ mod tests { fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool { false } - - fn decommit_code(&mut self, _: U256) -> Vec { - unimplemented!() - } } } diff --git a/tests/bytecode_behaviour.rs b/tests/bytecode_behaviour.rs index 7b02ce0c..436ee6f0 100644 --- a/tests/bytecode_behaviour.rs +++ b/tests/bytecode_behaviour.rs @@ -4,11 +4,11 @@ use eravm_stable_interface::Tracer; use u256::U256; use vm2::{ decode::decode_program, initial_decommit, testworld::TestWorld, ExecutionEnd, Program, - VirtualMachine, + VirtualMachine, World, }; 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( From af081769102bbf64bcab966c9bfba5deae633ef0 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 21 Aug 2024 20:06:37 +0200 Subject: [PATCH 48/61] clippy --- src/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm.rs b/src/vm.rs index aea23b0e..a84d257f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -285,7 +285,7 @@ impl VirtualMachine { } #[cfg(feature = "trace")] - fn print_instruction(&self, instruction: *const crate::instruction::Instruction) { + fn print_instruction(&self, instruction: *const crate::instruction::Instruction) { print!("{:?}: ", unsafe { instruction.offset_from(self.state.current_frame.program.instruction(0).unwrap()) }); From 0e30b0d53c322b56f84da70258347ff786fb18d5 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 21 Aug 2024 20:08:50 +0200 Subject: [PATCH 49/61] don't care about type complexity of tests --- src/single_instruction_test/program.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/single_instruction_test/program.rs b/src/single_instruction_test/program.rs index 646d326c..50ba1bb4 100644 --- a/src/single_instruction_test/program.rs +++ b/src/single_instruction_test/program.rs @@ -12,6 +12,7 @@ pub struct Program { // Need a two-instruction array so that incrementing the program counter is safe first_instruction: MockRead; 2]>>, + #[allow(clippy::type_complexity)] other_instruction: MockRead; 2]>>>, code_page: Arc<[U256]>, From ea4659d57be6a634c17815ad320cb9ecc72225c6 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 22 Aug 2024 14:20:51 +0100 Subject: [PATCH 50/61] take metadata from workspace Co-authored-by: Fedor Sakharov --- eravm-stable-interface/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eravm-stable-interface/Cargo.toml b/eravm-stable-interface/Cargo.toml index 36f46668..d6faf0cb 100644 --- a/eravm-stable-interface/Cargo.toml +++ b/eravm-stable-interface/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "eravm-stable-interface" -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" -authors = ["The Matter Labs Team "] +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true [dependencies] primitive-types = "0.12.1" From 25294951012fa4d8a4b856db4bdd13559d19e209 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 22 Aug 2024 15:28:30 +0200 Subject: [PATCH 51/61] properly inherit from workspace --- Cargo.toml | 11 ++++++++--- afl-fuzz/Cargo.toml | 2 +- eravm-stable-interface/Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c7be0ff..d4a57ea6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "vm2" version = "0.1.0" -edition = "2021" +edition.workspace = true homepage = "https://zksync.io/" -license = "MIT OR Apache-2.0" -authors = ["The Matter Labs Team "] +license.workspace = true +authors.workspace = true [dependencies] eravm-stable-interface = { path = "./eravm-stable-interface" } @@ -34,3 +34,8 @@ single_instruction_test = ["arbitrary", "u256/arbitrary", "zk_evm", "anyhow"] [workspace] members = [".", "afl-fuzz", "eravm-stable-interface"] + +[workspace.package] +edition = "2021" +license = "MIT OR Apache-2.0" +authors = ["The Matter Labs Team "] \ No newline at end of file diff --git a/afl-fuzz/Cargo.toml b/afl-fuzz/Cargo.toml index 844abac9..b658bee1 100644 --- a/afl-fuzz/Cargo.toml +++ b/afl-fuzz/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "differential_fuzzing" version = "0.1.0" -edition = "2021" +edition.workspace = true publish = false [dependencies] diff --git a/eravm-stable-interface/Cargo.toml b/eravm-stable-interface/Cargo.toml index d6faf0cb..9d0757b2 100644 --- a/eravm-stable-interface/Cargo.toml +++ b/eravm-stable-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eravm-stable-interface" -version.workspace = true +version = "0.1.0" edition.workspace = true license.workspace = true authors.workspace = true From fa11e78647f75bca245a4e6130dfc6c2f1b72bbd Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 22 Aug 2024 18:42:45 +0200 Subject: [PATCH 52/61] inline Stack::set --- src/stack.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stack.rs b/src/stack.rs index dcaf7b7e..b605ce3a 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -25,6 +25,7 @@ impl Stack { self.slots[slot as usize] } + #[inline(always)] pub(crate) fn set(&mut self, slot: u16, value: U256) { let written_area = slot as usize / DIRTY_AREA_SIZE; self.dirty_areas |= 1 << written_area; From 6811a52dce148165fb662cca249d7cfbb6918541 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 22 Aug 2024 18:58:53 +0200 Subject: [PATCH 53/61] inline more --- src/instruction_handlers/pointer.rs | 4 ++++ src/stack.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index d9cb87fc..f6e4ec4b 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -51,12 +51,14 @@ pub trait PtrOp: OpcodeType { } impl PtrOp for PointerAdd { + #[inline(always)] fn perform(in1: U256, in2: U256) -> Option { ptr_add_sub::(in1, in2) } } impl PtrOp for PointerSub { + #[inline(always)] fn perform(in1: U256, in2: U256) -> Option { ptr_add_sub::(in1, in2) } @@ -80,6 +82,7 @@ fn ptr_add_sub(mut in1: U256, in2: U256) -> Option { } impl PtrOp for PointerPack { + #[inline(always)] fn perform(in1: U256, in2: U256) -> Option { if in2.low_u128() != 0 { None @@ -90,6 +93,7 @@ impl PtrOp for PointerPack { } impl PtrOp for PointerShrink { + #[inline(always)] fn perform(mut in1: U256, in2: U256) -> Option { let pointer: &mut FatPointer = (&mut in1).into(); pointer.length = pointer.length.checked_sub(in2.low_u32())?; diff --git a/src/stack.rs b/src/stack.rs index b605ce3a..9774e954 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -21,6 +21,7 @@ impl Stack { unsafe { Box::from_raw(alloc_zeroed(Layout::new::()).cast::()) } } + #[inline(always)] pub(crate) fn get(&self, slot: u16) -> U256 { self.slots[slot as usize] } @@ -46,14 +47,17 @@ impl Stack { self.pointer_flags = Default::default(); } + #[inline(always)] pub(crate) fn get_pointer_flag(&self, slot: u16) -> bool { self.pointer_flags.get(slot) } + #[inline(always)] pub(crate) fn set_pointer_flag(&mut self, slot: u16) { self.pointer_flags.set(slot); } + #[inline(always)] pub(crate) fn clear_pointer_flag(&mut self, slot: u16) { self.pointer_flags.clear(slot); } From c339dde79db8bec214faf5b7e15d2a2a70c4a684 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Thu, 22 Aug 2024 19:12:42 +0200 Subject: [PATCH 54/61] inline bitset --- src/bitset.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bitset.rs b/src/bitset.rs index cf49e042..5c78f316 100644 --- a/src/bitset.rs +++ b/src/bitset.rs @@ -2,22 +2,26 @@ pub struct Bitset([u64; 1 << 10]); impl Bitset { + #[inline(always)] pub fn get(&self, i: u16) -> bool { let (slot, bit) = slot_and_bit(i); self.0[slot] & bit != 0 } + #[inline(always)] pub fn set(&mut self, i: u16) { let (slot, bit) = slot_and_bit(i); self.0[slot] |= bit; } + #[inline(always)] pub fn clear(&mut self, i: u16) { let (slot, bit) = slot_and_bit(i); self.0[slot] &= !bit; } } +#[inline(always)] fn slot_and_bit(i: u16) -> (usize, u64) { ((i >> 6) as usize, 1u64 << (i & 0b111111)) } From 37e86e0caca68426c71b77101430fff206fca03c Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Fri, 23 Aug 2024 19:11:36 +0200 Subject: [PATCH 55/61] fix clippy --- src/heap.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/heap.rs b/src/heap.rs index 4514d7d9..4421c4f0 100644 --- a/src/heap.rs +++ b/src/heap.rs @@ -47,9 +47,7 @@ impl Heap { /// Needed only by tracers pub(crate) fn read_byte(&self, address: u32) -> u8 { let (page, offset) = address_to_page_offset(address); - self.page(page as usize) - .map(|page| page.0[offset as usize]) - .unwrap_or(0) + self.page(page).map(|page| page.0[offset]).unwrap_or(0) } fn page(&self, idx: usize) -> Option<&HeapPage> { From 2cc5587a1661d731da87f24c7973ecabb8c35e19 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 26 Aug 2024 14:39:11 +0200 Subject: [PATCH 56/61] trace panic when farcall fails --- src/instruction_handlers/common.rs | 9 +++++++-- src/instruction_handlers/far_call.rs | 10 ++++++---- src/instruction_handlers/heap_access.rs | 2 +- src/instruction_handlers/ret.rs | 8 ++++++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 85a5cb4a..3e26bc30 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -24,14 +24,19 @@ pub(crate) fn instruction_boilerplate_ext( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, - business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W) -> ExecutionStatus, + business_logic: impl FnOnce( + &mut VirtualMachine, + &Arguments, + &mut T, + &mut W, + ) -> ExecutionStatus, ) -> ExecutionStatus { tracer.before_instruction::(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) + business_logic(vm, &(*instruction).arguments, tracer, world) }; tracer.after_instruction::(vm); diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 06105960..a473fe65 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -1,5 +1,5 @@ use super::{ - common::instruction_boilerplate, + common::instruction_boilerplate_ext, heap_access::grow_heap, ret::{panic_from_failed_far_call, RETURN_COST}, AuxHeap, Heap, @@ -43,7 +43,7 @@ fn far_call< world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::, _, _>(vm, world, tracer, |vm, args, world| { + instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, tracer, 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); @@ -106,8 +106,8 @@ fn far_call< 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, exception_handler); - return; + panic_from_failed_far_call(vm, tracer, exception_handler); + return ExecutionStatus::Running; }; let stipend = if is_evm_interpreter { @@ -152,6 +152,8 @@ fn far_call< | u8::from(abi.is_constructor_call); vm.state.registers[2] = call_type.into(); + + ExecutionStatus::Running }) } diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index eb164941..defc7838 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -107,7 +107,7 @@ fn store< world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::(vm, world, tracer, |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); diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index 30a6000f..7b8df36c 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -18,7 +18,7 @@ fn ret( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, _| { + instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, _, _| { let mut return_type = RT::VALUE; let near_call_leftover_gas = vm.state.current_frame.gas; @@ -120,11 +120,13 @@ 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, + tracer: &mut T, exception_handler: u16, ) { + tracer.before_instruction::, _>(vm); + // Gas is already subtracted in the far call code. // No need to roll back, as no changes are made in this "frame". @@ -136,6 +138,8 @@ 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); } /// Panics, burning all available gas. From c640a06ed201b4fbd3d8a1181c0799c1da58d9c0 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 26 Aug 2024 15:01:16 +0200 Subject: [PATCH 57/61] improved comment --- src/vm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vm.rs b/src/vm.rs index a6369a17..b41eac67 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -37,7 +37,8 @@ pub struct VirtualMachine { pub(crate) stack_pool: StackPool, - // Instructions that are jumped to when things go wrong. + /// Instruction that is jumped to when things go wrong while executing another. + /// Boxed, so the pointer isn't invalidated by moves. pub(crate) panic: Box>, } From e448ac7794cabd72bb2954643806eedd4a0103f8 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 26 Aug 2024 17:20:02 +0200 Subject: [PATCH 58/61] move code from dispatch to boilerplate --- Cargo.toml | 1 - src/instruction_handlers/common.rs | 45 +++--- src/instruction_handlers/ret.rs | 220 +++++++++++++++-------------- src/vm.rs | 78 +--------- 4 files changed, 150 insertions(+), 194 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4a57ea6..1590bdf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ harness = false [features] default = [] -trace = [] single_instruction_test = ["arbitrary", "u256/arbitrary", "zk_evm", "anyhow"] [workspace] diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 3e26bc30..40c00352 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,5 +1,6 @@ +use super::free_panic; use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine}; -use eravm_stable_interface::{OpcodeType, Tracer}; +use eravm_stable_interface::{opcodes, OpcodeType, Tracer}; #[inline(always)] pub(crate) fn instruction_boilerplate( @@ -8,15 +9,10 @@ pub(crate) fn instruction_boilerplate( tracer: &mut T, business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W), ) -> ExecutionStatus { - tracer.before_instruction::(vm); - 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::(vm); - - ExecutionStatus::Running + instruction_boilerplate_ext::(vm, world, tracer, |vm, args, _, world| { + business_logic(vm, args, world); + ExecutionStatus::Running + }) } #[inline(always)] @@ -31,14 +27,27 @@ pub(crate) fn instruction_boilerplate_ext( &mut W, ) -> ExecutionStatus, ) -> ExecutionStatus { - tracer.before_instruction::(vm); - let result = unsafe { - let instruction = vm.state.current_frame.pc; - vm.state.current_frame.pc = instruction.add(1); + let args = unsafe { &(*vm.state.current_frame.pc).arguments }; - business_logic(vm, &(*instruction).arguments, tracer, world) - }; - tracer.after_instruction::(vm); + if vm.state.use_gas(args.get_static_gas_cost()).is_err() + || !args.mode_requirements().met( + vm.state.current_frame.is_kernel, + vm.state.current_frame.is_static, + ) + { + return free_panic(vm, tracer); + } - result + if args.predicate().satisfied(&vm.state.flags) { + tracer.before_instruction::(vm); + vm.state.current_frame.pc = unsafe { vm.state.current_frame.pc.add(1) }; + let result = business_logic(vm, args, tracer, world); + tracer.after_instruction::(vm); + result + } else { + tracer.before_instruction::(vm); + vm.state.current_frame.pc = unsafe { vm.state.current_frame.pc.add(1) }; + tracer.after_instruction::(vm); + ExecutionStatus::Running + } } diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index 7b8df36c..c3c6d053 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -13,111 +13,140 @@ use eravm_stable_interface::{ }; use u256::U256; -fn ret( +fn naked_ret( vm: &mut VirtualMachine, - world: &mut W, - tracer: &mut T, + args: &Arguments, ) -> ExecutionStatus { - instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, _, _| { - let mut return_type = RT::VALUE; - let near_call_leftover_gas = vm.state.current_frame.gas; + let mut return_type = RT::VALUE; + let near_call_leftover_gas = vm.state.current_frame.gas; + + let (snapshot, leftover_gas) = if let Some(FrameRemnant { + exception_handler, + snapshot, + }) = vm.state.current_frame.pop_near_call() + { + if TO_LABEL { + let pc = Immediate1::get(args, &mut vm.state).low_u32() as u16; + vm.state.current_frame.set_pc_from_u16(pc); + } else if return_type.is_failure() { + vm.state.current_frame.set_pc_from_u16(exception_handler) + } - let (snapshot, leftover_gas) = if let Some(FrameRemnant { - exception_handler, - snapshot, - }) = vm.state.current_frame.pop_near_call() - { - if TO_LABEL { - let pc = Immediate1::get(args, &mut vm.state).low_u32() as u16; - vm.state.current_frame.set_pc_from_u16(pc); - } else if return_type.is_failure() { - vm.state.current_frame.set_pc_from_u16(exception_handler) + (snapshot, near_call_leftover_gas) + } else { + let return_value_or_panic = if return_type == ReturnType::Panic { + None + } else { + let (raw_abi, is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); + let result = get_far_call_calldata(raw_abi, is_pointer, vm, false).filter(|pointer| { + vm.state.current_frame.is_kernel + || pointer.memory_page != vm.state.current_frame.calldata_heap + }); + + if result.is_none() { + return_type = ReturnType::Panic; } + result + }; - (snapshot, near_call_leftover_gas) - } else { - let return_value_or_panic = if return_type == ReturnType::Panic { - None - } else { - let (raw_abi, is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); - let result = - get_far_call_calldata(raw_abi, is_pointer, vm, false).filter(|pointer| { - vm.state.current_frame.is_kernel - || pointer.memory_page != vm.state.current_frame.calldata_heap - }); - - if result.is_none() { - return_type = ReturnType::Panic; - } - result - }; + let leftover_gas = vm + .state + .current_frame + .gas + .saturating_sub(vm.state.current_frame.stipend); - let leftover_gas = vm - .state - .current_frame - .gas - .saturating_sub(vm.state.current_frame.stipend); - - let Some(FrameRemnant { - exception_handler, - snapshot, - }) = vm.pop_frame( - return_value_or_panic - .as_ref() - .map(|pointer| pointer.memory_page), - ) - else { - // The initial frame is not rolled back, even if it fails. - // It is the caller's job to clean up when the execution as a whole fails because - // the caller may take external snapshots while the VM is in the initial frame and - // these would break were the initial frame to be rolled back. - - // But to continue execution would be nonsensical and can cause UB because there - // is no next instruction after a panic arising from some other instruction. - vm.state.current_frame.pc = invalid_instruction(); - - return if let Some(return_value) = return_value_or_panic { - let output = vm.state.heaps[return_value.memory_page] - .read_range_big_endian( - return_value.start..return_value.start + return_value.length, - ) - .to_vec(); - if return_type == ReturnType::Revert { - ExecutionStatus::Stopped(ExecutionEnd::Reverted(output)) - } else { - ExecutionStatus::Stopped(ExecutionEnd::ProgramFinished(output)) - } + let Some(FrameRemnant { + exception_handler, + snapshot, + }) = vm.pop_frame( + return_value_or_panic + .as_ref() + .map(|pointer| pointer.memory_page), + ) + else { + // The initial frame is not rolled back, even if it fails. + // It is the caller's job to clean up when the execution as a whole fails because + // the caller may take external snapshots while the VM is in the initial frame and + // these would break were the initial frame to be rolled back. + + // But to continue execution would be nonsensical and can cause UB because there + // is no next instruction after a panic arising from some other instruction. + vm.state.current_frame.pc = invalid_instruction(); + + return if let Some(return_value) = return_value_or_panic { + let output = vm.state.heaps[return_value.memory_page] + .read_range_big_endian( + return_value.start..return_value.start + return_value.length, + ) + .to_vec(); + if return_type == ReturnType::Revert { + ExecutionStatus::Stopped(ExecutionEnd::Reverted(output)) } else { - ExecutionStatus::Stopped(ExecutionEnd::Panicked) - }; + ExecutionStatus::Stopped(ExecutionEnd::ProgramFinished(output)) + } + } else { + ExecutionStatus::Stopped(ExecutionEnd::Panicked) }; + }; - vm.state.set_context_u128(0); - vm.state.registers = [U256::zero(); 16]; - - if let Some(return_value) = return_value_or_panic { - vm.state.registers[1] = return_value.into_u256(); - } - vm.state.register_pointer_flags = 2; - - if return_type.is_failure() { - vm.state.current_frame.set_pc_from_u16(exception_handler) - } + vm.state.set_context_u128(0); + vm.state.registers = [U256::zero(); 16]; - (snapshot, leftover_gas) - }; + if let Some(return_value) = return_value_or_panic { + vm.state.registers[1] = return_value.into_u256(); + } + vm.state.register_pointer_flags = 2; if return_type.is_failure() { - vm.world_diff.rollback(snapshot); + vm.state.current_frame.set_pc_from_u16(exception_handler) } - vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); - vm.state.current_frame.gas += leftover_gas; + (snapshot, leftover_gas) + }; + + if return_type.is_failure() { + vm.world_diff.rollback(snapshot); + } - ExecutionStatus::Running + vm.state.flags = Flags::new(return_type == ReturnType::Panic, false, false); + vm.state.current_frame.gas += leftover_gas; + + ExecutionStatus::Running +} + +fn ret( + vm: &mut VirtualMachine, + world: &mut W, + tracer: &mut T, +) -> ExecutionStatus { + instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, _, _| { + naked_ret::(vm, args) }) } +/// Turn the current instruction into a panic at no extra cost. (Great value, I know.) +/// +/// Call this when: +/// - gas runs out when paying for the fixed cost of an instruction +/// - causing side effects in a static context +/// - using privileged instructions while not in a system call +/// - the far call stack overflows +/// +/// For all other panics, point the instruction pointer at [PANIC] instead. +pub(crate) fn free_panic( + vm: &mut VirtualMachine, + tracer: &mut T, +) -> ExecutionStatus { + tracer.before_instruction::, _>(vm); + // args aren't used for panics unless TO_LABEL + let result = naked_ret::( + vm, + &Arguments::new(Predicate::Always, 0, ModeRequirements::none()), + ); + tracer.before_instruction::, _>(vm); + 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( @@ -152,23 +181,6 @@ pub fn invalid_instruction<'a, T, W>() -> &'a Instruction { pub(crate) const RETURN_COST: u32 = 5; -/// Turn the current instruction into a panic at no extra cost. (Great value, I know.) -/// -/// Call this when: -/// - gas runs out when paying for the fixed cost of an instruction -/// - causing side effects in a static context -/// - using privileged instructions while not in a system call -/// - 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 W, - tracer: &mut T, -) -> ExecutionStatus { - ret::(vm, world, tracer) -} - use super::monomorphization::*; use eravm_stable_interface::opcodes::{Normal, Panic, Revert}; diff --git a/src/vm.rs b/src/vm.rs index b41eac67..ebd90203 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,7 +6,6 @@ use crate::world_diff::ExternalSnapshot; use crate::{ callframe::{Callframe, FrameRemnant}, decommit::u256_into_address, - instruction_handlers::free_panic, stack::StackPool, state::State, world_diff::{Snapshot, WorldDiff}, @@ -14,7 +13,7 @@ use crate::{ }; use crate::{Instruction, ModeRequirements, Predicate}; use eravm_stable_interface::opcodes::TypeLevelCallingMode; -use eravm_stable_interface::{opcodes, CallingMode, HeapId, Tracer}; +use eravm_stable_interface::{CallingMode, HeapId, Tracer}; use u256::H160; #[derive(Debug)] @@ -78,33 +77,10 @@ impl VirtualMachine { pub fn run(&mut self, world: &mut W, tracer: &mut T) -> ExecutionEnd { unsafe { loop { - let args = &(*self.state.current_frame.pc).arguments; - - if self.state.use_gas(args.get_static_gas_cost()).is_err() - || !args.mode_requirements().met( - self.state.current_frame.is_kernel, - self.state.current_frame.is_static, - ) + if let ExecutionStatus::Stopped(end) = + ((*self.state.current_frame.pc).handler)(self, world, tracer) { - if let ExecutionStatus::Stopped(end) = free_panic(self, world, tracer) { - return end; - }; - continue; - } - - #[cfg(feature = "trace")] - self.print_instruction(self.state.current_frame.pc); - - if args.predicate().satisfied(&self.state.flags) { - if let ExecutionStatus::Stopped(end) = - ((*self.state.current_frame.pc).handler)(self, world, tracer) - { - return end; - }; - } else { - tracer.before_instruction::(self); - self.state.current_frame.pc = self.state.current_frame.pc.add(1); - tracer.after_instruction::(self); + return end; } } } @@ -126,31 +102,10 @@ impl VirtualMachine { let end = unsafe { loop { - let args = &(*self.state.current_frame.pc).arguments; - - if self.state.use_gas(args.get_static_gas_cost()).is_err() - || !args.mode_requirements().met( - self.state.current_frame.is_kernel, - self.state.current_frame.is_static, - ) + if let ExecutionStatus::Stopped(end) = + ((*self.state.current_frame.pc).handler)(self, world, tracer) { - if let ExecutionStatus::Stopped(e) = free_panic(self, world, tracer) { - break e; - }; - continue; - } - - #[cfg(feature = "trace")] - self.print_instruction(self.state.current_frame.pc); - - if args.predicate().satisfied(&self.state.flags) { - if let ExecutionStatus::Stopped(end) = - ((*self.state.current_frame.pc).handler)(self, world, tracer) - { - break end; - }; - } else { - self.state.current_frame.pc = self.state.current_frame.pc.add(1); + break end; } if self.state.total_unspent_gas() < minimum_gas { @@ -285,25 +240,6 @@ impl VirtualMachine { }) } - #[cfg(feature = "trace")] - fn print_instruction(&self, instruction: *const crate::instruction::Instruction) { - print!("{:?}: ", unsafe { - instruction.offset_from(self.state.current_frame.program.instruction(0).unwrap()) - }); - self.state.registers[1..] - .iter() - .zip(1..) - .for_each(|(&(mut x), i)| { - if self.state.register_pointer_flags & (1 << i) != 0 { - x.0[0] &= 0x00000000_ffffffffu64; - x.0[1] &= 0xffffffff_00000000u64; - } - print!("{x:?} ") - }); - print!("{}", self.state.current_frame.gas); - println!(); - } - pub(crate) fn start_new_tx(&mut self) { self.state.transaction_number = self.state.transaction_number.wrapping_add(1); self.world_diff.clear_transient_storage() From 3126fb06624e31354f497d9a30064c43b3b2b138 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Tue, 27 Aug 2024 15:36:19 +0200 Subject: [PATCH 59/61] clean up --- src/instruction_handlers/common.rs | 2 +- src/instruction_handlers/mod.rs | 2 +- src/single_instruction_test/vm.rs | 24 +++--------------------- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 40c00352..20d89fc6 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -1,4 +1,4 @@ -use super::free_panic; +use super::ret::free_panic; use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMachine}; use eravm_stable_interface::{opcodes, OpcodeType, Tracer}; diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index 67214e54..1f7e32fc 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -3,7 +3,7 @@ pub use eravm_stable_interface::opcodes::{ RotateRight, ShiftLeft, ShiftRight, Sub, Xor, }; pub use heap_access::{AuxHeap, Heap, HeapInterface}; -pub(crate) use ret::{free_panic, invalid_instruction, RETURN_COST}; +pub(crate) use ret::{invalid_instruction, RETURN_COST}; mod binop; mod common; diff --git a/src/single_instruction_test/vm.rs b/src/single_instruction_test/vm.rs index f7682126..62b8ca14 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -1,8 +1,8 @@ use super::{heap::Heaps, stack::StackPool}; use crate::{ addressing_modes::Arguments, callframe::Callframe, fat_pointer::FatPointer, - instruction::ExecutionStatus, instruction_handlers::free_panic, HeapId, Instruction, - ModeRequirements, Predicate, Settings, State, VirtualMachine, World, + instruction::ExecutionStatus, HeapId, Instruction, ModeRequirements, Predicate, Settings, + State, VirtualMachine, World, }; use arbitrary::Arbitrary; use eravm_stable_interface::Tracer; @@ -11,25 +11,7 @@ use u256::U256; impl VirtualMachine { pub fn run_single_instruction(&mut self, world: &mut W, tracer: &mut T) -> ExecutionStatus { - unsafe { - let args = &(*self.state.current_frame.pc).arguments; - - if self.state.use_gas(args.get_static_gas_cost()).is_err() - || !args.mode_requirements().met( - self.state.current_frame.is_kernel, - self.state.current_frame.is_static, - ) - { - return free_panic(self, world, tracer); - } - - if args.predicate().satisfied(&self.state.flags) { - ((*self.state.current_frame.pc).handler)(self, world, tracer) - } else { - self.state.current_frame.pc = self.state.current_frame.pc.add(1); - ExecutionStatus::Running - } - } + unsafe { ((*self.state.current_frame.pc).handler)(self, world, tracer) } } pub fn is_in_valid_state(&self) -> bool { From f4bf057e296a82ea41ef02fc1834c3642d2851b2 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 28 Aug 2024 17:21:50 +0200 Subject: [PATCH 60/61] derive Hash for Opcode --- eravm-stable-interface/src/tracer_interface.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index d79ce461..40538407 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -94,7 +94,7 @@ pub mod opcodes { } } -#[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum Opcode { Nop, Add, @@ -141,14 +141,14 @@ pub enum Opcode { TransientStorageWrite, } -#[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum CallingMode { Normal, Delegate, Mimic, } -#[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum ReturnType { Normal, Revert, From 55a1829f2ea54b58db58f4dfa14bc5f1a5feff8f Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Wed, 28 Aug 2024 17:22:21 +0200 Subject: [PATCH 61/61] fix bug: calling before_instruction instead of after --- src/instruction_handlers/ret.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index c3c6d053..a4f4e292 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -143,7 +143,7 @@ pub(crate) fn free_panic( vm, &Arguments::new(Predicate::Always, 0, ModeRequirements::none()), ); - tracer.before_instruction::, _>(vm); + tracer.after_instruction::, _>(vm); result }