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 1/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)] From 9b0300184ad0a71ead01e8775b967c9776f382ac Mon Sep 17 00:00:00 2001 From: juan518munoz <62400508+juan518munoz@users.noreply.github.com> Date: Fri, 16 Aug 2024 17:39:02 -0300 Subject: [PATCH 2/3] Adapt zksync era testing (#201) * adapt to zksync-era tests * Remove hash_map from Storage trait --------- Co-authored-by: Marcos Nicolau --- era-compiler-tester | 2 +- src/call_frame.rs | 6 ++-- src/execution.rs | 12 ++++---- src/heaps.rs | 2 +- src/lib.rs | 2 +- src/rollbacks.rs | 40 +++++++++++++++++++++----- src/state.rs | 10 +++---- src/store.rs | 2 +- src/tracers/last_state_saver_tracer.rs | 1 + src/value.rs | 2 +- src/vm.rs | 2 +- 11 files changed, 54 insertions(+), 27 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/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 d23992a3..6cc0d819 100644 --- a/src/state.rs +++ b/src/state.rs @@ -39,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, diff --git a/src/store.rs b/src/store.rs index e96e43a8..f8171045 100644 --- a/src/store.rs +++ b/src/store.rs @@ -58,7 +58,7 @@ impl Storage for InitialStorageMemory { } /// 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 c1c1d2e6d8d790124d692b6ebdb9a41d0baa4e4b Mon Sep 17 00:00:00 2001 From: Francisco Krause Arnim <56402156+fkrause98@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:34:35 -0300 Subject: [PATCH 3/3] Tests: era_vm + zksync era (#204) * submodules: add zksync-era * ci: add era tests step * makefile: add era-test * makefile: update era-test target * ci: fix ci yml * ci: remove unused job * makefile: fix era-test * ci: try again * ci: remove compile needed step * ci: add yarn * ci: properly run ci * ci: fix * ci: checkout * ci: properly do init * ci: try again * ci: try again * ci: properly set zksync_home * ci: add zk to path * ci: start containers * ci: try -v switch * ci: correct docker switch * ci: create data folder * ci: postgres folder * cI: fix volumes paths * ci: use era's flake for deps * ci: add nix * ci: replace nix with simply doing cargo install --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++++++++++ .gitmodules | 5 ++++- Makefile | 5 ++++- zksync-era | 1 + 4 files changed, 53 insertions(+), 2 deletions(-) create mode 160000 zksync-era diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4bd8a0a..263c3e5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -440,3 +440,47 @@ jobs: system-contracts: system-contracts-${{ matrix.encoding == 'test' && 'test' || 'prod' }}.o encoding: ${{ matrix.encoding == 'test' && '--use-test-encoding' || '' }} run: ./target/release/compiler-tester --load-system-contracts ${{ env.system-contracts}} ${{ env.encoding }} --target EraVM ${{ matrix.mode }} --path ${{ matrix.testgroup }} + + + zksync_era_tests: + runs-on: ubuntu-latest + steps: + - name: Checkout vm sources + uses: actions/checkout@v4 + with: + path: ${{ github.workspace }}/era_vm + + - name: Setup compiler-tester submodule + working-directory: ${{ github.workspace }}/era_vm + run: make submodules + + - name: Setup nodejs + yarn + uses: actions/setup-node@v4 + with: + node-version: 18.20.2 + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + + - name: Rustup toolchain install + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_VERSION }} + + - name: Setup zksync-era + working-directory: ${{ github.workspace }}/era_vm/zksync-era + run: | + cargo install sqlx-cli --version 0.8.0 + mkdir -p ./volumes/reth/data + mkdir -p ./volumes/postgres + docker compose -v up -d + sleep 15 + export ZKSYNC_HOME=$(pwd) + export PATH=$PATH:./bin + zk + zk init + + - name: Run tests + working-directory: ${{ github.workspace }}/era_vm + run: make era-test diff --git a/.gitmodules b/.gitmodules index a2fd0cad..7c267ab0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "era-compiler-tester"] path = era-compiler-tester - url = https://github.com/lambdaclass/era-compiler-tester.git \ No newline at end of file + url = https://github.com/lambdaclass/era-compiler-tester.git +[submodule "zksync-era"] + path = zksync-era + url = https://github.com/lambdaclass/zksync-era.git diff --git a/Makefile b/Makefile index 106b1a53..28a6c0c4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: clean lint test deps submodules +.PHONY: clean lint test deps submodules era-test LLVM_PATH?=$(shell pwd)/era-compiler-tester/target-llvm/target-final/ @@ -26,3 +26,6 @@ test: deps # CI test uses LLVM from the era-compiler-llvm repository, doesn't need to build it ci-test: export LLVM_SYS_170_PREFIX=$(LLVM_PATH) && $(MAKE) test + +era-test: submodules + cd ./zksync-era/core/lib/multivm && cargo t era_vm diff --git a/zksync-era b/zksync-era new file mode 160000 index 00000000..67689581 --- /dev/null +++ b/zksync-era @@ -0,0 +1 @@ +Subproject commit 67689581833f5994477b06c34e9d4ca41288edff