From 7d2fd8b5084116496aca1203b5bd29a0b26482e8 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau <76252340+MarcosNicolau@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:57:39 -0300 Subject: [PATCH 1/3] Refactor rollbacks (#197) * Create World struct * Add storage_read/write for World * Add WorldSnapshot to frame struct * Add rollbacks on ret and inexplicit panics * Adapt far_call and near_call to new world struct * Adapt log and decommit opcodes to world * Adapt vm to world * Fix transient_stroage_read on world * Add event to world and made it rollbackable * Remove unnecessary results in World impl * Add getters for world rollbackable fields * Re-add InMemory storage * Temporarily update submodule to point to adaptation branch * Fix submodule branch * Update makefile to update submdodule to specified branch * Remove submodule update remote * Add missing rollback on initial_frames * Move rollback functions to its own file * Hide borrows * Refactor struct names Now the VmState struct refers to the World and the VmState before got renamed for Execution. * Cargo fmt * Add update remote in submodule * Remove submodule update remote * Update submodule * Remove branch from .gitmodule * Add EOL to .gitmodukes [skip-ci] --- era-compiler-tester | 2 +- src/address_operands.rs | 20 +- src/call_frame.rs | 27 +- src/execution.rs | 470 ++++++++++++++++++++++ src/heaps.rs | 2 +- src/lib.rs | 6 +- src/op_handlers/add.rs | 4 +- src/op_handlers/and.rs | 4 +- src/op_handlers/aux_heap_read.rs | 4 +- src/op_handlers/aux_heap_write.rs | 4 +- src/op_handlers/context.rs | 20 +- src/op_handlers/div.rs | 4 +- src/op_handlers/event.rs | 5 +- src/op_handlers/far_call.rs | 36 +- src/op_handlers/fat_pointer_read.rs | 4 +- src/op_handlers/heap_read.rs | 4 +- src/op_handlers/heap_write.rs | 4 +- src/op_handlers/jump.rs | 4 +- src/op_handlers/log.rs | 37 +- src/op_handlers/mul.rs | 4 +- src/op_handlers/near_call.rs | 16 +- src/op_handlers/opcode_decommit.rs | 8 +- src/op_handlers/or.rs | 4 +- src/op_handlers/precompile_call.rs | 4 +- src/op_handlers/ptr_add.rs | 4 +- src/op_handlers/ptr_pack.rs | 4 +- src/op_handlers/ptr_shrink.rs | 4 +- src/op_handlers/ptr_sub.rs | 4 +- src/op_handlers/ret.rs | 34 +- src/op_handlers/shift.rs | 10 +- src/op_handlers/sub.rs | 4 +- src/op_handlers/unimplemented.rs | 4 +- src/op_handlers/xor.rs | 4 +- src/output.rs | 4 +- src/ptr_operator.rs | 6 +- src/rollbacks.rs | 55 +++ src/state.rs | 514 ++++--------------------- src/store.rs | 109 +----- src/tracers/blob_saver_tracer.rs | 4 +- src/tracers/last_state_saver_tracer.rs | 8 +- src/tracers/print_tracer.rs | 4 +- src/tracers/tracer.rs | 8 +- src/vm.rs | 240 +++++------- 43 files changed, 876 insertions(+), 845 deletions(-) create mode 100644 src/execution.rs create mode 100644 src/rollbacks.rs diff --git a/era-compiler-tester b/era-compiler-tester index d640c64b..f691279f 160000 --- a/era-compiler-tester +++ b/era-compiler-tester @@ -1 +1 @@ -Subproject commit d640c64b47e2b1dc0d19b32f23e72d0fecfcd027 +Subproject commit f691279fed07d57ccb78ddf87b67741832921743 diff --git a/src/address_operands.rs b/src/address_operands.rs index d0b4d0ac..e50e6cc4 100644 --- a/src/address_operands.rs +++ b/src/address_operands.rs @@ -3,24 +3,24 @@ use zkevm_opcode_defs::{ImmMemHandlerFlags, Operand, RegOrImmFlags}; use crate::{ eravm_error::{EraVmError, OperandError}, - state::VMState, + execution::Execution, utils::LowUnsigned, value::TaggedValue, Opcode, }; -fn only_reg_read(vm: &VMState, opcode: &Opcode) -> (TaggedValue, TaggedValue) { +fn only_reg_read(vm: &Execution, opcode: &Opcode) -> (TaggedValue, TaggedValue) { let src0 = vm.get_register(opcode.src0_index); let src1 = vm.get_register(opcode.src1_index); (src0, src1) } -fn only_imm16_read(vm: &VMState, opcode: &Opcode) -> (TaggedValue, TaggedValue) { +fn only_imm16_read(vm: &Execution, opcode: &Opcode) -> (TaggedValue, TaggedValue) { let src1 = vm.get_register(opcode.src1_index); (TaggedValue::new_raw_integer(U256::from(opcode.imm0)), src1) } -fn reg_and_imm_read(vm: &VMState, opcode: &Opcode) -> (TaggedValue, TaggedValue) { +fn reg_and_imm_read(vm: &Execution, opcode: &Opcode) -> (TaggedValue, TaggedValue) { let src0 = vm.get_register(opcode.src0_index); let src1 = vm.get_register(opcode.src1_index); let offset = opcode.imm0; @@ -32,7 +32,7 @@ fn reg_and_imm_read(vm: &VMState, opcode: &Opcode) -> (TaggedValue, TaggedValue) } pub fn address_operands_read( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, ) -> Result<(TaggedValue, TaggedValue), EraVmError> { let (op1, op2) = match opcode.src0_operand_type { @@ -105,7 +105,7 @@ enum OutputOperandPosition { } fn only_reg_write( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, output_op_pos: OutputOperandPosition, res: TaggedValue, @@ -116,14 +116,14 @@ fn only_reg_write( } } -fn dest_stack_address(vm: &mut VMState, opcode: &Opcode) -> TaggedValue { +fn dest_stack_address(vm: &mut Execution, opcode: &Opcode) -> TaggedValue { let dst0 = vm.get_register(opcode.dst0_index); let offset = opcode.imm1; dst0 + TaggedValue::new_raw_integer(U256::from(offset)) } pub fn address_operands_store( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, res: TaggedValue, ) -> Result<(), EraVmError> { @@ -131,7 +131,7 @@ pub fn address_operands_store( } pub fn address_operands_div_mul( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, res: (TaggedValue, TaggedValue), ) -> Result<(), EraVmError> { @@ -139,7 +139,7 @@ pub fn address_operands_div_mul( } fn address_operands( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, res: (TaggedValue, Option), ) -> Result<(), EraVmError> { diff --git a/src/call_frame.rs b/src/call_frame.rs index 7baebb69..5eff6f84 100644 --- a/src/call_frame.rs +++ b/src/call_frame.rs @@ -2,16 +2,15 @@ use std::num::Saturating; use u256::U256; use zkevm_opcode_defs::ethereum_types::Address; -use crate::{state::Stack, store::SnapShot, utils::is_kernel}; +use crate::{execution::Stack, state::StateSnapshot, utils::is_kernel}; #[derive(Debug, Clone)] pub struct CallFrame { pub pc: u64, - pub transient_storage_snapshot: SnapShot, pub gas_left: Saturating, pub exception_handler: u64, pub sp: u32, - pub storage_snapshot: SnapShot, + pub snapshot: StateSnapshot, } #[derive(Debug, Clone)] @@ -64,17 +63,11 @@ impl Context { calldata_heap_id: u32, exception_handler: u64, context_u128: u128, - transient_storage_snapshot: SnapShot, - storage_snapshot: SnapShot, + snapshot: StateSnapshot, is_static: bool, ) -> Self { Self { - frame: CallFrame::new_far_call_frame( - gas_stipend, - exception_handler, - storage_snapshot, - transient_storage_snapshot, - ), + frame: CallFrame::new_far_call_frame(gas_stipend, exception_handler, snapshot), near_call_frames: vec![], contract_address, caller, @@ -98,16 +91,14 @@ impl CallFrame { pub fn new_far_call_frame( gas_stipend: u32, exception_handler: u64, - storage_snapshot: SnapShot, - transient_storage_snapshot: SnapShot, + snapshot: StateSnapshot, ) -> Self { Self { pc: 0, gas_left: Saturating(gas_stipend), - transient_storage_snapshot, exception_handler, sp: 0, - storage_snapshot, + snapshot, } } @@ -116,17 +107,15 @@ impl CallFrame { sp: u32, pc: u64, gas_stipend: u32, - transient_storage_snapshot: SnapShot, exception_handler: u64, - storage_snapshot: SnapShot, + snapshot: StateSnapshot, ) -> Self { Self { pc, gas_left: Saturating(gas_stipend), - transient_storage_snapshot, exception_handler, sp, - storage_snapshot, + snapshot, } } } diff --git a/src/execution.rs b/src/execution.rs new file mode 100644 index 00000000..4b203722 --- /dev/null +++ b/src/execution.rs @@ -0,0 +1,470 @@ +use std::num::Saturating; + +use crate::call_frame::{CallFrame, Context}; +use crate::heaps::Heaps; + +use crate::eravm_error::{ContextError, EraVmError, HeapError, StackError}; +use crate::state::StateSnapshot; +use crate::{ + opcode::Predicate, + value::{FatPointer, TaggedValue}, + Opcode, +}; +use u256::{H160, U256}; +use zkevm_opcode_defs::ethereum_types::Address; +use zkevm_opcode_defs::MEMORY_GROWTH_ERGS_PER_BYTE; + +pub const CALLDATA_HEAP: u32 = 1; +pub const FIRST_HEAP: u32 = 2; +pub const FIRST_AUX_HEAP: u32 = 3; + +#[derive(Debug, Clone)] +pub struct Stack { + pub stack: Vec, +} + +#[derive(Debug, Clone)] +pub struct Heap { + heap: Vec, +} +#[derive(Debug, Clone)] +pub struct Execution { + // The first register, r0, is actually always zero and not really used. + // Writing to it does nothing. + registers: [TaggedValue; 15], + /// Overflow or less than flag + pub flag_lt_of: bool, // We only use the first three bits for the flags here: LT, GT, EQ. + /// Greater Than flag + pub flag_gt: bool, + /// Equal flag + pub flag_eq: bool, + pub running_contexts: Vec, + pub program: Vec, + pub tx_number: u64, + pub heaps: Heaps, + pub register_context_u128: u128, + pub default_aa_code_hash: [u8; 32], + pub evm_interpreter_code_hash: [u8; 32], + pub hook_address: u32, + pub use_hooks: bool, +} + +// Totally arbitrary, probably we will have to change it later. +pub const DEFAULT_INITIAL_GAS: u32 = 1 << 16; +impl Execution { + #[allow(clippy::too_many_arguments)] + pub fn new( + program_code: Vec, + calldata: Vec, + contract_address: H160, + caller: H160, + context_u128: u128, + default_aa_code_hash: [u8; 32], + evm_interpreter_code_hash: [u8; 32], + hook_address: u32, + use_hooks: bool, + ) -> Self { + let mut registers = [TaggedValue::default(); 15]; + let calldata_ptr = FatPointer { + page: CALLDATA_HEAP, + offset: 0, + start: 0, + len: calldata.len() as u32, + }; + + registers[0] = TaggedValue::new_pointer(calldata_ptr.encode()); + + let context = Context::new( + program_code.clone(), + u32::MAX - 0x80000000, + contract_address, + contract_address, + caller, + FIRST_HEAP, + FIRST_AUX_HEAP, + CALLDATA_HEAP, + 0, + context_u128, + StateSnapshot::default(), + false, + ); + + let heaps = Heaps::new(calldata); + + Self { + registers, + flag_lt_of: false, + flag_gt: false, + flag_eq: false, + running_contexts: vec![context], + + program: program_code, + tx_number: 0, + heaps, + register_context_u128: context_u128, + default_aa_code_hash, + evm_interpreter_code_hash, + hook_address, + use_hooks, + } + } + + pub fn clear_registers(&mut self) { + for register in self.registers.iter_mut() { + *register = TaggedValue::new_raw_integer(U256::zero()); + } + } + + pub fn clear_flags(&mut self) { + self.flag_lt_of = false; + self.flag_gt = false; + self.flag_eq = false; + } + + pub fn clear_pointer_flags(&mut self) { + for register in self.registers.iter_mut() { + register.to_raw_integer(); + } + } + + #[allow(clippy::too_many_arguments)] + pub fn push_far_call_frame( + &mut self, + program_code: Vec, + gas_stipend: u32, + code_address: Address, + contract_address: Address, + caller: Address, + heap_id: u32, + aux_heap_id: u32, + calldata_heap_id: u32, + exception_handler: u64, + context_u128: u128, + snapshot: StateSnapshot, + is_static: bool, + ) -> Result<(), EraVmError> { + let new_context = Context::new( + program_code, + gas_stipend, + contract_address, + code_address, + caller, + heap_id, + aux_heap_id, + calldata_heap_id, + exception_handler, + context_u128, + snapshot, + is_static, + ); + self.running_contexts.push(new_context); + Ok(()) + } + pub fn pop_context(&mut self) -> Result { + self.running_contexts.pop().ok_or(ContextError::NoContract) + } + + pub fn pop_frame(&mut self) -> Result { + let current_context = self.current_context_mut()?; + if current_context.near_call_frames.is_empty() { + let context = self.pop_context()?; + Ok(context.frame) + } else { + current_context + .near_call_frames + .pop() + .ok_or(ContextError::NoContract) + } + } + + pub fn push_near_call_frame(&mut self, near_call_frame: CallFrame) -> Result<(), EraVmError> { + self.current_context_mut()? + .near_call_frames + .push(near_call_frame); + Ok(()) + } + + pub fn current_context_mut(&mut self) -> Result<&mut Context, ContextError> { + self.running_contexts + .last_mut() + .ok_or(ContextError::NoContract) + } + + pub fn current_context(&self) -> Result<&Context, ContextError> { + self.running_contexts.last().ok_or(ContextError::NoContract) + } + + pub fn current_frame_mut(&mut self) -> Result<&mut CallFrame, ContextError> { + let current_context = self.current_context_mut()?; + if current_context.near_call_frames.is_empty() { + Ok(&mut current_context.frame) + } else { + current_context + .near_call_frames + .last_mut() + .ok_or(ContextError::NoContract) + } + } + + pub fn current_frame(&self) -> Result<&CallFrame, ContextError> { + let current_context = self.current_context()?; + if current_context.near_call_frames.is_empty() { + Ok(¤t_context.frame) + } else { + current_context + .near_call_frames + .last() + .ok_or(ContextError::NoContract) + } + } + + pub fn can_execute(&self, opcode: &Opcode) -> Result { + let predicate_holds = match opcode.predicate { + Predicate::Always => true, + Predicate::Gt => self.flag_gt, + Predicate::Lt => self.flag_lt_of, + Predicate::Eq => self.flag_eq, + Predicate::Ge => self.flag_eq || self.flag_gt, + Predicate::Le => self.flag_eq || self.flag_lt_of, + Predicate::Ne => !self.flag_eq, + Predicate::GtOrLt => self.flag_gt || self.flag_lt_of, + }; + if opcode.variant.requires_kernel_mode() && !self.current_context()?.is_kernel() { + return Err(EraVmError::VmNotInKernelMode); + } + if self.current_context()?.is_static && !opcode.variant.can_be_used_in_static_context() { + return Err(EraVmError::OpcodeIsNotStatic); + } + Ok(predicate_holds) + } + + pub fn get_register(&self, index: u8) -> TaggedValue { + if index != 0 { + return self.registers[(index - 1) as usize]; + } + + TaggedValue::default() + } + + pub fn set_register(&mut self, index: u8, value: TaggedValue) { + if index == 0 { + return; + } + + self.registers[(index - 1) as usize] = value; + } + + pub fn get_opcode_with_test_encode(&self) -> Result { + let current_context = self.current_context()?; + let pc = self.current_frame()?.pc; + // Since addressing is word-sized (i.e. one address equals a u256 value), + // when using u128 encoding we actually have 2 opcodes pointed + // by our program counter (pc). + // And then, we have two cases: + // - pc mod 2 ≣ 1 -> Take the low 128 bits of the word and decode the opcode. + // - pc mod 2 ≣ 0 -> Take the high 128 bits of the word and decode the opcode . + // U256 provides the low_u128 method which is self-describing. + let raw_op = current_context + .code_page + // pc / 2 + .get(pc as usize >> 1); + let opcode = match pc % 2 { + 1 => raw_op.low_u128(), + _ => (raw_op >> 128).low_u128(), + }; + Opcode::try_from_raw_opcode_test_encode(opcode) + } + pub fn get_opcode(&self) -> Result { + let current_context = self.current_context()?; + let pc = self.current_frame()?.pc; + let raw_opcode = current_context.code_page.get(pc as usize / 4); + + let raw_op = match pc % 4 { + 3 => (raw_opcode & u64::MAX.into()).as_u64(), + 2 => ((raw_opcode >> 64) & u64::MAX.into()).as_u64(), + 1 => ((raw_opcode >> 128) & u64::MAX.into()).as_u64(), + _ => ((raw_opcode >> 192) & u64::MAX.into()).as_u64(), // 0 + }; + + Opcode::try_from_raw_opcode(raw_op) + } + pub fn decrease_gas(&mut self, cost: u32) -> Result<(), EraVmError> { + let underflows = cost > self.current_frame()?.gas_left.0; + if underflows { + self.set_gas_left(0)?; + return Err(EraVmError::OutOfGas); + } + self.current_frame_mut()?.gas_left -= cost; + Ok(()) + } + + pub fn set_gas_left(&mut self, gas: u32) -> Result<(), EraVmError> { + self.current_frame_mut()?.gas_left = Saturating(gas); + Ok(()) + } + + pub fn gas_left(&self) -> Result { + Ok(self.current_frame()?.gas_left.0) + } + + pub fn in_near_call(&self) -> Result { + Ok(!self.current_context()?.near_call_frames.is_empty()) + } + + pub fn in_far_call(&self) -> bool { + self.running_contexts.len() > 1 + } +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +impl Stack { + pub fn new() -> Self { + Self { stack: vec![] } + } + + pub fn push(&mut self, value: TaggedValue) { + self.stack.push(value); + } + + pub fn fill_with_zeros(&mut self, value: usize) { + for _ in 0..value { + self.stack.push(TaggedValue { + value: U256::zero(), + is_pointer: false, + }); + } + } + + pub fn get_with_offset(&self, offset: u16, sp: u32) -> Result { + if offset as u32 > sp || offset == 0 { + return Err(StackError::ReadOutOfBounds); + } + let index = (sp - offset as u32) as usize; + if index >= self.stack.len() { + return Ok(TaggedValue::default()); + } + Ok(self.stack[index]) + } + + pub fn get_absolute(&self, index: u16, sp: u32) -> Result { + if index as u32 >= sp { + return Err(StackError::ReadOutOfBounds); + } + let index = index as usize; + if index >= self.stack.len() { + return Ok(TaggedValue::default()); + } + Ok(self.stack[index]) + } + + pub fn store_with_offset( + &mut self, + offset: u16, + value: TaggedValue, + sp: u32, + ) -> Result<(), StackError> { + if offset as u32 > sp || offset == 0 { + return Err(StackError::StoreOutOfBounds); + } + let index = (sp - offset as u32) as usize; + if index >= self.stack.len() { + self.fill_with_zeros(index - self.stack.len() + 1); + } + self.stack[index] = value; + Ok(()) + } + + pub fn store_absolute(&mut self, index: u16, value: TaggedValue) -> Result<(), StackError> { + let index = index as usize; + if index >= self.stack.len() { + self.fill_with_zeros(index - self.stack.len() + 1); + } + self.stack[index] = value; + Ok(()) + } +} + +impl Default for Heap { + fn default() -> Self { + Self::new(vec![]) + } +} + +impl Heap { + pub fn new(values: Vec) -> Self { + Self { heap: values } + } + // Returns how many ergs the expand costs + pub fn expand_memory(&mut self, address: u32) -> u32 { + if address >= self.heap.len() as u32 { + let old_size = self.heap.len() as u32; + self.heap.resize(address as usize, 0); + return MEMORY_GROWTH_ERGS_PER_BYTE * (address - old_size); + } + 0 + } + + pub fn store(&mut self, address: u32, value: U256) { + let mut bytes: [u8; 32] = [0; 32]; + value.to_big_endian(&mut bytes); + for (i, item) in bytes.iter().enumerate() { + self.heap[address as usize + i] = *item; + } + } + + pub fn read(&self, address: u32) -> U256 { + let mut result = U256::zero(); + + for i in 0..32 { + result |= U256::from(self.heap[address as usize + (31 - i)]) << (i * 8); + } + result + } + + pub fn expanded_read(&mut self, address: u32) -> (U256, u32) { + let gas_cost = self.expand_memory(address + 32); + let result = self.read(address); + (result, gas_cost) + } + + pub fn read_byte(&self, address: u32) -> u8 { + self.heap[address as usize] + } + + pub fn read_from_pointer(&self, pointer: &FatPointer) -> U256 { + let mut result = U256::zero(); + for i in 0..32 { + let addr = pointer.start + pointer.offset + (31 - i); + if addr < pointer.start + pointer.len { + result |= U256::from(self.heap[addr as usize]) << (i * 8); + } + } + result + } + + pub fn read_unaligned_from_pointer(&self, pointer: &FatPointer) -> Result, HeapError> { + let mut result = Vec::new(); + let start = pointer.start + pointer.offset; + let finish = start + pointer.len; + for i in start..finish { + if i as usize >= self.heap.len() { + return Err(HeapError::ReadOutOfBounds); + } + result.push(self.heap[i as usize]); + } + Ok(result) + } + + pub fn len(&self) -> usize { + self.heap.len() + } + + pub fn is_empty(&self) -> bool { + self.heap.is_empty() + } +} diff --git a/src/heaps.rs b/src/heaps.rs index caaafb6f..6d8a9442 100644 --- a/src/heaps.rs +++ b/src/heaps.rs @@ -1,6 +1,6 @@ use zkevm_opcode_defs::system_params::NEW_FRAME_MEMORY_STIPEND; -use crate::{eravm_error::HeapError, state::Heap}; +use crate::{eravm_error::HeapError, execution::Heap}; #[derive(Debug, Clone, Default)] pub struct Heaps { diff --git a/src/lib.rs b/src/lib.rs index 385d6347..753422be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,21 @@ mod address_operands; pub mod call_frame; mod eravm_error; +pub mod execution; pub mod heaps; mod op_handlers; mod opcode; pub mod output; mod precompiles; mod ptr_operator; -pub mod state; pub mod store; pub mod tracers; pub mod utils; pub mod value; pub mod vm; +pub use execution::Execution; pub use opcode::Opcode; -pub use state::VMState; pub use vm::EraVM; +mod rollbacks; +pub mod state; use zkevm_opcode_defs::Opcode as Variant; diff --git a/src/op_handlers/add.rs b/src/op_handlers/add.rs index 4bce9fb3..ac3e2d46 100644 --- a/src/op_handlers/add.rs +++ b/src/op_handlers/add.rs @@ -1,9 +1,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn add(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn add(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); // res = (src0 + src1) mod (2**256); diff --git a/src/op_handlers/and.rs b/src/op_handlers/and.rs index a62001cc..318013c7 100644 --- a/src/op_handlers/and.rs +++ b/src/op_handlers/and.rs @@ -1,9 +1,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn and(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn and(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let res = src0.value & src1.value; diff --git a/src/op_handlers/aux_heap_read.rs b/src/op_handlers/aux_heap_read.rs index 33de5876..a4628c3d 100644 --- a/src/op_handlers/aux_heap_read.rs +++ b/src/op_handlers/aux_heap_read.rs @@ -4,9 +4,9 @@ use zkevm_opcode_defs::MAX_OFFSET_TO_DEREF_LOW_U32; use crate::address_operands::address_operands_read; use crate::eravm_error::{EraVmError, HeapError, OperandError}; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn aux_heap_read(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn aux_heap_read(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, _) = address_operands_read(vm, opcode)?; if src0.is_pointer { return Err(OperandError::InvalidSrcPointer(opcode.variant).into()); diff --git a/src/op_handlers/aux_heap_write.rs b/src/op_handlers/aux_heap_write.rs index 38bfbe80..a9feebc8 100644 --- a/src/op_handlers/aux_heap_write.rs +++ b/src/op_handlers/aux_heap_write.rs @@ -4,9 +4,9 @@ use zkevm_opcode_defs::MAX_OFFSET_TO_DEREF_LOW_U32; use crate::address_operands::address_operands_read; use crate::eravm_error::{EraVmError, HeapError, OperandError}; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn aux_heap_write(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn aux_heap_write(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; if src0.is_pointer { return Err(OperandError::InvalidSrcPointer(opcode.variant).into()); diff --git a/src/op_handlers/context.rs b/src/op_handlers/context.rs index 50f72f3d..01d74d93 100644 --- a/src/op_handlers/context.rs +++ b/src/op_handlers/context.rs @@ -5,7 +5,7 @@ use zkevm_opcode_defs::VmMetaParameters; use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::{EraVmError, HeapError}; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; // consider moving this function to a utils crate // taken from matter-labs zk evm implementation @@ -16,23 +16,23 @@ fn address_to_u256(address: &Address) -> U256 { U256::from_big_endian(&buffer) } -pub fn this(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn this(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer(address_to_u256(&vm.current_context()?.contract_address)); address_operands_store(vm, opcode, res) } -pub fn caller(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn caller(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer(address_to_u256(&vm.current_context()?.caller)); address_operands_store(vm, opcode, res) } -pub fn code_address(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn code_address(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer(address_to_u256(&vm.current_context()?.code_address)); address_operands_store(vm, opcode, res) } -pub fn meta(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn meta(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer( (VmMetaParameters { heap_size: vm @@ -55,28 +55,28 @@ pub fn meta(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { address_operands_store(vm, opcode, res) } -pub fn ergs_left(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn ergs_left(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer(U256::from(vm.current_frame()?.gas_left.0)); address_operands_store(vm, opcode, res) } -pub fn sp(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn sp(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let sp = vm.current_frame()?.sp; address_operands_store(vm, opcode, TaggedValue::new_raw_integer(U256::from(sp))) } -pub fn get_context_u128(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn get_context_u128(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer(U256::from(vm.current_context()?.context_u128)); address_operands_store(vm, opcode, res) } -pub fn set_context_u128(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn set_context_u128(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, _) = address_operands_read(vm, opcode)?; vm.register_context_u128 = src0.value.as_u128(); Ok(()) } -pub fn increment_tx_number(vm: &mut VMState, _opcode: &Opcode) -> Result<(), EraVmError> { +pub fn increment_tx_number(vm: &mut Execution, _opcode: &Opcode) -> Result<(), EraVmError> { vm.tx_number += 1; Ok(()) } diff --git a/src/op_handlers/div.rs b/src/op_handlers/div.rs index 24c9b0a5..2cda9e3c 100644 --- a/src/op_handlers/div.rs +++ b/src/op_handlers/div.rs @@ -3,9 +3,9 @@ use u256::U256; use crate::address_operands::{address_operands_div_mul, address_operands_read}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn div(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn div(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); let mut quotient = U256::zero(); diff --git a/src/op_handlers/event.rs b/src/op_handlers/event.rs index 79377a15..9a8fbe87 100644 --- a/src/op_handlers/event.rs +++ b/src/op_handlers/event.rs @@ -3,11 +3,12 @@ use zkevm_opcode_defs::ADDRESS_EVENT_WRITER; use crate::{ eravm_error::EraVmError, + execution::Execution, state::{Event, VMState}, Opcode, }; -pub fn event(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn event(vm: &mut Execution, opcode: &Opcode, state: &mut VMState) -> Result<(), EraVmError> { if vm.current_context()?.contract_address == H160::from_low_u64_be(ADDRESS_EVENT_WRITER as u64) { let key = vm.get_register(opcode.src0_index).value; @@ -20,7 +21,7 @@ pub fn event(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { tx_number: vm.tx_number as u16, }; - vm.events.push(event); + state.record_event(event); } Ok(()) } diff --git a/src/op_handlers/far_call.rs b/src/op_handlers/far_call.rs index 1051885f..e3348da7 100644 --- a/src/op_handlers/far_call.rs +++ b/src/op_handlers/far_call.rs @@ -8,8 +8,10 @@ use zkevm_opcode_defs::{ use crate::{ address_operands::address_operands_read, eravm_error::{EraVmError, HeapError}, + execution::Execution, + rollbacks::Rollbackable, state::VMState, - store::{ContractStorage, StateStorage, StorageError, StorageKey}, + store::{StorageError, StorageKey}, utils::{address_into_u256, is_kernel}, value::{FatPointer, TaggedValue}, Opcode, @@ -49,7 +51,7 @@ impl PointerSource { } pub fn get_forward_memory_pointer( source: U256, - vm: &mut VMState, + vm: &mut Execution, is_pointer: bool, ) -> Result { let pointer_kind = PointerSource::from_abi((source.0[3] >> 32) as u8); @@ -90,7 +92,7 @@ pub fn get_forward_memory_pointer( fn far_call_params_from_register( source: TaggedValue, - vm: &mut VMState, + vm: &mut Execution, ) -> Result { let is_pointer = source.is_pointer; let source = source.value; @@ -122,7 +124,7 @@ fn address_from_u256(register_value: &U256) -> H160 { } fn decommit_code_hash( - state_storage: &mut StateStorage, + state: &mut VMState, address: Address, default_aa_code_hash: [u8; 32], evm_interpreter_code_hash: [u8; 32], @@ -133,10 +135,10 @@ fn decommit_code_hash( Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); let storage_key = StorageKey::new(deployer_system_contract_address, address_into_u256(address)); - let code_info = match state_storage.storage_read(storage_key)? { + let code_info = match state.storage_read(storage_key)? { Some(code_info) => code_info, None => { - state_storage.storage_write(storage_key, U256::zero())?; + state.storage_write(storage_key, U256::zero()); U256::zero() } }; @@ -200,26 +202,23 @@ fn decommit_code_hash( } pub fn far_call( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, far_call: &FarCallOpcode, - state_storage: &mut StateStorage, - contract_storage: &mut dyn ContractStorage, - transient_storage: &StateStorage, + state: &mut VMState, ) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let contract_address = address_from_u256(&src1.value); let exception_handler = opcode.imm0 as u64; - let storage_snapshot = state_storage.create_snapshot(); - let transient_storage_snapshot = transient_storage.create_snapshot(); + let snapshot = state.snapshot(); let mut abi = get_far_call_arguments(src0.value); abi.is_constructor_call = abi.is_constructor_call && vm.current_context()?.is_kernel(); abi.is_system_call = abi.is_system_call && is_kernel(&contract_address); let (code_key, is_evm) = decommit_code_hash( - state_storage, + state, contract_address, vm.default_aa_code_hash, vm.evm_interpreter_code_hash, @@ -240,7 +239,7 @@ pub fn far_call( .checked_add(stipend) .expect("stipend must not cause overflow"); - let program_code = contract_storage + let program_code = state .decommit(code_key)? .ok_or(StorageError::KeyNotPresent)?; let new_heap = vm.heaps.allocate(); @@ -260,8 +259,7 @@ pub fn far_call( forward_memory.page, exception_handler, vm.register_context_u128, - transient_storage_snapshot, - storage_snapshot, + snapshot, is_new_frame_static && !is_evm, )?; } @@ -286,8 +284,7 @@ pub fn far_call( forward_memory.page, exception_handler, vm.register_context_u128, - transient_storage_snapshot, - storage_snapshot, + snapshot, is_new_frame_static && !is_evm, )?; } @@ -306,8 +303,7 @@ pub fn far_call( forward_memory.page, exception_handler, this_context.context_u128, - transient_storage_snapshot, - storage_snapshot, + snapshot, is_new_frame_static && !is_evm, )?; } diff --git a/src/op_handlers/fat_pointer_read.rs b/src/op_handlers/fat_pointer_read.rs index ba4d65ff..4e79d69b 100644 --- a/src/op_handlers/fat_pointer_read.rs +++ b/src/op_handlers/fat_pointer_read.rs @@ -3,9 +3,9 @@ use u256::U256; use crate::address_operands::address_operands_read; use crate::eravm_error::{EraVmError, HeapError, OperandError}; use crate::value::{FatPointer, TaggedValue}; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn fat_pointer_read(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn fat_pointer_read(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, _) = address_operands_read(vm, opcode)?; if !src0.is_pointer { return Err(OperandError::InvalidSrcNotPointer(opcode.variant).into()); diff --git a/src/op_handlers/heap_read.rs b/src/op_handlers/heap_read.rs index 3ecbc08b..daac031a 100644 --- a/src/op_handlers/heap_read.rs +++ b/src/op_handlers/heap_read.rs @@ -4,9 +4,9 @@ use zkevm_opcode_defs::MAX_OFFSET_TO_DEREF_LOW_U32; use crate::address_operands::address_operands_read; use crate::eravm_error::{EraVmError, HeapError, OperandError}; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn heap_read(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn heap_read(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, _) = address_operands_read(vm, opcode)?; if src0.is_pointer { return Err(OperandError::InvalidSrcPointer(opcode.variant).into()); diff --git a/src/op_handlers/heap_write.rs b/src/op_handlers/heap_write.rs index 69671b1a..e2dfbe7e 100644 --- a/src/op_handlers/heap_write.rs +++ b/src/op_handlers/heap_write.rs @@ -5,9 +5,9 @@ use crate::address_operands::address_operands_read; use crate::eravm_error::{EraVmError, HeapError, OperandError}; use crate::value::TaggedValue; use crate::vm::ExecutionOutput; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn heap_write(vm: &mut VMState, opcode: &Opcode) -> Result { +pub fn heap_write(vm: &mut Execution, opcode: &Opcode) -> Result { let (src0, src1) = address_operands_read(vm, opcode)?; if src0.is_pointer { return Err(OperandError::InvalidSrcPointer(opcode.variant).into()); diff --git a/src/op_handlers/jump.rs b/src/op_handlers/jump.rs index 73f30dd8..a1e45ced 100644 --- a/src/op_handlers/jump.rs +++ b/src/op_handlers/jump.rs @@ -1,9 +1,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn jump(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn jump(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, _) = address_operands_read(vm, opcode)?; let next_pc = src0.value.low_u64(); diff --git a/src/op_handlers/log.rs b/src/op_handlers/log.rs index 60d846e1..c9dc58fd 100644 --- a/src/op_handlers/log.rs +++ b/src/op_handlers/log.rs @@ -2,79 +2,80 @@ use u256::U256; use crate::{ eravm_error::EraVmError, - state::VMState, - store::{L2ToL1Log, StateStorage, StorageKey}, + execution::Execution, + state::{L2ToL1Log, VMState}, + store::StorageKey, value::TaggedValue, Opcode, }; pub fn storage_write( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, - state_storage: &mut StateStorage, + state: &mut VMState, ) -> Result<(), EraVmError> { let key_for_contract_storage = vm.get_register(opcode.src0_index).value; let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); let value = vm.get_register(opcode.src1_index).value; - state_storage.storage_write(key, value)?; + state.storage_write(key, value); Ok(()) } pub fn storage_read( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, - state_storage: &StateStorage, + state: &VMState, ) -> Result<(), EraVmError> { let key_for_contract_storage = vm.get_register(opcode.src0_index).value; let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); - let value = state_storage.storage_read(key)?.unwrap_or(U256::zero()); + let value = state.storage_read(key)?.unwrap_or(U256::zero()); vm.set_register(opcode.dst0_index, TaggedValue::new_raw_integer(value)); Ok(()) } pub fn transient_storage_write( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, - transient_storage: &mut StateStorage, + state: &mut VMState, ) -> Result<(), EraVmError> { let key_for_contract_storage = vm.get_register(opcode.src0_index).value; let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); let value = vm.get_register(opcode.src1_index).value; - transient_storage.storage_write(key, value)?; + state.transient_storage_write(key, value); Ok(()) } pub fn transient_storage_read( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, - transient_storage: &StateStorage, + state: &VMState, ) -> Result<(), EraVmError> { let key_for_contract_storage = vm.get_register(opcode.src0_index).value; let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); - let value = transient_storage.storage_read(key)?.unwrap_or(U256::zero()); + let value = state.transient_storage_read(key)?.unwrap_or(U256::zero()); vm.set_register(opcode.dst0_index, TaggedValue::new_raw_integer(value)); Ok(()) } pub fn add_l2_to_l1_message( - vm_state: &mut VMState, + vm_state: &mut Execution, opcode: &Opcode, - state_storage: &mut StateStorage, + state: &mut VMState, ) -> Result<(), EraVmError> { let key = vm_state.get_register(opcode.src0_index).value; let value = vm_state.get_register(opcode.src1_index).value; let is_service = opcode.imm0 == 1; - state_storage.record_l2_to_l1_log(L2ToL1Log { + state.record_l2_to_l1_log(L2ToL1Log { key, value, is_service, address: vm_state.current_context()?.contract_address, shard_id: 0, tx_number: vm_state.tx_number as u16, - })?; + }); Ok(()) } diff --git a/src/op_handlers/mul.rs b/src/op_handlers/mul.rs index 07db6eaa..b34f1830 100644 --- a/src/op_handlers/mul.rs +++ b/src/op_handlers/mul.rs @@ -3,9 +3,9 @@ use u256::{U256, U512}; use crate::address_operands::{address_operands_div_mul, address_operands_read}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn mul(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn mul(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0.value, src1.value); let src0 = U512::from(src0); diff --git a/src/op_handlers/near_call.rs b/src/op_handlers/near_call.rs index c8ef3368..839df086 100644 --- a/src/op_handlers/near_call.rs +++ b/src/op_handlers/near_call.rs @@ -2,15 +2,11 @@ use u256::U256; use crate::call_frame::CallFrame; use crate::eravm_error::EraVmError; -use crate::store::StateStorage; -use crate::{opcode::Opcode, state::VMState}; +use crate::rollbacks::Rollbackable; +use crate::state::VMState; +use crate::{execution::Execution, opcode::Opcode}; -pub fn near_call( - vm: &mut VMState, - opcode: &Opcode, - state_storage: &StateStorage, - transient_storage: &StateStorage, -) -> Result<(), EraVmError> { +pub fn near_call(vm: &mut Execution, opcode: &Opcode, state: &VMState) -> Result<(), EraVmError> { let abi_reg = vm.get_register(opcode.src0_index); let call_pc = opcode.imm0 as u64; let exception_handler = opcode.imm1; @@ -28,16 +24,14 @@ pub fn near_call( let current_frame = vm.current_frame_mut()?; let new_sp = current_frame.sp; - let transient_storage_snapshot = transient_storage.create_snapshot(); // Create new frame let new_frame = CallFrame::new_near_call_frame( new_sp, call_pc, callee_ergs, - transient_storage_snapshot, exception_handler as u64, - state_storage.create_snapshot(), + state.snapshot(), ); vm.push_near_call_frame(new_frame) diff --git a/src/op_handlers/opcode_decommit.rs b/src/op_handlers/opcode_decommit.rs index aa24b151..bd4e7ccf 100644 --- a/src/op_handlers/opcode_decommit.rs +++ b/src/op_handlers/opcode_decommit.rs @@ -1,16 +1,16 @@ use crate::{ address_operands::{address_operands_read, address_operands_store}, eravm_error::{EraVmError, HeapError}, + execution::Execution, state::VMState, - store::ContractStorage, value::{FatPointer, TaggedValue}, Opcode, }; pub fn opcode_decommit( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, - storage: &mut dyn ContractStorage, + state: &mut VMState, ) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; @@ -20,7 +20,7 @@ pub fn opcode_decommit( vm.decrease_gas(extra_cost)?; - let code = storage + let code = state .decommit(code_hash)? .ok_or(EraVmError::DecommitFailed)?; diff --git a/src/op_handlers/or.rs b/src/op_handlers/or.rs index 80bd7b40..fbefbf27 100644 --- a/src/op_handlers/or.rs +++ b/src/op_handlers/or.rs @@ -1,9 +1,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn or(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn or(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let res = src0.value | src1.value; diff --git a/src/op_handlers/precompile_call.rs b/src/op_handlers/precompile_call.rs index 651c4475..b3788e79 100644 --- a/src/op_handlers/precompile_call.rs +++ b/src/op_handlers/precompile_call.rs @@ -9,16 +9,16 @@ use zkevm_opcode_defs::{ use crate::{ address_operands::{address_operands_read, address_operands_store}, eravm_error::EraVmError, + execution::Execution, precompiles::{ ecrecover::ecrecover_function, keccak256::keccak256_rounds_function, secp256r1_verify::secp256r1_verify_function, sha256::sha256_rounds_function, }, - state::VMState, value::TaggedValue, Opcode, }; -pub fn precompile_call(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn precompile_call(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let aux_data = PrecompileAuxData::from_u256(src1.value); diff --git a/src/op_handlers/ptr_add.rs b/src/op_handlers/ptr_add.rs index ce0da67c..ae1ebeab 100644 --- a/src/op_handlers/ptr_add.rs +++ b/src/op_handlers/ptr_add.rs @@ -1,12 +1,12 @@ use crate::{ eravm_error::{EraVmError, OperandError}, + execution::Execution, ptr_operator::{ptr_operands_read, ptr_operands_store}, - state::VMState, value::FatPointer, Opcode, }; -pub fn ptr_add(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn ptr_add(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (pointer, diff, src0) = ptr_operands_read(vm, opcode)?; let (new_offset, overflow) = pointer.offset.overflowing_add(diff); diff --git a/src/op_handlers/ptr_pack.rs b/src/op_handlers/ptr_pack.rs index 25fe07de..5f165b78 100644 --- a/src/op_handlers/ptr_pack.rs +++ b/src/op_handlers/ptr_pack.rs @@ -3,12 +3,12 @@ use u256::U256; use crate::{ address_operands::{address_operands_read, address_operands_store}, eravm_error::{EraVmError, OperandError}, - state::VMState, + execution::Execution, value::TaggedValue, Opcode, }; -pub fn ptr_pack(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn ptr_pack(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; if !src0.is_pointer || src1.is_pointer { diff --git a/src/op_handlers/ptr_shrink.rs b/src/op_handlers/ptr_shrink.rs index d2a072c8..9094865c 100644 --- a/src/op_handlers/ptr_shrink.rs +++ b/src/op_handlers/ptr_shrink.rs @@ -1,12 +1,12 @@ use crate::{ eravm_error::{EraVmError, OperandError}, + execution::Execution, ptr_operator::{ptr_operands_read, ptr_operands_store}, - state::VMState, value::FatPointer, Opcode, }; -pub fn ptr_shrink(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn ptr_shrink(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (pointer, diff, src0) = ptr_operands_read(vm, opcode)?; let (new_len, overflow) = pointer.len.overflowing_sub(diff); diff --git a/src/op_handlers/ptr_sub.rs b/src/op_handlers/ptr_sub.rs index cabb1bbd..63181740 100644 --- a/src/op_handlers/ptr_sub.rs +++ b/src/op_handlers/ptr_sub.rs @@ -1,12 +1,12 @@ use crate::{ eravm_error::{EraVmError, OperandError}, + execution::Execution, ptr_operator::{ptr_operands_read, ptr_operands_store}, - state::VMState, value::FatPointer, Opcode, }; -pub fn ptr_sub(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn ptr_sub(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (pointer, diff, src0) = ptr_operands_read(vm, opcode)?; let (new_offset, overflow) = pointer.offset.overflowing_sub(diff); diff --git a/src/op_handlers/ret.rs b/src/op_handlers/ret.rs index c9dfb160..d7bfdcf9 100644 --- a/src/op_handlers/ret.rs +++ b/src/op_handlers/ret.rs @@ -3,8 +3,9 @@ use zkevm_opcode_defs::RetOpcode; use crate::{ eravm_error::EraVmError, + execution::Execution, + rollbacks::Rollbackable, state::VMState, - store::StateStorage, value::{FatPointer, TaggedValue}, Opcode, }; @@ -16,7 +17,7 @@ fn is_failure(return_type: RetOpcode) -> bool { } fn get_result( - vm: &mut VMState, + vm: &mut Execution, reg_index: u8, return_type: RetOpcode, ) -> Result { @@ -32,10 +33,9 @@ fn get_result( } pub fn ret( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, - state_storage: &mut StateStorage, - transient_storage: &mut StateStorage, + state: &mut VMState, return_type: RetOpcode, ) -> Result { let is_failure = is_failure(return_type); @@ -44,17 +44,13 @@ pub fn ret( vm.flag_lt_of = return_type == RetOpcode::Panic; vm.flag_gt = false; - if is_failure { - state_storage.rollback(&vm.current_frame()?.storage_snapshot); - transient_storage.rollback(&vm.current_frame()?.transient_storage_snapshot); - } - if vm.in_near_call()? { let previous_frame = vm.pop_frame()?; if opcode.flag0_set { let to_label = opcode.imm0; vm.current_frame_mut()?.pc = to_label as u64; } else if is_failure { + state.rollback(previous_frame.snapshot); vm.current_frame_mut()?.pc = previous_frame.exception_handler; } else { vm.current_frame_mut()?.pc += 1; @@ -69,6 +65,7 @@ pub fn ret( let previous_frame = vm.pop_frame()?; vm.current_frame_mut()?.gas_left += previous_frame.gas_left; if is_failure { + state.rollback(previous_frame.snapshot); vm.current_frame_mut()?.pc = previous_frame.exception_handler; } else { vm.current_frame_mut()?.pc += 1; @@ -76,6 +73,9 @@ pub fn ret( Ok(false) } else { + if is_failure { + state.rollback(vm.current_frame()?.snapshot.clone()); + } if return_type == RetOpcode::Panic { return Ok(true); } @@ -85,22 +85,16 @@ pub fn ret( } } -pub fn inexplicit_panic( - vm: &mut VMState, - state_storage: &mut StateStorage, - transient_storage: &mut StateStorage, -) -> Result { +pub fn inexplicit_panic(vm: &mut Execution, state: &mut VMState) -> Result { vm.flag_eq = false; vm.flag_lt_of = true; vm.flag_gt = false; - state_storage.rollback(&vm.current_frame()?.storage_snapshot); - transient_storage.rollback(&vm.current_frame()?.transient_storage_snapshot); - if vm.in_near_call()? { let previous_frame = vm.pop_frame()?; vm.current_frame_mut()?.pc = previous_frame.exception_handler; vm.current_frame_mut()?.gas_left += previous_frame.gas_left; + state.rollback(previous_frame.snapshot); Ok(false) } else if vm.in_far_call() { @@ -111,15 +105,17 @@ pub fn inexplicit_panic( let previous_frame = vm.pop_frame()?; vm.current_frame_mut()?.gas_left += previous_frame.gas_left; vm.current_frame_mut()?.pc = previous_frame.exception_handler; + state.rollback(previous_frame.snapshot); Ok(false) } else { + state.rollback(vm.current_frame()?.snapshot.clone()); Ok(true) } } // When executing a far_call, if the opcode fails, we need to run the exception handler provided in the args // We don't need to: run ret.panic, pop a frame and run its exception handler -pub fn panic_from_far_call(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn panic_from_far_call(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let far_call_exception_handler = opcode.imm0 as u64; let result = TaggedValue::new_pointer(U256::zero()); vm.register_context_u128 = 0_u128; diff --git a/src/op_handlers/shift.rs b/src/op_handlers/shift.rs index 3c881c25..7074e069 100644 --- a/src/op_handlers/shift.rs +++ b/src/op_handlers/shift.rs @@ -2,9 +2,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn shl(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn shl(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); @@ -20,7 +20,7 @@ pub fn shl(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { address_operands_store(vm, opcode, TaggedValue::new_raw_integer(res)) } -pub fn shr(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn shr(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); @@ -36,7 +36,7 @@ pub fn shr(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { address_operands_store(vm, opcode, TaggedValue::new_raw_integer(res)) } -pub fn rol(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn rol(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); @@ -52,7 +52,7 @@ pub fn rol(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { address_operands_store(vm, opcode, TaggedValue::new_raw_integer(result)) } -pub fn ror(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn ror(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); diff --git a/src/op_handlers/sub.rs b/src/op_handlers/sub.rs index 01fe40cb..3e58252e 100644 --- a/src/op_handlers/sub.rs +++ b/src/op_handlers/sub.rs @@ -1,9 +1,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn sub(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn sub(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0_t, src1_t) = address_operands_read(vm, opcode)?; let (src0, src1) = (src0_t.value, src1_t.value); diff --git a/src/op_handlers/unimplemented.rs b/src/op_handlers/unimplemented.rs index 05566469..75501998 100644 --- a/src/op_handlers/unimplemented.rs +++ b/src/op_handlers/unimplemented.rs @@ -1,7 +1,7 @@ use crate::eravm_error::{EraVmError, OpcodeError}; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn unimplemented(_vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn unimplemented(_vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { eprintln!("Unimplemented instruction: {:?}!", opcode.variant); Err(OpcodeError::UnimplementedOpcode.into()) } diff --git a/src/op_handlers/xor.rs b/src/op_handlers/xor.rs index d74118d1..c74bbccf 100644 --- a/src/op_handlers/xor.rs +++ b/src/op_handlers/xor.rs @@ -1,9 +1,9 @@ use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::EraVmError; use crate::value::TaggedValue; -use crate::{opcode::Opcode, state::VMState}; +use crate::{execution::Execution, opcode::Opcode}; -pub fn xor(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn xor(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let res = src0.value ^ src1.value; diff --git a/src/output.rs b/src/output.rs index ad325da4..32f54bf9 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,10 +1,10 @@ use u256::U256; -use crate::{eravm_error::EraVmError, state::VMState}; +use crate::{eravm_error::EraVmError, execution::Execution}; pub struct Output { pub storage_zero: U256, - pub vm_state: VMState, + pub vm_state: Execution, pub reverted: bool, pub reason: Option, } diff --git a/src/ptr_operator.rs b/src/ptr_operator.rs index 0f7ecff0..52569640 100644 --- a/src/ptr_operator.rs +++ b/src/ptr_operator.rs @@ -1,13 +1,13 @@ use crate::{ address_operands::{address_operands_read, address_operands_store}, eravm_error::{EraVmError, OperandError}, - state::VMState, + execution::Execution, value::{FatPointer, TaggedValue}, Opcode, }; pub fn ptr_operands_read( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, ) -> Result<(FatPointer, u32, TaggedValue), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; @@ -26,7 +26,7 @@ pub fn ptr_operands_read( } pub fn ptr_operands_store( - vm: &mut VMState, + vm: &mut Execution, opcode: &Opcode, new_pointer: FatPointer, src0: TaggedValue, diff --git a/src/rollbacks.rs b/src/rollbacks.rs new file mode 100644 index 00000000..14107575 --- /dev/null +++ b/src/rollbacks.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; + +pub trait Rollbackable { + type Snapshot; + fn rollback(&mut self, snapshot: Self::Snapshot); + fn snapshot(&self) -> Self::Snapshot; +} + +#[derive(Debug, Default)] +pub struct RollbackableHashMap { + pub map: HashMap, +} + +impl Rollbackable for RollbackableHashMap { + type Snapshot = HashMap; + fn rollback(&mut self, snapshot: Self::Snapshot) { + self.map = snapshot; + } + + fn snapshot(&self) -> Self::Snapshot { + self.map.clone() + } +} + +#[derive(Debug, Default)] +pub struct RollbackableVec { + pub entries: Vec, +} + +impl Rollbackable for RollbackableVec { + type Snapshot = Vec; + + fn rollback(&mut self, snapshot: Self::Snapshot) { + self.entries = snapshot; + } + fn snapshot(&self) -> Self::Snapshot { + self.entries.clone() + } +} + +#[derive(Debug, Default)] +pub struct RollbackablePrimitive { + pub value: T, +} + +impl Rollbackable for RollbackablePrimitive { + type Snapshot = T; + fn rollback(&mut self, snapshot: Self::Snapshot) { + self.value = snapshot; + } + + fn snapshot(&self) -> Self::Snapshot { + self.value + } +} diff --git a/src/state.rs b/src/state.rs index 8846845c..d19c6f01 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,484 +1,128 @@ -use std::num::Saturating; - -use crate::call_frame::{CallFrame, Context}; -use crate::heaps::Heaps; - -use crate::eravm_error::{ContextError, EraVmError, HeapError, StackError}; -use crate::store::SnapShot; use crate::{ - opcode::Predicate, - value::{FatPointer, TaggedValue}, - Opcode, + rollbacks::{Rollbackable, RollbackableHashMap, RollbackableVec}, + store::{ContractStorage, InitialStorage, StorageError, StorageKey}, }; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use u256::{H160, U256}; -use zkevm_opcode_defs::ethereum_types::Address; -use zkevm_opcode_defs::MEMORY_GROWTH_ERGS_PER_BYTE; -pub const CALLDATA_HEAP: u32 = 1; -pub const FIRST_HEAP: u32 = 2; -pub const FIRST_AUX_HEAP: u32 = 3; - -#[derive(Debug, Clone)] -pub struct Stack { - pub stack: Vec, +#[derive(Debug, PartialEq, Clone, Default)] +pub struct L2ToL1Log { + pub key: U256, + pub value: U256, + pub is_service: bool, + pub address: H160, + pub shard_id: u8, + pub tx_number: u16, } -#[derive(Debug, Clone)] -pub struct Heap { - heap: Vec, +#[derive(Debug, PartialEq, Default, Clone)] +pub struct Event { + pub key: U256, + pub value: U256, + pub is_first: bool, + pub shard_id: u8, + pub tx_number: u16, } -#[derive(Debug, Clone)] + +#[derive(Debug)] pub struct VMState { - // The first register, r0, is actually always zero and not really used. - // Writing to it does nothing. - registers: [TaggedValue; 15], - /// Overflow or less than flag - pub flag_lt_of: bool, // We only use the first three bits for the flags here: LT, GT, EQ. - /// Greater Than flag - pub flag_gt: bool, - /// Equal flag - pub flag_eq: bool, - pub running_contexts: Vec, - pub program: Vec, - pub tx_number: u64, - pub heaps: Heaps, - pub events: Vec, - pub register_context_u128: u128, - pub default_aa_code_hash: [u8; 32], - pub evm_interpreter_code_hash: [u8; 32], - pub hook_address: u32, - pub use_hooks: bool, + pub initial_storage: Rc>, + pub contracts_storage: Rc>, + storage_changes: RollbackableHashMap, + transient_storage: RollbackableHashMap, + l2_to_l1_logs: RollbackableVec, + events: RollbackableVec, } -// Totally arbitrary, probably we will have to change it later. -pub const DEFAULT_INITIAL_GAS: u32 = 1 << 16; impl VMState { - #[allow(clippy::too_many_arguments)] pub fn new( - program_code: Vec, - calldata: Vec, - contract_address: H160, - caller: H160, - context_u128: u128, - default_aa_code_hash: [u8; 32], - evm_interpreter_code_hash: [u8; 32], - hook_address: u32, - use_hooks: bool, + initial_storage: Rc>, + contracts_storage: Rc>, ) -> Self { - let mut registers = [TaggedValue::default(); 15]; - let calldata_ptr = FatPointer { - page: CALLDATA_HEAP, - offset: 0, - start: 0, - len: calldata.len() as u32, - }; - - registers[0] = TaggedValue::new_pointer(calldata_ptr.encode()); - - let context = Context::new( - program_code.clone(), - u32::MAX - 0x80000000, - contract_address, - contract_address, - caller, - FIRST_HEAP, - FIRST_AUX_HEAP, - CALLDATA_HEAP, - 0, - context_u128, - SnapShot::default(), - SnapShot::default(), - false, - ); - - let heaps = Heaps::new(calldata); - Self { - registers, - flag_lt_of: false, - flag_gt: false, - flag_eq: false, - running_contexts: vec![context], - - program: program_code, - tx_number: 0, - heaps, - events: vec![], - register_context_u128: context_u128, - default_aa_code_hash, - evm_interpreter_code_hash, - hook_address, - use_hooks, - } - } - - pub fn clear_registers(&mut self) { - for register in self.registers.iter_mut() { - *register = TaggedValue::new_raw_integer(U256::zero()); - } - } - - pub fn clear_flags(&mut self) { - self.flag_lt_of = false; - self.flag_gt = false; - self.flag_eq = false; - } - - pub fn clear_pointer_flags(&mut self) { - for register in self.registers.iter_mut() { - register.to_raw_integer(); - } - } - - #[allow(clippy::too_many_arguments)] - pub fn push_far_call_frame( - &mut self, - program_code: Vec, - gas_stipend: u32, - code_address: Address, - contract_address: Address, - caller: Address, - heap_id: u32, - aux_heap_id: u32, - calldata_heap_id: u32, - exception_handler: u64, - context_u128: u128, - transient_storage_snapshot: SnapShot, - storage_snapshot: SnapShot, - is_static: bool, - ) -> Result<(), EraVmError> { - let new_context = Context::new( - program_code, - gas_stipend, - contract_address, - code_address, - caller, - heap_id, - aux_heap_id, - calldata_heap_id, - exception_handler, - context_u128, - transient_storage_snapshot, - storage_snapshot, - is_static, - ); - self.running_contexts.push(new_context); - Ok(()) - } - pub fn pop_context(&mut self) -> Result { - self.running_contexts.pop().ok_or(ContextError::NoContract) - } - - pub fn pop_frame(&mut self) -> Result { - let current_context = self.current_context_mut()?; - if current_context.near_call_frames.is_empty() { - let context = self.pop_context()?; - Ok(context.frame) - } else { - current_context - .near_call_frames - .pop() - .ok_or(ContextError::NoContract) + initial_storage, + contracts_storage, + storage_changes: RollbackableHashMap::::default(), + transient_storage: RollbackableHashMap::::default(), + l2_to_l1_logs: RollbackableVec::::default(), + events: RollbackableVec::::default(), } } - pub fn push_near_call_frame(&mut self, near_call_frame: CallFrame) -> Result<(), EraVmError> { - self.current_context_mut()? - .near_call_frames - .push(near_call_frame); - Ok(()) - } - - pub fn current_context_mut(&mut self) -> Result<&mut Context, ContextError> { - self.running_contexts - .last_mut() - .ok_or(ContextError::NoContract) - } - - pub fn current_context(&self) -> Result<&Context, ContextError> { - self.running_contexts.last().ok_or(ContextError::NoContract) - } - - pub fn current_frame_mut(&mut self) -> Result<&mut CallFrame, ContextError> { - let current_context = self.current_context_mut()?; - if current_context.near_call_frames.is_empty() { - Ok(&mut current_context.frame) - } else { - current_context - .near_call_frames - .last_mut() - .ok_or(ContextError::NoContract) - } + pub fn storage_changes(&self) -> &HashMap { + &self.storage_changes.map } - pub fn current_frame(&self) -> Result<&CallFrame, ContextError> { - let current_context = self.current_context()?; - if current_context.near_call_frames.is_empty() { - Ok(¤t_context.frame) - } else { - current_context - .near_call_frames - .last() - .ok_or(ContextError::NoContract) - } + pub fn transient_storage(&self) -> &HashMap { + &self.transient_storage.map } - pub fn can_execute(&self, opcode: &Opcode) -> Result { - let predicate_holds = match opcode.predicate { - Predicate::Always => true, - Predicate::Gt => self.flag_gt, - Predicate::Lt => self.flag_lt_of, - Predicate::Eq => self.flag_eq, - Predicate::Ge => self.flag_eq || self.flag_gt, - Predicate::Le => self.flag_eq || self.flag_lt_of, - Predicate::Ne => !self.flag_eq, - Predicate::GtOrLt => self.flag_gt || self.flag_lt_of, - }; - if opcode.variant.requires_kernel_mode() && !self.current_context()?.is_kernel() { - return Err(EraVmError::VmNotInKernelMode); - } - if self.current_context()?.is_static && !opcode.variant.can_be_used_in_static_context() { - return Err(EraVmError::OpcodeIsNotStatic); - } - Ok(predicate_holds) + pub fn l2_to_l1_logs(&self) -> &Vec { + &self.l2_to_l1_logs.entries } - pub fn get_register(&self, index: u8) -> TaggedValue { - if index != 0 { - return self.registers[(index - 1) as usize]; - } - - TaggedValue::default() + pub fn events(&self) -> &Vec { + &self.events.entries } - pub fn set_register(&mut self, index: u8, value: TaggedValue) { - if index == 0 { - return; + pub fn storage_read(&self, key: StorageKey) -> Result, StorageError> { + match self.storage_changes.map.get(&key) { + None => self.initial_storage.borrow().storage_read(key), + value => Ok(value.copied()), } - - self.registers[(index - 1) as usize] = value; } - pub fn get_opcode_with_test_encode(&self) -> Result { - let current_context = self.current_context()?; - let pc = self.current_frame()?.pc; - // Since addressing is word-sized (i.e. one address equals a u256 value), - // when using u128 encoding we actually have 2 opcodes pointed - // by our program counter (pc). - // And then, we have two cases: - // - pc mod 2 ≣ 1 -> Take the low 128 bits of the word and decode the opcode. - // - pc mod 2 ≣ 0 -> Take the high 128 bits of the word and decode the opcode . - // U256 provides the low_u128 method which is self-describing. - let raw_op = current_context - .code_page - // pc / 2 - .get(pc as usize >> 1); - let opcode = match pc % 2 { - 1 => raw_op.low_u128(), - _ => (raw_op >> 128).low_u128(), - }; - Opcode::try_from_raw_opcode_test_encode(opcode) + pub fn storage_write(&mut self, key: StorageKey, value: U256) { + self.storage_changes.map.insert(key, value); } - pub fn get_opcode(&self) -> Result { - let current_context = self.current_context()?; - let pc = self.current_frame()?.pc; - let raw_opcode = current_context.code_page.get(pc as usize / 4); - let raw_op = match pc % 4 { - 3 => (raw_opcode & u64::MAX.into()).as_u64(), - 2 => ((raw_opcode >> 64) & u64::MAX.into()).as_u64(), - 1 => ((raw_opcode >> 128) & u64::MAX.into()).as_u64(), - _ => ((raw_opcode >> 192) & u64::MAX.into()).as_u64(), // 0 - }; - - Opcode::try_from_raw_opcode(raw_op) - } - pub fn decrease_gas(&mut self, cost: u32) -> Result<(), EraVmError> { - let underflows = cost > self.current_frame()?.gas_left.0; - if underflows { - self.set_gas_left(0)?; - return Err(EraVmError::OutOfGas); - } - self.current_frame_mut()?.gas_left -= cost; - Ok(()) + pub fn transient_storage_read(&self, key: StorageKey) -> Result, StorageError> { + Ok(self.transient_storage.map.get(&key).copied()) } - pub fn set_gas_left(&mut self, gas: u32) -> Result<(), EraVmError> { - self.current_frame_mut()?.gas_left = Saturating(gas); - Ok(()) + pub fn transient_storage_write(&mut self, key: StorageKey, value: U256) { + self.transient_storage.map.insert(key, value); } - pub fn gas_left(&self) -> Result { - Ok(self.current_frame()?.gas_left.0) + pub fn record_l2_to_l1_log(&mut self, msg: L2ToL1Log) { + self.l2_to_l1_logs.entries.push(msg); } - pub fn in_near_call(&self) -> Result { - Ok(!self.current_context()?.near_call_frames.is_empty()) + pub fn record_event(&mut self, event: Event) { + self.events.entries.push(event); } - pub fn in_far_call(&self) -> bool { - self.running_contexts.len() > 1 + pub fn decommit(&mut self, hash: U256) -> Result>, StorageError> { + self.contracts_storage.borrow().decommit(hash) } } -impl Default for Stack { - fn default() -> Self { - Self::new() - } +#[derive(Clone, Default, PartialEq, Debug)] +// a copy of rollbackable fields +pub struct StateSnapshot { + // this casts allows us to get the Snapshot type from the Rollbackable trait + storage_changes: as Rollbackable>::Snapshot, + transient_storage: as Rollbackable>::Snapshot, + l2_to_l1_logs: as Rollbackable>::Snapshot, + events: as Rollbackable>::Snapshot, } -impl Stack { - pub fn new() -> Self { - Self { stack: vec![] } - } +impl Rollbackable for VMState { + type Snapshot = StateSnapshot; - pub fn push(&mut self, value: TaggedValue) { - self.stack.push(value); + fn rollback(&mut self, snapshot: Self::Snapshot) { + self.storage_changes.rollback(snapshot.storage_changes); + self.transient_storage.rollback(snapshot.transient_storage); + self.l2_to_l1_logs.rollback(snapshot.l2_to_l1_logs); + self.events.rollback(snapshot.events) } - pub fn fill_with_zeros(&mut self, value: usize) { - for _ in 0..value { - self.stack.push(TaggedValue { - value: U256::zero(), - is_pointer: false, - }); + fn snapshot(&self) -> Self::Snapshot { + Self::Snapshot { + l2_to_l1_logs: self.l2_to_l1_logs.snapshot(), + storage_changes: self.storage_changes.snapshot(), + transient_storage: self.transient_storage.snapshot(), + events: self.events.snapshot(), } } - - pub fn get_with_offset(&self, offset: u16, sp: u32) -> Result { - if offset as u32 > sp || offset == 0 { - return Err(StackError::ReadOutOfBounds); - } - let index = (sp - offset as u32) as usize; - if index >= self.stack.len() { - return Ok(TaggedValue::default()); - } - Ok(self.stack[index]) - } - - pub fn get_absolute(&self, index: u16, sp: u32) -> Result { - if index as u32 >= sp { - return Err(StackError::ReadOutOfBounds); - } - let index = index as usize; - if index >= self.stack.len() { - return Ok(TaggedValue::default()); - } - Ok(self.stack[index]) - } - - pub fn store_with_offset( - &mut self, - offset: u16, - value: TaggedValue, - sp: u32, - ) -> Result<(), StackError> { - if offset as u32 > sp || offset == 0 { - return Err(StackError::StoreOutOfBounds); - } - let index = (sp - offset as u32) as usize; - if index >= self.stack.len() { - self.fill_with_zeros(index - self.stack.len() + 1); - } - self.stack[index] = value; - Ok(()) - } - - pub fn store_absolute(&mut self, index: u16, value: TaggedValue) -> Result<(), StackError> { - let index = index as usize; - if index >= self.stack.len() { - self.fill_with_zeros(index - self.stack.len() + 1); - } - self.stack[index] = value; - Ok(()) - } -} - -impl Default for Heap { - fn default() -> Self { - Self::new(vec![]) - } -} - -impl Heap { - pub fn new(values: Vec) -> Self { - Self { heap: values } - } - // Returns how many ergs the expand costs - pub fn expand_memory(&mut self, address: u32) -> u32 { - if address >= self.heap.len() as u32 { - let old_size = self.heap.len() as u32; - self.heap.resize(address as usize, 0); - return MEMORY_GROWTH_ERGS_PER_BYTE * (address - old_size); - } - 0 - } - - pub fn store(&mut self, address: u32, value: U256) { - let mut bytes: [u8; 32] = [0; 32]; - value.to_big_endian(&mut bytes); - for (i, item) in bytes.iter().enumerate() { - self.heap[address as usize + i] = *item; - } - } - - pub fn read(&self, address: u32) -> U256 { - let mut result = U256::zero(); - - for i in 0..32 { - result |= U256::from(self.heap[address as usize + (31 - i)]) << (i * 8); - } - result - } - - pub fn expanded_read(&mut self, address: u32) -> (U256, u32) { - let gas_cost = self.expand_memory(address + 32); - let result = self.read(address); - (result, gas_cost) - } - - pub fn read_byte(&self, address: u32) -> u8 { - self.heap[address as usize] - } - - pub fn read_from_pointer(&self, pointer: &FatPointer) -> U256 { - let mut result = U256::zero(); - for i in 0..32 { - let addr = pointer.start + pointer.offset + (31 - i); - if addr < pointer.start + pointer.len { - result |= U256::from(self.heap[addr as usize]) << (i * 8); - } - } - result - } - - pub fn read_unaligned_from_pointer(&self, pointer: &FatPointer) -> Result, HeapError> { - let mut result = Vec::new(); - let start = pointer.start + pointer.offset; - let finish = start + pointer.len; - for i in start..finish { - if i as usize >= self.heap.len() { - return Err(HeapError::ReadOutOfBounds); - } - result.push(self.heap[i as usize]); - } - Ok(result) - } - - pub fn len(&self) -> usize { - self.heap.len() - } - - pub fn is_empty(&self) -> bool { - self.heap.is_empty() - } -} - -#[derive(Debug, Clone)] -pub struct Event { - pub key: U256, - pub value: U256, - pub is_first: bool, - pub shard_id: u8, - pub tx_number: u16, } diff --git a/src/store.rs b/src/store.rs index 7eeb5821..95c1c944 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,5 +1,3 @@ -use std::cell::RefCell; -use std::rc::Rc; use std::{collections::HashMap, fmt::Debug}; use thiserror::Error; use u256::{H160, U256}; @@ -10,14 +8,16 @@ use zkevm_opcode_defs::{ use crate::eravm_error::EraVmError; use crate::utils::address_into_u256; -#[derive(Debug, Clone)] -pub struct L2ToL1Log { - pub key: U256, - pub value: U256, - pub is_service: bool, +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] +pub struct StorageKey { pub address: H160, - pub shard_id: u8, - pub tx_number: u16, + pub key: U256, +} + +impl StorageKey { + pub fn new(address: H160, key: U256) -> Self { + Self { address, key } + } } pub trait InitialStorage: Debug { @@ -41,6 +41,7 @@ impl InitialStorage for InitialStorageMemory { pub trait ContractStorage: Debug { fn decommit(&self, hash: U256) -> Result>, StorageError>; } + #[derive(Debug)] pub struct ContractStorageMemory { pub contract_storage: HashMap>, @@ -52,84 +53,6 @@ impl ContractStorage for ContractStorageMemory { } } -#[derive(Debug)] -pub struct StateStorage { - pub storage_changes: HashMap, - pub initial_storage: Rc>, - l2_to_l1_logs: Vec, -} - -impl Default for StateStorage { - fn default() -> Self { - Self { - storage_changes: HashMap::new(), - initial_storage: Rc::new(RefCell::new(InitialStorageMemory { - initial_storage: HashMap::new(), - })), - l2_to_l1_logs: Vec::new(), - } - } -} - -impl StateStorage { - pub fn new(initial_storage: Rc>) -> Self { - Self { - storage_changes: HashMap::new(), - initial_storage, - l2_to_l1_logs: Vec::new(), - } - } - - pub fn storage_read(&self, key: StorageKey) -> Result, StorageError> { - match self.storage_changes.get(&key) { - None => self.initial_storage.borrow().storage_read(key), - value => Ok(value.copied()), - } - } - - pub fn storage_write(&mut self, key: StorageKey, value: U256) -> Result<(), StorageError> { - self.storage_changes.insert(key, value); - Ok(()) - } - - pub fn record_l2_to_l1_log(&mut self, msg: L2ToL1Log) -> Result<(), StorageError> { - self.l2_to_l1_logs.push(msg); - Ok(()) - } - - pub fn create_snapshot(&self) -> SnapShot { - SnapShot { - storage_changes: self.storage_changes.clone(), - } - } - - pub fn rollback(&mut self, snapshot: &SnapShot) { - let keys = snapshot.storage_changes.keys(); - for key in keys { - snapshot - .storage_changes - .get(key) - .map(|value| self.storage_write(*key, *value)); - } - let current_keys = self.storage_changes.keys(); - let mut keys_to_remove = Vec::new(); - for key in current_keys { - let res = snapshot.storage_changes.get(key); - if res.is_none() { - keys_to_remove.push(*key); - }; - } - for key in keys_to_remove { - self.storage_changes.remove(&key); - } - } -} - -#[derive(Debug, Clone, Default)] -pub struct SnapShot { - pub storage_changes: HashMap, -} - /// Error type for storage operations. #[derive(Error, Debug)] pub enum StorageError { @@ -141,18 +64,6 @@ pub enum StorageError { ReadError, } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub struct StorageKey { - pub address: H160, - pub key: U256, -} - -impl StorageKey { - pub fn new(address: H160, key: U256) -> Self { - Self { address, key } - } -} - /// 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. diff --git a/src/tracers/blob_saver_tracer.rs b/src/tracers/blob_saver_tracer.rs index d0301e50..dd86ba70 100644 --- a/src/tracers/blob_saver_tracer.rs +++ b/src/tracers/blob_saver_tracer.rs @@ -1,7 +1,7 @@ use super::tracer::Tracer; use crate::{ eravm_error::{EraVmError, HeapError}, - state::VMState, + execution::Execution, value::FatPointer, Opcode, }; @@ -53,7 +53,7 @@ fn hash_evm_bytecode(bytecode: &[u8]) -> H256 { } impl Tracer for BlobSaverTracer { - fn before_execution(&mut self, _opcode: &Opcode, vm: &mut VMState) -> Result<(), EraVmError> { + fn before_execution(&mut self, _opcode: &Opcode, vm: &mut Execution) -> Result<(), EraVmError> { let current_callstack = vm.current_context()?; // Here we assume that the only case when PC is 0 at the start of the execution of the contract. diff --git a/src/tracers/last_state_saver_tracer.rs b/src/tracers/last_state_saver_tracer.rs index 533e0c25..9ef4b07e 100644 --- a/src/tracers/last_state_saver_tracer.rs +++ b/src/tracers/last_state_saver_tracer.rs @@ -1,5 +1,5 @@ use crate::eravm_error::EraVmError; -use crate::{state::VMState, Opcode}; +use crate::{execution::Execution, Opcode}; use super::tracer::Tracer; use u256::H160; @@ -9,13 +9,13 @@ use zkevm_opcode_defs::RetOpcode; #[derive(Debug)] pub struct LastStateSaverTracer { - pub vm_state: VMState, + pub vm_state: Execution, } impl LastStateSaverTracer { pub fn new() -> Self { Self { - vm_state: VMState::new( + vm_state: Execution::new( vec![], vec![], H160::zero(), @@ -37,7 +37,7 @@ impl Default for LastStateSaverTracer { } impl Tracer for LastStateSaverTracer { - fn before_execution(&mut self, opcode: &Opcode, vm: &mut VMState) -> Result<(), EraVmError> { + fn before_execution(&mut self, opcode: &Opcode, vm: &mut Execution) -> Result<(), EraVmError> { if opcode.variant == Variant::Ret(RetOpcode::Ok) { self.vm_state = vm.clone(); } diff --git a/src/tracers/print_tracer.rs b/src/tracers/print_tracer.rs index 4758f473..02054e13 100644 --- a/src/tracers/print_tracer.rs +++ b/src/tracers/print_tracer.rs @@ -6,7 +6,7 @@ use crate::address_operands::address_operands_read; use crate::eravm_error::EraVmError; use crate::eravm_error::HeapError; use crate::value::FatPointer; -use crate::{state::VMState, Opcode}; +use crate::{execution::Execution, Opcode}; use super::tracer::Tracer; @@ -14,7 +14,7 @@ pub struct PrintTracer {} impl Tracer for PrintTracer { #[allow(clippy::println_empty_string)] - fn before_execution(&mut self, opcode: &Opcode, vm: &mut VMState) -> Result<(), EraVmError> { + fn before_execution(&mut self, opcode: &Opcode, vm: &mut Execution) -> Result<(), EraVmError> { let opcode_variant = opcode.variant; const DEBUG_SLOT: u32 = 1024; diff --git a/src/tracers/tracer.rs b/src/tracers/tracer.rs index 9a036d20..92365b98 100644 --- a/src/tracers/tracer.rs +++ b/src/tracers/tracer.rs @@ -1,7 +1,11 @@ -use crate::{eravm_error::EraVmError, state::VMState, Opcode}; +use crate::{eravm_error::EraVmError, execution::Execution, Opcode}; pub trait Tracer { - fn before_execution(&mut self, _opcode: &Opcode, _vm: &mut VMState) -> Result<(), EraVmError> { + fn before_execution( + &mut self, + _opcode: &Opcode, + _vm: &mut Execution, + ) -> Result<(), EraVmError> { Ok(()) } } diff --git a/src/vm.rs b/src/vm.rs index 6cd8e9e3..ded57866 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -41,10 +41,11 @@ use crate::op_handlers::shift::{rol, ror, shl, shr}; use crate::op_handlers::sub::sub; use crate::op_handlers::unimplemented::unimplemented; use crate::op_handlers::xor::xor; -use crate::store::{ContractStorage, InitialStorage, StateStorage}; +use crate::state::VMState; +use crate::store::{ContractStorage, InitialStorage}; use crate::tracers::blob_saver_tracer::BlobSaverTracer; use crate::value::{FatPointer, TaggedValue}; -use crate::{eravm_error::EraVmError, tracers::tracer::Tracer, VMState}; +use crate::{eravm_error::EraVmError, tracers::tracer::Tracer, Execution}; use crate::{Opcode, Variant}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -58,9 +59,7 @@ pub enum ExecutionOutput { #[derive(Debug)] pub struct EraVM { pub state: VMState, - pub contract_storage: Rc>, - pub state_storage: StateStorage, - pub transient_storage: StateStorage, + pub execution: Execution, } pub enum EncodingMode { @@ -70,15 +69,13 @@ pub enum EncodingMode { impl EraVM { pub fn new( - state: VMState, + execution: Execution, initial_storage: Rc>, contract_storage: Rc>, ) -> Self { Self { - state, - contract_storage, - state_storage: StateStorage::new(initial_storage), - transient_storage: StateStorage::default(), + state: VMState::new(initial_storage, contract_storage), + execution, } } @@ -135,21 +132,17 @@ impl EraVM { ) -> Result { loop { let opcode = match enc_mode { - EncodingMode::Testing => self.state.get_opcode_with_test_encode()?, - EncodingMode::Production => self.state.get_opcode()?, + EncodingMode::Testing => self.execution.get_opcode_with_test_encode()?, + EncodingMode::Production => self.execution.get_opcode()?, }; for tracer in tracers.iter_mut() { - tracer.before_execution(&opcode, &mut self.state)?; + tracer.before_execution(&opcode, &mut self.execution)?; } - let can_execute = self.state.can_execute(&opcode); + let can_execute = self.execution.can_execute(&opcode); - if self.state.decrease_gas(opcode.gas_cost).is_err() || can_execute.is_err() { - match inexplicit_panic( - &mut self.state, - &mut self.state_storage, - &mut self.transient_storage, - ) { + if self.execution.decrease_gas(opcode.gas_cost).is_err() || can_execute.is_err() { + match inexplicit_panic(&mut self.execution, &mut self.state) { Ok(false) => continue, _ => return Ok(ExecutionOutput::Panic), } @@ -159,151 +152,130 @@ impl EraVM { let result = match opcode.variant { Variant::Invalid(_) => Err(OpcodeError::InvalidOpCode.into()), Variant::Nop(_) => { - address_operands_read(&mut self.state, &opcode)?; + address_operands_read(&mut self.execution, &opcode)?; address_operands_store( - &mut self.state, + &mut self.execution, &opcode, TaggedValue::new_raw_integer(0.into()), ) } - Variant::Add(_) => add(&mut self.state, &opcode), - Variant::Sub(_) => sub(&mut self.state, &opcode), - Variant::Jump(_) => jump(&mut self.state, &opcode), - Variant::Mul(_) => mul(&mut self.state, &opcode), - Variant::Div(_) => div(&mut self.state, &opcode), + Variant::Add(_) => add(&mut self.execution, &opcode), + Variant::Sub(_) => sub(&mut self.execution, &opcode), + Variant::Jump(_) => jump(&mut self.execution, &opcode), + Variant::Mul(_) => mul(&mut self.execution, &opcode), + Variant::Div(_) => div(&mut self.execution, &opcode), Variant::Context(context_variant) => match context_variant { - ContextOpcode::AuxMutating0 => unimplemented(&mut self.state, &opcode), - ContextOpcode::Caller => caller(&mut self.state, &opcode), - ContextOpcode::CodeAddress => code_address(&mut self.state, &opcode), - ContextOpcode::ErgsLeft => ergs_left(&mut self.state, &opcode), - ContextOpcode::GetContextU128 => get_context_u128(&mut self.state, &opcode), + ContextOpcode::AuxMutating0 => unimplemented(&mut self.execution, &opcode), + ContextOpcode::Caller => caller(&mut self.execution, &opcode), + ContextOpcode::CodeAddress => code_address(&mut self.execution, &opcode), + ContextOpcode::ErgsLeft => ergs_left(&mut self.execution, &opcode), + ContextOpcode::GetContextU128 => { + get_context_u128(&mut self.execution, &opcode) + } ContextOpcode::IncrementTxNumber => { - increment_tx_number(&mut self.state, &opcode) + increment_tx_number(&mut self.execution, &opcode) } - ContextOpcode::Meta => meta(&mut self.state, &opcode), - ContextOpcode::SetContextU128 => set_context_u128(&mut self.state, &opcode), - ContextOpcode::Sp => sp(&mut self.state, &opcode), - ContextOpcode::This => this(&mut self.state, &opcode), + ContextOpcode::Meta => meta(&mut self.execution, &opcode), + ContextOpcode::SetContextU128 => { + set_context_u128(&mut self.execution, &opcode) + } + ContextOpcode::Sp => sp(&mut self.execution, &opcode), + ContextOpcode::This => this(&mut self.execution, &opcode), }, Variant::Shift(shift_variant) => match shift_variant { - ShiftOpcode::Shl => shl(&mut self.state, &opcode), - ShiftOpcode::Shr => shr(&mut self.state, &opcode), - ShiftOpcode::Rol => rol(&mut self.state, &opcode), - ShiftOpcode::Ror => ror(&mut self.state, &opcode), + ShiftOpcode::Shl => shl(&mut self.execution, &opcode), + ShiftOpcode::Shr => shr(&mut self.execution, &opcode), + ShiftOpcode::Rol => rol(&mut self.execution, &opcode), + ShiftOpcode::Ror => ror(&mut self.execution, &opcode), }, Variant::Binop(binop) => match binop { - BinopOpcode::Xor => xor(&mut self.state, &opcode), - BinopOpcode::And => and(&mut self.state, &opcode), - BinopOpcode::Or => or(&mut self.state, &opcode), + BinopOpcode::Xor => xor(&mut self.execution, &opcode), + BinopOpcode::And => and(&mut self.execution, &opcode), + BinopOpcode::Or => or(&mut self.execution, &opcode), }, Variant::Ptr(ptr_variant) => match ptr_variant { - PtrOpcode::Add => ptr_add(&mut self.state, &opcode), - PtrOpcode::Sub => ptr_sub(&mut self.state, &opcode), - PtrOpcode::Pack => ptr_pack(&mut self.state, &opcode), - PtrOpcode::Shrink => ptr_shrink(&mut self.state, &opcode), + PtrOpcode::Add => ptr_add(&mut self.execution, &opcode), + PtrOpcode::Sub => ptr_sub(&mut self.execution, &opcode), + PtrOpcode::Pack => ptr_pack(&mut self.execution, &opcode), + PtrOpcode::Shrink => ptr_shrink(&mut self.execution, &opcode), }, - Variant::NearCall(_) => near_call( - &mut self.state, - &opcode, - &self.state_storage, - &self.transient_storage, - ), + Variant::NearCall(_) => near_call(&mut self.execution, &opcode, &self.state), Variant::Log(log_variant) => match log_variant { LogOpcode::StorageRead => { - storage_read(&mut self.state, &opcode, &self.state_storage) + storage_read(&mut self.execution, &opcode, &self.state) } LogOpcode::StorageWrite => { - storage_write(&mut self.state, &opcode, &mut self.state_storage) + storage_write(&mut self.execution, &opcode, &mut self.state) } LogOpcode::ToL1Message => { - add_l2_to_l1_message(&mut self.state, &opcode, &mut self.state_storage) + add_l2_to_l1_message(&mut self.execution, &opcode, &mut self.state) + } + LogOpcode::PrecompileCall => precompile_call(&mut self.execution, &opcode), + LogOpcode::Event => event(&mut self.execution, &opcode, &mut self.state), + LogOpcode::Decommit => { + opcode_decommit(&mut self.execution, &opcode, &mut self.state) + } + LogOpcode::TransientStorageRead => { + transient_storage_read(&mut self.execution, &opcode, &self.state) + } + LogOpcode::TransientStorageWrite => { + transient_storage_write(&mut self.execution, &opcode, &mut self.state) } - LogOpcode::PrecompileCall => precompile_call(&mut self.state, &opcode), - LogOpcode::Event => event(&mut self.state, &opcode), - LogOpcode::Decommit => opcode_decommit( - &mut self.state, - &opcode, - &mut *self.contract_storage.borrow_mut(), - ), - LogOpcode::TransientStorageRead => transient_storage_read( - &mut self.state, - &opcode, - &self.transient_storage, - ), - LogOpcode::TransientStorageWrite => transient_storage_write( - &mut self.state, - &opcode, - &mut self.transient_storage, - ), }, Variant::FarCall(far_call_variant) => { let res = far_call( - &mut self.state, + &mut self.execution, &opcode, &far_call_variant, - &mut self.state_storage, - &mut *self.contract_storage.borrow_mut(), - &self.transient_storage, + &mut self.state, ); if res.is_err() { - panic_from_far_call(&mut self.state, &opcode)?; + panic_from_far_call(&mut self.execution, &opcode)?; continue; } Ok(()) } Variant::Ret(ret_variant) => match ret_variant { - RetOpcode::Ok => match ret( - &mut self.state, - &opcode, - &mut self.state_storage, - &mut self.transient_storage, - ret_variant, - ) { - Ok(should_break) => { - if should_break { - let result = retrieve_result(&mut self.state)?; - return Ok(ExecutionOutput::Ok(result)); + RetOpcode::Ok => { + match ret(&mut self.execution, &opcode, &mut self.state, ret_variant) { + Ok(should_break) => { + if should_break { + let result = retrieve_result(&mut self.execution)?; + return Ok(ExecutionOutput::Ok(result)); + } + Ok(()) } - Ok(()) + Err(e) => Err(e), } - Err(e) => Err(e), - }, - RetOpcode::Revert => match ret( - &mut self.state, - &opcode, - &mut self.state_storage, - &mut self.transient_storage, - ret_variant, - ) { - Ok(should_break) => { - if should_break { - let result = retrieve_result(&mut self.state)?; - return Ok(ExecutionOutput::Revert(result)); + } + RetOpcode::Revert => { + match ret(&mut self.execution, &opcode, &mut self.state, ret_variant) { + Ok(should_break) => { + if should_break { + let result = retrieve_result(&mut self.execution)?; + return Ok(ExecutionOutput::Revert(result)); + } + Ok(()) } - Ok(()) + Err(e) => Err(e), } - Err(e) => Err(e), - }, - RetOpcode::Panic => match ret( - &mut self.state, - &opcode, - &mut self.state_storage, - &mut self.transient_storage, - ret_variant, - ) { - Ok(should_break) => { - if should_break { - return Ok(ExecutionOutput::Panic); + } + RetOpcode::Panic => { + match ret(&mut self.execution, &opcode, &mut self.state, ret_variant) { + Ok(should_break) => { + if should_break { + return Ok(ExecutionOutput::Panic); + } + Ok(()) } - Ok(()) + Err(e) => Err(e), } - Err(e) => Err(e), - }, + } }, Variant::UMA(uma_variant) => match uma_variant { - UMAOpcode::HeapRead => heap_read(&mut self.state, &opcode), + UMAOpcode::HeapRead => heap_read(&mut self.execution, &opcode), UMAOpcode::HeapWrite => { - let result = heap_write(&mut self.state, &opcode); + let result = heap_write(&mut self.execution, &opcode); match result { exec_hook @ Ok(ExecutionOutput::SuspendedOnHook { .. }) => { return exec_hook @@ -313,11 +285,11 @@ impl EraVM { } } - UMAOpcode::AuxHeapRead => aux_heap_read(&mut self.state, &opcode), - UMAOpcode::AuxHeapWrite => aux_heap_write(&mut self.state, &opcode), - UMAOpcode::FatPointerRead => fat_pointer_read(&mut self.state, &opcode), - UMAOpcode::StaticMemoryRead => unimplemented(&mut self.state, &opcode), - UMAOpcode::StaticMemoryWrite => unimplemented(&mut self.state, &opcode), + UMAOpcode::AuxHeapRead => aux_heap_read(&mut self.execution, &opcode), + UMAOpcode::AuxHeapWrite => aux_heap_write(&mut self.execution, &opcode), + UMAOpcode::FatPointerRead => fat_pointer_read(&mut self.execution, &opcode), + UMAOpcode::StaticMemoryRead => unimplemented(&mut self.execution, &opcode), + UMAOpcode::StaticMemoryWrite => unimplemented(&mut self.execution, &opcode), }, }; if let Err(err) = result { @@ -325,25 +297,21 @@ impl EraVM { return Ok(ExecutionOutput::Panic); } - match inexplicit_panic( - &mut self.state, - &mut self.state_storage, - &mut self.transient_storage, - ) { + match inexplicit_panic(&mut self.execution, &mut self.state) { Ok(false) => continue, _ => return Ok(ExecutionOutput::Panic), } } - set_pc(&mut self.state, &opcode)?; + set_pc(&mut self.execution, &opcode)?; } else { - self.state.current_frame_mut()?.pc += 1; + self.execution.current_frame_mut()?.pc += 1; } } } } // Sets the next PC according to the next opcode -fn set_pc(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { +fn set_pc(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { let current_pc = vm.current_frame()?.pc; vm.current_frame_mut()?.pc = match opcode.variant { @@ -357,7 +325,7 @@ fn set_pc(vm: &mut VMState, opcode: &Opcode) -> Result<(), EraVmError> { Ok(()) } -fn retrieve_result(vm: &mut VMState) -> Result, EraVmError> { +fn retrieve_result(vm: &mut Execution) -> Result, EraVmError> { let fat_pointer_src0 = FatPointer::decode(vm.get_register(1).value); let range = fat_pointer_src0.start..fat_pointer_src0.start + fat_pointer_src0.len; let mut result: Vec = vec![0; range.len()]; From 7fc5aabc68f975a9b82610414264e3566daef2e2 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau <76252340+MarcosNicolau@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:54:50 -0300 Subject: [PATCH 2/3] Implement pubdata and refunds (#200) * Add HashSet rollbackable structAdd HashSet rollbackable struct * Add new fields to support pubdata and refunds in state * Add new unique Storage trait * Add pubdata and refunds logic to storage reads * Implement Storage trait for InitialStorageMemory * Add pubdata and refunds logic to opcodes * Update submodule * Address clippy warnings * Add pubdata_costs getter to state * Improve comments on prepaid costs [skip ci] * Add pubdata to meta if contex is in kernel only * Refactor names and comments in storage_write for pubdata [skip ci] --- .gitmodules | 2 +- era-compiler-tester | 2 +- src/execution.rs | 5 + src/op_handlers/context.rs | 9 +- src/op_handlers/far_call.rs | 11 +-- src/op_handlers/log.rs | 14 +-- src/op_handlers/opcode_decommit.rs | 2 +- src/op_handlers/precompile_call.rs | 10 +- src/rollbacks.rs | 18 +++- src/state.rs | 142 +++++++++++++++++++++++++---- src/store.rs | 48 +++++----- src/vm.rs | 20 ++-- 12 files changed, 206 insertions(+), 77 deletions(-) diff --git a/.gitmodules b/.gitmodules index 203428a9..a2fd0cad 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "era-compiler-tester"] path = era-compiler-tester - url = https://github.com/lambdaclass/era-compiler-tester.git + url = https://github.com/lambdaclass/era-compiler-tester.git \ No newline at end of file diff --git a/era-compiler-tester b/era-compiler-tester index f691279f..85f5cb88 160000 --- a/era-compiler-tester +++ b/era-compiler-tester @@ -1 +1 @@ -Subproject commit f691279fed07d57ccb78ddf87b67741832921743 +Subproject commit 85f5cb88bad91c32e11e4d326abcc7a868f5ca97 diff --git a/src/execution.rs b/src/execution.rs index 4b203722..88f8ee3e 100644 --- a/src/execution.rs +++ b/src/execution.rs @@ -298,6 +298,11 @@ impl Execution { Ok(()) } + pub fn increase_gas(&mut self, to_add: u32) -> Result<(), EraVmError> { + self.current_frame_mut()?.gas_left += to_add; + Ok(()) + } + pub fn set_gas_left(&mut self, gas: u32) -> Result<(), EraVmError> { self.current_frame_mut()?.gas_left = Saturating(gas); Ok(()) diff --git a/src/op_handlers/context.rs b/src/op_handlers/context.rs index 01d74d93..fd95cdc3 100644 --- a/src/op_handlers/context.rs +++ b/src/op_handlers/context.rs @@ -4,6 +4,7 @@ use zkevm_opcode_defs::VmMetaParameters; use crate::address_operands::{address_operands_read, address_operands_store}; use crate::eravm_error::{EraVmError, HeapError}; +use crate::state::VMState; use crate::value::TaggedValue; use crate::{execution::Execution, opcode::Opcode}; @@ -32,7 +33,7 @@ pub fn code_address(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmErro address_operands_store(vm, opcode, res) } -pub fn meta(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn meta(vm: &mut Execution, opcode: &Opcode, state: &VMState) -> Result<(), EraVmError> { let res = TaggedValue::new_raw_integer( (VmMetaParameters { heap_size: vm @@ -48,7 +49,11 @@ pub fn meta(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { this_shard_id: 0, caller_shard_id: 0, code_shard_id: 0, - aux_field_0: 0, // TODO: Add pubdata + aux_field_0: if vm.current_context()?.is_kernel() { + state.pubdata() as u32 + } else { + 0 + }, }) .to_u256(), ); diff --git a/src/op_handlers/far_call.rs b/src/op_handlers/far_call.rs index e3348da7..f16a02d3 100644 --- a/src/op_handlers/far_call.rs +++ b/src/op_handlers/far_call.rs @@ -135,13 +135,8 @@ fn decommit_code_hash( Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); let storage_key = StorageKey::new(deployer_system_contract_address, address_into_u256(address)); - let code_info = match state.storage_read(storage_key)? { - Some(code_info) => code_info, - None => { - state.storage_write(storage_key, U256::zero()); - U256::zero() - } - }; + // reading when decommiting doesn't refund + let (code_info, _) = state.storage_read(storage_key); let mut code_info_bytes = [0; 32]; code_info.to_big_endian(&mut code_info_bytes); @@ -240,7 +235,7 @@ pub fn far_call( .expect("stipend must not cause overflow"); let program_code = state - .decommit(code_key)? + .decommit(code_key) .ok_or(StorageError::KeyNotPresent)?; let new_heap = vm.heaps.allocate(); let new_aux_heap = vm.heaps.allocate(); diff --git a/src/op_handlers/log.rs b/src/op_handlers/log.rs index c9dc58fd..f1b8d04d 100644 --- a/src/op_handlers/log.rs +++ b/src/op_handlers/log.rs @@ -1,5 +1,3 @@ -use u256::U256; - use crate::{ eravm_error::EraVmError, execution::Execution, @@ -18,19 +16,21 @@ pub fn storage_write( let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); let value = vm.get_register(opcode.src1_index).value; - state.storage_write(key, value); + let refund = state.storage_write(key, value); + vm.increase_gas(refund)?; Ok(()) } pub fn storage_read( vm: &mut Execution, opcode: &Opcode, - state: &VMState, + state: &mut VMState, ) -> Result<(), EraVmError> { let key_for_contract_storage = vm.get_register(opcode.src0_index).value; let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); - let value = state.storage_read(key)?.unwrap_or(U256::zero()); + let (value, refund) = state.storage_read(key); + vm.increase_gas(refund)?; vm.set_register(opcode.dst0_index, TaggedValue::new_raw_integer(value)); Ok(()) } @@ -51,12 +51,12 @@ pub fn transient_storage_write( pub fn transient_storage_read( vm: &mut Execution, opcode: &Opcode, - state: &VMState, + state: &mut VMState, ) -> Result<(), EraVmError> { let key_for_contract_storage = vm.get_register(opcode.src0_index).value; let address = vm.current_context()?.contract_address; let key = StorageKey::new(address, key_for_contract_storage); - let value = state.transient_storage_read(key)?.unwrap_or(U256::zero()); + let value = state.transient_storage_read(key); vm.set_register(opcode.dst0_index, TaggedValue::new_raw_integer(value)); Ok(()) } diff --git a/src/op_handlers/opcode_decommit.rs b/src/op_handlers/opcode_decommit.rs index bd4e7ccf..16f6faa4 100644 --- a/src/op_handlers/opcode_decommit.rs +++ b/src/op_handlers/opcode_decommit.rs @@ -21,7 +21,7 @@ pub fn opcode_decommit( vm.decrease_gas(extra_cost)?; let code = state - .decommit(code_hash)? + .decommit(code_hash) .ok_or(EraVmError::DecommitFailed)?; let code_len_in_bytes = code.len() * 32; diff --git a/src/op_handlers/precompile_call.rs b/src/op_handlers/precompile_call.rs index b3788e79..613cdc51 100644 --- a/src/op_handlers/precompile_call.rs +++ b/src/op_handlers/precompile_call.rs @@ -14,16 +14,22 @@ use crate::{ ecrecover::ecrecover_function, keccak256::keccak256_rounds_function, secp256r1_verify::secp256r1_verify_function, sha256::sha256_rounds_function, }, + state::VMState, value::TaggedValue, Opcode, }; -pub fn precompile_call(vm: &mut Execution, opcode: &Opcode) -> Result<(), EraVmError> { +pub fn precompile_call( + vm: &mut Execution, + opcode: &Opcode, + state: &mut VMState, +) -> Result<(), EraVmError> { let (src0, src1) = address_operands_read(vm, opcode)?; let aux_data = PrecompileAuxData::from_u256(src1.value); - vm.decrease_gas(aux_data.extra_ergs_cost)?; + state.add_pubdata(aux_data.extra_pubdata_cost as i32); + let mut abi = PrecompileCallABI::from_u256(src0.value); if abi.memory_page_to_read == 0 { abi.memory_page_to_read = vm.current_context()?.heap_id; diff --git a/src/rollbacks.rs b/src/rollbacks.rs index 14107575..27339a10 100644 --- a/src/rollbacks.rs +++ b/src/rollbacks.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; pub trait Rollbackable { type Snapshot; @@ -53,3 +53,19 @@ impl Rollbackable for RollbackablePrimitive { self.value } } + +#[derive(Debug, Default)] +pub struct RollbackableHashSet { + pub map: HashSet, +} + +impl Rollbackable for RollbackableHashSet { + type Snapshot = HashSet; + fn rollback(&mut self, snapshot: Self::Snapshot) { + self.map = snapshot; + } + + fn snapshot(&self) -> Self::Snapshot { + self.map.clone() + } +} diff --git a/src/state.rs b/src/state.rs index d19c6f01..5a4ac386 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,20 @@ use crate::{ - rollbacks::{Rollbackable, RollbackableHashMap, RollbackableVec}, - store::{ContractStorage, InitialStorage, StorageError, StorageKey}, + rollbacks::{ + Rollbackable, RollbackableHashMap, RollbackableHashSet, RollbackablePrimitive, + RollbackableVec, + }, + store::{Storage, StorageKey}, }; use std::{cell::RefCell, collections::HashMap, rc::Rc}; use u256::{H160, U256}; +use zkevm_opcode_defs::system_params::{ + STORAGE_ACCESS_COLD_READ_COST, STORAGE_ACCESS_COLD_WRITE_COST, STORAGE_ACCESS_WARM_READ_COST, + STORAGE_ACCESS_WARM_WRITE_COST, +}; + +const WARM_READ_REFUND: u32 = STORAGE_ACCESS_COLD_READ_COST - STORAGE_ACCESS_WARM_READ_COST; +const WARM_WRITE_REFUND: u32 = STORAGE_ACCESS_COLD_WRITE_COST - STORAGE_ACCESS_WARM_WRITE_COST; +const COLD_WRITE_AFTER_WARM_READ_REFUND: u32 = STORAGE_ACCESS_COLD_READ_COST; #[derive(Debug, PartialEq, Clone, Default)] pub struct L2ToL1Log { @@ -26,26 +37,37 @@ pub struct Event { #[derive(Debug)] pub struct VMState { - pub initial_storage: Rc>, - pub contracts_storage: Rc>, + pub storage: Rc>, storage_changes: RollbackableHashMap, transient_storage: RollbackableHashMap, l2_to_l1_logs: RollbackableVec, events: RollbackableVec, + // holds the sum of pubdata_costs + pubdata: RollbackablePrimitive, + pubdata_costs: RollbackableVec, + paid_changes: RollbackableHashMap, + refunds: RollbackableVec, + + // this fields don't get rollbacked on reverts(but the bootloader might) + // that is why we add them as rollbackable as well + read_storage_slots: RollbackableHashSet, + written_storage_slots: RollbackableHashSet, } impl VMState { - pub fn new( - initial_storage: Rc>, - contracts_storage: Rc>, - ) -> Self { + pub fn new(storage: Rc>) -> Self { Self { - initial_storage, - contracts_storage, + storage, storage_changes: RollbackableHashMap::::default(), transient_storage: RollbackableHashMap::::default(), l2_to_l1_logs: RollbackableVec::::default(), events: RollbackableVec::::default(), + pubdata: RollbackablePrimitive::::default(), + pubdata_costs: RollbackableVec::::default(), + paid_changes: RollbackableHashMap::::default(), + refunds: RollbackableVec::::default(), + read_storage_slots: RollbackableHashSet::::default(), + written_storage_slots: RollbackableHashSet::::default(), } } @@ -65,22 +87,96 @@ impl VMState { &self.events.entries } - pub fn storage_read(&self, key: StorageKey) -> Result, StorageError> { - match self.storage_changes.map.get(&key) { - None => self.initial_storage.borrow().storage_read(key), - value => Ok(value.copied()), + pub fn pubdata_costs(&self) -> &Vec { + &self.pubdata_costs.entries + } + + pub fn pubdata(&self) -> i32 { + self.pubdata.value + } + + pub fn add_pubdata(&mut self, to_add: i32) { + self.pubdata.value += to_add; + } + + pub fn storage_read(&mut self, key: StorageKey) -> (U256, u32) { + let value = self.storage_read_inner(&key).unwrap_or_default(); + let storage = self.storage.borrow(); + + let refund = + if storage.is_free_storage_slot(&key) || self.read_storage_slots.map.contains(&key) { + WARM_READ_REFUND + } else { + self.read_storage_slots.map.insert(key); + 0 + }; + + self.pubdata_costs.entries.push(0); + + (value, refund) + } + + fn storage_read_inner(&self, key: &StorageKey) -> Option { + match self.storage_changes.map.get(key) { + None => self.storage.borrow_mut().storage_read(key), + value => value.copied(), } } - pub fn storage_write(&mut self, key: StorageKey, value: U256) { + pub fn storage_write(&mut self, key: StorageKey, value: U256) -> u32 { self.storage_changes.map.insert(key, value); + let mut storage = self.storage.borrow_mut(); + + if storage.is_free_storage_slot(&key) { + let refund = WARM_WRITE_REFUND; + self.refunds.entries.push(refund); + self.pubdata_costs.entries.push(0); + return refund; + } + + // after every write, we store the current cost paid + // on subsequent writes, we don't charge for what has already been paid + // but for the newer price, which if it is lower might end up in a refund + let current_cost = storage.cost_of_writing_storage(&key, value); + let prev_cost = *self.paid_changes.map.get(&key).unwrap_or(&0); + self.paid_changes.map.insert(key, current_cost); + + let refund = if self.written_storage_slots.map.contains(&key) { + WARM_WRITE_REFUND + } else { + self.written_storage_slots.map.insert(key); + + if self.read_storage_slots.map.contains(&key) { + COLD_WRITE_AFTER_WARM_READ_REFUND + } else { + self.read_storage_slots.map.insert(key); + 0 + } + }; + + // Note, that the diff may be negative, e.g. in case the new write returns to the original value. + // The end result is that users pay as much pubdata in total as would have been required to set + // the slots to their final values. + // The only case where users may overpay is when a previous transaction ends up with a negative pubdata total. + let pubdata_cost = (current_cost as i32) - (prev_cost as i32); + self.pubdata.value += pubdata_cost; + self.refunds.entries.push(refund); + self.pubdata_costs.entries.push(pubdata_cost); + + refund } - pub fn transient_storage_read(&self, key: StorageKey) -> Result, StorageError> { - Ok(self.transient_storage.map.get(&key).copied()) + pub fn transient_storage_read(&mut self, key: StorageKey) -> U256 { + self.pubdata_costs.entries.push(0); + self.transient_storage + .map + .get(&key) + .copied() + .unwrap_or_default() } pub fn transient_storage_write(&mut self, key: StorageKey, value: U256) { + self.pubdata_costs.entries.push(0); self.transient_storage.map.insert(key, value); } @@ -92,8 +188,8 @@ impl VMState { self.events.entries.push(event); } - pub fn decommit(&mut self, hash: U256) -> Result>, StorageError> { - self.contracts_storage.borrow().decommit(hash) + pub fn decommit(&mut self, hash: U256) -> Option> { + self.storage.borrow_mut().decommit(hash) } } @@ -105,6 +201,10 @@ pub struct StateSnapshot { transient_storage: as Rollbackable>::Snapshot, l2_to_l1_logs: as Rollbackable>::Snapshot, events: as Rollbackable>::Snapshot, + pubdata: as Rollbackable>::Snapshot, + pubdata_costs: as Rollbackable>::Snapshot, + paid_changes: as Rollbackable>::Snapshot, + refunds: as Rollbackable>::Snapshot, } impl Rollbackable for VMState { @@ -123,6 +223,10 @@ impl Rollbackable for VMState { storage_changes: self.storage_changes.snapshot(), transient_storage: self.transient_storage.snapshot(), events: self.events.snapshot(), + pubdata: self.pubdata.snapshot(), + pubdata_costs: self.pubdata_costs.snapshot(), + paid_changes: self.paid_changes.snapshot(), + refunds: self.refunds.snapshot(), } } } diff --git a/src/store.rs b/src/store.rs index 95c1c944..e96e43a8 100644 --- a/src/store.rs +++ b/src/store.rs @@ -20,36 +20,40 @@ impl StorageKey { } } -pub trait InitialStorage: Debug { - fn storage_read(&self, key: StorageKey) -> Result, StorageError>; +pub trait Storage: Debug { + fn decommit(&mut self, hash: U256) -> Option>; + + fn storage_read(&mut self, key: &StorageKey) -> Option; + + fn cost_of_writing_storage(&mut self, key: &StorageKey, value: U256) -> u32; + + fn is_free_storage_slot(&self, key: &StorageKey) -> bool; } #[derive(Debug, Clone)] pub struct InitialStorageMemory { - pub initial_storage: HashMap, + pub contracts: HashMap>, + pub storage: HashMap, } // The initial storage acts as a read-only storage with initial values // Any changes to the storage are stored in the state storage // This specific implementation is just a simple way of doing it, so that the compiler tester can use it for testing -impl InitialStorage for InitialStorageMemory { - fn storage_read(&self, key: StorageKey) -> Result, StorageError> { - Ok(self.initial_storage.get(&key).copied()) +impl Storage for InitialStorageMemory { + fn storage_read(&mut self, key: &StorageKey) -> Option { + self.storage.get(key).copied() } -} -pub trait ContractStorage: Debug { - fn decommit(&self, hash: U256) -> Result>, StorageError>; -} + fn decommit(&mut self, hash: U256) -> Option> { + self.contracts.get(&hash).cloned() + } -#[derive(Debug)] -pub struct ContractStorageMemory { - pub contract_storage: HashMap>, -} + fn cost_of_writing_storage(&mut self, _key: &StorageKey, _value: U256) -> u32 { + 0 + } -impl ContractStorage for ContractStorageMemory { - fn decommit(&self, hash: U256) -> Result>, StorageError> { - Ok(self.contract_storage.get(&hash).cloned()) + fn is_free_storage_slot(&self, _key: &StorageKey) -> bool { + false } } @@ -68,18 +72,14 @@ pub enum StorageError { /// 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( - initial_storage: &dyn InitialStorage, - contract_storage: &dyn ContractStorage, + storage: &mut dyn Storage, address: H160, evm_interpreter_code_hash: [u8; 32], ) -> Result, EraVmError> { let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); let storage_key = StorageKey::new(deployer_system_contract_address, address_into_u256(address)); - let code_info = initial_storage - .storage_read(storage_key) - .unwrap() - .unwrap_or_default(); + let code_info = storage.storage_read(&storage_key).unwrap(); let mut code_info_bytes = [0; 32]; code_info.to_big_endian(&mut code_info_bytes); @@ -91,7 +91,7 @@ pub fn initial_decommit( code_info_bytes[1] = 0; let code_key: U256 = U256::from_big_endian(&code_info_bytes); - let code = contract_storage.decommit(code_key)?; + let code = storage.decommit(code_key); match code { Some(code) => Ok(code), None => Err(EraVmError::StorageError(StorageError::KeyNotPresent)), diff --git a/src/vm.rs b/src/vm.rs index ded57866..19d0faf8 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -42,7 +42,7 @@ use crate::op_handlers::sub::sub; use crate::op_handlers::unimplemented::unimplemented; use crate::op_handlers::xor::xor; use crate::state::VMState; -use crate::store::{ContractStorage, InitialStorage}; +use crate::store::Storage; use crate::tracers::blob_saver_tracer::BlobSaverTracer; use crate::value::{FatPointer, TaggedValue}; use crate::{eravm_error::EraVmError, tracers::tracer::Tracer, Execution}; @@ -68,13 +68,9 @@ pub enum EncodingMode { } impl EraVM { - pub fn new( - execution: Execution, - initial_storage: Rc>, - contract_storage: Rc>, - ) -> Self { + pub fn new(execution: Execution, storage: Rc>) -> Self { Self { - state: VMState::new(initial_storage, contract_storage), + state: VMState::new(storage), execution, } } @@ -175,7 +171,7 @@ impl EraVM { ContextOpcode::IncrementTxNumber => { increment_tx_number(&mut self.execution, &opcode) } - ContextOpcode::Meta => meta(&mut self.execution, &opcode), + ContextOpcode::Meta => meta(&mut self.execution, &opcode, &self.state), ContextOpcode::SetContextU128 => { set_context_u128(&mut self.execution, &opcode) } @@ -202,7 +198,7 @@ impl EraVM { Variant::NearCall(_) => near_call(&mut self.execution, &opcode, &self.state), Variant::Log(log_variant) => match log_variant { LogOpcode::StorageRead => { - storage_read(&mut self.execution, &opcode, &self.state) + storage_read(&mut self.execution, &opcode, &mut self.state) } LogOpcode::StorageWrite => { storage_write(&mut self.execution, &opcode, &mut self.state) @@ -210,13 +206,15 @@ impl EraVM { LogOpcode::ToL1Message => { add_l2_to_l1_message(&mut self.execution, &opcode, &mut self.state) } - LogOpcode::PrecompileCall => precompile_call(&mut self.execution, &opcode), + LogOpcode::PrecompileCall => { + precompile_call(&mut self.execution, &opcode, &mut self.state) + } LogOpcode::Event => event(&mut self.execution, &opcode, &mut self.state), LogOpcode::Decommit => { opcode_decommit(&mut self.execution, &opcode, &mut self.state) } LogOpcode::TransientStorageRead => { - transient_storage_read(&mut self.execution, &opcode, &self.state) + transient_storage_read(&mut self.execution, &opcode, &mut self.state) } LogOpcode::TransientStorageWrite => { transient_storage_write(&mut self.execution, &opcode, &mut self.state) From ca4033022c4dd760a38e4883a5a95bf58aa55d83 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau <76252340+MarcosNicolau@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:10:24 -0300 Subject: [PATCH 3/3] Decommited hashes (#202) * Add HashSet rollbackable structAdd HashSet rollbackable struct * Add new fields to support pubdata and refunds in state * Add new unique Storage trait * Add pubdata and refunds logic to storage reads * Implement Storage trait for InitialStorageMemory * Add pubdata and refunds logic to opcodes * Update submodule * Address clippy warnings * Add pubdata_costs getter to state * Add decommited hashes to state * Add pay for decommit --- src/op_handlers/far_call.rs | 20 +++++++++++++++++--- src/state.rs | 13 ++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/op_handlers/far_call.rs b/src/op_handlers/far_call.rs index f16a02d3..56391df1 100644 --- a/src/op_handlers/far_call.rs +++ b/src/op_handlers/far_call.rs @@ -129,7 +129,7 @@ fn decommit_code_hash( default_aa_code_hash: [u8; 32], evm_interpreter_code_hash: [u8; 32], is_constructor_call: bool, -) -> Result<(U256, bool), EraVmError> { +) -> Result<(U256, bool, u32), EraVmError> { let mut is_evm = false; let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); @@ -193,7 +193,16 @@ fn decommit_code_hash( code_info_bytes[1] = 0; - Ok((U256::from_big_endian(&code_info_bytes), is_evm)) + let code_key = U256::from_big_endian(&code_info_bytes); + + let cost = if state.decommitted_hashes().contains(&code_key) { + 0 + } else { + let code_length_in_words = u16::from_be_bytes([code_info_bytes[2], code_info_bytes[3]]); + code_length_in_words as u32 * zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT + }; + + Ok((U256::from_big_endian(&code_info_bytes), is_evm, cost)) } pub fn far_call( @@ -212,7 +221,7 @@ pub fn far_call( abi.is_constructor_call = abi.is_constructor_call && vm.current_context()?.is_kernel(); abi.is_system_call = abi.is_system_call && is_kernel(&contract_address); - let (code_key, is_evm) = decommit_code_hash( + let (code_key, is_evm, decommit_cost) = decommit_code_hash( state, contract_address, vm.default_aa_code_hash, @@ -220,6 +229,11 @@ pub fn far_call( abi.is_constructor_call, )?; + // Unlike all other gas costs, this one is not paid if low on gas. + if decommit_cost < vm.gas_left()? { + vm.decrease_gas(decommit_cost)?; + } + let FarCallParams { ergs_passed, forward_memory, diff --git a/src/state.rs b/src/state.rs index 5a4ac386..d23992a3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,7 +5,11 @@ use crate::{ }, store::{Storage, StorageKey}, }; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + rc::Rc, +}; use u256::{H160, U256}; use zkevm_opcode_defs::system_params::{ STORAGE_ACCESS_COLD_READ_COST, STORAGE_ACCESS_COLD_WRITE_COST, STORAGE_ACCESS_WARM_READ_COST, @@ -52,6 +56,7 @@ pub struct VMState { // that is why we add them as rollbackable as well read_storage_slots: RollbackableHashSet, written_storage_slots: RollbackableHashSet, + decommitted_hashes: RollbackableHashSet, } impl VMState { @@ -68,6 +73,7 @@ impl VMState { refunds: RollbackableVec::::default(), read_storage_slots: RollbackableHashSet::::default(), written_storage_slots: RollbackableHashSet::::default(), + decommitted_hashes: RollbackableHashSet::::default(), } } @@ -189,8 +195,13 @@ impl VMState { } pub fn decommit(&mut self, hash: U256) -> Option> { + self.decommitted_hashes.map.insert(hash); self.storage.borrow_mut().decommit(hash) } + + pub fn decommitted_hashes(&self) -> &HashSet { + &self.decommitted_hashes.map + } } #[derive(Clone, Default, PartialEq, Debug)]