diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 1f804c6a..94ff9847 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -218,8 +218,11 @@ async fn main() -> anyhow::Result<()> { let fee_input_provider = TestNodeFeeInputProvider::from_fork(fork_details.as_ref()); let filters = Arc::new(RwLock::new(EthFilters::default())); - let system_contracts = - SystemContracts::from_options(&config.system_contracts_options, config.use_evm_emulator); + let system_contracts = SystemContracts::from_options( + &config.system_contracts_options, + config.use_evm_emulator, + config.use_zkos, + ); let (node_inner, _fork_storage, blockchain, time) = InMemoryNodeInner::init( fork_details, diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 0cc553da..e7ceafdb 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -70,6 +70,8 @@ pub struct TestNodeConfig { pub override_bytecodes_dir: Option, /// Enables EVM emulation mode pub use_evm_emulator: bool, + /// Enables ZKOS mode (experimental) + pub use_zkos: bool, /// Optional chain ID for the node pub chain_id: Option, /// L1 gas price (optional override) @@ -155,6 +157,7 @@ impl Default for TestNodeConfig { system_contracts_options: Default::default(), override_bytecodes_dir: None, use_evm_emulator: false, + use_zkos: false, chain_id: None, // Gas configuration defaults @@ -362,6 +365,15 @@ impl TestNodeConfig { "Disabled".red() } ); + tracing::info!( + "ZK OS: {}", + if self.use_zkos { + "Enabled".green() + } else { + "Disabled".red() + } + ); + println!("\n"); tracing::info!("========================================"); for host in &self.host { diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 435abbb9..75bbb313 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -49,4 +49,4 @@ httptest.workspace = true tempdir.workspace = true zksync-web3-rs.workspace = true test-case.workspace = true -backon.workspace = true +backon.workspace = true \ No newline at end of file diff --git a/crates/core/src/node/debug.rs b/crates/core/src/node/debug.rs index 7c8c68a7..214d80e3 100644 --- a/crates/core/src/node/debug.rs +++ b/crates/core/src/node/debug.rs @@ -57,9 +57,12 @@ impl InMemoryNode { ))); } - let allow_no_target = system_contracts.evm_emulator.is_some(); - let mut l2_tx = L2Tx::from_request(request.into(), MAX_TX_SIZE, allow_no_target) - .map_err(Web3Error::SerializationError)?; + let mut l2_tx = L2Tx::from_request( + request.into(), + MAX_TX_SIZE, + self.system_contracts.allow_no_target(), + ) + .map_err(Web3Error::SerializationError)?; let execution_mode = zksync_multivm::interface::TxExecutionMode::EthCall; // init vm diff --git a/crates/core/src/node/eth.rs b/crates/core/src/node/eth.rs index 89fabfdf..9de40a91 100644 --- a/crates/core/src/node/eth.rs +++ b/crates/core/src/node/eth.rs @@ -2,21 +2,20 @@ use std::collections::HashSet; use anyhow::Context as _; use colored::Colorize; -use zksync_multivm::interface::{ExecutionResult, TxExecutionMode}; +use zksync_multivm::interface::ExecutionResult; use zksync_multivm::vm_latest::constants::ETH_CALL_GAS_LIMIT; use zksync_types::h256_to_u256; use zksync_types::{ api, api::{Block, BlockIdVariant, BlockNumber, TransactionVariant}, - get_code_key, get_nonce_key, + get_code_key, l2::L2Tx, transaction_request::TransactionRequest, - utils::storage_key_for_standard_token_balance, - PackedEthSignature, L2_BASE_TOKEN_ADDRESS, MAX_L1_TRANSACTION_GAS_LIMIT, + PackedEthSignature, MAX_L1_TRANSACTION_GAS_LIMIT, }; use zksync_types::{ web3::{self, Bytes}, - AccountTreeId, Address, H160, H256, U256, U64, + Address, H160, H256, U256, U64, }; use zksync_web3_decl::{ error::Web3Error, @@ -29,15 +28,19 @@ use crate::{ utils::{h256_to_u64, TransparentError}, }; +use super::keys::StorageKeyLayout; + impl InMemoryNode { pub async fn call_impl( &self, req: zksync_types::transaction_request::CallRequest, ) -> Result { let system_contracts = self.system_contracts.contracts_for_l2_call().clone(); - let allow_no_target = system_contracts.evm_emulator.is_some(); - - let mut tx = L2Tx::from_request(req.into(), MAX_TX_SIZE, allow_no_target)?; + let mut tx = L2Tx::from_request( + req.into(), + MAX_TX_SIZE, + self.system_contracts.allow_no_target(), + )?; tx.common_data.fee.gas_limit = ETH_CALL_GAS_LIMIT.into(); let call_result = self .run_l2_call(tx, system_contracts) @@ -78,13 +81,8 @@ impl InMemoryNode { let chain_id = self.inner.read().await.fork_storage.chain_id; let (tx_req, hash) = TransactionRequest::from_bytes(&tx_bytes.0, chain_id)?; - // Impersonation does not matter in this context so we assume the tx is not impersonated: - // system contracts here are fetched solely to check for EVM emulator. - let system_contracts = self - .system_contracts - .contracts(TxExecutionMode::VerifyExecute, false); - let allow_no_target = system_contracts.evm_emulator.is_some(); - let mut l2_tx = L2Tx::from_request(tx_req, MAX_TX_SIZE, allow_no_target)?; + let mut l2_tx = + L2Tx::from_request(tx_req, MAX_TX_SIZE, self.system_contracts.allow_no_target())?; l2_tx.set_input(tx_bytes.0, hash); if hash != l2_tx.hash() { @@ -144,13 +142,8 @@ impl InMemoryNode { 27, ))?; - // Impersonation does not matter in this context so we assume the tx is not impersonated: - // system contracts here are fetched solely to check for EVM emulator. - let system_contracts = self - .system_contracts - .contracts(TxExecutionMode::VerifyExecute, false); - let allow_no_target = system_contracts.evm_emulator.is_some(); - let mut l2_tx: L2Tx = L2Tx::from_request(tx_req, MAX_TX_SIZE, allow_no_target)?; + let mut l2_tx: L2Tx = + L2Tx::from_request(tx_req, MAX_TX_SIZE, self.system_contracts.allow_no_target())?; // `v` was overwritten with 0 during converting into l2 tx let mut signature = vec![0u8; 65]; @@ -183,8 +176,8 @@ impl InMemoryNode { // TODO: Support _block: Option, ) -> anyhow::Result { - let balance_key = storage_key_for_standard_token_balance( - AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), + let balance_key = StorageKeyLayout::get_storage_key_for_base_token( + self.system_contracts.use_zkos, &address, ); @@ -281,7 +274,7 @@ impl InMemoryNode { _block: Option, ) -> anyhow::Result { let inner = self.inner.read().await; - let nonce_key = get_nonce_key(&address); + let nonce_key = StorageKeyLayout::get_nonce_key(self.system_contracts.use_zkos, &address); match inner.fork_storage.read_value_internal(&nonce_key) { Ok(result) => Ok(h256_to_u64(result).into()), @@ -619,7 +612,7 @@ mod tests { utils::deployed_address_create, Bloom, K256PrivateKey, L2BlockNumber, StorageKey, EMPTY_UNCLES_HASH, }; - use zksync_types::{u256_to_h256, web3, Nonce}; + use zksync_types::{u256_to_h256, web3, AccountTreeId, Nonce}; use zksync_web3_decl::types::{SyncState, ValueOrArray}; async fn test_node(url: &str) -> InMemoryNode { diff --git a/crates/core/src/node/in_memory.rs b/crates/core/src/node/in_memory.rs index 80d809ce..30ced093 100644 --- a/crates/core/src/node/in_memory.rs +++ b/crates/core/src/node/in_memory.rs @@ -2,9 +2,9 @@ use super::inner::fork::ForkDetails; use super::inner::node_executor::NodeExecutorHandle; use super::inner::InMemoryNodeInner; +use super::vm::AnvilVM; use crate::deps::{storage_view::StorageView, InMemoryStorage}; use crate::filters::EthFilters; -use crate::formatter; use crate::node::call_error_tracer::CallErrorTracer; use crate::node::error::LoadStateError; use crate::node::fee_model::TestNodeFeeInputProvider; @@ -16,6 +16,7 @@ use crate::node::state::VersionedState; use crate::node::{BlockSealer, BlockSealerMode, NodeExecutor, TxPool}; use crate::observability::Observability; use crate::system_contracts::SystemContracts; +use crate::{delegate_vm, formatter}; use anvil_zksync_config::constants::{ LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS, TEST_NODE_NETWORK_ID, }; @@ -36,13 +37,15 @@ use std::sync::Arc; use tokio::sync::RwLock; use zksync_contracts::BaseSystemContracts; use zksync_multivm::interface::storage::{ReadStorage, StoragePtr}; +use zksync_multivm::interface::VmFactory; use zksync_multivm::interface::{ - ExecutionResult, InspectExecutionMode, L1BatchEnv, L2BlockEnv, TxExecutionMode, VmFactory, - VmInterface, + ExecutionResult, InspectExecutionMode, L1BatchEnv, L2BlockEnv, TxExecutionMode, VmInterface, }; use zksync_multivm::tracers::CallTracer; use zksync_multivm::utils::{get_batch_base_fee, get_max_batch_gas_limit}; -use zksync_multivm::vm_latest::{HistoryDisabled, ToTracerPointer, Vm}; +use zksync_multivm::vm_latest::Vm; + +use zksync_multivm::vm_latest::{HistoryDisabled, ToTracerPointer}; use zksync_multivm::VmVersion; use zksync_types::api::{Block, DebugCall, TransactionReceipt, TransactionVariant}; use zksync_types::block::unpack_block_info; @@ -381,7 +384,18 @@ impl InMemoryNode { let system_env = inner.create_system_env(base_contracts, execution_mode); let storage = StorageView::new(&inner.fork_storage).into_rc_ptr(); - let mut vm: Vm<_, HistoryDisabled> = Vm::new(batch_env, system_env, storage); + + let mut vm = if self.system_contracts.use_zkos { + AnvilVM::ZKOs(super::zkos::ZKOsVM::<_, HistoryDisabled>::new( + batch_env, + system_env, + storage, + // TODO: this might be causing a deadlock.. check.. + &inner.fork_storage.inner.read().unwrap().raw_storage, + )) + } else { + AnvilVM::ZKSync(Vm::new(batch_env, system_env, storage)) + }; // We must inject *some* signature (otherwise bootloader code fails to generate hash). if l2_tx.common_data.signature.is_empty() { @@ -389,7 +403,7 @@ impl InMemoryNode { } let tx: Transaction = l2_tx.into(); - vm.push_transaction(tx.clone()); + delegate_vm!(vm, push_transaction(tx.clone())); let call_tracer_result = Arc::new(OnceCell::default()); @@ -397,7 +411,10 @@ impl InMemoryNode { CallErrorTracer::new().into_tracer_pointer(), CallTracer::new(call_tracer_result.clone()).into_tracer_pointer(), ]; - let tx_result = vm.inspect(&mut tracers.into(), InspectExecutionMode::OneTx); + let tx_result = delegate_vm!( + vm, + inspect(&mut tracers.into(), InspectExecutionMode::OneTx) + ); let call_traces = Arc::try_unwrap(call_tracer_result) .unwrap() @@ -635,6 +652,7 @@ impl InMemoryNode { let system_contracts = SystemContracts::from_options( &config.system_contracts_options, config.use_evm_emulator, + config.use_zkos, ); let (inner, _, blockchain, time) = InMemoryNodeInner::init( fork, diff --git a/crates/core/src/node/in_memory_ext.rs b/crates/core/src/node/in_memory_ext.rs index a07bb238..d1193ccc 100644 --- a/crates/core/src/node/in_memory_ext.rs +++ b/crates/core/src/node/in_memory_ext.rs @@ -2,17 +2,14 @@ use super::inner::fork::ForkDetails; use super::pool::TxBatch; use super::sealer::BlockSealerMode; use super::InMemoryNode; +use crate::node::keys::StorageKeyLayout; use crate::utils::bytecode_to_factory_dep; use anvil_zksync_types::api::{DetailedTransaction, ResetRequest}; use anyhow::anyhow; use std::time::Duration; use zksync_types::api::{Block, TransactionVariant}; use zksync_types::u256_to_h256; -use zksync_types::{ - get_code_key, get_nonce_key, - utils::{nonces_to_full_nonce, storage_key_for_eth_balance}, - L2BlockNumber, StorageKey, -}; +use zksync_types::{get_code_key, utils::nonces_to_full_nonce, L2BlockNumber, StorageKey}; use zksync_types::{AccountTreeId, Address, H256, U256, U64}; type Result = anyhow::Result; @@ -170,7 +167,10 @@ impl InMemoryNode { pub async fn set_balance(&self, address: Address, balance: U256) -> bool { let writer = self.inner.write().await; - let balance_key = storage_key_for_eth_balance(&address); + let balance_key = StorageKeyLayout::get_storage_key_for_base_token( + self.system_contracts.use_zkos, + &address, + ); writer .fork_storage .set_value(balance_key, u256_to_h256(balance)); @@ -184,7 +184,7 @@ impl InMemoryNode { pub async fn set_nonce(&self, address: Address, nonce: U256) -> bool { let writer = self.inner.write().await; - let nonce_key = get_nonce_key(&address); + let nonce_key = StorageKeyLayout::get_nonce_key(self.system_contracts.use_zkos, &address); let enforced_full_nonce = nonces_to_full_nonce(nonce, nonce); tracing::info!( "👷 Nonces for address {:?} have been set to {}", @@ -323,17 +323,7 @@ impl InMemoryNode { .strip_prefix("0x") .ok_or_else(|| anyhow!("code must be 0x-prefixed"))?; let code_bytes = hex::decode(code_slice)?; - let hashcode = bytecode_to_factory_dep(code_bytes)?; - let hash = u256_to_h256(hashcode.0); - let code = hashcode - .1 - .iter() - .flat_map(|entry| { - let mut bytes = vec![0u8; 32]; - entry.to_big_endian(&mut bytes); - bytes.to_vec() - }) - .collect(); + let (hash, code) = bytecode_to_factory_dep(code_bytes)?; writer.fork_storage.store_factory_dep(hash, code); writer.fork_storage.set_value(code_key, hash); Ok(()) diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index 55daf3ea..73d947e4 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -7,16 +7,20 @@ use crate::deps::storage_view::StorageView; use crate::filters::EthFilters; use crate::node::call_error_tracer::CallErrorTracer; use crate::node::error::LoadStateError; +use crate::node::keys::StorageKeyLayout; use crate::node::state::StateV1; use crate::node::storage_logs::print_storage_logs_details; +use crate::node::vm::AnvilVM; +use crate::node::zkos::ZKOsVM; use crate::node::{ compute_hash, create_block, ImpersonationManager, Snapshot, TestNodeFeeInputProvider, TransactionResult, TxExecutionInfo, VersionedState, ESTIMATE_GAS_ACCEPTABLE_OVERESTIMATION, MAX_PREVIOUS_STATES, MAX_TX_SIZE, }; + use crate::system_contracts::SystemContracts; use crate::utils::{bytecode_to_factory_dep, create_debug_output}; -use crate::{formatter, utils}; +use crate::{delegate_vm, formatter, utils}; use anvil_zksync_config::constants::NON_FORK_FIRST_BLOCK_TIMESTAMP; use anvil_zksync_config::TestNodeConfig; use anvil_zksync_types::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; @@ -28,11 +32,14 @@ use std::sync::Arc; use tokio::sync::RwLock; use zksync_contracts::BaseSystemContracts; use zksync_multivm::interface::storage::{ReadStorage, WriteStorage}; +use zksync_multivm::interface::VmFactory; use zksync_multivm::interface::{ Call, ExecutionResult, InspectExecutionMode, L1BatchEnv, L2BlockEnv, SystemEnv, - TxExecutionMode, VmExecutionResultAndLogs, VmFactory, VmInterface, VmInterfaceExt, + TxExecutionMode, VmExecutionResultAndLogs, VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, }; +use zksync_multivm::vm_latest::Vm; + use zksync_multivm::tracers::CallTracer; use zksync_multivm::utils::{ adjust_pubdata_price_for_tx, derive_base_fee_and_gas_per_pubdata, derive_overhead, @@ -41,21 +48,21 @@ use zksync_multivm::utils::{ use zksync_multivm::vm_latest::constants::{ BATCH_COMPUTATIONAL_GAS_LIMIT, BATCH_GAS_LIMIT, MAX_VM_PUBDATA_PER_BATCH, }; -use zksync_multivm::vm_latest::{HistoryDisabled, HistoryEnabled, ToTracerPointer, Vm}; -use zksync_multivm::{HistoryMode, VmVersion}; +use zksync_multivm::vm_latest::{ + HistoryDisabled, HistoryEnabled, HistoryMode, ToTracerPointer, TracerPointer, +}; +use zksync_multivm::VmVersion; use zksync_types::api::{BlockIdVariant, TransactionVariant}; use zksync_types::block::build_bloom; use zksync_types::fee::Fee; use zksync_types::fee_model::{BatchFeeInput, PubdataIndependentBatchFeeModelInput}; use zksync_types::l2::{L2Tx, TransactionType}; use zksync_types::transaction_request::CallRequest; -use zksync_types::utils::{ - decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance, -}; +use zksync_types::utils::{decompose_full_nonce, nonces_to_full_nonce}; use zksync_types::web3::{Bytes, Index}; use zksync_types::{ - api, get_nonce_key, h256_to_address, h256_to_u256, u256_to_h256, AccountTreeId, Address, Bloom, - BloomInput, L1BatchNumber, L2BlockNumber, StorageKey, StorageValue, Transaction, + api, h256_to_address, h256_to_u256, u256_to_h256, AccountTreeId, Address, Bloom, BloomInput, + L1BatchNumber, L2BlockNumber, StorageKey, StorageValue, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, H160, H256, MAX_L2_TX_GAS_LIMIT, U256, U64, }; use zksync_web3_decl::error::Web3Error; @@ -313,11 +320,14 @@ impl InMemoryNodeInner { /// This is because external users of the library may call this function to perform an isolated /// VM operation (optionally without bootloader execution) with an external storage and get the results back. /// So any data populated in [Self::run_l2_tx] will not be available for the next invocation. - fn run_l2_tx_raw( + fn run_l2_tx_raw( &self, l2_tx: L2Tx, - vm: &mut Vm, - ) -> anyhow::Result { + vm: &mut VM, + ) -> anyhow::Result + where + ::TracerDispatcher: From>>, + { let tx: Transaction = l2_tx.into(); let call_tracer_result = Arc::new(OnceCell::default()); @@ -337,7 +347,7 @@ impl InMemoryNodeInner { .into_owned(); let tx_result = vm.inspect(&mut tracers.into(), InspectExecutionMode::OneTx); - let call_traces = call_tracer_result.get().unwrap(); + let call_traces = call_tracer_result.get(); let spent_on_pubdata = tx_result.statistics.gas_used - tx_result.statistics.computational_gas_used as u64; @@ -376,29 +386,31 @@ impl InMemoryNodeInner { formatter.print_vm_details(&tx_result); } - if !self.config.disable_console_log { - self.console_log_handler.handle_calls_recursive(call_traces); - } + if let Some(call_traces) = call_traces { + if !self.config.disable_console_log { + self.console_log_handler.handle_calls_recursive(call_traces); + } - if self.config.show_calls != ShowCalls::None { - tracing::info!(""); - tracing::info!( - "[Transaction Execution] ({} calls)", - call_traces[0].calls.len() - ); - let num_calls = call_traces.len(); - for (i, call) in call_traces.iter().enumerate() { - let is_last_sibling = i == num_calls - 1; - let mut formatter = formatter::Formatter::new(); - formatter.print_call( - tx.initiator_account(), - tx.execute.contract_address, - call, - is_last_sibling, - self.config.show_calls, - self.config.show_outputs, - self.config.resolve_hashes, + if self.config.show_calls != ShowCalls::None { + tracing::info!(""); + tracing::info!( + "[Transaction Execution] ({} calls)", + call_traces[0].calls.len() ); + let num_calls = call_traces.len(); + for (i, call) in call_traces.iter().enumerate() { + let is_last_sibling = i == num_calls - 1; + let mut formatter = formatter::Formatter::new(); + formatter.print_call( + tx.initiator_account(), + tx.execute.contract_address, + call, + is_last_sibling, + self.config.show_calls, + self.config.show_outputs, + self.config.resolve_hashes, + ); + } } } // Print event logs if enabled @@ -421,23 +433,30 @@ impl InMemoryNodeInner { })?; bytecodes.insert(hash, bytecode); } + // Also add bytecodes that were created by EVM. + for entry in &tx_result.dynamic_factory_deps { + bytecodes.insert(*entry.0, entry.1.clone()); + } Ok(TxExecutionOutput { result: tx_result, - call_traces: call_traces.clone(), + call_traces: call_traces.cloned().unwrap_or_default(), bytecodes, }) } /// Runs L2 transaction and commits it to a new block. - fn run_l2_tx( + fn run_l2_tx( &mut self, l2_tx: L2Tx, l2_tx_index: u64, block_ctx: &BlockContext, batch_env: &L1BatchEnv, - vm: &mut Vm, - ) -> anyhow::Result { + vm: &mut VM, + ) -> anyhow::Result + where + ::TracerDispatcher: From>>, + { let tx_hash = l2_tx.hash(); let transaction_type = l2_tx.common_data.transaction_type; @@ -467,16 +486,7 @@ impl InMemoryNodeInner { // Write all the factory deps. for (hash, code) in bytecodes.iter() { - self.fork_storage.store_factory_dep( - u256_to_h256(*hash), - code.iter() - .flat_map(|entry| { - let mut bytes = vec![0u8; 32]; - entry.to_big_endian(&mut bytes); - bytes.to_vec() - }) - .collect(), - ) + self.fork_storage.store_factory_dep(*hash, code.clone()) } let logs = result @@ -544,7 +554,18 @@ impl InMemoryNodeInner { block_ctx: &mut BlockContext, ) -> Vec { let storage = StorageView::new(self.fork_storage.clone()).into_rc_ptr(); - let mut vm: Vm<_, HistoryEnabled> = Vm::new(batch_env.clone(), system_env, storage.clone()); + + let mut vm = if self.system_contracts.use_zkos { + AnvilVM::ZKOs(ZKOsVM::<_, HistoryEnabled>::new( + batch_env.clone(), + system_env, + storage.clone(), + // TODO: this might be causing a deadlock.. check.. + &self.fork_storage.inner.read().unwrap().raw_storage, + )) + } else { + AnvilVM::ZKSync(Vm::new(batch_env.clone(), system_env, storage.clone())) + }; // Compute block hash. Note that the computed block hash here will be different than that in production. let tx_hashes = txs.iter().map(|t| t.hash()).collect::>(); @@ -557,21 +578,31 @@ impl InMemoryNodeInner { for tx in txs { // Executing a next transaction means that a previous transaction was either rolled back (in which case its snapshot // was already removed), or that we build on top of it (in which case, it can be removed now). - vm.pop_snapshot_no_rollback(); + delegate_vm!(vm, pop_snapshot_no_rollback()); // Save pre-execution VM snapshot. - vm.make_snapshot(); - match self.run_l2_tx(tx, tx_index, block_ctx, &batch_env, &mut vm) { + delegate_vm!(vm, make_snapshot()); + let result = match vm { + AnvilVM::ZKSync(ref mut vm) => { + self.run_l2_tx(tx, tx_index, block_ctx, &batch_env, vm) + } + AnvilVM::ZKOs(ref mut vm) => { + self.run_l2_tx(tx, tx_index, block_ctx, &batch_env, vm) + } + }; + + match result { + //self.run_l2_tx(tx, tx_index, block_ctx, &batch_env, &mut vm) { Ok(tx_result) => { tx_results.push(tx_result); tx_index += 1; } Err(e) => { tracing::error!("Error while executing transaction: {e}"); - vm.rollback_to_the_latest_snapshot(); + delegate_vm!(vm, rollback_to_the_latest_snapshot()); } } } - vm.execute(InspectExecutionMode::Bootloader); + delegate_vm!(vm, execute(InspectExecutionMode::Bootloader)); // Write all the mutated keys (storage slots). for (key, value) in storage.borrow().modified_storage_keys() { @@ -725,12 +756,11 @@ impl InMemoryNodeInner { .system_contracts .contracts_for_fee_estimate(impersonating) .clone(); - let allow_no_target = system_contracts.evm_emulator.is_some(); let mut l2_tx = L2Tx::from_request( request_with_gas_per_pubdata_overridden.into(), MAX_TX_SIZE, - allow_no_target, + self.system_contracts.allow_no_target(), ) .map_err(Web3Error::SerializationError)?; @@ -797,6 +827,7 @@ impl InMemoryNodeInner { batch_env.clone(), system_env.clone(), &self.fork_storage, + self.system_contracts.use_zkos, ); if result.statistics.pubdata_published > MAX_VM_PUBDATA_PER_BATCH.try_into().unwrap() { @@ -834,6 +865,7 @@ impl InMemoryNodeInner { batch_env.clone(), system_env.clone(), &self.fork_storage, + self.system_contracts.use_zkos, ); if estimate_gas_result.result.is_failed() { @@ -865,6 +897,7 @@ impl InMemoryNodeInner { batch_env, system_env, &self.fork_storage, + self.system_contracts.use_zkos, ); let overhead = derive_overhead( @@ -979,6 +1012,7 @@ impl InMemoryNodeInner { batch_env: L1BatchEnv, system_env: SystemEnv, fork_storage: &ForkStorage, + is_zkos: bool, ) -> VmExecutionResultAndLogs { let tx: Transaction = l2_tx.clone().into(); @@ -997,7 +1031,7 @@ impl InMemoryNodeInner { // The nonce needs to be updated let nonce = l2_tx.nonce(); - let nonce_key = get_nonce_key(&l2_tx.initiator_account()); + let nonce_key = StorageKeyLayout::get_nonce_key(is_zkos, &l2_tx.initiator_account()); let full_nonce = storage.borrow_mut().read_value(&nonce_key); let (_, deployment_nonce) = decompose_full_nonce(h256_to_u256(full_nonce)); let enforced_full_nonce = nonces_to_full_nonce(U256::from(nonce.0), deployment_nonce); @@ -1007,7 +1041,7 @@ impl InMemoryNodeInner { // We need to explicitly put enough balance into the account of the users let payer = l2_tx.payer(); - let balance_key = storage_key_for_eth_balance(&payer); + let balance_key = StorageKeyLayout::get_storage_key_for_base_token(is_zkos, &payer); let mut current_balance = h256_to_u256(storage.borrow_mut().read_value(&balance_key)); let added_balance = l2_tx.common_data.fee.gas_limit * l2_tx.common_data.fee.max_fee_per_gas; current_balance += added_balance; @@ -1015,12 +1049,26 @@ impl InMemoryNodeInner { .borrow_mut() .set_value(balance_key, u256_to_h256(current_balance)); - let mut vm: Vm<_, HistoryDisabled> = Vm::new(batch_env, system_env, storage.clone()); + let mut vm = if is_zkos { + let mut vm = ZKOsVM::<_, HistoryDisabled>::new( + batch_env, + system_env, + storage, + // TODO: this might be causing a deadlock.. check.. + &fork_storage.inner.read().unwrap().raw_storage, + ); + // Temporary hack - as we update the 'storage' just above, but zkos loads its full + // state from fork_storage (that is not updated). + vm.update_inconsistent_keys(&[&nonce_key, &balance_key]); + AnvilVM::ZKOs(vm) + } else { + AnvilVM::ZKSync(Vm::new(batch_env, system_env, storage)) + }; let tx: Transaction = l2_tx.into(); - vm.push_transaction(tx); + delegate_vm!(vm, push_transaction(tx)); - vm.execute(InspectExecutionMode::OneTx) + delegate_vm!(vm, execute(InspectExecutionMode::OneTx)) } /// Creates a [Snapshot] of the current state of the node. @@ -1268,7 +1316,10 @@ impl InMemoryNodeInner { /// Adds a lot of tokens to a given account with a specified balance. pub fn set_rich_account(&mut self, address: H160, balance: U256) { - let key = storage_key_for_eth_balance(&address); + let key = StorageKeyLayout::get_storage_key_for_base_token( + self.system_contracts.use_zkos, + &address, + ); let keys = { let mut storage_view = StorageView::new(&self.fork_storage); @@ -1288,7 +1339,7 @@ impl InMemoryNodeInner { pub struct TxExecutionOutput { result: VmExecutionResultAndLogs, call_traces: Vec, - bytecodes: HashMap>, + bytecodes: HashMap>, } /// Keeps track of a block's batch number, miniblock number and timestamp. @@ -1331,6 +1382,7 @@ impl InMemoryNodeInner { let system_contracts = SystemContracts::from_options( &config.system_contracts_options, config.use_evm_emulator, + config.use_zkos, ); let (inner, _, _, _) = InMemoryNodeInner::init( None, diff --git a/crates/core/src/node/keys.rs b/crates/core/src/node/keys.rs new file mode 100644 index 00000000..776a459b --- /dev/null +++ b/crates/core/src/node/keys.rs @@ -0,0 +1,24 @@ +use zksync_types::{Address, StorageKey}; + +pub struct StorageKeyLayout {} + +impl StorageKeyLayout { + pub fn get_nonce_key(is_zkos: bool, account: &Address) -> StorageKey { + if is_zkos { + crate::node::zkos::zkos_get_nonce_key(account) + } else { + zksync_types::get_nonce_key(account) + } + } + + pub fn get_storage_key_for_base_token(is_zkos: bool, address: &Address) -> StorageKey { + if is_zkos { + crate::node::zkos::zkos_storage_key_for_eth_balance(address) + } else { + zksync_types::utils::storage_key_for_standard_token_balance( + zksync_types::AccountTreeId::new(zksync_types::L2_BASE_TOKEN_ADDRESS), + address, + ) + } + } +} diff --git a/crates/core/src/node/mod.rs b/crates/core/src/node/mod.rs index 5201158f..d435e438 100644 --- a/crates/core/src/node/mod.rs +++ b/crates/core/src/node/mod.rs @@ -9,10 +9,13 @@ mod impersonate; mod in_memory; mod in_memory_ext; mod inner; +mod keys; mod pool; mod sealer; mod state; mod storage_logs; +mod vm; +mod zkos; mod zks; pub use self::{ diff --git a/crates/core/src/node/vm.rs b/crates/core/src/node/vm.rs new file mode 100644 index 00000000..293cf04e --- /dev/null +++ b/crates/core/src/node/vm.rs @@ -0,0 +1,18 @@ +use zksync_multivm::{interface::storage::WriteStorage, vm_latest::Vm, HistoryMode}; + +use super::zkos::ZKOsVM; + +pub enum AnvilVM { + ZKOs(ZKOsVM), + ZKSync(Vm), +} + +#[macro_export] +macro_rules! delegate_vm { + ($variable:expr, $function:ident($($params:tt)*)) => { + match &mut $variable { + AnvilVM::ZKOs(vm) => vm.$function($($params)*), + AnvilVM::ZKSync(vm) => vm.$function($($params)*), + } + }; +} diff --git a/crates/core/src/node/zkos.rs b/crates/core/src/node/zkos.rs new file mode 100644 index 00000000..6f53ce98 --- /dev/null +++ b/crates/core/src/node/zkos.rs @@ -0,0 +1,123 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + +//! Interfaces that use zkos for VM execution. +//! This is still experimental code. +use zksync_multivm::{ + interface::{ + storage::{StoragePtr, WriteStorage}, + L1BatchEnv, SystemEnv, VmExecutionResultAndLogs, VmInterface, VmInterfaceHistoryEnabled, + }, + vm_latest::TracerPointer, + HistoryMode, +}; +use zksync_types::{Address, StorageKey, Transaction}; + +use crate::deps::InMemoryStorage; + +pub fn zkos_get_nonce_key(account: &Address) -> StorageKey { + todo!() +} + +pub fn zkos_storage_key_for_eth_balance(address: &Address) -> StorageKey { + todo!(); +} + +pub struct ZKOsVM { + pub storage: StoragePtr, + + _phantom: std::marker::PhantomData, +} + +impl ZKOsVM { + pub fn new( + batch_env: L1BatchEnv, + system_env: SystemEnv, + storage: StoragePtr, + raw_storage: &InMemoryStorage, + ) -> Self { + todo!() + } + + /// If any keys are updated in storage externally, but not reflected in internal tree. + pub fn update_inconsistent_keys(&mut self, inconsistent_nodes: &[&StorageKey]) { + todo!() + } +} + +pub struct ZkOsTracerDispatcher { + _tracers: Vec, + _marker: std::marker::PhantomData, +} + +impl Default for ZkOsTracerDispatcher { + fn default() -> Self { + Self { + _tracers: Default::default(), + _marker: Default::default(), + } + } +} + +impl From>> + for ZkOsTracerDispatcher +{ + fn from(_value: Vec>) -> Self { + Self { + _tracers: Default::default(), + _marker: Default::default(), + } + } +} + +impl VmInterface for ZKOsVM { + type TracerDispatcher = ZkOsTracerDispatcher; + + fn push_transaction( + &mut self, + tx: Transaction, + ) -> zksync_multivm::interface::PushTransactionResult<'_> { + todo!() + } + + fn inspect( + &mut self, + _dispatcher: &mut Self::TracerDispatcher, + execution_mode: zksync_multivm::interface::InspectExecutionMode, + ) -> VmExecutionResultAndLogs { + todo!() + } + + fn start_new_l2_block(&mut self, _l2_block_env: zksync_multivm::interface::L2BlockEnv) { + todo!() + } + + fn inspect_transaction_with_bytecode_compression( + &mut self, + _tracer: &mut Self::TracerDispatcher, + _tx: Transaction, + _with_compression: bool, + ) -> ( + zksync_multivm::interface::BytecodeCompressionResult<'_>, + VmExecutionResultAndLogs, + ) { + todo!() + } + + fn finish_batch( + &mut self, + _pubdata_builder: std::rc::Rc, + ) -> zksync_multivm::interface::FinishedL1Batch { + todo!() + } +} + +impl VmInterfaceHistoryEnabled for ZKOsVM { + fn make_snapshot(&mut self) {} + + fn rollback_to_the_latest_snapshot(&mut self) { + panic!("Not implemented for zkos"); + } + + fn pop_snapshot_no_rollback(&mut self) {} +} diff --git a/crates/core/src/system_contracts.rs b/crates/core/src/system_contracts.rs index 3e6fb6ad..3bb9295d 100644 --- a/crates/core/src/system_contracts.rs +++ b/crates/core/src/system_contracts.rs @@ -17,19 +17,28 @@ pub struct SystemContracts { fee_estimate_contracts: BaseSystemContracts, baseline_impersonating_contracts: BaseSystemContracts, fee_estimate_impersonating_contracts: BaseSystemContracts, + use_evm_emulator: bool, + // For now, store the zkos switch flag here. + // Long term, we should probably refactor this code, and add another struct ('System') + // that would hold separate things for ZKOS and for EraVM. (but that's too early for now). + pub use_zkos: bool, } impl Default for SystemContracts { /// Creates SystemContracts that use compiled-in contracts. fn default() -> Self { - SystemContracts::from_options(&SystemContractsOptions::BuiltIn, false) + SystemContracts::from_options(&SystemContractsOptions::BuiltIn, false, false) } } impl SystemContracts { /// Creates the SystemContracts that use the complied contracts from ZKSYNC_HOME path. /// These are loaded at binary runtime. - pub fn from_options(options: &SystemContractsOptions, use_evm_emulator: bool) -> Self { + pub fn from_options( + options: &SystemContractsOptions, + use_evm_emulator: bool, + use_zkos: bool, + ) -> Self { Self { baseline_contracts: baseline_contracts(options, use_evm_emulator), playground_contracts: playground(options, use_evm_emulator), @@ -42,9 +51,17 @@ impl SystemContracts { options, use_evm_emulator, ), + use_evm_emulator, + use_zkos, } } + /// Whether it accepts the transactions that have 'null' as target. + /// This is used only when EVM emulator is enabled, or we're running in zkos mode. + pub fn allow_no_target(&self) -> bool { + self.use_zkos || self.use_evm_emulator + } + pub fn contracts_for_l2_call(&self) -> &BaseSystemContracts { self.contracts(TxExecutionMode::EthCall, false) } diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 9b3756f9..9e688b85 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -20,15 +20,6 @@ use zksync_types::{ }; use zksync_web3_decl::error::Web3Error; -pub(crate) fn bytes_to_be_words(bytes: &[u8]) -> Vec { - assert_eq!( - bytes.len() % 32, - 0, - "Bytes must be divisible by 32 to split into chunks" - ); - bytes.chunks(32).map(U256::from_big_endian).collect() -} - /// Takes long integers and returns them in human friendly format with "_". /// For example: 12_334_093 pub fn to_human_size(input: U256) -> String { @@ -50,17 +41,10 @@ pub fn to_human_size(input: U256) -> String { // TODO: Approach to encoding bytecode has changed in the core, so this function is likely no // longer needed. See `zksync_contracts::SystemContractCode` for general approach. -// -// Use of `bytecode_to_factory_dep` needs to be refactored and vendored `bytes_to_be_words` -// should be removed. -pub fn bytecode_to_factory_dep(bytecode: Vec) -> Result<(U256, Vec), anyhow::Error> { +pub fn bytecode_to_factory_dep(bytecode: Vec) -> Result<(H256, Vec), anyhow::Error> { zksync_types::bytecode::validate_bytecode(&bytecode).context("Invalid bytecode")?; let bytecode_hash = zksync_types::bytecode::BytecodeHash::for_bytecode(&bytecode).value(); - let bytecode_hash = U256::from_big_endian(bytecode_hash.as_bytes()); - - let bytecode_words = bytes_to_be_words(&bytecode); - - Ok((bytecode_hash, bytecode_words)) + Ok((bytecode_hash, bytecode)) } /// Returns the actual [U64] block number from [BlockNumber]. diff --git a/docs/rustbook/src/usage/experimental-zkos.md b/docs/rustbook/src/usage/experimental-zkos.md new file mode 100644 index 00000000..8b12262f --- /dev/null +++ b/docs/rustbook/src/usage/experimental-zkos.md @@ -0,0 +1,24 @@ +# ZKOS (experimental) + +ZKOS is the new backend for proving. The features below are still experimental and might break without warning. + + +## Usage + +``` +RUSTUP_TOOLCHAIN=nightly-2024-11-12 cargo run --features zkos -- --chain-id 31337 +``` + +Afterwards, any regular forge script should work: + +``` +forge script script/Counter.s.sol --rpc-url http://localhost:8011 --private-key 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 --broadcast --slow -g 400 +``` + +## Caveats + +There is still no gas equivalency -- that's why this `-g` option that is increasing the gas limits. + +Currently chain_id is hardcoded inside zkos to 31337, soon it will be passed as argument. + +Many things will not work yet, but basic things like deploying contracts & calling them should work. \ No newline at end of file