From c862649e8295cd56e51aca86944be56dd7c49af5 Mon Sep 17 00:00:00 2001 From: Juan Munoz Date: Fri, 16 Aug 2024 16:28:46 -0300 Subject: [PATCH 1/2] adapt to zksync-era tests --- era-compiler-tester | 2 +- src/call_frame.rs | 6 ++-- src/execution.rs | 12 ++++---- src/heaps.rs | 2 +- src/lib.rs | 2 +- src/op_handlers/far_call.rs | 20 +++++++++++-- src/rollbacks.rs | 40 +++++++++++++++++++++----- src/state.rs | 23 +++++++++++---- src/store.rs | 8 +++++- src/tracers/last_state_saver_tracer.rs | 1 + src/value.rs | 2 +- src/vm.rs | 2 +- 12 files changed, 89 insertions(+), 31 deletions(-) diff --git a/era-compiler-tester b/era-compiler-tester index 85f5cb88..eab44db2 160000 --- a/era-compiler-tester +++ b/era-compiler-tester @@ -1 +1 @@ -Subproject commit 85f5cb88bad91c32e11e4d326abcc7a868f5ca97 +Subproject commit eab44db21bd7b7e2ff907530133283477626ea02 diff --git a/src/call_frame.rs b/src/call_frame.rs index 5eff6f84..6ff089bb 100644 --- a/src/call_frame.rs +++ b/src/call_frame.rs @@ -4,7 +4,7 @@ use zkevm_opcode_defs::ethereum_types::Address; use crate::{execution::Stack, state::StateSnapshot, utils::is_kernel}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct CallFrame { pub pc: u64, pub gas_left: Saturating, @@ -13,7 +13,7 @@ pub struct CallFrame { pub snapshot: StateSnapshot, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct CodePage(Vec); impl CodePage { @@ -25,7 +25,7 @@ impl CodePage { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Context { pub frame: CallFrame, pub near_call_frames: Vec, diff --git a/src/execution.rs b/src/execution.rs index 88f8ee3e..34af025b 100644 --- a/src/execution.rs +++ b/src/execution.rs @@ -18,16 +18,17 @@ pub const CALLDATA_HEAP: u32 = 1; pub const FIRST_HEAP: u32 = 2; pub const FIRST_AUX_HEAP: u32 = 3; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Stack { pub stack: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Heap { heap: Vec, } -#[derive(Debug, Clone)] + +#[derive(Debug, Clone, PartialEq)] pub struct Execution { // The first register, r0, is actually always zero and not really used. // Writing to it does nothing. @@ -49,8 +50,6 @@ pub struct Execution { 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( @@ -63,6 +62,7 @@ impl Execution { evm_interpreter_code_hash: [u8; 32], hook_address: u32, use_hooks: bool, + initial_gas: u32, ) -> Self { let mut registers = [TaggedValue::default(); 15]; let calldata_ptr = FatPointer { @@ -76,7 +76,7 @@ impl Execution { let context = Context::new( program_code.clone(), - u32::MAX - 0x80000000, + initial_gas, contract_address, contract_address, caller, diff --git a/src/heaps.rs b/src/heaps.rs index 6d8a9442..e02f4747 100644 --- a/src/heaps.rs +++ b/src/heaps.rs @@ -2,7 +2,7 @@ use zkevm_opcode_defs::system_params::NEW_FRAME_MEMORY_STIPEND; use crate::{eravm_error::HeapError, execution::Heap}; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct Heaps { heaps: Vec, } diff --git a/src/lib.rs b/src/lib.rs index 753422be..73c1a451 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,6 @@ pub mod vm; pub use execution::Execution; pub use opcode::Opcode; pub use vm::EraVM; -mod rollbacks; +pub mod rollbacks; pub mod state; use zkevm_opcode_defs::Opcode as Variant; 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/rollbacks.rs b/src/rollbacks.rs index 27339a10..14aecd43 100644 --- a/src/rollbacks.rs +++ b/src/rollbacks.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, +}; pub trait Rollbackable { type Snapshot; @@ -6,12 +9,20 @@ pub trait Rollbackable { fn snapshot(&self) -> Self::Snapshot; } -#[derive(Debug, Default)] -pub struct RollbackableHashMap { +#[derive(Debug, Default, Clone)] +pub struct RollbackableHashMap { pub map: HashMap, } -impl Rollbackable for RollbackableHashMap { +impl RollbackableHashMap { + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } +} + +impl Rollbackable for RollbackableHashMap { type Snapshot = HashMap; fn rollback(&mut self, snapshot: Self::Snapshot) { self.map = snapshot; @@ -22,11 +33,26 @@ impl Rollbackable for RollbackableHashMap { } } -#[derive(Debug, Default)] +impl Iterator for RollbackableHashMap { + type Item = (K, V); + fn next(&mut self) -> Option { + self.map.iter().next().map(|(k, v)| (k.clone(), v.clone())) + } +} + +#[derive(Debug, Default, Clone)] pub struct RollbackableVec { pub entries: Vec, } +impl RollbackableVec { + pub fn new() -> Self { + Self { + entries: Vec::new(), + } + } +} + impl Rollbackable for RollbackableVec { type Snapshot = Vec; @@ -38,7 +64,7 @@ impl Rollbackable for RollbackableVec { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct RollbackablePrimitive { pub value: T, } @@ -54,7 +80,7 @@ impl Rollbackable for RollbackablePrimitive { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct RollbackableHashSet { pub map: HashSet, } diff --git a/src/state.rs b/src/state.rs index 5a4ac386..6cc0d819 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, @@ -35,13 +39,13 @@ pub struct Event { pub tx_number: u16, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct VMState { pub storage: Rc>, - storage_changes: RollbackableHashMap, - transient_storage: RollbackableHashMap, - l2_to_l1_logs: RollbackableVec, - events: RollbackableVec, + pub storage_changes: RollbackableHashMap, + pub transient_storage: RollbackableHashMap, + pub l2_to_l1_logs: RollbackableVec, + pub events: RollbackableVec, // holds the sum of pubdata_costs pubdata: RollbackablePrimitive, pubdata_costs: RollbackableVec, @@ -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)] diff --git a/src/store.rs b/src/store.rs index e96e43a8..557b5447 100644 --- a/src/store.rs +++ b/src/store.rs @@ -28,6 +28,8 @@ pub trait Storage: Debug { fn cost_of_writing_storage(&mut self, key: &StorageKey, value: U256) -> u32; fn is_free_storage_slot(&self, key: &StorageKey) -> bool; + + fn hash_map(&self) -> Result, StorageError>; } #[derive(Debug, Clone)] @@ -55,10 +57,14 @@ impl Storage for InitialStorageMemory { fn is_free_storage_slot(&self, _key: &StorageKey) -> bool { false } + + fn hash_map(&self) -> Result, StorageError> { + Ok(self.storage.clone()) + } } /// Error type for storage operations. -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum StorageError { #[error("Key not present in storage")] KeyNotPresent, diff --git a/src/tracers/last_state_saver_tracer.rs b/src/tracers/last_state_saver_tracer.rs index 9ef4b07e..5efbd292 100644 --- a/src/tracers/last_state_saver_tracer.rs +++ b/src/tracers/last_state_saver_tracer.rs @@ -25,6 +25,7 @@ impl LastStateSaverTracer { Default::default(), 0, false, + 0, ), } } diff --git a/src/value.rs b/src/value.rs index 65383814..431b70ec 100644 --- a/src/value.rs +++ b/src/value.rs @@ -2,7 +2,7 @@ use u256::U256; /// In the zkEVM, all data in the stack and on registers is tagged to determine /// whether they are a pointer or not. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct TaggedValue { pub value: U256, pub is_pointer: bool, diff --git a/src/vm.rs b/src/vm.rs index 19d0faf8..2cb94089 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -56,7 +56,7 @@ pub enum ExecutionOutput { SuspendedOnHook { hook: u32, pc_to_resume_from: u16 }, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EraVM { pub state: VMState, pub execution: Execution, From bbac76ddbf405686f83e564eb90481c2d74a9093 Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Fri, 16 Aug 2024 16:32:45 -0300 Subject: [PATCH 2/2] Remove hash_map from Storage trait --- src/store.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/store.rs b/src/store.rs index 557b5447..f8171045 100644 --- a/src/store.rs +++ b/src/store.rs @@ -28,8 +28,6 @@ pub trait Storage: Debug { fn cost_of_writing_storage(&mut self, key: &StorageKey, value: U256) -> u32; fn is_free_storage_slot(&self, key: &StorageKey) -> bool; - - fn hash_map(&self) -> Result, StorageError>; } #[derive(Debug, Clone)] @@ -57,10 +55,6 @@ impl Storage for InitialStorageMemory { fn is_free_storage_slot(&self, _key: &StorageKey) -> bool { false } - - fn hash_map(&self) -> Result, StorageError> { - Ok(self.storage.clone()) - } } /// Error type for storage operations.