diff --git a/Cargo.lock b/Cargo.lock index 20b72299e3b..4708e6e7d61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -999,6 +999,17 @@ dependencies = [ "zkevm_circuits 1.4.1", ] +[[package]] +name = "circuit_encodings" +version = "0.1.50" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.5.0#05502ec874bd1dbfd8a72cd7df340a2fe3f6d3a0" +dependencies = [ + "derivative", + "serde", + "zk_evm 1.5.0", + "zkevm_circuits 1.5.0", +] + [[package]] name = "circuit_sequencer_api" version = "0.1.0" @@ -1050,6 +1061,19 @@ dependencies = [ "zk_evm 1.4.1", ] +[[package]] +name = "circuit_sequencer_api" +version = "0.1.50" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.5.0#05502ec874bd1dbfd8a72cd7df340a2fe3f6d3a0" +dependencies = [ + "bellman_ce", + "circuit_encodings 0.1.50", + "derivative", + "rayon", + "serde", + "zk_evm 1.5.0", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -1808,6 +1832,7 @@ dependencies = [ "ff 0.13.0", "generic-array", "group 0.13.0", + "pem-rfc7468", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", @@ -3669,6 +3694,7 @@ dependencies = [ "circuit_sequencer_api 0.1.40", "circuit_sequencer_api 0.1.41", "circuit_sequencer_api 0.1.42", + "circuit_sequencer_api 0.1.50", "ethabi", "hex", "itertools 0.10.5", @@ -3681,6 +3707,7 @@ dependencies = [ "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", "zk_evm 1.4.0", "zk_evm 1.4.1", + "zk_evm 1.5.0", "zksync_contracts", "zksync_eth_signer", "zksync_state", @@ -4154,6 +4181,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.7", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "packed_simd" version = "0.3.9" @@ -4511,6 +4550,15 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.7", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -7817,6 +7865,20 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zk_evm" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.5.0#06deb82ef51666b21ac5ebb543366906db04916b" +dependencies = [ + "anyhow", + "lazy_static", + "num", + "serde", + "serde_json", + "static_assertions", + "zk_evm_abstractions 1.5.0", +] + [[package]] name = "zk_evm_abstractions" version = "0.1.0" @@ -7841,6 +7903,18 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zk_evm_abstractions" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.5.0#e464b2cf2b146d883be80e7d690c752bf670ff05" +dependencies = [ + "anyhow", + "num_enum 0.6.1", + "serde", + "static_assertions", + "zkevm_opcode_defs 1.5.0", +] + [[package]] name = "zkevm_circuits" version = "1.4.0" @@ -7883,6 +7957,25 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zkevm_circuits" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.5.0#b2d0db2f08de3037aebcaa8b394981e57158bdbe" +dependencies = [ + "arrayvec 0.7.4", + "boojum", + "cs_derive", + "derivative", + "hex", + "itertools 0.10.5", + "rand 0.4.6", + "rand 0.8.5", + "seq-macro", + "serde", + "smallvec", + "zkevm_opcode_defs 1.5.0", +] + [[package]] name = "zkevm_opcode_defs" version = "1.3.1" @@ -7922,6 +8015,22 @@ dependencies = [ "sha3 0.10.8", ] +[[package]] +name = "zkevm_opcode_defs" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.5.0#dc74cf4e1167a9636e14725da09c8020a8bfa26b" +dependencies = [ + "bitflags 2.4.1", + "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types", + "k256 0.13.2", + "lazy_static", + "p256", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", +] + [[package]] name = "zksync_basic_types" version = "0.1.0" @@ -7955,9 +8064,11 @@ version = "0.1.0" dependencies = [ "circuit_sequencer_api 0.1.40", "circuit_sequencer_api 0.1.41", + "circuit_sequencer_api 0.1.50", "multivm", "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", "zk_evm 1.4.1", + "zk_evm 1.5.0", "zksync_types", "zksync_utils", ] diff --git a/Cargo.toml b/Cargo.toml index a009a3735e4..588ea8dbd11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,11 +150,13 @@ url = "2" web3 = "0.19.0" # "Internal" dependencies +# TODO: use the v1.5.0 circuit_sequencer_api once the prover is integrated circuit_sequencer_api = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.2" } circuit_sequencer_api_1_3_3 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.3.3" } circuit_sequencer_api_1_4_0 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.0" } circuit_sequencer_api_1_4_1 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.1" } circuit_sequencer_api_1_4_2 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.2" } +circuit_sequencer_api_1_5_0 = { package = "circuit_sequencer_api", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.5.0" } crypto_codegen = { package = "codegen", git = "https://github.com/matter-labs/solidity_plonk_verifier.git", branch = "dev" } kzg = { package = "kzg", git = "https://github.com/matter-labs/era-zkevm_test_harness.git", branch = "v1.4.2" } vise = { git = "https://github.com/matter-labs/vise.git", version = "0.1.0", rev = "1c9cc500e92cf9ea052b230e114a6f9cce4fb2c1" } @@ -164,6 +166,7 @@ zk_evm_1_3_1 = { package = "zk_evm", git = "https://github.com/matter-labs/era-z zk_evm_1_3_3 = { package = "zk_evm", git = "https://github.com/matter-labs/era-zk_evm.git", tag = "v1.3.3-rc2" } zk_evm_1_4_0 = { package = "zk_evm", git = "https://github.com/matter-labs/era-zk_evm.git", branch = "v1.4.0" } zk_evm_1_4_1 = { package = "zk_evm", git = "https://github.com/matter-labs/era-zk_evm.git", branch = "v1.4.1" } +zk_evm_1_5_0 = { package = "zk_evm", git = "https://github.com/matter-labs/era-zk_evm.git", branch = "v1.5.0" } zksync_concurrency = { version = "0.1.0", git = "https://github.com/matter-labs/era-consensus.git", rev = "8833a9b7fef89d1ffc5c08d53a3560164bc1c694" } zksync_consensus_bft = { version = "0.1.0", git = "https://github.com/matter-labs/era-consensus.git", rev = "8833a9b7fef89d1ffc5c08d53a3560164bc1c694" } zksync_consensus_crypto = { version = "0.1.0", git = "https://github.com/matter-labs/era-consensus.git", rev = "8833a9b7fef89d1ffc5c08d53a3560164bc1c694" } diff --git a/checks-config/era.dic b/checks-config/era.dic index b83368526be..ccd80148636 100644 --- a/checks-config/era.dic +++ b/checks-config/era.dic @@ -929,3 +929,10 @@ SIGINT opentelemetry PubdataSendingMode FriGpuProverArchiver +vm +demuxer +2k +4k +superset +80M +780kb diff --git a/contracts b/contracts index e0a33ce73c4..a1a05513c11 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit e0a33ce73c4decd381446a6eb812b14c2ff69c47 +Subproject commit a1a05513c110586f7a1d8b5fa46115cc7307587a diff --git a/core/bin/system-constants-generator/src/utils.rs b/core/bin/system-constants-generator/src/utils.rs index ee8749624d8..cf29e4e75c7 100644 --- a/core/bin/system-constants-generator/src/utils.rs +++ b/core/bin/system-constants-generator/src/utils.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use multivm::{ interface::{ - dyn_tracers::vm_1_4_1::DynTracer, tracer::VmExecutionStopReason, L1BatchEnv, L2BlockEnv, + dyn_tracers::vm_1_5_0::DynTracer, tracer::VmExecutionStopReason, L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmInterface, }, vm_latest::{ @@ -335,7 +335,7 @@ pub(super) fn execute_user_txs_in_test_gas_vm( VmSpentResourcesResult { // It is assumed that the entire `gas_used` was spent on computation and so it safe to convert to u32 gas_consumed: result.statistics.gas_used as u32, - total_gas_paid: total_gas_paid_upfront.as_u32() - total_gas_refunded as u32, + total_gas_paid: (total_gas_paid_upfront.as_u64() - total_gas_refunded) as u32, pubdata_published: metrics.size() as u32, total_pubdata_paid: 0, } diff --git a/core/lib/basic_types/src/protocol_version.rs b/core/lib/basic_types/src/protocol_version.rs index 282696ffdaa..fedec53aebc 100644 --- a/core/lib/basic_types/src/protocol_version.rs +++ b/core/lib/basic_types/src/protocol_version.rs @@ -45,15 +45,16 @@ pub enum ProtocolVersionId { Version21, Version22, Version23, + Version24, } impl ProtocolVersionId { pub fn latest() -> Self { - Self::Version22 + Self::Version23 } pub fn next() -> Self { - Self::Version23 + Self::Version24 } /// Returns VM version to be used by API for this protocol version. @@ -83,7 +84,8 @@ impl ProtocolVersionId { ProtocolVersionId::Version20 => VmVersion::Vm1_4_1, ProtocolVersionId::Version21 => VmVersion::Vm1_4_2, ProtocolVersionId::Version22 => VmVersion::Vm1_4_2, - ProtocolVersionId::Version23 => VmVersion::Vm1_4_2, + ProtocolVersionId::Version23 => VmVersion::Vm1_5_0, + ProtocolVersionId::Version24 => VmVersion::Vm1_5_0, } } @@ -131,15 +133,11 @@ impl ProtocolVersionId { } pub fn is_pre_1_5_0(&self) -> bool { - // In the current codebase all the protocol versions are pre-1.5.0. - // This method will be updated once the v1.5.0 is added to the server - true + self < &ProtocolVersionId::Version23 } pub fn is_post_1_5_0(&self) -> bool { - // In the current codebase all the protocol versions are pre-1.5.0. - // This method will be updated once the v1.5.0 is added to the server - false + self >= &ProtocolVersionId::Version23 } } @@ -225,7 +223,8 @@ impl From for VmVersion { ProtocolVersionId::Version20 => VmVersion::Vm1_4_1, ProtocolVersionId::Version21 => VmVersion::Vm1_4_2, ProtocolVersionId::Version22 => VmVersion::Vm1_4_2, - ProtocolVersionId::Version23 => VmVersion::Vm1_4_2, + ProtocolVersionId::Version23 => VmVersion::Vm1_5_0, + ProtocolVersionId::Version24 => VmVersion::Vm1_5_0, } } } diff --git a/core/lib/basic_types/src/vm_version.rs b/core/lib/basic_types/src/vm_version.rs index 5fa6c76b933..feb0d321344 100644 --- a/core/lib/basic_types/src/vm_version.rs +++ b/core/lib/basic_types/src/vm_version.rs @@ -10,11 +10,12 @@ pub enum VmVersion { VmBoojumIntegration, Vm1_4_1, Vm1_4_2, + Vm1_5_0, } impl VmVersion { /// Returns the latest supported VM version. pub const fn latest() -> VmVersion { - Self::Vm1_4_2 + Self::Vm1_5_0 } } diff --git a/core/lib/commitment_utils/Cargo.toml b/core/lib/commitment_utils/Cargo.toml index 52368c5bc65..b3d8e74b2fa 100644 --- a/core/lib/commitment_utils/Cargo.toml +++ b/core/lib/commitment_utils/Cargo.toml @@ -14,7 +14,9 @@ zksync_types.workspace = true zksync_utils.workspace = true circuit_sequencer_api_1_4_0.workspace = true circuit_sequencer_api_1_4_1.workspace = true +circuit_sequencer_api_1_5_0.workspace = true +zk_evm_1_5_0.workspace = true zk_evm_1_4_1.workspace = true zk_evm_1_3_3.workspace = true multivm.workspace = true diff --git a/core/lib/commitment_utils/src/lib.rs b/core/lib/commitment_utils/src/lib.rs index e61c93773e7..0fabf18d1df 100644 --- a/core/lib/commitment_utils/src/lib.rs +++ b/core/lib/commitment_utils/src/lib.rs @@ -8,6 +8,10 @@ use zk_evm_1_4_1::{ aux_structures::Timestamp as Timestamp_1_4_1, zk_evm_abstractions::queries::LogQuery as LogQuery_1_4_1, }; +use zk_evm_1_5_0::{ + aux_structures::Timestamp as Timestamp_1_5_0, + zk_evm_abstractions::queries::LogQuery as LogQuery_1_5_0, +}; use zksync_types::{zk_evm_types::LogQuery, ProtocolVersionId, VmVersion, H256, U256}; use zksync_utils::expand_memory_contents; @@ -32,6 +36,14 @@ pub fn events_queue_commitment( .collect(), ), )), + VmVersion::Vm1_5_0 => Some(H256( + circuit_sequencer_api_1_5_0::commitments::events_queue_commitment_fixed( + &events_queue + .iter() + .map(|x| to_log_query_1_5_0(*x)) + .collect(), + ), + )), _ => None, } } @@ -60,6 +72,11 @@ pub fn bootloader_initial_content_commitment( &full_bootloader_memory, ), )), + VmVersion::Vm1_5_0 => Some(H256( + circuit_sequencer_api_1_5_0::commitments::initial_heap_content_commitment_fixed( + &full_bootloader_memory, + ), + )), _ => unreachable!(), } } @@ -95,3 +112,19 @@ fn to_log_query_1_4_1(log_query: LogQuery) -> LogQuery_1_4_1 { is_service: log_query.is_service, } } + +fn to_log_query_1_5_0(log_query: LogQuery) -> LogQuery_1_5_0 { + LogQuery_1_5_0 { + timestamp: Timestamp_1_5_0(log_query.timestamp.0), + tx_number_in_block: log_query.tx_number_in_block, + aux_byte: log_query.aux_byte, + shard_id: log_query.shard_id, + address: log_query.address, + key: log_query.key, + read_value: log_query.read_value, + written_value: log_query.written_value, + rw_flag: log_query.rw_flag, + rollback: log_query.rollback, + is_service: log_query.is_service, + } +} diff --git a/core/lib/config/src/configs/eth_sender.rs b/core/lib/config/src/configs/eth_sender.rs index 848f467db0d..1d9669ad41f 100644 --- a/core/lib/config/src/configs/eth_sender.rs +++ b/core/lib/config/src/configs/eth_sender.rs @@ -48,6 +48,7 @@ impl ETHConfig { pricing_formula_parameter_b: 1.0005, internal_l1_pricing_multiplier: 0.8, internal_enforced_l1_gas_price: None, + internal_enforced_pubdata_price: None, poll_period: 5, max_l1_gas_price: None, num_samples_for_blob_base_fee_estimate: 10, @@ -163,6 +164,8 @@ pub struct GasAdjusterConfig { pub internal_l1_pricing_multiplier: f64, /// If equal to Some(x), then it will always provide `x` as the L1 gas price pub internal_enforced_l1_gas_price: Option, + /// If equal to Some(x), then it will always provide `x` as the pubdata price + pub internal_enforced_pubdata_price: Option, /// Node polling period in seconds pub poll_period: u64, /// Max number of l1 gas price that is allowed to be used. diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 2150c6a8777..31f2d3d1f40 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -372,6 +372,7 @@ impl Distribution for EncodeDist { pricing_formula_parameter_b: self.sample(rng), internal_l1_pricing_multiplier: self.sample(rng), internal_enforced_l1_gas_price: self.sample(rng), + internal_enforced_pubdata_price: self.sample(rng), poll_period: self.sample(rng), max_l1_gas_price: self.sample(rng), num_samples_for_blob_base_fee_estimate: self.sample(rng), diff --git a/core/lib/constants/src/contracts.rs b/core/lib/constants/src/contracts.rs index cd071252bcf..10137a774ee 100644 --- a/core/lib/constants/src/contracts.rs +++ b/core/lib/constants/src/contracts.rs @@ -110,11 +110,28 @@ pub const EC_MUL_PRECOMPILE_ADDRESS: Address = H160([ 0x00, 0x00, 0x00, 0x07, ]); +pub const P256VERIFY_PRECOMPILE_ADDRESS: Address = H160([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, +]); + +pub const CODE_ORACLE_ADDRESS: Address = H160([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x12, +]); + pub const ERC20_TRANSFER_TOPIC: H256 = H256([ 221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239, ]); +/// This contract is specifically outside of the system contract address space as it can relay any call so it breaks +/// the trust of system contracts. +pub const GAS_BOUND_CALLER_ADDRESS: Address = H160([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, +]); + // TODO (SMA-240): Research whether using zero address is ok pub const MINT_AND_BURN_ADDRESS: H160 = H160::zero(); diff --git a/core/lib/constants/src/system_context.rs b/core/lib/constants/src/system_context.rs index 6a90469fb1f..33cd5bdf0c2 100644 --- a/core/lib/constants/src/system_context.rs +++ b/core/lib/constants/src/system_context.rs @@ -23,7 +23,7 @@ pub const SYSTEM_CONTEXT_BLOCK_GAS_LIMIT_POSITION: H256 = H256([ // Some dummy value. We will put the real block gas limit later on. pub const SYSTEM_CONTEXT_BLOCK_GAS_LIMIT: H256 = H256([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]); pub const SYSTEM_CONTEXT_COINBASE_POSITION: H256 = H256([ diff --git a/core/lib/contracts/src/lib.rs b/core/lib/contracts/src/lib.rs index fe390b4cd9e..20ec434c0bf 100644 --- a/core/lib/contracts/src/lib.rs +++ b/core/lib/contracts/src/lib.rs @@ -319,6 +319,13 @@ impl BaseSystemContracts { BaseSystemContracts::load_with_bootloader(bootloader_bytecode) } + pub fn playground_post_1_5_0() -> Self { + let bootloader_bytecode = read_zbin_bytecode( + "etc/multivm_bootloaders/vm_1_5_0/playground_batch.yul/playground_batch.yul.zbin", + ); + BaseSystemContracts::load_with_bootloader(bootloader_bytecode) + } + pub fn estimate_gas_pre_virtual_blocks() -> Self { let bootloader_bytecode = read_zbin_bytecode( "etc/multivm_bootloaders/vm_1_3_2/fee_estimate.yul/fee_estimate.yul.zbin", @@ -368,6 +375,13 @@ impl BaseSystemContracts { BaseSystemContracts::load_with_bootloader(bootloader_bytecode) } + pub fn estimate_gas_post_1_5_0() -> Self { + let bootloader_bytecode = read_zbin_bytecode( + "etc/multivm_bootloaders/vm_1_5_0/fee_estimate.yul/fee_estimate.yul.zbin", + ); + BaseSystemContracts::load_with_bootloader(bootloader_bytecode) + } + pub fn hashes(&self) -> BaseSystemContractsHashes { BaseSystemContractsHashes { bootloader: self.bootloader.hash, diff --git a/core/lib/env_config/src/eth_sender.rs b/core/lib/env_config/src/eth_sender.rs index 5c825f12c90..779dda99164 100644 --- a/core/lib/env_config/src/eth_sender.rs +++ b/core/lib/env_config/src/eth_sender.rs @@ -69,6 +69,7 @@ mod tests { pricing_formula_parameter_b: 1.0005, internal_l1_pricing_multiplier: 0.8, internal_enforced_l1_gas_price: None, + internal_enforced_pubdata_price: None, poll_period: 15, max_l1_gas_price: Some(100000000), num_samples_for_blob_base_fee_estimate: 10, diff --git a/core/lib/multivm/Cargo.toml b/core/lib/multivm/Cargo.toml index b69e04afddf..670da1ed5ed 100644 --- a/core/lib/multivm/Cargo.toml +++ b/core/lib/multivm/Cargo.toml @@ -10,6 +10,7 @@ keywords.workspace = true categories.workspace = true [dependencies] +zk_evm_1_5_0.workspace = true zk_evm_1_4_1.workspace = true zk_evm_1_4_0.workspace = true zk_evm_1_3_3.workspace = true @@ -19,6 +20,7 @@ circuit_sequencer_api_1_3_3.workspace = true circuit_sequencer_api_1_4_0.workspace = true circuit_sequencer_api_1_4_1.workspace = true circuit_sequencer_api_1_4_2.workspace = true +circuit_sequencer_api_1_5_0.workspace = true zksync_types.workspace = true zksync_state.workspace = true diff --git a/core/lib/multivm/src/glue/history_mode.rs b/core/lib/multivm/src/glue/history_mode.rs index f718a8fe6b0..37539b62558 100644 --- a/core/lib/multivm/src/glue/history_mode.rs +++ b/core/lib/multivm/src/glue/history_mode.rs @@ -9,7 +9,7 @@ pub trait HistoryMode: + GlueInto + GlueInto + GlueInto - + GlueInto + + GlueInto { type VmM6Mode: crate::vm_m6::HistoryMode; type Vm1_3_2Mode: crate::vm_1_3_2::HistoryMode; @@ -18,7 +18,7 @@ pub trait HistoryMode: type VmBoojumIntegration: crate::vm_boojum_integration::HistoryMode; type Vm1_4_1: crate::vm_1_4_1::HistoryMode; type Vm1_4_2: crate::vm_1_4_2::HistoryMode; - type VmLatest: crate::vm_latest::HistoryMode; + type Vm1_5_0: crate::vm_latest::HistoryMode; } impl GlueFrom for crate::vm_m6::HistoryEnabled { @@ -115,7 +115,7 @@ impl HistoryMode for crate::vm_latest::HistoryEnabled { type VmBoojumIntegration = crate::vm_boojum_integration::HistoryEnabled; type Vm1_4_1 = crate::vm_1_4_1::HistoryEnabled; type Vm1_4_2 = crate::vm_1_4_2::HistoryEnabled; - type VmLatest = crate::vm_latest::HistoryEnabled; + type Vm1_5_0 = crate::vm_latest::HistoryEnabled; } impl HistoryMode for crate::vm_latest::HistoryDisabled { @@ -126,5 +126,5 @@ impl HistoryMode for crate::vm_latest::HistoryDisabled { type VmBoojumIntegration = crate::vm_boojum_integration::HistoryDisabled; type Vm1_4_1 = crate::vm_1_4_1::HistoryDisabled; type Vm1_4_2 = crate::vm_1_4_2::HistoryDisabled; - type VmLatest = crate::vm_latest::HistoryDisabled; + type Vm1_5_0 = crate::vm_latest::HistoryDisabled; } diff --git a/core/lib/multivm/src/glue/tracers/mod.rs b/core/lib/multivm/src/glue/tracers/mod.rs index ad56cb02bea..8a138d9461f 100644 --- a/core/lib/multivm/src/glue/tracers/mod.rs +++ b/core/lib/multivm/src/glue/tracers/mod.rs @@ -54,7 +54,7 @@ pub trait MultiVMTracer: } pub trait IntoLatestTracer { - fn latest(&self) -> crate::vm_latest::TracerPointer; + fn latest(&self) -> crate::vm_latest::TracerPointer; } pub trait IntoVmVirtualBlocksTracer { @@ -99,9 +99,9 @@ impl IntoLatestTracer for T where S: WriteStorage, H: HistoryMode, - T: crate::vm_latest::VmTracer + Clone + 'static, + T: crate::vm_latest::VmTracer + Clone + 'static, { - fn latest(&self) -> crate::vm_latest::TracerPointer { + fn latest(&self) -> crate::vm_latest::TracerPointer { Box::new(self.clone()) } } diff --git a/core/lib/multivm/src/glue/types/mod.rs b/core/lib/multivm/src/glue/types/mod.rs index 481abfdf85f..217bf5671c7 100644 --- a/core/lib/multivm/src/glue/types/mod.rs +++ b/core/lib/multivm/src/glue/types/mod.rs @@ -10,3 +10,4 @@ mod zk_evm_1_3_1; mod zk_evm_1_3_3; mod zk_evm_1_4_0; mod zk_evm_1_4_1; +mod zk_evm_1_5_0; diff --git a/core/lib/multivm/src/glue/types/zk_evm_1_5_0.rs b/core/lib/multivm/src/glue/types/zk_evm_1_5_0.rs new file mode 100644 index 00000000000..eb1c8e1dd7e --- /dev/null +++ b/core/lib/multivm/src/glue/types/zk_evm_1_5_0.rs @@ -0,0 +1,90 @@ +use zksync_utils::u256_to_h256; + +use crate::glue::{GlueFrom, GlueInto}; + +impl GlueFrom for zksync_types::zk_evm_types::Timestamp { + fn glue_from(timestamp: zk_evm_1_5_0::aux_structures::Timestamp) -> Self { + zksync_types::zk_evm_types::Timestamp(timestamp.0) + } +} + +impl GlueFrom for zksync_types::zk_evm_types::LogQuery { + fn glue_from(query: zk_evm_1_5_0::aux_structures::LogQuery) -> Self { + zksync_types::zk_evm_types::LogQuery { + address: query.address, + key: query.key, + written_value: query.written_value, + timestamp: query.timestamp.glue_into(), + shard_id: query.shard_id, + rollback: query.rollback, + tx_number_in_block: query.tx_number_in_block, + aux_byte: query.aux_byte, + read_value: query.read_value, + rw_flag: query.rw_flag, + is_service: query.is_service, + } + } +} + +impl GlueFrom for zk_evm_1_5_0::aux_structures::Timestamp { + fn glue_from(timestamp: zksync_types::zk_evm_types::Timestamp) -> Self { + zk_evm_1_5_0::aux_structures::Timestamp(timestamp.0) + } +} + +impl GlueFrom for zk_evm_1_5_0::aux_structures::LogQuery { + fn glue_from(query: zksync_types::zk_evm_types::LogQuery) -> Self { + zk_evm_1_5_0::aux_structures::LogQuery { + address: query.address, + key: query.key, + written_value: query.written_value, + timestamp: query.timestamp.glue_into(), + shard_id: query.shard_id, + rollback: query.rollback, + tx_number_in_block: query.tx_number_in_block, + aux_byte: query.aux_byte, + read_value: query.read_value, + rw_flag: query.rw_flag, + is_service: query.is_service, + } + } +} + +impl GlueFrom + for zksync_types::zk_evm_types::FarCallOpcode +{ + fn glue_from(value: zk_evm_1_5_0::zkevm_opcode_defs::FarCallOpcode) -> Self { + match value { + zk_evm_1_5_0::zkevm_opcode_defs::FarCallOpcode::Normal => Self::Normal, + zk_evm_1_5_0::zkevm_opcode_defs::FarCallOpcode::Delegate => Self::Delegate, + zk_evm_1_5_0::zkevm_opcode_defs::FarCallOpcode::Mimic => Self::Mimic, + } + } +} + +impl GlueFrom + for zk_evm_1_5_0::zkevm_opcode_defs::FarCallOpcode +{ + fn glue_from(value: zksync_types::zk_evm_types::FarCallOpcode) -> Self { + match value { + zksync_types::zk_evm_types::FarCallOpcode::Normal => Self::Normal, + zksync_types::zk_evm_types::FarCallOpcode::Delegate => Self::Delegate, + zksync_types::zk_evm_types::FarCallOpcode::Mimic => Self::Mimic, + } + } +} + +impl GlueFrom + for zksync_types::l2_to_l1_log::L2ToL1Log +{ + fn glue_from(event: zk_evm_1_5_0::reference_impls::event_sink::EventMessage) -> Self { + Self { + shard_id: event.shard_id, + is_service: event.is_first, + tx_number_in_block: event.tx_number_in_block, + sender: event.address, + key: u256_to_h256(event.key), + value: u256_to_h256(event.value), + } + } +} diff --git a/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/mod.rs b/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/mod.rs index 8a0fbbe93cd..03a38eddf68 100644 --- a/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/mod.rs +++ b/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/mod.rs @@ -1,3 +1,4 @@ pub mod vm_1_3_3; pub mod vm_1_4_0; pub mod vm_1_4_1; +pub mod vm_1_5_0; diff --git a/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/vm_1_5_0.rs b/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/vm_1_5_0.rs new file mode 100644 index 00000000000..f306190184c --- /dev/null +++ b/core/lib/multivm/src/interface/traits/tracers/dyn_tracers/vm_1_5_0.rs @@ -0,0 +1,33 @@ +use zk_evm_1_5_0::{ + abstractions::Memory, + tracing::{AfterDecodingData, AfterExecutionData, BeforeExecutionData, VmLocalStateData}, +}; +use zksync_state::StoragePtr; + +/// Version of `zk_evm_1_5_0::Tracer` suitable for dynamic dispatch. +pub trait DynTracer { + fn before_decoding(&mut self, _state: VmLocalStateData<'_>, _memory: &M) {} + fn after_decoding( + &mut self, + _state: VmLocalStateData<'_>, + _data: AfterDecodingData, + _memory: &M, + ) { + } + fn before_execution( + &mut self, + _state: VmLocalStateData<'_>, + _data: BeforeExecutionData, + _memory: &M, + _storage: StoragePtr, + ) { + } + fn after_execution( + &mut self, + _state: VmLocalStateData<'_>, + _data: AfterExecutionData, + _memory: &M, + _storage: StoragePtr, + ) { + } +} diff --git a/core/lib/multivm/src/lib.rs b/core/lib/multivm/src/lib.rs index 037f47b785f..007c69fdf7f 100644 --- a/core/lib/multivm/src/lib.rs +++ b/core/lib/multivm/src/lib.rs @@ -2,8 +2,8 @@ #![warn(unused_extern_crates)] #![warn(unused_imports)] -pub use circuit_sequencer_api_1_4_1 as circuit_sequencer_api_latest; -pub use zk_evm_1_4_1 as zk_evm_latest; +pub use circuit_sequencer_api_1_5_0 as circuit_sequencer_api_latest; +pub use zk_evm_1_5_0 as zk_evm_latest; pub use zksync_types::vm_version::VmVersion; pub use self::versions::{ diff --git a/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs b/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs index 8a1c7b725d1..c5e4e0cdded 100644 --- a/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs +++ b/core/lib/multivm/src/tracers/call_tracer/vm_latest/mod.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ tracing::{AfterExecutionData, VmLocalStateData}, zkevm_opcode_defs::{ FarCallABI, FatPointer, Opcode, RetOpcode, CALL_IMPLICIT_CALLDATA_FAT_PTR_REGISTER, @@ -16,7 +16,7 @@ use zksync_types::{ use crate::{ glue::GlueInto, interface::{ - tracer::VmExecutionStopReason, traits::tracers::dyn_tracers::vm_1_4_1::DynTracer, + tracer::VmExecutionStopReason, traits::tracers::dyn_tracers::vm_1_5_0::DynTracer, VmRevertReason, }, tracers::call_tracer::CallTracer, diff --git a/core/lib/multivm/src/tracers/multivm_dispatcher.rs b/core/lib/multivm/src/tracers/multivm_dispatcher.rs index f5801aff653..5b0d36b5e79 100644 --- a/core/lib/multivm/src/tracers/multivm_dispatcher.rs +++ b/core/lib/multivm/src/tracers/multivm_dispatcher.rs @@ -30,7 +30,7 @@ impl Default for TracerDispatcher { } impl From> - for crate::vm_latest::TracerDispatcher + for crate::vm_latest::TracerDispatcher { fn from(value: TracerDispatcher) -> Self { Self::new(value.tracers.into_iter().map(|x| x.latest()).collect()) diff --git a/core/lib/multivm/src/tracers/prestate_tracer/vm_latest/mod.rs b/core/lib/multivm/src/tracers/prestate_tracer/vm_latest/mod.rs index ceb11005456..c93974085fe 100644 --- a/core/lib/multivm/src/tracers/prestate_tracer/vm_latest/mod.rs +++ b/core/lib/multivm/src/tracers/prestate_tracer/vm_latest/mod.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::tracing::{BeforeExecutionData, VmLocalStateData}; +use zk_evm_1_5_0::tracing::{BeforeExecutionData, VmLocalStateData}; use zksync_state::{StoragePtr, WriteStorage}; use zksync_types::StorageKey; @@ -7,7 +7,7 @@ use super::{ StorageAccess, }; use crate::{ - interface::dyn_tracers::vm_1_4_1::DynTracer, + interface::dyn_tracers::vm_1_5_0::DynTracer, tracers::prestate_tracer::U256, vm_latest::{BootloaderState, HistoryMode, SimpleMemory, VmTracer, ZkSyncVmState}, }; @@ -41,8 +41,8 @@ impl VmTracer for PrestateTracer { .map(|k| get_account_data(k.0, state, &modified_storage_keys)) .collect::(); } else { - let read_keys = &state.storage.read_keys; - let map = read_keys.inner().clone(); + let read_storage_keys = &state.storage.read_storage_keys; + let map = read_storage_keys.inner().clone(); let res = map .iter() .map(|k| get_account_data(k.0, state, &modified_storage_keys)) diff --git a/core/lib/multivm/src/tracers/storage_invocation/vm_latest/mod.rs b/core/lib/multivm/src/tracers/storage_invocation/vm_latest/mod.rs index 3fa04401782..48802a27b49 100644 --- a/core/lib/multivm/src/tracers/storage_invocation/vm_latest/mod.rs +++ b/core/lib/multivm/src/tracers/storage_invocation/vm_latest/mod.rs @@ -3,7 +3,7 @@ use zksync_state::WriteStorage; use crate::{ interface::{ tracer::{TracerExecutionStatus, TracerExecutionStopReason}, - traits::tracers::dyn_tracers::vm_1_4_1::DynTracer, + traits::tracers::dyn_tracers::vm_1_5_0::DynTracer, Halt, }, tracers::storage_invocation::StorageInvocations, diff --git a/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs b/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs index cca90179427..072aa9de00b 100644 --- a/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs +++ b/core/lib/multivm/src/tracers/validator/vm_latest/mod.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ tracing::{BeforeExecutionData, VmLocalStateData}, zkevm_opcode_defs::{ContextOpcode, FarCallABI, LogOpcode, Opcode}, }; @@ -11,7 +11,7 @@ use zksync_utils::{h256_to_account_address, u256_to_account_address, u256_to_h25 use crate::{ interface::{ - traits::tracers::dyn_tracers::vm_1_4_1::DynTracer, + traits::tracers::dyn_tracers::vm_1_5_0::DynTracer, types::tracer::{TracerExecutionStatus, TracerExecutionStopReason}, Halt, }, @@ -20,9 +20,7 @@ use crate::{ ValidationRoundResult, ValidationTracer, }, vm_latest::{ - tracers::utils::{ - computational_gas_price, get_calldata_page_via_abi, print_debug_if_needed, VmHook, - }, + tracers::utils::{computational_gas_price, get_calldata_page_via_abi, VmHook}, BootloaderState, SimpleMemory, VmTracer, ZkSyncVmState, }, HistoryMode, @@ -33,7 +31,7 @@ impl ValidationTracer { &mut self, state: VmLocalStateData<'_>, data: BeforeExecutionData, - memory: &SimpleMemory, + memory: &SimpleMemory, storage: StoragePtr, ) -> ValidationRoundResult { if self.computational_gas_used > self.computational_gas_limit { @@ -127,14 +125,14 @@ impl ValidationTracer { } } -impl DynTracer> +impl DynTracer> for ValidationTracer { fn before_execution( &mut self, state: VmLocalStateData<'_>, data: BeforeExecutionData, - memory: &SimpleMemory, + memory: &SimpleMemory, storage: StoragePtr, ) { // For now, we support only validations for users. @@ -149,8 +147,6 @@ impl DynTracer> } let hook = VmHook::from_opcode_memory(&state, &data); - print_debug_if_needed(&hook, &state, memory); - let current_mode = self.validation_mode; match (current_mode, hook) { (ValidationTracerMode::NoValidation, VmHook::AccountValidationEntered) => { @@ -182,10 +178,10 @@ impl DynTracer> } } -impl VmTracer for ValidationTracer { +impl VmTracer for ValidationTracer { fn finish_cycle( &mut self, - _state: &mut ZkSyncVmState, + _state: &mut ZkSyncVmState, _bootloader_state: &mut BootloaderState, ) -> TracerExecutionStatus { if self.should_stop_execution { diff --git a/core/lib/multivm/src/utils.rs b/core/lib/multivm/src/utils.rs index 9dbb2389371..1c8330cf2ae 100644 --- a/core/lib/multivm/src/utils.rs +++ b/core/lib/multivm/src/utils.rs @@ -47,6 +47,9 @@ pub fn derive_base_fee_and_gas_per_pubdata( VmVersion::Vm1_4_2 => crate::vm_1_4_2::utils::fee::derive_base_fee_and_gas_per_pubdata( batch_fee_input.into_pubdata_independent(), ), + VmVersion::Vm1_5_0 => crate::vm_latest::utils::fee::derive_base_fee_and_gas_per_pubdata( + batch_fee_input.into_pubdata_independent(), + ), } } @@ -70,6 +73,7 @@ pub fn get_batch_base_fee(l1_batch_env: &L1BatchEnv, vm_version: VmVersion) -> u } VmVersion::Vm1_4_1 => crate::vm_1_4_1::utils::fee::get_batch_base_fee(l1_batch_env), VmVersion::Vm1_4_2 => crate::vm_1_4_2::utils::fee::get_batch_base_fee(l1_batch_env), + VmVersion::Vm1_5_0 => crate::vm_latest::utils::fee::get_batch_base_fee(l1_batch_env), } } @@ -195,6 +199,7 @@ pub fn derive_overhead( } VmVersion::Vm1_4_1 => crate::vm_1_4_1::utils::overhead::derive_overhead(encoded_len), VmVersion::Vm1_4_2 => crate::vm_1_4_2::utils::overhead::derive_overhead(encoded_len), + VmVersion::Vm1_5_0 => crate::vm_latest::utils::overhead::derive_overhead(encoded_len), } } @@ -218,6 +223,7 @@ pub fn get_bootloader_encoding_space(version: VmVersion) -> u32 { } VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::BOOTLOADER_TX_ENCODING_SPACE, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::BOOTLOADER_TX_ENCODING_SPACE, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::BOOTLOADER_TX_ENCODING_SPACE, } } @@ -237,6 +243,7 @@ pub fn get_bootloader_max_txs_in_batch(version: VmVersion) -> usize { VmVersion::VmBoojumIntegration => crate::vm_boojum_integration::constants::MAX_TXS_IN_BLOCK, VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::MAX_TXS_IN_BATCH, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::MAX_TXS_IN_BATCH, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::MAX_TXS_IN_BATCH, } } @@ -257,6 +264,7 @@ pub fn gas_bootloader_batch_tip_overhead(version: VmVersion) -> u32 { } VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::BOOTLOADER_BATCH_TIP_OVERHEAD, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::BOOTLOADER_BATCH_TIP_OVERHEAD, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::BOOTLOADER_BATCH_TIP_OVERHEAD, } } @@ -277,6 +285,9 @@ pub fn circuit_statistics_bootloader_batch_tip_overhead(version: VmVersion) -> u VmVersion::Vm1_4_2 => { crate::vm_1_4_2::constants::BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD as usize } + VmVersion::Vm1_5_0 => { + crate::vm_latest::constants::BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD as usize + } } } @@ -297,6 +308,9 @@ pub fn execution_metrics_bootloader_batch_tip_overhead(version: VmVersion) -> us VmVersion::Vm1_4_2 => { crate::vm_1_4_2::constants::BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD as usize } + VmVersion::Vm1_5_0 => { + crate::vm_latest::constants::BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD as usize + } } } @@ -318,6 +332,7 @@ pub fn get_max_gas_per_pubdata_byte(version: VmVersion) -> u64 { } VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::MAX_GAS_PER_PUBDATA_BYTE, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::MAX_GAS_PER_PUBDATA_BYTE, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::MAX_GAS_PER_PUBDATA_BYTE, } } @@ -341,6 +356,7 @@ pub fn get_used_bootloader_memory_bytes(version: VmVersion) -> usize { } VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::USED_BOOTLOADER_MEMORY_BYTES, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::USED_BOOTLOADER_MEMORY_BYTES, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::USED_BOOTLOADER_MEMORY_BYTES, } } @@ -364,6 +380,7 @@ pub fn get_used_bootloader_memory_words(version: VmVersion) -> usize { } VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::USED_BOOTLOADER_MEMORY_WORDS, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::USED_BOOTLOADER_MEMORY_WORDS, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::USED_BOOTLOADER_MEMORY_WORDS, } } @@ -385,6 +402,7 @@ pub fn get_max_batch_gas_limit(version: VmVersion) -> u64 { } VmVersion::Vm1_4_1 => crate::vm_1_4_1::constants::BLOCK_GAS_LIMIT as u64, VmVersion::Vm1_4_2 => crate::vm_1_4_2::constants::BLOCK_GAS_LIMIT as u64, + VmVersion::Vm1_5_0 => crate::vm_latest::constants::BATCH_GAS_LIMIT, } } @@ -405,5 +423,6 @@ pub fn get_max_batch_base_layer_circuits(version: VmVersion) -> usize { // We avoid providing `0` for the old versions to avoid potential errors when working with old versions. crate::vm_1_4_2::constants::MAX_BASE_LAYER_CIRCUITS } + VmVersion::Vm1_5_0 => crate::vm_latest::constants::MAX_BASE_LAYER_CIRCUITS, } } diff --git a/core/lib/multivm/src/versions/vm_1_4_1/tracers/circuits_capacity.rs b/core/lib/multivm/src/versions/vm_1_4_1/tracers/circuits_capacity.rs index 0c5d93a7c6a..b93eb88a21b 100644 --- a/core/lib/multivm/src/versions/vm_1_4_1/tracers/circuits_capacity.rs +++ b/core/lib/multivm/src/versions/vm_1_4_1/tracers/circuits_capacity.rs @@ -64,5 +64,6 @@ pub(crate) fn circuit_statistic_from_cycles(cycles: CircuitCycleStatistic) -> Ci / GEOMETRY_CONFIG.cycles_per_ecrecover_circuit as f32, sha256: cycles.sha256_cycles as f32 / GEOMETRY_CONFIG.cycles_per_sha256_circuit as f32, secp256k1_verify: 0.0, + transient_storage_checker: 0.0, } } diff --git a/core/lib/multivm/src/versions/vm_1_4_2/mod.rs b/core/lib/multivm/src/versions/vm_1_4_2/mod.rs index c3df28f6c31..c23fc7076b2 100644 --- a/core/lib/multivm/src/versions/vm_1_4_2/mod.rs +++ b/core/lib/multivm/src/versions/vm_1_4_2/mod.rs @@ -28,8 +28,10 @@ pub mod constants; mod implementation; mod old_vm; mod oracles; -#[cfg(test)] -mod tests; +// ``` +// #[cfg(test)] +// mod tests; +// ``` pub(crate) mod tracers; mod types; pub mod utils; diff --git a/core/lib/multivm/src/versions/vm_1_4_2/tracers/circuits_capacity.rs b/core/lib/multivm/src/versions/vm_1_4_2/tracers/circuits_capacity.rs index dc5d0caa77c..8cabd911cc6 100644 --- a/core/lib/multivm/src/versions/vm_1_4_2/tracers/circuits_capacity.rs +++ b/core/lib/multivm/src/versions/vm_1_4_2/tracers/circuits_capacity.rs @@ -65,5 +65,6 @@ pub(crate) fn circuit_statistic_from_cycles(cycles: CircuitCycleStatistic) -> Ci / GEOMETRY_CONFIG.cycles_per_ecrecover_circuit as f32, sha256: cycles.sha256_cycles as f32 / GEOMETRY_CONFIG.cycles_per_sha256_circuit as f32, secp256k1_verify: 0.0, + transient_storage_checker: 0.0, } } diff --git a/core/lib/multivm/src/versions/vm_1_4_2/tracers/pubdata_tracer.rs b/core/lib/multivm/src/versions/vm_1_4_2/tracers/pubdata_tracer.rs index 20d4da00bd3..579213be248 100644 --- a/core/lib/multivm/src/versions/vm_1_4_2/tracers/pubdata_tracer.rs +++ b/core/lib/multivm/src/versions/vm_1_4_2/tracers/pubdata_tracer.rs @@ -59,7 +59,9 @@ impl PubdataTracer { // Creates the pubdata tracer with constant state diffs. // To be used in tests only. + // While this function is never used, we keep it to keep the old version of the code intact. #[cfg(test)] + #[allow(dead_code)] pub(crate) fn new_with_forced_state_diffs( l1_batch_env: L1BatchEnv, execution_mode: VmExecutionMode, diff --git a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs index b0ddcf821d8..fedbfd47c8e 100644 --- a/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs +++ b/core/lib/multivm/src/versions/vm_boojum_integration/tracers/circuits_capacity.rs @@ -64,5 +64,6 @@ pub(crate) fn circuit_statistic_from_cycles(cycles: CircuitCycleStatistic) -> Ci / GEOMETRY_CONFIG.cycles_per_ecrecover_circuit as f32, sha256: cycles.sha256_cycles as f32 / GEOMETRY_CONFIG.cycles_per_sha256_circuit as f32, secp256k1_verify: 0.0, + transient_storage_checker: 0.0, } } diff --git a/core/lib/multivm/src/versions/vm_latest/constants.rs b/core/lib/multivm/src/versions/vm_latest/constants.rs index 1314125a4c5..df4f651e5ec 100644 --- a/core/lib/multivm/src/versions/vm_latest/constants.rs +++ b/core/lib/multivm/src/versions/vm_latest/constants.rs @@ -1,6 +1,6 @@ -use circuit_sequencer_api_1_4_2::{BLOB_CHUNK_SIZE, ELEMENTS_PER_4844_BLOCK}; -use zk_evm_1_4_1::aux_structures::MemoryPage; -pub use zk_evm_1_4_1::zkevm_opcode_defs::system_params::{ +use circuit_sequencer_api_1_5_0::{BLOB_CHUNK_SIZE, ELEMENTS_PER_4844_BLOCK}; +use zk_evm_1_5_0::aux_structures::MemoryPage; +pub use zk_evm_1_5_0::zkevm_opcode_defs::system_params::{ ERGS_PER_CIRCUIT, INITIAL_STORAGE_WRITE_PUBDATA_BYTES, }; use zksync_system_constants::{MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS}; @@ -8,36 +8,26 @@ use zksync_system_constants::{MAX_L2_TX_GAS_LIMIT, MAX_NEW_FACTORY_DEPS}; use crate::vm_latest::old_vm::utils::heap_page_from_base; /// The amount of ergs to be reserved at the end of the batch to ensure that it has enough ergs to verify compression, etc. -// TODO(EVM-513): remove allowing the dead code -#[allow(dead_code)] -pub(crate) const BOOTLOADER_BATCH_TIP_OVERHEAD: u32 = 170_000_000; +pub(crate) const BOOTLOADER_BATCH_TIP_OVERHEAD: u32 = 400_000_000; -// TODO(EVM-513): remove allowing the dead code -#[allow(dead_code)] -pub(crate) const BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD: u32 = 5000; -// TODO(EVM-513): remove allowing the dead code -#[allow(dead_code)] -pub(crate) const BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD: u32 = 1500; +pub(crate) const BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD: u32 = 12_000; +pub(crate) const BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD: u32 = 2000; + +/// In the version `1.5.0` the maximal number of circuits per batch has been increased from `24100` to `34100`. +pub(crate) const MAX_BASE_LAYER_CIRCUITS: usize = 34100; /// The size of the bootloader memory in bytes which is used by the protocol. /// While the maximal possible size is a lot higher, we restrict ourselves to a certain limit to reduce /// the requirements on RAM. -/// In this version of the VM the used bootloader memory bytes has increased from `24_000_000` to `30_000_000`. -pub(crate) const USED_BOOTLOADER_MEMORY_BYTES: usize = 30_000_000; -// TODO(EVM-513): remove allowing the dead code -#[allow(dead_code)] +/// In this version of the VM the used bootloader memory bytes has increased from `30_000_000` to `59_000_000`. +pub(crate) const USED_BOOTLOADER_MEMORY_BYTES: usize = 59_000_000; pub(crate) const USED_BOOTLOADER_MEMORY_WORDS: usize = USED_BOOTLOADER_MEMORY_BYTES / 32; -// This the number of pubdata such that it should be always possible to publish -// from a single transaction. Note, that these pubdata bytes include only bytes that are -// to be published inside the body of transaction (i.e. excluding of factory deps). -pub(crate) const GUARANTEED_PUBDATA_PER_L1_BATCH: u64 = 2500; - -// The users should always be able to provide `MAX_GAS_PER_PUBDATA_BYTE` gas per pubdata in their -// transactions so that they are able to send at least `GUARANTEED_PUBDATA_PER_L1_BATCH` bytes per -// transaction. -pub(crate) const MAX_GAS_PER_PUBDATA_BYTE: u64 = - MAX_L2_TX_GAS_LIMIT / GUARANTEED_PUBDATA_PER_L1_BATCH; +/// We want `MAX_GAS_PER_PUBDATA_BYTE` multiplied by the u32::MAX (i.e. the maximal possible value of the pubdata counter) +/// to be a safe integer with a good enough margin. +/// Note, that in the version `1.5.0` we've only increased the max gas per pubdata byte to 50k for backward compatibility with the +/// old SDK, where the default signed value is 50k. +pub(crate) const MAX_GAS_PER_PUBDATA_BYTE: u64 = 50_000; // The maximal number of transactions in a single batch. // In this version of the VM the limit has been increased from `1024` to to `10000`. @@ -81,7 +71,7 @@ pub(crate) const TX_OVERHEAD_SLOTS: usize = MAX_TXS_IN_BATCH; pub(crate) const TX_TRUSTED_GAS_LIMIT_OFFSET: usize = TX_OVERHEAD_OFFSET + TX_OVERHEAD_SLOTS; pub(crate) const TX_TRUSTED_GAS_LIMIT_SLOTS: usize = MAX_TXS_IN_BATCH; -pub(crate) const COMPRESSED_BYTECODES_SLOTS: usize = 65536; +pub(crate) const COMPRESSED_BYTECODES_SLOTS: usize = 196608; pub(crate) const PRIORITY_TXS_L1_DATA_OFFSET: usize = COMPRESSED_BYTECODES_OFFSET + COMPRESSED_BYTECODES_SLOTS; @@ -90,22 +80,20 @@ pub(crate) const PRIORITY_TXS_L1_DATA_SLOTS: usize = 2; pub const OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_OFFSET: usize = PRIORITY_TXS_L1_DATA_OFFSET + PRIORITY_TXS_L1_DATA_SLOTS; -/// One of "worst case" scenarios for the number of state diffs in a batch is when 260 kb of pubdata is spent -/// on repeated writes, that are all zeroed out. In this case, the number of diffs is 260 kb / 5 = 52k. This means that they will have -/// accommodate 14144000 bytes of calldata for the uncompressed state diffs. Adding 260k on top leaves us with -/// roughly 14404000 bytes needed for calldata. 450125 slots are needed to accommodate this amount of data. -/// We round up to 451000 slots just in case. +/// One of "worst case" scenarios for the number of state diffs in a batch is when 780kb of pubdata is spent +/// on repeated writes, that are all zeroed out. In this case, the number of diffs is `780kb / 5 = 156k`. This means that they will have +/// accommodate 42432000 bytes of calldata for the uncompressed state diffs. Adding 780kb on top leaves us with +/// roughly 43212000 bytes needed for calldata. +/// 1350375 slots are needed to accommodate this amount of data. We round up to 1360000 slots just in case. /// /// In theory though much more calldata could be used (if for instance 1 byte is used for enum index). It is the responsibility of the /// operator to ensure that it can form the correct calldata for the L1Messenger. -pub(crate) const OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS: usize = 451000; +pub(crate) const OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS: usize = 1360000; pub(crate) const BOOTLOADER_TX_DESCRIPTION_OFFSET: usize = OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_OFFSET + OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS; /// The size of the bootloader memory dedicated to the encodings of transactions -// TODO(EVM-513): remove allowing the dead code -#[allow(dead_code)] pub(crate) const BOOTLOADER_TX_ENCODING_SPACE: u32 = (USED_BOOTLOADER_MEMORY_WORDS - TX_DESCRIPTION_OFFSET - MAX_TXS_IN_BATCH) as u32; @@ -127,9 +115,9 @@ pub const BOOTLOADER_HEAP_PAGE: u32 = heap_page_from_base(MemoryPage(INITIAL_BAS /// The 'type' / 'opcode' is put into VM_HOOK_POSITION slot, /// and VM_HOOKS_PARAMS_COUNT parameters (each 32 bytes) are put in the slots before. /// So the layout looks like this: -/// `[param 0][param 1][vmhook opcode]` +/// `[param 0][param 1][param 2][vmhook opcode]` pub const VM_HOOK_POSITION: u32 = RESULT_SUCCESS_FIRST_SLOT - 1; -pub const VM_HOOK_PARAMS_COUNT: u32 = 2; +pub const VM_HOOK_PARAMS_COUNT: u32 = 3; pub const VM_HOOK_PARAMS_START_POSITION: u32 = VM_HOOK_POSITION - VM_HOOK_PARAMS_COUNT; /// Arbitrary space in memory closer to the end of the page @@ -139,8 +127,9 @@ pub const RESULT_SUCCESS_FIRST_SLOT: u32 = /// How many gas bootloader is allowed to spend within one block. /// Note that this value doesn't correspond to the gas limit of any particular transaction /// (except for the fact that, of course, gas limit for each transaction should be <= `BLOCK_GAS_LIMIT`). + pub const BATCH_COMPUTATIONAL_GAS_LIMIT: u32 = - zk_evm_1_4_1::zkevm_opcode_defs::system_params::VM_INITIAL_FRAME_ERGS; + zk_evm_1_5_0::zkevm_opcode_defs::system_params::VM_INITIAL_FRAME_ERGS; /// The maximal number of gas that is supposed to be spent in a batch. This value is displayed in the system context as well /// as the API for each batch. @@ -164,17 +153,15 @@ pub(crate) const TX_OPERATOR_L2_BLOCK_INFO_SLOTS: usize = pub(crate) const COMPRESSED_BYTECODES_OFFSET: usize = TX_OPERATOR_L2_BLOCK_INFO_OFFSET + TX_OPERATOR_L2_BLOCK_INFO_SLOTS; -/// The maximal gas limit that gets passed into an L1->L2 transaction. +/// The maximal gas limit that gets passed as compute for an L2 transaction. This is also the maximal limit allowed +/// for L1->L2 transactions. /// /// It is equal to the `MAX_GAS_PER_TRANSACTION` in the bootloader. -/// We need to limit the number of gas that can be passed into the L1->L2 transaction due to the fact +/// For L2 transaction we need to limit the amount of gas that can be spent on compute while the rest can only be spent on pubdata. +/// For L1->L2 transactions We need to limit the number of gas that can be passed into the L1->L2 transaction due to the fact /// that unlike L2 transactions they can not be rejected by the operator and must be executed. In turn, /// this means that if a transaction spends more than `MAX_GAS_PER_TRANSACTION`, it use up all the limited resources of a batch. -/// -/// It the gas limit cap on Mailbox for a priority transactions should generally be low enough to never cross that boundary, since -/// artificially limiting the gas price is bad UX. However, during the transition between the pre-1.4.1 fee model and the 1.4.1 one, -/// we need to process such transactions somehow. -pub(crate) const PRIORITY_TX_MAX_GAS_LIMIT: usize = 80_000_000; +pub(crate) const TX_MAX_COMPUTE_GAS_LIMIT: usize = 80_000_000; /// The amount of gas to be charged for occupying a single slot of a transaction. /// It is roughly equal to `80kk/MAX_TRANSACTIONS_PER_BATCH`, i.e. how many gas would an L1->L2 transaction @@ -193,5 +180,5 @@ pub(crate) const TX_SLOT_OVERHEAD_GAS: u32 = 10_000; pub(crate) const TX_MEMORY_OVERHEAD_GAS: u32 = 10; const ZK_SYNC_BYTES_PER_BLOB: usize = BLOB_CHUNK_SIZE * ELEMENTS_PER_4844_BLOCK; -pub const MAX_BLOBS_PER_BATCH: usize = 2; +pub const MAX_BLOBS_PER_BATCH: usize = 6; pub const MAX_VM_PUBDATA_PER_BATCH: usize = MAX_BLOBS_PER_BATCH * ZK_SYNC_BYTES_PER_BLOB; diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs index 1608b0b506b..f6bdc840a75 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/execution.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_state::WriteStorage; use crate::{ @@ -20,7 +20,7 @@ use crate::{ impl Vm { pub(crate) fn inspect_inner( &mut self, - dispatcher: TracerDispatcher, + dispatcher: TracerDispatcher, execution_mode: VmExecutionMode, custom_pubdata_tracer: Option>, ) -> VmExecutionResultAndLogs { @@ -44,14 +44,14 @@ impl Vm { /// Collect the result from the default tracers. fn inspect_and_collect_results( &mut self, - dispatcher: TracerDispatcher, + dispatcher: TracerDispatcher, execution_mode: VmExecutionMode, with_refund_tracer: bool, custom_pubdata_tracer: Option>, ) -> (VmExecutionStopReason, VmExecutionResultAndLogs) { let refund_tracers = with_refund_tracer.then_some(RefundsTracer::new(self.batch_env.clone())); - let mut tx_tracer: DefaultExecutionTracer = DefaultExecutionTracer::new( + let mut tx_tracer: DefaultExecutionTracer = DefaultExecutionTracer::new( self.system_env.default_validation_computational_gas_limit, execution_mode, dispatcher, @@ -64,7 +64,6 @@ impl Vm { let timestamp_initial = Timestamp(self.state.local_state.timestamp); let cycles_initial = self.state.local_state.monotonic_cycle_counter; let gas_remaining_before = self.gas_remaining(); - let spent_pubdata_counter_before = self.state.local_state.spent_pubdata_counter; let stop_reason = self.execute_with_default_tracer(&mut tx_tracer); @@ -81,10 +80,8 @@ impl Vm { let statistics = self.get_statistics( timestamp_initial, cycles_initial, - &tx_tracer, gas_remaining_before, gas_remaining_after, - spent_pubdata_counter_before, pubdata_published, logs.total_log_queries_count, circuit_statistic_from_cycles(tx_tracer.circuits_tracer.statistics), @@ -104,7 +101,7 @@ impl Vm { /// Execute vm with given tracers until the stop reason is reached. fn execute_with_default_tracer( &mut self, - tracer: &mut DefaultExecutionTracer, + tracer: &mut DefaultExecutionTracer, ) -> VmExecutionStopReason { tracer.initialize_tracer(&mut self.state); let result = loop { diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/gas.rs b/core/lib/multivm/src/versions/vm_latest/implementation/gas.rs index 4025087518c..40cedb60a39 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/gas.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/gas.rs @@ -1,32 +1,13 @@ use zksync_state::WriteStorage; -use crate::{ - interface::VmInterface, - vm_latest::{tracers::DefaultExecutionTracer, vm::Vm}, - HistoryMode, -}; +use crate::{interface::VmInterface, vm_latest::vm::Vm, HistoryMode}; impl Vm { - pub(crate) fn calculate_computational_gas_used( - &self, - tracer: &DefaultExecutionTracer, - gas_remaining_before: u32, - spent_pubdata_counter_before: u32, - ) -> u32 { - let total_gas_used = gas_remaining_before + pub(crate) fn calculate_computational_gas_used(&self, gas_remaining_before: u32) -> u32 { + // Starting from VM version 1.5.0 pubdata was implicitly charged from users' gasLimit instead of + // explicitly reduced from the `gas` in the VM state + gas_remaining_before .checked_sub(self.gas_remaining()) - .expect("underflow"); - let gas_used_on_pubdata = - tracer.gas_spent_on_pubdata(&self.state.local_state) - spent_pubdata_counter_before; - total_gas_used - .checked_sub(gas_used_on_pubdata) - .unwrap_or_else(|| { - tracing::error!( - "Gas used on pubdata is greater than total gas used. On pubdata: {}, total: {}", - gas_used_on_pubdata, - total_gas_used - ); - 0 - }) + .expect("underflow") } } diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/logs.rs b/core/lib/multivm/src/versions/vm_latest/implementation/logs.rs index 99fa413938a..5ec6ef062b8 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/logs.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/logs.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_state::WriteStorage; use zksync_types::{ event::extract_l2tol1logs_from_l1_messenger, diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs b/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs index 6f27c031d09..d226e3af572 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/snapshots.rs @@ -1,7 +1,7 @@ use std::time::Duration; use vise::{Buckets, EncodeLabelSet, EncodeLabelValue, Family, Histogram, Metrics}; -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_state::WriteStorage; use crate::vm_latest::{ diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs b/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs index ba20ab46c56..7fbd6fb2be0 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/statistics.rs @@ -1,10 +1,10 @@ -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_state::WriteStorage; use zksync_types::{circuit::CircuitStatistic, U256}; use crate::{ interface::{VmExecutionStatistics, VmMemoryMetrics}, - vm_latest::{tracers::DefaultExecutionTracer, vm::Vm}, + vm_latest::vm::Vm, HistoryMode, }; @@ -18,19 +18,13 @@ impl Vm { &self, timestamp_initial: Timestamp, cycles_initial: u32, - tracer: &DefaultExecutionTracer, gas_remaining_before: u32, gas_remaining_after: u32, - spent_pubdata_counter_before: u32, pubdata_published: u32, total_log_queries_count: usize, circuit_statistic: CircuitStatistic, ) -> VmExecutionStatistics { - let computational_gas_used = self.calculate_computational_gas_used( - tracer, - gas_remaining_before, - spent_pubdata_counter_before, - ); + let computational_gas_used = self.calculate_computational_gas_used(gas_remaining_before); VmExecutionStatistics { contracts_used: self .state diff --git a/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs b/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs index 31b2cce33f1..bcfc7293491 100644 --- a/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs +++ b/core/lib/multivm/src/versions/vm_latest/implementation/tx.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_state::WriteStorage; use zksync_types::{l1::is_l1_tx_type, Transaction}; diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/event_sink.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/event_sink.rs index 7e3097aaeb4..e0569f3586d 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/event_sink.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/event_sink.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use itertools::Itertools; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ abstractions::EventSink, aux_structures::{LogQuery, Timestamp}, reference_impls::event_sink::EventMessage, diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/events.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/events.rs index fc97b6f4a41..14fcb870284 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/events.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/events.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::{ethereum_types::Address, reference_impls::event_sink::EventMessage}; +use zk_evm_1_5_0::{ethereum_types::Address, reference_impls::event_sink::EventMessage}; use zksync_types::{L1BatchNumber, VmEvent, EVENT_WRITER_ADDRESS, H256}; use zksync_utils::{be_chunks_to_h256_words, h256_to_account_address}; diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs index a8e7d46bba5..5891b3322bf 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/history_recorder.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash}; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ aux_structures::Timestamp, vm_state::PrimitiveValue, zkevm_opcode_defs::{self}, @@ -329,10 +329,6 @@ impl HistoryRecorder Option { - self.apply_historic_record(HashMapHistoryEvent { key, value: None }, timestamp) - } } /// A stack of stacks. The inner stacks are called frames. @@ -777,9 +773,69 @@ impl HistoryRecorder, H> { } } +#[derive(Debug, Default)] +pub struct TransientStorageWrapper { + inner: HashMap, +} + +impl WithHistory for TransientStorageWrapper { + type HistoryRecord = StorageHistoryRecord; + type ReturnValue = U256; + + fn apply_historic_record( + &mut self, + item: Self::HistoryRecord, + ) -> (Self::HistoryRecord, Self::ReturnValue) { + let prev_value = self + .inner + .insert(item.key, item.value) + .unwrap_or(U256::zero()); + + let reverse_item = StorageHistoryRecord { + key: item.key, + value: prev_value, + }; + + (reverse_item, prev_value) + } +} + +impl HistoryRecorder { + pub(crate) fn read_from_transient_storage(&self, key: &StorageKey) -> U256 { + self.inner.inner.get(key).copied().unwrap_or_default() + } + + pub(crate) fn write_to_storage( + &mut self, + key: StorageKey, + value: U256, + timestamp: Timestamp, + ) -> U256 { + self.apply_historic_record(StorageHistoryRecord { key, value }, timestamp) + } + + /// Performs zeroing out the storage, while maintaining history about it, + /// making it reversible. + /// + /// Note that while the history is preserved, the inner parts are fully cleared out. + /// TODO(X): potentially optimize this function by allowing rollbacks only at the bounds of transactions. + pub(crate) fn zero_out(&mut self, timestamp: Timestamp) { + let keys = self + .inner + .inner + .drain() + .map(|(key, _)| key) + .collect::>(); + + for key in keys { + self.write_to_storage(key, U256::zero(), timestamp); + } + } +} + #[cfg(test)] mod tests { - use zk_evm_1_4_1::{aux_structures::Timestamp, vm_state::PrimitiveValue}; + use zk_evm_1_5_0::{aux_structures::Timestamp, vm_state::PrimitiveValue}; use zksync_types::U256; use crate::vm_latest::{ diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/memory.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/memory.rs index 120ff43acff..7655051ee68 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/memory.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/memory.rs @@ -1,10 +1,10 @@ -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ abstractions::{Memory, MemoryType}, aux_structures::{MemoryPage, MemoryQuery, Timestamp}, vm_state::PrimitiveValue, zkevm_opcode_defs::FatPointer, }; -use zksync_types::U256; +use zksync_types::{Address, CODE_ORACLE_ADDRESS, U256}; use crate::vm_latest::old_vm::{ history_recorder::{ @@ -170,6 +170,11 @@ impl Memory for SimpleMemory { MemoryType::Code => { unreachable!("code should be through specialized query"); } + MemoryType::StaticMemory => { + // While `MemoryType::StaticMemory` is formally supported by `vm@1.5.0`, it is never + // used in the system contracts. + unreachable!() + } } let page = query.location.page.0 as usize; @@ -274,6 +279,7 @@ impl Memory for SimpleMemory { fn finish_global_frame( &mut self, base_page: MemoryPage, + last_callstack_this: Address, returndata_fat_pointer: FatPointer, timestamp: Timestamp, ) { @@ -281,6 +287,11 @@ impl Memory for SimpleMemory { let current_observable_pages = self.observable_pages.inner().current_frame(); let returndata_page = returndata_fat_pointer.memory_page; + // This is code oracle and some preimage has been decommitted into its memory. + // We must keep this memory page forever for future decommits. + let is_returndata_page_static = + last_callstack_this == CODE_ORACLE_ADDRESS && returndata_fat_pointer.length > 0; + for &page in current_observable_pages { // If the page's number is greater than or equal to the `base_page`, // it means that it was created by the internal calls of this contract. @@ -294,8 +305,12 @@ impl Memory for SimpleMemory { self.observable_pages.clear_frame(timestamp); self.observable_pages.merge_frame(timestamp); - self.observable_pages - .push_to_frame(returndata_page, timestamp); + // If returndata page is static, we do not add it to the list of observable pages, + // effectively preventing it from being cleared in the future. + if !is_returndata_page_static { + self.observable_pages + .push_to_frame(returndata_page, timestamp); + } } } diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/decommitter.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/decommitter.rs index 86e8f6f6da1..85b18e203ce 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/decommitter.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/decommitter.rs @@ -1,19 +1,23 @@ use std::{collections::HashMap, fmt::Debug}; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ abstractions::{DecommittmentProcessor, Memory, MemoryType}, aux_structures::{ DecommittmentQuery, MemoryIndex, MemoryLocation, MemoryPage, MemoryQuery, Timestamp, }, + zkevm_opcode_defs::{ + ContractCodeSha256, VersionedHashDef, VersionedHashHeader, VersionedHashNormalizedPreimage, + }, }; use zksync_state::{ReadStorage, StoragePtr}; -use zksync_types::U256; -use zksync_utils::{bytecode::bytecode_len_in_words, bytes_to_be_words, u256_to_h256}; +use zksync_types::{H256, U256}; +use zksync_utils::{bytes_to_be_words, h256_to_u256, u256_to_h256}; use super::OracleWithHistory; use crate::vm_latest::old_vm::history_recorder::{ HistoryEnabled, HistoryMode, HistoryRecorder, WithHistory, }; + /// The main job of the DecommiterOracle is to implement the DecommittmentProcessor trait - that is /// used by the VM to 'load' bytecodes into memory. #[derive(Debug)] @@ -56,7 +60,7 @@ impl DecommitterOracle { .storage .borrow_mut() .load_factory_dep(u256_to_h256(hash)) - .expect("Trying to decode unexisting hash"); + .unwrap_or_else(|| panic!("Trying to decommit unexisting hash: {}", hash)); let value = bytes_to_be_words(value); self.known_bytecodes.insert(hash, value.clone(), timestamp); @@ -72,14 +76,6 @@ impl DecommitterOracle { } } - pub fn get_used_bytecode_hashes(&self) -> Vec { - self.decommitted_code_hashes - .inner() - .iter() - .map(|item| *item.0) - .collect() - } - pub fn get_decommitted_bytecodes_after_timestamp(&self, timestamp: Timestamp) -> usize { // Note, that here we rely on the fact that for each used bytecode // there is one and only one corresponding event in the history of it. @@ -160,76 +156,111 @@ impl OracleWithHistory for DecommitterOracle DecommittmentProcessor for DecommitterOracle { - /// Loads a given bytecode hash into memory (see trait description for more details). - fn decommit_into_memory( + /// Prepares the decommitment query for the given bytecode hash. + /// The main purpose of this method is to tell the VM whether this bytecode is fresh (i.e. decommitted for the first time) + /// or not. + fn prepare_to_decommit( &mut self, - monotonic_cycle_counter: u32, + _monotonic_cycle_counter: u32, mut partial_query: DecommittmentQuery, - memory: &mut M, - ) -> Result< - ( - zk_evm_1_4_1::aux_structures::DecommittmentQuery, - Option>, - ), - anyhow::Error, - > { - self.decommitment_requests.push((), partial_query.timestamp); - // First - check if we didn't fetch this bytecode in the past. - // If we did - we can just return the page that we used before (as the memory is readonly). + ) -> anyhow::Result { + let (stored_hash, length) = stored_hash_from_query(&partial_query); + partial_query.decommitted_length = length; + if let Some(memory_page) = self .decommitted_code_hashes .inner() - .get(&partial_query.hash) + .get(&stored_hash) .copied() { partial_query.is_fresh = false; partial_query.memory_page = MemoryPage(memory_page); - partial_query.decommitted_length = - bytecode_len_in_words(&u256_to_h256(partial_query.hash)); - Ok((partial_query, None)) + Ok(partial_query) } else { - // We are fetching a fresh bytecode that we didn't read before. - let values = self.get_bytecode(partial_query.hash, partial_query.timestamp); - let page_to_use = partial_query.memory_page; - let timestamp = partial_query.timestamp; - partial_query.decommitted_length = values.len() as u16; partial_query.is_fresh = true; - // Create a template query, that we'll use for writing into memory. - // value & index are set to 0 - as they will be updated in the inner loop below. - let mut tmp_q = MemoryQuery { - timestamp, - location: MemoryLocation { - memory_type: MemoryType::Code, - page: page_to_use, - index: MemoryIndex(0), - }, - value: U256::zero(), - value_is_pointer: false, - rw_flag: true, - }; - self.decommitted_code_hashes - .insert(partial_query.hash, page_to_use.0, timestamp); - - // Copy the bytecode (that is stored in 'values' Vec) into the memory page. - if B { - for (i, value) in values.iter().enumerate() { - tmp_q.location.index = MemoryIndex(i as u32); - tmp_q.value = *value; - memory.specialized_code_query(monotonic_cycle_counter, tmp_q); - } - // If we're in the witness mode - we also have to return the values. - Ok((partial_query, Some(values))) - } else { - for (i, value) in values.into_iter().enumerate() { - tmp_q.location.index = MemoryIndex(i as u32); - tmp_q.value = value; - memory.specialized_code_query(monotonic_cycle_counter, tmp_q); - } - - Ok((partial_query, None)) + Ok(partial_query) + } + } + + /// Loads a given bytecode hash into memory (see trait description for more details). + fn decommit_into_memory( + &mut self, + monotonic_cycle_counter: u32, + partial_query: DecommittmentQuery, + memory: &mut M, + ) -> anyhow::Result>> { + assert!(partial_query.is_fresh); + + self.decommitment_requests.push((), partial_query.timestamp); + + let stored_hash = stored_hash_from_query(&partial_query).0; + + // We are fetching a fresh bytecode that we didn't read before. + let values = self.get_bytecode(stored_hash, partial_query.timestamp); + let page_to_use = partial_query.memory_page; + let timestamp = partial_query.timestamp; + + // Create a template query, that we'll use for writing into memory. + // value & index are set to 0 - as they will be updated in the inner loop below. + let mut tmp_q = MemoryQuery { + timestamp, + location: MemoryLocation { + memory_type: MemoryType::Code, + page: page_to_use, + index: MemoryIndex(0), + }, + value: U256::zero(), + value_is_pointer: false, + rw_flag: true, + }; + self.decommitted_code_hashes + .insert(stored_hash, page_to_use.0, timestamp); + + // Copy the bytecode (that is stored in 'values' Vec) into the memory page. + if B { + for (i, value) in values.iter().enumerate() { + tmp_q.location.index = MemoryIndex(i as u32); + tmp_q.value = *value; + memory.specialized_code_query(monotonic_cycle_counter, tmp_q); + } + // If we're in the witness mode - we also have to return the values. + Ok(Some(values)) + } else { + for (i, value) in values.into_iter().enumerate() { + tmp_q.location.index = MemoryIndex(i as u32); + tmp_q.value = value; + memory.specialized_code_query(monotonic_cycle_counter, tmp_q); } + + Ok(None) } } } + +fn concat_header_and_preimage( + header: VersionedHashHeader, + normalized_preimage: VersionedHashNormalizedPreimage, +) -> [u8; 32] { + let mut buffer = [0u8; 32]; + + buffer[0..4].copy_from_slice(&header.0); + buffer[4..32].copy_from_slice(&normalized_preimage.0); + + buffer +} + +/// For a given decommitment query, returns a pair of the stored hash as U256 and the length of the preimage in 32-byte words. +fn stored_hash_from_query(partial_query: &DecommittmentQuery) -> (U256, u16) { + let full_hash = + concat_header_and_preimage(partial_query.header, partial_query.normalized_preimage); + + let versioned_hash = + ContractCodeSha256::try_deserialize(full_hash).expect("Invalid ContractCodeSha256 hash"); + + let stored_hash = H256(ContractCodeSha256::serialize_to_stored(versioned_hash).unwrap()); + let length = versioned_hash.code_length_in_words; + + (h256_to_u256(stored_hash), length) +} diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/mod.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/mod.rs index 7d463ad082b..b58589f02e1 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/mod.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; pub(crate) mod decommitter; pub(crate) mod precompile; diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs index 8de51a9619d..edbd7687fd8 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/oracles/precompile.rs @@ -1,9 +1,11 @@ use std::convert::TryFrom; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ abstractions::{Memory, PrecompileCyclesWitness, PrecompilesProcessor}, aux_structures::{LogQuery, MemoryQuery, Timestamp}, - zk_evm_abstractions::precompiles::{ecrecover, keccak256, sha256, PrecompileAddress}, + zk_evm_abstractions::precompiles::{ + ecrecover, keccak256, secp256r1_verify, sha256, PrecompileAddress, + }, }; use super::OracleWithHistory; @@ -97,6 +99,14 @@ impl PrecompilesProcessor for PrecompilesProcessorWithHistory ) .0 } + PrecompileAddress::Secp256r1Verify => { + secp256r1_verify::secp256r1_verify_function::( + monotonic_cycle_counter, + query, + memory, + ) + .0 + } }; self.precompile_cycles_history diff --git a/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs b/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs index 6320c6bcb1f..dd354e983d9 100644 --- a/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/old_vm/utils.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ aux_structures::{MemoryPage, Timestamp}, vm_state::PrimitiveValue, zkevm_opcode_defs::{ diff --git a/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs b/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs index 07ec473624b..2f2d3891340 100644 --- a/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs +++ b/core/lib/multivm/src/versions/vm_latest/oracles/storage.rs @@ -1,9 +1,13 @@ use std::collections::HashMap; -use zk_evm_1_4_1::{ - abstractions::{RefundType, RefundedAmounts, Storage as VmStorageOracle}, - aux_structures::{LogQuery, Timestamp}, - zkevm_opcode_defs::system_params::INITIAL_STORAGE_WRITE_PUBDATA_BYTES, +use zk_evm_1_5_0::{ + abstractions::{Storage as VmStorageOracle, StorageAccessRefund}, + aux_structures::{LogQuery, PubdataCost, Timestamp}, + 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, STORAGE_AUX_BYTE, + TRANSIENT_STORAGE_AUX_BYTE, + }, }; use zksync_state::{StoragePtr, WriteStorage}; use zksync_types::{ @@ -22,7 +26,8 @@ use crate::{ old_vm::{ history_recorder::{ AppDataFrameManagerWithHistory, HashMapHistoryEvent, HistoryEnabled, HistoryMode, - HistoryRecorder, StorageWrapper, VectorHistoryEvent, WithHistory, + HistoryRecorder, StorageWrapper, TransientStorageWrapper, VectorHistoryEvent, + WithHistory, }, oracles::OracleWithHistory, }, @@ -30,6 +35,16 @@ use crate::{ }, }; +/// We employ the following rules for cold/warm storage rules: +/// - We price a single "I/O" access as 2k ergs. This means that reading a single storage slot +/// would cost 2k ergs, while writing to it would 4k ergs (since it involves both reading during execution and writing at the end of it). +/// - Thereafter, "warm" reads cost 30 ergs, while "warm" writes cost 60 ergs. Warm writes to account cost more for the fact that they may be reverted +/// and so require more RAM to store them. + +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; + // While the storage does not support different shards, it was decided to write the // code of the StorageOracle with the shard parameters in mind. pub(crate) fn triplet_to_storage_key(_shard_id: u8, address: Address, key: U256) -> StorageKey { @@ -47,14 +62,17 @@ pub struct StorageOracle { // after the execution ended. pub(crate) storage: HistoryRecorder, H>, - pub(crate) frames_stack: AppDataFrameManagerWithHistory, H>, + // Wrapper over transient storage. + pub(crate) transient_storage: HistoryRecorder, + + pub(crate) storage_frames_stack: AppDataFrameManagerWithHistory, H>, + + pub(crate) transient_storage_frames_stack: AppDataFrameManagerWithHistory, H>, - // The changes that have been paid for in previous transactions. + // The changes that have been paid for in the current transaction. // It is a mapping from storage key to the number of *bytes* that was paid by the user // to cover this slot. - pub(crate) pre_paid_changes: HistoryRecorder, H>, - - // The changes that have been paid for in the current transaction + // Note, that this field has to be rolled back in case of a panicking frame. pub(crate) paid_changes: HistoryRecorder, H>, // The map that contains all the first values read from storage for each slot. @@ -62,25 +80,34 @@ pub struct StorageOracle { // for unused slots. pub(crate) initial_values: HistoryRecorder, H>, - // Storage refunds that oracle has returned in `estimate_refunds_for_write`. - pub(crate) returned_refunds: HistoryRecorder, H>, + // Storage I/O refunds that oracle has returned in `get_access_refund`. + pub(crate) returned_io_refunds: HistoryRecorder, H>, + + // The pubdata costs that oracle has returned in `execute_partial_query`. + // Note, that these can be negative, since the user may rollback some storage changes. + pub(crate) returned_pubdata_costs: HistoryRecorder, H>, - // Keeps track of storage keys that were ever written to. - pub(crate) written_keys: HistoryRecorder, HistoryEnabled>, - // Keeps track of storage keys that were ever read. - pub(crate) read_keys: HistoryRecorder, HistoryEnabled>, + // Keeps track of storage keys that were ever written to. This is needed for circuits tracer, this is why + // we don't roll this value back in case of a panicked frame. + pub(crate) written_storage_keys: HistoryRecorder, HistoryEnabled>, + // Keeps track of storage keys that were ever read. This is needed for circuits tracer, this is why + // we don't roll this value back in case of a panicked frame. + // Note, that it is a superset of `written_storage_keys`, since every written key was also read at some point. + pub(crate) read_storage_keys: HistoryRecorder, HistoryEnabled>, } impl OracleWithHistory for StorageOracle { fn rollback_to_timestamp(&mut self, timestamp: Timestamp) { self.storage.rollback_to_timestamp(timestamp); - self.frames_stack.rollback_to_timestamp(timestamp); - self.pre_paid_changes.rollback_to_timestamp(timestamp); + self.storage_frames_stack.rollback_to_timestamp(timestamp); + self.transient_storage_frames_stack + .rollback_to_timestamp(timestamp); self.paid_changes.rollback_to_timestamp(timestamp); self.initial_values.rollback_to_timestamp(timestamp); - self.returned_refunds.rollback_to_timestamp(timestamp); - self.written_keys.rollback_to_timestamp(timestamp); - self.read_keys.rollback_to_timestamp(timestamp); + self.returned_io_refunds.rollback_to_timestamp(timestamp); + self.returned_pubdata_costs.rollback_to_timestamp(timestamp); + self.written_storage_keys.rollback_to_timestamp(timestamp); + self.read_storage_keys.rollback_to_timestamp(timestamp); } } @@ -88,25 +115,28 @@ impl StorageOracle { pub fn new(storage: StoragePtr) -> Self { Self { storage: HistoryRecorder::from_inner(StorageWrapper::new(storage)), - frames_stack: Default::default(), - pre_paid_changes: Default::default(), + transient_storage: Default::default(), + storage_frames_stack: Default::default(), + transient_storage_frames_stack: Default::default(), paid_changes: Default::default(), initial_values: Default::default(), - returned_refunds: Default::default(), - written_keys: Default::default(), - read_keys: Default::default(), + returned_io_refunds: Default::default(), + returned_pubdata_costs: Default::default(), + written_storage_keys: Default::default(), + read_storage_keys: Default::default(), } } pub fn delete_history(&mut self) { self.storage.delete_history(); - self.frames_stack.delete_history(); - self.pre_paid_changes.delete_history(); + self.transient_storage.delete_history(); + self.storage_frames_stack.delete_history(); self.paid_changes.delete_history(); self.initial_values.delete_history(); - self.returned_refunds.delete_history(); - self.written_keys.delete_history(); - self.read_keys.delete_history(); + self.returned_io_refunds.delete_history(); + self.returned_pubdata_costs.delete_history(); + self.written_storage_keys.delete_history(); + self.read_storage_keys.delete_history(); } fn is_storage_key_free(&self, key: &StorageKey) -> bool { @@ -124,37 +154,24 @@ impl StorageOracle { } } - fn read_value(&mut self, mut query: LogQuery) -> LogQuery { - let key = triplet_to_storage_key(query.shard_id, query.address, query.key); - - if !self.read_keys.inner().contains_key(&key) { - self.read_keys.insert(key, (), query.timestamp); - } - let current_value = self.storage.read_from_storage(&key); - - query.read_value = current_value; - - self.set_initial_value(&key, current_value, query.timestamp); - - self.frames_stack.push_forward( - Box::new(StorageLogQuery { - log_query: query.glue_into(), - log_type: StorageLogQueryType::Read, - }), - query.timestamp, - ); + fn record_storage_read(&mut self, query: LogQuery) { + let storage_log_query = StorageLogQuery { + log_query: query, + log_type: StorageLogQueryType::Read, + }; - query + self.storage_frames_stack + .push_forward(Box::new(storage_log_query), query.timestamp); } - fn write_value(&mut self, query: LogQuery) -> LogQuery { + fn write_storage_value(&mut self, query: LogQuery) { let key = triplet_to_storage_key(query.shard_id, query.address, query.key); - if !self.written_keys.inner().contains_key(&key) { - self.written_keys.insert(key, (), query.timestamp); + if !self.written_storage_keys.inner().contains_key(&key) { + self.written_storage_keys.insert(key, (), query.timestamp); } - let current_value = - self.storage - .write_to_storage(key, query.written_value, query.timestamp); + + self.storage + .write_to_storage(key, query.written_value, query.timestamp); let is_initial_write = self.storage.get_ptr().borrow_mut().is_write_initial(&key); let log_query_type = if is_initial_write { @@ -163,20 +180,34 @@ impl StorageOracle { StorageLogQueryType::RepeatedWrite }; - self.set_initial_value(&key, current_value, query.timestamp); - let mut storage_log_query = StorageLogQuery { - log_query: query.glue_into(), + log_query: query, log_type: log_query_type, }; - self.frames_stack + self.storage_frames_stack .push_forward(Box::new(storage_log_query), query.timestamp); storage_log_query.log_query.rollback = true; - self.frames_stack + self.storage_frames_stack .push_rollback(Box::new(storage_log_query), query.timestamp); - storage_log_query.log_query.rollback = false; + } + + fn record_transient_storage_read(&mut self, query: LogQuery) { + self.transient_storage_frames_stack + .push_forward(Box::new(query), query.timestamp); + } + + fn write_transient_storage_value(&mut self, mut query: LogQuery) { + let key = triplet_to_storage_key(query.shard_id, query.address, query.key); + + self.transient_storage + .write_to_storage(key, query.written_value, query.timestamp); + + self.transient_storage_frames_stack + .push_forward(Box::new(query), query.timestamp); - query + query.rollback = true; + self.transient_storage_frames_stack + .push_rollback(Box::new(query), query.timestamp); } // Returns the amount of funds that has been already paid for writes into the storage slot @@ -185,67 +216,52 @@ impl StorageOracle { .inner() .get(storage_key) .copied() - .unwrap_or_else(|| { - self.pre_paid_changes - .inner() - .get(storage_key) - .copied() - .unwrap_or(0) - }) - } - - // Remembers the changes that have been paid for in the current transaction. - // It also returns how much pubdata did the user pay for and how much was actually published. - pub(crate) fn save_paid_changes(&mut self, timestamp: Timestamp) -> u32 { - let mut published = 0; - - let modified_keys = self - .paid_changes - .inner() - .iter() - .map(|(k, v)| (*k, *v)) - .collect::>(); - - for (key, _) in modified_keys { - // It is expected that for each slot for which we have paid changes, there is some - // first slot value already read. - let first_slot_value = self.initial_values.inner().get(&key).copied().unwrap(); - - // This is the value has been written to the storage slot at the end. - let current_slot_value = self.storage.read_from_storage(&key); - - let required_pubdata = - self.base_price_for_write(&key, first_slot_value, current_slot_value); - - // We assume that `prepaid_for_slot` represents both the number of pubdata published and the number of bytes paid by the previous transactions - // as they should be identical. - let prepaid_for_slot = self - .pre_paid_changes - .inner() - .get(&key) - .copied() - .unwrap_or_default(); - - published += required_pubdata.saturating_sub(prepaid_for_slot); - - // We remove the slot from the paid changes and move to the pre-paid changes as - // the transaction ends. - self.paid_changes.remove(key, timestamp); - self.pre_paid_changes - .insert(key, prepaid_for_slot.max(required_pubdata), timestamp); - } - - published + .unwrap_or(0) } fn base_price_for_write_query(&self, query: &LogQuery) -> u32 { let storage_key = storage_key_of_log(query); + self.base_price_for_key_write(&storage_key, query.written_value) + } + + fn base_price_for_key_write(&self, storage_key: &StorageKey, new_value: U256) -> u32 { let initial_value = self - .get_initial_value(&storage_key) - .unwrap_or(self.storage.read_from_storage(&storage_key)); + .get_initial_value(storage_key) + .unwrap_or(self.storage.read_from_storage(storage_key)); - self.base_price_for_write(&storage_key, initial_value, query.written_value) + self.base_price_for_write(storage_key, initial_value, new_value) + } + + fn get_storage_access_refund(&self, query: &LogQuery) -> StorageAccessRefund { + let key = storage_key_of_log(query); + if query.rw_flag { + // It is a write + + if self.written_storage_keys.inner().contains_key(&key) { + // It is a warm write + StorageAccessRefund::Warm { + ergs: WARM_WRITE_REFUND, + } + } else if self.read_storage_keys.inner().contains_key(&key) { + // If the user has read the value, but never written to it, the user will have to compensate for the write, but not for the read. + // So the user will pay `STORAGE_ACCESS_COLD_WRITE_COST - STORAGE_ACCESS_COLD_READ_COST` for such write. + StorageAccessRefund::Warm { + ergs: COLD_WRITE_AFTER_WARM_READ_REFUND, + } + } else { + // It is a cold write + StorageAccessRefund::Cold + } + } else if self.read_storage_keys.inner().contains_key(&key) { + // It is a warm read + StorageAccessRefund::Warm { + ergs: WARM_READ_REFUND, + } + } else { + // It is a cold read + StorageAccessRefund::Cold + } } pub(crate) fn base_price_for_write( @@ -267,35 +283,12 @@ impl StorageOracle { get_pubdata_price_bytes(prev_value, new_value, is_initial_write) } - // Returns the price of the update in terms of pubdata bytes. - // TODO (SMA-1701): update VM to accept gas instead of pubdata. - fn value_update_price(&mut self, query: &LogQuery) -> u32 { - let storage_key = storage_key_of_log(query); - - let base_cost = self.base_price_for_write_query(query); - - let initial_value = self - .get_initial_value(&storage_key) - .unwrap_or(self.storage.read_from_storage(&storage_key)); - - self.set_initial_value(&storage_key, initial_value, query.timestamp); - - let already_paid = self.prepaid_for_write(&storage_key); - - if base_cost <= already_paid { - // Some other transaction has already paid for this slot, no need to pay anything - 0u32 - } else { - base_cost - already_paid - } - } - /// Returns storage log queries from current frame where `log.log_query.timestamp >= from_timestamp`. pub(crate) fn storage_log_queries_after_timestamp( &self, from_timestamp: Timestamp, ) -> &[Box] { - let logs = self.frames_stack.forward().current_frame(); + let logs = self.storage_frames_stack.forward().current_frame(); // Select all of the last elements where `l.log_query.timestamp >= from_timestamp`. // Note, that using binary search here is dangerous, because the logs are not sorted by timestamp. @@ -306,12 +299,12 @@ impl StorageOracle { pub(crate) fn get_final_log_queries(&self) -> Vec { assert_eq!( - self.frames_stack.len(), + self.storage_frames_stack.len(), 1, "VM finished execution in unexpected state" ); - self.frames_stack + self.storage_frames_stack .forward() .current_frame() .iter() @@ -320,7 +313,7 @@ impl StorageOracle { } pub(crate) fn get_size(&self) -> usize { - let frames_stack_size = self.frames_stack.get_size(); + let frames_stack_size = self.storage_frames_stack.get_size(); let paid_changes_size = self.paid_changes.inner().len() * std::mem::size_of::<(StorageKey, u32)>(); @@ -330,7 +323,7 @@ impl StorageOracle { pub(crate) fn get_history_size(&self) -> usize { let storage_size = self.storage.borrow_history(|h| h.len(), 0) * std::mem::size_of::< as WithHistory>::HistoryRecord>(); - let frames_stack_size = self.frames_stack.get_history_size(); + let frames_stack_size = self.storage_frames_stack.get_history_size(); let paid_changes_size = self.paid_changes.borrow_history(|h| h.len(), 0) * std::mem::size_of::< as WithHistory>::HistoryRecord>(); storage_size + frames_stack_size + paid_changes_size @@ -344,7 +337,7 @@ impl VmStorageOracle for StorageOracle { &mut self, _monotonic_cycle_counter: u32, mut query: LogQuery, - ) -> LogQuery { + ) -> (LogQuery, PubdataCost) { // ``` // tracing::trace!( // "execute partial query cyc {:?} addr {:?} key {:?}, rw {:?}, wr {:?}, tx {:?}", @@ -357,55 +350,90 @@ impl VmStorageOracle for StorageOracle { // ); // ``` assert!(!query.rollback); - if query.rw_flag { - // The number of bytes that have been compensated by the user to perform this write - let storage_key = storage_key_of_log(&query); - let read_value = self.storage.read_from_storage(&storage_key); - query.read_value = read_value; + let storage_key = storage_key_of_log(&query); + + let read_value = if query.aux_byte == TRANSIENT_STORAGE_AUX_BYTE { + self.transient_storage + .read_from_transient_storage(&storage_key) + } else if query.aux_byte == STORAGE_AUX_BYTE { + if !self.read_storage_keys.inner().contains_key(&storage_key) { + self.read_storage_keys + .insert(storage_key, (), query.timestamp); + } + + self.set_initial_value(&storage_key, query.read_value, query.timestamp); + + self.storage.read_from_storage(&storage_key) + } else { + // Just in case + unreachable!(); + }; + + query.read_value = read_value; + + let pubdata_cost = if query.aux_byte == STORAGE_AUX_BYTE && query.rw_flag { // It is considered that the user has paid for the whole base price for the writes let to_pay_by_user = self.base_price_for_write_query(&query); let prepaid = self.prepaid_for_write(&storage_key); - if to_pay_by_user > prepaid { - self.paid_changes.apply_historic_record( - HashMapHistoryEvent { - key: storage_key, - value: Some(to_pay_by_user), - }, - query.timestamp, - ); + // Note, that the diff may be negative, e.g. in case the new write returns to the previous value. + let diff = (to_pay_by_user as i32) - (prepaid as i32); + + self.paid_changes.apply_historic_record( + HashMapHistoryEvent { + key: storage_key, + value: Some(to_pay_by_user), + }, + query.timestamp, + ); + + PubdataCost(diff) + } else { + PubdataCost(0) + }; + + if query.aux_byte == TRANSIENT_STORAGE_AUX_BYTE { + if query.rw_flag { + self.write_transient_storage_value(query); + } else { + self.record_transient_storage_read(query); + } + } else if query.aux_byte == STORAGE_AUX_BYTE { + if query.rw_flag { + self.write_storage_value(query); + } else { + self.record_storage_read(query); } - self.write_value(query) } else { - self.read_value(query) + // Just in case + unreachable!(); } + + self.returned_pubdata_costs + .apply_historic_record(VectorHistoryEvent::Push(pubdata_cost.0), query.timestamp); + + (query, pubdata_cost) } - // We can return the size of the refund before each storage query. - // Note, that while the `RefundType` allows to provide refunds both in - // `ergs` and `pubdata`, only refunds in pubdata will be compensated for the users - fn estimate_refunds_for_write( + // We can evaluate a query cost (or more precisely - get expected refunds) + // before actually executing query + fn get_access_refund( &mut self, // to avoid any hacks inside, like prefetch _monotonic_cycle_counter: u32, partial_query: &LogQuery, - ) -> RefundType { - let storage_key = storage_key_of_log(partial_query); - let mut partial_query = *partial_query; - let read_value = self.storage.read_from_storage(&storage_key); - partial_query.read_value = read_value; - - let price_to_pay = self - .value_update_price(&partial_query) - .min(INITIAL_STORAGE_WRITE_PUBDATA_BYTES as u32); - - let refund = RefundType::RepeatedWrite(RefundedAmounts { - ergs: 0, - // `INITIAL_STORAGE_WRITE_PUBDATA_BYTES` is the default amount of pubdata bytes the user pays for. - pubdata_bytes: (INITIAL_STORAGE_WRITE_PUBDATA_BYTES as u32) - price_to_pay, - }); - self.returned_refunds.apply_historic_record( - VectorHistoryEvent::Push(refund.pubdata_refund()), + ) -> StorageAccessRefund { + let refund = if partial_query.aux_byte == TRANSIENT_STORAGE_AUX_BYTE { + // Any transient access is warm. Also, no refund needs to be provided as it is already cheap + StorageAccessRefund::Warm { ergs: 0 } + } else if partial_query.aux_byte == STORAGE_AUX_BYTE { + self.get_storage_access_refund(partial_query) + } else { + unreachable!() + }; + + self.returned_io_refunds.apply_historic_record( + VectorHistoryEvent::Push(refund.refund()), partial_query.timestamp, ); @@ -414,7 +442,8 @@ impl VmStorageOracle for StorageOracle { // Indicate a start of execution frame for rollback purposes fn start_frame(&mut self, timestamp: Timestamp) { - self.frames_stack.push_frame(timestamp); + self.storage_frames_stack.push_frame(timestamp); + self.transient_storage_frames_stack.push_frame(timestamp); } // Indicate that execution frame went out from the scope, so we can @@ -424,7 +453,13 @@ impl VmStorageOracle for StorageOracle { // otherwise we place rollbacks of child before rollbacks of the parent if panicked { // perform actual rollback - for query in self.frames_stack.rollback().current_frame().iter().rev() { + for query in self + .storage_frames_stack + .rollback() + .current_frame() + .iter() + .rev() + { let read_value = match query.log_type { StorageLogQueryType::Read => { // Having Read logs in rollback is not possible @@ -436,7 +471,6 @@ impl VmStorageOracle for StorageOracle { } }; - let LogQuery { written_value, .. } = query.log_query.glue_into(); let key = triplet_to_storage_key( query.log_query.shard_id, query.log_query.address, @@ -449,16 +483,57 @@ impl VmStorageOracle for StorageOracle { read_value, timestamp, ); + // Now, we also need to roll back the changes to the paid changes. + let price_for_writing_new_value = self.base_price_for_key_write(&key, read_value); + self.paid_changes.apply_historic_record( + HashMapHistoryEvent { + key, + value: Some(price_for_writing_new_value), + }, + timestamp, + ); + // Additional validation that the current value was correct // Unwrap is safe because the return value from `write_inner` is the previous value in this leaf. // It is impossible to set leaf value to `None` - assert_eq!(current_value, written_value); + assert_eq!(current_value, query.log_query.written_value); } + self.storage_frames_stack + .move_rollback_to_forward(|_| true, timestamp); - self.frames_stack + for query in self + .transient_storage_frames_stack + .rollback() + .current_frame() + .iter() + .rev() + { + let read_value = if query.rw_flag { + query.read_value + } else { + // Having Read logs in rollback is not possible + tracing::warn!("Read log in rollback queue {:?}", query); + continue; + }; + + let current_value = self.transient_storage.write_to_storage( + triplet_to_storage_key(query.shard_id, query.address, query.key), + read_value, + timestamp, + ); + + assert_eq!(current_value, query.written_value); + } + + self.transient_storage_frames_stack .move_rollback_to_forward(|_| true, timestamp); } - self.frames_stack.merge_frame(timestamp); + self.storage_frames_stack.merge_frame(timestamp); + self.transient_storage_frames_stack.merge_frame(timestamp); + } + + fn start_new_tx(&mut self, timestamp: Timestamp) { + self.transient_storage.zero_out(timestamp); } } @@ -489,6 +564,10 @@ fn get_pubdata_price_bytes(initial_value: U256, final_value: U256, is_initial: b #[cfg(test)] mod tests { + use zksync_state::{InMemoryStorage, StorageView}; + use zksync_types::H256; + use zksync_utils::h256_to_u256; + use super::*; #[test] @@ -511,4 +590,185 @@ mod tests { (compression_len + BYTES_PER_ENUMERATION_INDEX as usize) as u32 ); } + + enum TestQueryType { + StorageRead, + StorageWrite, + } + + fn make_storage_query( + key: StorageKey, + value: U256, + timestamp: Timestamp, + query_type: TestQueryType, + ) -> LogQuery { + let (rw_flag, aux_byte) = match query_type { + TestQueryType::StorageRead => (false, STORAGE_AUX_BYTE), + TestQueryType::StorageWrite => (true, STORAGE_AUX_BYTE), + }; + + LogQuery { + shard_id: 0, + address: *key.address(), + key: h256_to_u256(*key.key()), + read_value: U256::zero(), + written_value: value, + rw_flag, + aux_byte, + tx_number_in_block: 0, + timestamp, + is_service: false, + rollback: false, + } + } + + #[test] + fn test_hot_cold_storage() { + let storage = StorageView::new(InMemoryStorage::default()).to_rc_ptr(); + + let mut storage_oracle = StorageOracle::<_, HistoryEnabled>::new(storage.clone()); + + storage_oracle.start_frame(Timestamp(0)); + + let key1 = StorageKey::new(AccountTreeId::new(Address::default()), H256::default()); + let key2 = StorageKey::new( + AccountTreeId::new(Address::default()), + H256::from_low_u64_be(1), + ); + + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageRead + ) + ) == StorageAccessRefund::Cold + ); + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageWrite + ) + ) == StorageAccessRefund::Cold + ); + + storage_oracle.execute_partial_query( + 0, + make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageRead, + ), + ); + + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageRead + ) + ) == StorageAccessRefund::Warm { + ergs: WARM_READ_REFUND + } + ); + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageWrite + ) + ) == StorageAccessRefund::Warm { + ergs: COLD_WRITE_AFTER_WARM_READ_REFUND + } + ); + + storage_oracle.execute_partial_query( + 0, + make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageWrite, + ), + ); + + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageRead + ) + ) == StorageAccessRefund::Warm { + ergs: WARM_READ_REFUND + } + ); + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key1, + U256::default(), + Timestamp(0), + TestQueryType::StorageWrite + ) + ) == StorageAccessRefund::Warm { + ergs: WARM_WRITE_REFUND + } + ); + + // Now we also test that a write can make both writes and reads warm: + storage_oracle.execute_partial_query( + 0, + make_storage_query( + key2, + U256::default(), + Timestamp(0), + TestQueryType::StorageWrite, + ), + ); + + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key2, + U256::default(), + Timestamp(0), + TestQueryType::StorageRead + ) + ) == StorageAccessRefund::Warm { + ergs: WARM_READ_REFUND + } + ); + assert!( + storage_oracle.get_access_refund( + 0, + &make_storage_query( + key2, + U256::default(), + Timestamp(0), + TestQueryType::StorageWrite + ) + ) == StorageAccessRefund::Warm { + ergs: WARM_WRITE_REFUND + } + ); + } } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs index 5801270ee7e..9b3097b8f09 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/block_tip.rs @@ -1,14 +1,15 @@ use std::borrow::BorrowMut; use ethabi::Token; -use zk_evm_1_4_1::aux_structures::Timestamp; +use itertools::Itertools; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_contracts::load_sys_contract; use zksync_system_constants::{ CONTRACT_FORCE_DEPLOYER_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, L1_MESSENGER_ADDRESS, }; use zksync_types::{ - commitment::SerializeCommitment, get_code_key, l2_to_l1_log::L2ToL1Log, - writes::StateDiffRecord, Address, Execute, H256, U256, + commitment::SerializeCommitment, fee_model::BatchFeeInput, get_code_key, + l2_to_l1_log::L2ToL1Log, writes::StateDiffRecord, Address, Execute, H256, U256, }; use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; @@ -21,9 +22,11 @@ use crate::{ BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD, BOOTLOADER_BATCH_TIP_OVERHEAD, MAX_VM_PUBDATA_PER_BATCH, }, - tests::tester::{get_empty_storage, InMemoryStorageView, VmTesterBuilder}, + tests::tester::{ + default_l1_batch, get_empty_storage, InMemoryStorageView, VmTesterBuilder, + }, tracers::PubdataTracer, - HistoryEnabled, TracerDispatcher, + HistoryEnabled, L1BatchEnv, TracerDispatcher, }, }; @@ -41,7 +44,8 @@ struct MimicCallInfo { data: Vec, } -fn populate_mimic_calls(data: L1MessengerTestData) -> Vec { +const CALLS_PER_TX: usize = 1_000; +fn populate_mimic_calls(data: L1MessengerTestData) -> Vec> { let complex_upgrade = get_complex_upgrade_abi(); let l1_messenger = load_sys_contract("L1Messenger"); @@ -87,13 +91,18 @@ fn populate_mimic_calls(data: L1MessengerTestData) -> Vec { Token::Bytes(call.data), ]) }) - .collect::>(); + .chunks(CALLS_PER_TX) + .into_iter() + .map(|chunk| { + complex_upgrade + .function("mimicCalls") + .unwrap() + .encode_input(&[Token::Array(chunk.collect_vec())]) + .unwrap() + }) + .collect_vec(); - complex_upgrade - .function("mimicCalls") - .unwrap() - .encode_input(&[Token::Array(encoded_calls)]) - .unwrap() + encoded_calls } struct TestStatistics { @@ -120,10 +129,19 @@ fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { .borrow_mut() .store_factory_dep(hash_bytecode(&complex_upgrade_code), complex_upgrade_code); + // We are measuring computational cost, so prices for pubdata don't matter, while they artificially dilute + // the gas limit + + let batch_env = L1BatchEnv { + fee_input: BatchFeeInput::pubdata_independent(100_000, 100_000, 100_000), + ..default_l1_batch(zksync_types::L1BatchNumber(1)) + }; + let mut vm = VmTesterBuilder::new(HistoryEnabled) .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_random_rich_accounts(1) + .with_l1_batch_env(batch_env) .build(); let bytecodes = test_data @@ -140,25 +158,29 @@ fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { .decommittment_processor .populate(bytecodes, Timestamp(0)); - let data = populate_mimic_calls(test_data.clone()); + let txs_data = populate_mimic_calls(test_data.clone()); let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: CONTRACT_FORCE_DEPLOYER_ADDRESS, - calldata: data, - value: U256::zero(), - factory_deps: None, - }, - None, - ); - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful for input: {:?}", - test_data - ); + for (i, data) in txs_data.into_iter().enumerate() { + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: CONTRACT_FORCE_DEPLOYER_ADDRESS, + calldata: data, + value: U256::zero(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx); + + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction {i} wasn't successful for input: {:#?}", + test_data + ); + } // Now we count how much ergs were spent at the end of the batch // It is assumed that the top level frame is the bootloader @@ -247,7 +269,6 @@ fn get_valid_bytecode_length(length: usize) -> usize { } #[test] -#[allow(clippy::vec_init_then_push)] fn test_dry_run_upper_bound() { // Some of the pubdata is consumed by constant fields (such as length of messages, number of logs, etc.). // While this leaves some room for error, at the end of the test we require that the `BOOTLOADER_BATCH_TIP_OVERHEAD` @@ -262,100 +283,107 @@ fn test_dry_run_upper_bound() { // 3. Lots of small bytecodes / one large bytecode. // 4. Lots of storage slot updates. - let mut statistics = Vec::new(); - - // max logs - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - l2_to_l1_logs: MAX_EFFECTIVE_PUBDATA_PER_BATCH / L2ToL1Log::SERIALIZED_SIZE, - ..Default::default() - }), - tag: "max_logs".to_string(), - }); - - // max messages - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // Each L2->L1 message is accompanied by a Log + its length, which is a 4 byte number, - // so the max number of pubdata is bound by it - messages: vec![ - vec![0; 0]; - MAX_EFFECTIVE_PUBDATA_PER_BATCH / (L2ToL1Log::SERIALIZED_SIZE + 4) - ], - ..Default::default() - }), - tag: "max_messages".to_string(), - }); - - // long message - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // Each L2->L1 message is accompanied by a Log, so the max number of pubdata is bound by it - messages: vec![vec![0; MAX_EFFECTIVE_PUBDATA_PER_BATCH]; 1], - ..Default::default() - }), - tag: "long_message".to_string(), - }); - - // max bytecodes - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // Each bytecode must be at least 32 bytes long. - // Each uncompressed bytecode is accompanied by its length, which is a 4 byte number - bytecodes: vec![vec![0; 32]; MAX_EFFECTIVE_PUBDATA_PER_BATCH / (32 + 4)], - ..Default::default() - }), - tag: "max_bytecodes".to_string(), - }); - - // long bytecode - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - bytecodes: vec![vec![0; get_valid_bytecode_length(MAX_EFFECTIVE_PUBDATA_PER_BATCH)]; 1], - ..Default::default() - }), - tag: "long_bytecode".to_string(), - }); - - // lots of small repeated writes - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // In theory each state diff can require only 5 bytes to be published (enum index + 4 bytes for the key) - state_diffs: generate_state_diffs(true, true, MAX_EFFECTIVE_PUBDATA_PER_BATCH / 5), - ..Default::default() - }), - tag: "small_repeated_writes".to_string(), - }); - - // lots of big repeated writes - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // Each big repeated write will approximately require 4 bytes for key + 1 byte for encoding type + 32 bytes for value - state_diffs: generate_state_diffs(true, false, MAX_EFFECTIVE_PUBDATA_PER_BATCH / 37), - ..Default::default() - }), - tag: "big_repeated_writes".to_string(), - }); - - // lots of small initial writes - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // Each small initial write will take at least 32 bytes for derived key + 1 bytes encoding zeroing out - state_diffs: generate_state_diffs(false, true, MAX_EFFECTIVE_PUBDATA_PER_BATCH / 33), - ..Default::default() - }), - tag: "small_initial_writes".to_string(), - }); - - // lots of large initial writes - statistics.push(StatisticsTagged { - statistics: execute_test(L1MessengerTestData { - // Each big write will take at least 32 bytes for derived key + 1 byte for encoding type + 32 bytes for value - state_diffs: generate_state_diffs(false, false, MAX_EFFECTIVE_PUBDATA_PER_BATCH / 65), - ..Default::default() - }), - tag: "big_initial_writes".to_string(), - }); + let statistics = vec![ + // max logs + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + l2_to_l1_logs: MAX_EFFECTIVE_PUBDATA_PER_BATCH / L2ToL1Log::SERIALIZED_SIZE, + ..Default::default() + }), + tag: "max_logs".to_string(), + }, + // max messages + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // Each L2->L1 message is accompanied by a Log + its length, which is a 4 byte number, + // so the max number of pubdata is bound by it + messages: vec![ + vec![0; 0]; + MAX_EFFECTIVE_PUBDATA_PER_BATCH / (L2ToL1Log::SERIALIZED_SIZE + 4) + ], + ..Default::default() + }), + tag: "max_messages".to_string(), + }, + // long message + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // Each L2->L1 message is accompanied by a Log, so the max number of pubdata is bound by it + messages: vec![vec![0; MAX_EFFECTIVE_PUBDATA_PER_BATCH]; 1], + ..Default::default() + }), + tag: "long_message".to_string(), + }, + // max bytecodes + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // Each bytecode must be at least 32 bytes long. + // Each uncompressed bytecode is accompanied by its length, which is a 4 byte number + bytecodes: vec![vec![0; 32]; MAX_EFFECTIVE_PUBDATA_PER_BATCH / (32 + 4)], + ..Default::default() + }), + tag: "max_bytecodes".to_string(), + }, + // long bytecode + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + bytecodes: vec![ + vec![0; get_valid_bytecode_length(MAX_EFFECTIVE_PUBDATA_PER_BATCH)]; + 1 + ], + ..Default::default() + }), + tag: "long_bytecode".to_string(), + }, + // lots of small repeated writes + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // In theory each state diff can require only 5 bytes to be published (enum index + 4 bytes for the key) + state_diffs: generate_state_diffs(true, true, MAX_EFFECTIVE_PUBDATA_PER_BATCH / 5), + ..Default::default() + }), + tag: "small_repeated_writes".to_string(), + }, + // lots of big repeated writes + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // Each big repeated write will approximately require 4 bytes for key + 1 byte for encoding type + 32 bytes for value + state_diffs: generate_state_diffs( + true, + false, + MAX_EFFECTIVE_PUBDATA_PER_BATCH / 37, + ), + ..Default::default() + }), + tag: "big_repeated_writes".to_string(), + }, + // lots of small initial writes + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // Each small initial write will take at least 32 bytes for derived key + 1 bytes encoding zeroing out + state_diffs: generate_state_diffs( + false, + true, + MAX_EFFECTIVE_PUBDATA_PER_BATCH / 33, + ), + ..Default::default() + }), + tag: "small_initial_writes".to_string(), + }, + // lots of large initial writes + StatisticsTagged { + statistics: execute_test(L1MessengerTestData { + // Each big write will take at least 32 bytes for derived key + 1 byte for encoding type + 32 bytes for value + state_diffs: generate_state_diffs( + false, + false, + MAX_EFFECTIVE_PUBDATA_PER_BATCH / 65, + ), + ..Default::default() + }), + tag: "big_initial_writes".to_string(), + }, + ]; // We use 2x overhead for the batch tip compared to the worst estimated scenario. let max_used_gas = statistics @@ -364,7 +392,7 @@ fn test_dry_run_upper_bound() { .max() .unwrap(); assert!( - max_used_gas.0 * 2 <= BOOTLOADER_BATCH_TIP_OVERHEAD, + max_used_gas.0 * 3 / 2 <= BOOTLOADER_BATCH_TIP_OVERHEAD, "BOOTLOADER_BATCH_TIP_OVERHEAD is too low for {} with result {}, BOOTLOADER_BATCH_TIP_OVERHEAD = {}", max_used_gas.1, max_used_gas.0, @@ -377,7 +405,7 @@ fn test_dry_run_upper_bound() { .max() .unwrap(); assert!( - circuit_statistics.0 * 2 <= BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD as u64, + circuit_statistics.0 * 3 / 2 <= BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD as u64, "BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD is too low for {} with result {}, BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD = {}", circuit_statistics.1, circuit_statistics.0, @@ -390,7 +418,7 @@ fn test_dry_run_upper_bound() { .max() .unwrap(); assert!( - execution_metrics_size.0 * 2 <= BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD as u64, + execution_metrics_size.0 * 3 / 2 <= BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD as u64, "BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD is too low for {} with result {}, BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD = {}", execution_metrics_size.1, execution_metrics_size.0, diff --git a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs index 1242c5f10da..c582bd28c88 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs @@ -34,8 +34,9 @@ fn test_circuits() { let s = res.statistics.circuit_statistic; // Check `circuit_statistic`. - const EXPECTED: [f32; 11] = [ - 1.1979, 0.1390, 1.5455, 0.0031, 1.0573, 0.00059, 0.003438, 0.00077, 0.1195, 0.1429, 0.0, + const EXPECTED: [f32; 13] = [ + 1.34935, 0.15026, 1.66666, 0.00315, 1.0594, 0.00058, 0.00348, 0.00076, 0.11945, 0.14285, + 0.0, 0.0, 0.0, ]; let actual = [ (s.main_vm, "main_vm"), @@ -49,6 +50,8 @@ fn test_circuits() { (s.keccak256, "keccak256"), (s.ecrecover, "ecrecover"), (s.sha256, "sha256"), + (s.secp256k1_verify, "secp256k1_verify"), + (s.transient_storage_checker, "transient_storage_checker"), ]; for ((actual, name), expected) in actual.iter().zip(EXPECTED) { if expected == 0.0 { diff --git a/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs b/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs new file mode 100644 index 00000000000..a8507cb92ca --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs @@ -0,0 +1,166 @@ +use ethabi::Token; +use zk_evm_1_5_0::aux_structures::Timestamp; +use zksync_types::{get_known_code_key, web3::signing::keccak256, Address, Execute, U256}; +use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{ + tests::{ + tester::{get_empty_storage, VmTesterBuilder}, + utils::{load_precompiles_contract, read_precompiles_contract, read_test_contract}, + }, + HistoryEnabled, + }, +}; + +fn generate_large_bytecode() -> Vec { + // This is the maximal possible size of a zkEVM bytecode + vec![2u8; ((1 << 16) - 1) * 32] +} + +#[test] +fn test_code_oracle() { + let precompiles_contract_address = Address::random(); + let precompile_contract_bytecode = read_precompiles_contract(); + + // Filling the zkevm bytecode + let normal_zkevm_bytecode = read_test_contract(); + let normal_zkevm_bytecode_hash = hash_bytecode(&normal_zkevm_bytecode); + let normal_zkevm_bytecode_keccak_hash = keccak256(&normal_zkevm_bytecode); + let mut storage = get_empty_storage(); + storage.set_value( + get_known_code_key(&normal_zkevm_bytecode_hash), + u256_to_h256(U256::one()), + ); + + // In this test, we aim to test whether a simple account interaction (without any fee logic) + // will work. The account will try to deploy a simple contract from integration tests. + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_custom_contracts(vec![( + precompile_contract_bytecode, + precompiles_contract_address, + false, + )]) + .with_storage(storage) + .build(); + + let precompile_contract = load_precompiles_contract(); + let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); + + vm.vm.state.decommittment_processor.populate( + vec![( + h256_to_u256(normal_zkevm_bytecode_hash), + bytes_to_be_words(normal_zkevm_bytecode), + )], + Timestamp(0), + ); + + let account = &mut vm.rich_accounts[0]; + + // Firstly, let's ensure that the contract works. + let tx1 = account.get_l2_tx_for_execute( + Execute { + contract_address: precompiles_contract_address, + calldata: call_code_oracle_function + .encode_input(&[ + Token::FixedBytes(normal_zkevm_bytecode_hash.0.to_vec()), + Token::FixedBytes(normal_zkevm_bytecode_keccak_hash.to_vec()), + ]) + .unwrap(), + value: U256::zero(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx1); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Transaction wasn't successful"); + + // Now, we ask for the same bytecode. We use to partially check whether the memory page with + // the decommitted bytecode gets erased (it shouldn't). + let tx2 = account.get_l2_tx_for_execute( + Execute { + contract_address: precompiles_contract_address, + calldata: call_code_oracle_function + .encode_input(&[ + Token::FixedBytes(normal_zkevm_bytecode_hash.0.to_vec()), + Token::FixedBytes(normal_zkevm_bytecode_keccak_hash.to_vec()), + ]) + .unwrap(), + value: U256::zero(), + factory_deps: None, + }, + None, + ); + vm.vm.push_transaction(tx2); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Transaction wasn't successful"); +} + +#[test] +fn test_code_oracle_big_bytecode() { + let precompiles_contract_address = Address::random(); + let precompile_contract_bytecode = read_precompiles_contract(); + + let big_zkevm_bytecode = generate_large_bytecode(); + let big_zkevm_bytecode_hash = hash_bytecode(&big_zkevm_bytecode); + let big_zkevm_bytecode_keccak_hash = keccak256(&big_zkevm_bytecode); + + let mut storage = get_empty_storage(); + storage.set_value( + get_known_code_key(&big_zkevm_bytecode_hash), + u256_to_h256(U256::one()), + ); + + // In this test, we aim to test whether a simple account interaction (without any fee logic) + // will work. The account will try to deploy a simple contract from integration tests. + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_custom_contracts(vec![( + precompile_contract_bytecode, + precompiles_contract_address, + false, + )]) + .with_storage(storage) + .build(); + + let precompile_contract = load_precompiles_contract(); + let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); + + vm.vm.state.decommittment_processor.populate( + vec![( + h256_to_u256(big_zkevm_bytecode_hash), + bytes_to_be_words(big_zkevm_bytecode), + )], + Timestamp(0), + ); + + let account = &mut vm.rich_accounts[0]; + + // Firstly, let's ensure that the contract works. + let tx1 = account.get_l2_tx_for_execute( + Execute { + contract_address: precompiles_contract_address, + calldata: call_code_oracle_function + .encode_input(&[ + Token::FixedBytes(big_zkevm_bytecode_hash.0.to_vec()), + Token::FixedBytes(big_zkevm_bytecode_keccak_hash.to_vec()), + ]) + .unwrap(), + value: U256::zero(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx1); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Transaction wasn't successful"); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs b/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs index 05e3e64f9c9..d23fa74edeb 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/default_aa.rs @@ -42,6 +42,7 @@ fn test_default_aa_interaction() { assert!(!result.result.is_failed(), "Transaction wasn't successful"); vm.vm.execute(VmExecutionMode::Batch); + vm.vm.get_current_execution_state(); // Both deployment and ordinary nonce should be incremented by one. diff --git a/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs b/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs index fe2987d76ac..2144ad9812d 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/l1_tx_execution.rs @@ -27,17 +27,19 @@ fn test_l1_tx_execution() { // Here instead of marking code hash via the bootloader means, we will be // using L1->L2 communication, the same it would likely be done during the priority mode. - // There are always at least 7 initial writes here, because we pay fees from l1: + // There are always at least 9 initial writes here, because we pay fees from l1: // - `totalSupply` of ETH token // - balance of the refund recipient // - balance of the bootloader // - `tx_rolling` hash + // - `gasPerPubdataByte` + // - `basePubdataSpent` // - rolling hash of L2->L1 logs // - transaction number in block counter // - L2->L1 log counter in `L1Messenger` - // TODO(PLA-537): right now we are using 4 slots instead of 7 due to 0 fee for transaction. - let basic_initial_writes = 4; + // TODO(PLA-537): right now we are using 5 slots instead of 9 due to 0 fee for transaction. + let basic_initial_writes = 5; let mut vm = VmTesterBuilder::new(HistoryEnabled) .with_empty_in_memory_storage() @@ -109,8 +111,9 @@ fn test_l1_tx_execution() { let res = vm.vm.execute(VmExecutionMode::OneTx); let storage_logs = res.logs.storage_logs; let res = StorageWritesDeduplicator::apply_on_empty_state(&storage_logs); - // We changed one slot inside contract - assert_eq!(res.initial_storage_writes - basic_initial_writes, 1); + // We changed one slot inside contract. However, the rewrite of the `basePubdataSpent` didn't happen, since it was the same + // as the start of the previous tx. Thus we have `+1` slot for the changed counter and `-1` slot for base pubdata spent + assert_eq!(res.initial_storage_writes - basic_initial_writes, 0); // No repeated writes let repeated_writes = res.repeated_storage_writes; @@ -119,7 +122,8 @@ fn test_l1_tx_execution() { vm.vm.push_transaction(tx); let storage_logs = vm.vm.execute(VmExecutionMode::OneTx).logs.storage_logs; let res = StorageWritesDeduplicator::apply_on_empty_state(&storage_logs); - // We do the same storage write, it will be deduplicated, so still 4 initial write and 0 repeated + // We do the same storage write, it will be deduplicated, so still 4 initial write and 0 repeated. + // But now the base pubdata spent has changed too. assert_eq!(res.initial_storage_writes - basic_initial_writes, 1); assert_eq!(res.repeated_storage_writes, repeated_writes); @@ -137,7 +141,7 @@ fn test_l1_tx_execution() { let res = StorageWritesDeduplicator::apply_on_empty_state(&result.logs.storage_logs); // There are only basic initial writes - assert_eq!(res.initial_storage_writes - basic_initial_writes, 2); + assert_eq!(res.initial_storage_writes - basic_initial_writes, 1); } #[test] diff --git a/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs b/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs index d103ebf7ebc..4c7fbf85c04 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/l2_blocks.rs @@ -3,7 +3,7 @@ //! The description for each of the tests can be found in the corresponding `.yul` file. //! -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_state::WriteStorage; use zksync_system_constants::REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE; use zksync_types::{ diff --git a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs index f3443f00e68..d416e2107b8 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -6,6 +6,7 @@ mod block_tip; mod bytecode_publishing; mod call_tracer; mod circuits; +mod code_oracle; mod gas_limit; mod get_used_contracts; mod is_write_initial; @@ -17,8 +18,11 @@ mod prestate_tracer; mod refunds; mod require_eip712; mod rollbacks; +mod sekp256r1; mod simple_execution; +mod storage; mod tester; mod tracing_execution_error; +mod transfer; mod upgrade; mod utils; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs index 2ba6763c285..652f9c0c03f 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::zk_evm_abstractions::precompiles::PrecompileAddress; +use zk_evm_1_5_0::zk_evm_abstractions::precompiles::PrecompileAddress; use zksync_types::{Address, Execute}; use crate::{ diff --git a/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs b/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs index 1811abdb9f0..c3c5ba896b8 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/require_eip712.rs @@ -109,6 +109,7 @@ async fn test_require_eip712() { vm.vm.push_transaction(transaction); let result = vm.vm.execute(VmExecutionMode::OneTx); assert!(!result.result.is_failed()); + assert_eq!( vm.get_eth_balance(beneficiary.address), U256::from(888000088) diff --git a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs index 188941d74d7..436981dd158 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -5,7 +5,7 @@ use zksync_types::{get_nonce_key, Execute, U256}; use crate::{ interface::{ - dyn_tracers::vm_1_4_1::DynTracer, + dyn_tracers::vm_1_5_0::DynTracer, tracer::{TracerExecutionStatus, TracerExecutionStopReason}, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceHistoryEnabled, }, diff --git a/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs b/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs new file mode 100644 index 00000000000..e70234da780 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs @@ -0,0 +1,74 @@ +use zk_evm_1_5_0::zkevm_opcode_defs::p256; +use zksync_system_constants::P256VERIFY_PRECOMPILE_ADDRESS; +use zksync_types::{web3::signing::keccak256, Execute, H256, U256}; +use zksync_utils::h256_to_u256; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{tests::tester::VmTesterBuilder, ExecutionResult, HistoryEnabled}, +}; + +#[test] +fn test_sekp256r1() { + // In this test, we aim to test whether a simple account interaction (without any fee logic) + // will work. The account will try to deploy a simple contract from integration tests. + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_execution_mode(TxExecutionMode::EthCall) + .with_random_rich_accounts(1) + .build(); + + let account = &mut vm.rich_accounts[0]; + + // The digest, secret key and public key were copied from the following test suit: `https://github.com/hyperledger/besu/blob/b6a6402be90339367d5bcabcd1cfd60df4832465/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256R1Test.java#L36` + let sk = p256::SecretKey::from_slice( + &hex::decode("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").unwrap(), + ) + .unwrap(); + let sk = p256::ecdsa::SigningKey::from(sk); + + let digest = keccak256(&hex::decode("5905238877c77421f73e43ee3da6f2d9e2ccad5fc942dcec0cbd25482935faaf416983fe165b1a045ee2bcd2e6dca3bdf46c4310a7461f9a37960ca672d3feb5473e253605fb1ddfd28065b53cb5858a8ad28175bf9bd386a5e471ea7a65c17cc934a9d791e91491eb3754d03799790fe2d308d16146d5c9b0d0debd97d79ce8").unwrap()); + let public_key_encoded = hex::decode("1ccbe91c075fc7f4f033bfa248db8fccd3565de94bbfb12f3c59ff46c271bf83ce4014c68811f9a21a1fdb2c0e6113e06db7ca93b7404e78dc7ccd5ca89a4ca9").unwrap(); + + let (sig, _) = sk.sign_prehash_recoverable(&digest).unwrap(); + let (r, s) = sig.split_bytes(); + + let mut encoded_r = [0u8; 32]; + encoded_r.copy_from_slice(&r); + + let mut encoded_s = [0u8; 32]; + encoded_s.copy_from_slice(&s); + + let mut x = [0u8; 32]; + x.copy_from_slice(&public_key_encoded[0..32]); + + let mut y = [0u8; 32]; + y.copy_from_slice(&public_key_encoded[32..64]); + + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: P256VERIFY_PRECOMPILE_ADDRESS, + calldata: [digest, encoded_r, encoded_s, x, y].concat(), + value: U256::zero(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx); + + let execution_result = vm.vm.execute(VmExecutionMode::Batch); + + let ExecutionResult::Success { output } = execution_result.result else { + panic!("batch failed") + }; + + let output = H256::from_slice(&output); + + assert_eq!( + h256_to_u256(output), + U256::from(1u32), + "verification was not successful" + ); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/storage.rs b/core/lib/multivm/src/versions/vm_latest/tests/storage.rs new file mode 100644 index 00000000000..42b59ba79bc --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/storage.rs @@ -0,0 +1,119 @@ +use ethabi::Token; +use zksync_contracts::{load_contract, read_bytecode}; +use zksync_types::{Address, Execute, U256}; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{tests::tester::VmTesterBuilder, HistoryEnabled}, +}; + +fn test_storage(first_tx_calldata: Vec, second_tx_calldata: Vec) -> u32 { + let bytecode = read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/storage/storage.sol/StorageTester.json", + ); + + let test_contract_address = Address::random(); + + // In this test, we aim to test whether a simple account interaction (without any fee logic) + // will work. The account will try to deploy a simple contract from integration tests. + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .with_custom_contracts(vec![(bytecode, test_contract_address, false)]) + .build(); + + let account = &mut vm.rich_accounts[0]; + + let tx1 = account.get_l2_tx_for_execute( + Execute { + contract_address: test_contract_address, + calldata: first_tx_calldata, + value: 0.into(), + factory_deps: None, + }, + None, + ); + + let tx2 = account.get_l2_tx_for_execute( + Execute { + contract_address: test_contract_address, + calldata: second_tx_calldata, + value: 0.into(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx1); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "First tx failed"); + + vm.vm.push_transaction(tx2); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Second tx failed"); + result.statistics.pubdata_published +} + +fn test_storage_one_tx(second_tx_calldata: Vec) -> u32 { + test_storage(vec![], second_tx_calldata) +} + +#[test] +fn test_storage_behavior() { + let contract = load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/storage/storage.sol/StorageTester.json", + ); + + // In all of the tests below we provide the first tx to ensure that the tracers will not include + // the statistics from the start of the bootloader and will only include those for the transaction itself. + + let base_pubdata = test_storage_one_tx(vec![]); + let simple_test_pubdata = test_storage_one_tx( + contract + .function("simpleWrite") + .unwrap() + .encode_input(&[]) + .unwrap(), + ); + let resetting_write_pubdata = test_storage_one_tx( + contract + .function("resettingWrite") + .unwrap() + .encode_input(&[]) + .unwrap(), + ); + let resetting_write_via_revert_pubdata = test_storage_one_tx( + contract + .function("resettingWriteViaRevert") + .unwrap() + .encode_input(&[]) + .unwrap(), + ); + + assert_eq!(simple_test_pubdata - base_pubdata, 65); + assert_eq!(resetting_write_pubdata - base_pubdata, 34); + assert_eq!(resetting_write_via_revert_pubdata - base_pubdata, 34); +} + +#[test] +fn test_transient_storage_behavior() { + let contract = load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/storage/storage.sol/StorageTester.json", + ); + + let first_tstore_test = contract + .function("testTransientStore") + .unwrap() + .encode_input(&[]) + .unwrap(); + // Second transaction checks that, as expected, the transient storage is cleared after the first transaction. + let second_tstore_test = contract + .function("assertTValue") + .unwrap() + .encode_input(&[Token::Uint(U256::zero())]) + .unwrap(); + + test_storage(first_tstore_test, second_tstore_test); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/inner_state.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/inner_state.rs index 67a6844ce93..10282235136 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/inner_state.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tester/inner_state.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use zk_evm_1_4_1::{aux_structures::Timestamp, vm_state::VmLocalState}; +use zk_evm_1_5_0::{aux_structures::Timestamp, vm_state::VmLocalState}; use zksync_state::WriteStorage; use zksync_types::{StorageKey, StorageValue, U256}; @@ -54,10 +54,10 @@ pub(crate) struct StorageOracleInnerState { pub(crate) frames_stack: AppDataFrameManagerWithHistory, H>, - pub(crate) pre_paid_changes: HistoryRecorder, H>, pub(crate) paid_changes: HistoryRecorder, H>, pub(crate) initial_values: HistoryRecorder, H>, - pub(crate) returned_refunds: HistoryRecorder, H>, + pub(crate) returned_io_refunds: HistoryRecorder, H>, + pub(crate) returned_pubdata_costs: HistoryRecorder, H>, } #[derive(Clone, PartialEq, Debug)] @@ -79,7 +79,7 @@ pub(crate) struct VmInstanceInnerState { impl Vm { // Dump inner state of the VM. - pub(crate) fn dump_inner_state(&self) -> VmInstanceInnerState { + pub(crate) fn dump_inner_state(&self) -> VmInstanceInnerState { let event_sink = self.state.event_sink.clone(); let precompile_processor_state = PrecompileProcessorTestInnerState { timestamp_history: self.state.precompiles_processor.timestamp_history.clone(), @@ -111,11 +111,11 @@ impl Vm { .modified_storage_keys() .clone(), ), - frames_stack: self.state.storage.frames_stack.clone(), - pre_paid_changes: self.state.storage.pre_paid_changes.clone(), + frames_stack: self.state.storage.storage_frames_stack.clone(), paid_changes: self.state.storage.paid_changes.clone(), initial_values: self.state.storage.initial_values.clone(), - returned_refunds: self.state.storage.returned_refunds.clone(), + returned_io_refunds: self.state.storage.returned_io_refunds.clone(), + returned_pubdata_costs: self.state.storage.returned_pubdata_costs.clone(), }; let local_state = self.state.local_state.clone(); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs b/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs new file mode 100644 index 00000000000..7e2ad46cdb2 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs @@ -0,0 +1,220 @@ +use ethabi::Token; +use zksync_contracts::{load_contract, read_bytecode}; +use zksync_system_constants::L2_ETH_TOKEN_ADDRESS; +use zksync_types::{utils::storage_key_for_eth_balance, AccountTreeId, Address, Execute, U256}; +use zksync_utils::u256_to_h256; + +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterface}, + vm_latest::{ + tests::{ + tester::{get_empty_storage, VmTesterBuilder}, + utils::get_balance, + }, + HistoryEnabled, + }, +}; + +enum TestOptions { + Send(U256), + Transfer(U256), +} + +fn test_send_or_transfer(test_option: TestOptions) { + let test_bytecode = read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/TransferTest.json", + ); + let recipeint_bytecode = read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/Recipient.json", + ); + let test_abi = load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/TransferTest.json", + ); + + let test_contract_address = Address::random(); + let recipient_address = Address::random(); + + let (value, calldata) = match test_option { + TestOptions::Send(value) => ( + value, + test_abi + .function("send") + .unwrap() + .encode_input(&[Token::Address(recipient_address), Token::Uint(value)]) + .unwrap(), + ), + TestOptions::Transfer(value) => ( + value, + test_abi + .function("transfer") + .unwrap() + .encode_input(&[Token::Address(recipient_address), Token::Uint(value)]) + .unwrap(), + ), + }; + + let mut storage = get_empty_storage(); + storage.set_value( + storage_key_for_eth_balance(&test_contract_address), + u256_to_h256(value), + ); + + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ + (test_bytecode, test_contract_address, false), + (recipeint_bytecode, recipient_address, false), + ]) + .build(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: test_contract_address, + calldata, + value: U256::zero(), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx); + let tx_result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !tx_result.result.is_failed(), + "Transaction wasn't successful" + ); + + let batch_result = vm.vm.execute(VmExecutionMode::Batch); + assert!(!batch_result.result.is_failed(), "Batch wasn't successful"); + + let new_recipient_balance = get_balance( + AccountTreeId::new(L2_ETH_TOKEN_ADDRESS), + &recipient_address, + vm.vm.state.storage.storage.get_ptr(), + ); + + assert_eq!(new_recipient_balance, value); +} + +#[test] +fn test_send_and_transfer() { + test_send_or_transfer(TestOptions::Send(U256::zero())); + test_send_or_transfer(TestOptions::Send(U256::from(10).pow(18.into()))); + test_send_or_transfer(TestOptions::Transfer(U256::zero())); + test_send_or_transfer(TestOptions::Transfer(U256::from(10).pow(18.into()))); +} + +fn test_reentrancy_protection_send_or_transfer(test_option: TestOptions) { + let test_bytecode = read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/TransferTest.json", + ); + let reentrant_recipeint_bytecode = read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/ReentrantRecipient.json", + ); + let test_abi = load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/TransferTest.json", + ); + let reentrant_recipient_abi = load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/ReentrantRecipient.json", + ); + + let test_contract_address = Address::random(); + let reentrant_recipeint_address = Address::random(); + + let (value, calldata) = match test_option { + TestOptions::Send(value) => ( + value, + test_abi + .function("send") + .unwrap() + .encode_input(&[ + Token::Address(reentrant_recipeint_address), + Token::Uint(value), + ]) + .unwrap(), + ), + TestOptions::Transfer(value) => ( + value, + test_abi + .function("transfer") + .unwrap() + .encode_input(&[ + Token::Address(reentrant_recipeint_address), + Token::Uint(value), + ]) + .unwrap(), + ), + }; + + let mut vm = VmTesterBuilder::new(HistoryEnabled) + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ + (test_bytecode, test_contract_address, false), + ( + reentrant_recipeint_bytecode, + reentrant_recipeint_address, + false, + ), + ]) + .build(); + + // First transaction, the job of which is to warm up the slots for balance of the recipient as well as its storage variable. + let account = &mut vm.rich_accounts[0]; + let tx1 = account.get_l2_tx_for_execute( + Execute { + contract_address: reentrant_recipeint_address, + calldata: reentrant_recipient_abi + .function("setX") + .unwrap() + .encode_input(&[]) + .unwrap(), + value: U256::from(1), + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx1); + let tx1_result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !tx1_result.result.is_failed(), + "Transaction 1 wasn't successful" + ); + + let tx2 = account.get_l2_tx_for_execute( + Execute { + contract_address: test_contract_address, + calldata, + value, + factory_deps: None, + }, + None, + ); + + vm.vm.push_transaction(tx2); + let tx2_result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + tx2_result.result.is_failed(), + "Transaction 2 should have failed, but it succeeded" + ); + + let batch_result = vm.vm.execute(VmExecutionMode::Batch); + assert!(!batch_result.result.is_failed(), "Batch wasn't successful"); +} + +#[test] +fn test_reentrancy_protection_send_and_transfer() { + test_reentrancy_protection_send_or_transfer(TestOptions::Send(U256::zero())); + test_reentrancy_protection_send_or_transfer(TestOptions::Send(U256::from(10).pow(18.into()))); + test_reentrancy_protection_send_or_transfer(TestOptions::Transfer(U256::zero())); + test_reentrancy_protection_send_or_transfer(TestOptions::Transfer( + U256::from(10).pow(18.into()), + )); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs index c2c91d7be28..559cf588453 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::aux_structures::Timestamp; +use zk_evm_1_5_0::aux_structures::Timestamp; use zksync_contracts::{deployer_contract, load_sys_contract, read_bytecode}; use zksync_state::WriteStorage; use zksync_test_account::TxType; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs index eeab7a91f51..37bdd0cef8e 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs @@ -116,6 +116,12 @@ pub(crate) fn read_precompiles_contract() -> Vec { ) } +pub(crate) fn load_precompiles_contract() -> Contract { + load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", + ) +} + pub(crate) fn read_complex_upgrade() -> Vec { read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json") } diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs index dc5d0caa77c..a570d3bd99b 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_capacity.rs @@ -1,4 +1,4 @@ -use circuit_sequencer_api_1_4_2::{geometry_config::get_geometry_config, toolset::GeometryConfig}; +use circuit_sequencer_api_1_5_0::{geometry_config::get_geometry_config, toolset::GeometryConfig}; use zksync_types::circuit::{CircuitCycleStatistic, CircuitStatistic}; // "Rich addressing" opcodes are opcodes that can write their return value/read the input onto the stack @@ -14,6 +14,10 @@ pub(crate) const STORAGE_READ_LOG_DEMUXER_CYCLES: u32 = 1; pub(crate) const STORAGE_READ_STORAGE_SORTER_CYCLES: u32 = 1; pub(crate) const STORAGE_READ_STORAGE_APPLICATION_CYCLES: u32 = 1; +pub(crate) const TRANSIENT_STORAGE_READ_RAM_CYCLES: u32 = 1; +pub(crate) const TRANSIENT_STORAGE_READ_LOG_DEMUXER_CYCLES: u32 = 1; +pub(crate) const TRANSIENT_STORAGE_READ_TRANSIENT_STORAGE_CHECKER_CYCLES: u32 = 1; + pub(crate) const EVENT_RAM_CYCLES: u32 = 1; pub(crate) const EVENT_LOG_DEMUXER_CYCLES: u32 = 2; pub(crate) const EVENT_EVENTS_SORTER_CYCLES: u32 = 2; @@ -23,6 +27,10 @@ pub(crate) const STORAGE_WRITE_LOG_DEMUXER_CYCLES: u32 = 2; pub(crate) const STORAGE_WRITE_STORAGE_SORTER_CYCLES: u32 = 2; pub(crate) const STORAGE_WRITE_STORAGE_APPLICATION_CYCLES: u32 = 2; +pub(crate) const TRANSIENT_STORAGE_WRITE_RAM_CYCLES: u32 = 1; +pub(crate) const TRANSIENT_STORAGE_WRITE_LOG_DEMUXER_CYCLES: u32 = 2; +pub(crate) const TRANSIENT_STORAGE_WRITE_TRANSIENT_STORAGE_CHECKER_CYCLES: u32 = 2; + pub(crate) const FAR_CALL_RAM_CYCLES: u32 = 1; pub(crate) const FAR_CALL_STORAGE_SORTER_CYCLES: u32 = 1; pub(crate) const FAR_CALL_CODE_DECOMMITTER_SORTER_CYCLES: u32 = 1; @@ -40,6 +48,9 @@ pub(crate) const UMA_READ_RAM_CYCLES: u32 = 3; pub(crate) const PRECOMPILE_RAM_CYCLES: u32 = 1; pub(crate) const PRECOMPILE_LOG_DEMUXER_CYCLES: u32 = 1; +pub(crate) const LOG_DECOMMIT_RAM_CYCLES: u32 = 1; +pub(crate) const LOG_DECOMMIT_DECOMMITTER_SORTER_CYCLES: u32 = 1; + const GEOMETRY_CONFIG: GeometryConfig = get_geometry_config(); pub(crate) fn circuit_statistic_from_cycles(cycles: CircuitCycleStatistic) -> CircuitStatistic { @@ -64,6 +75,9 @@ pub(crate) fn circuit_statistic_from_cycles(cycles: CircuitCycleStatistic) -> Ci ecrecover: cycles.ecrecover_cycles as f32 / GEOMETRY_CONFIG.cycles_per_ecrecover_circuit as f32, sha256: cycles.sha256_cycles as f32 / GEOMETRY_CONFIG.cycles_per_sha256_circuit as f32, - secp256k1_verify: 0.0, + secp256k1_verify: cycles.secp256k1_verify_cycles as f32 + / GEOMETRY_CONFIG.cycles_per_secp256r1_verify_circuit as f32, + transient_storage_checker: cycles.transient_storage_checker_cycles as f32 + / GEOMETRY_CONFIG.cycles_per_transient_storage_sorter as f32, } } diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs index 55199811f16..7c3012d03f1 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/circuits_tracer.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ tracing::{BeforeExecutionData, VmLocalStateData}, zk_evm_abstractions::precompiles::PrecompileAddress, zkevm_opcode_defs::{LogOpcode, Opcode, UMAOpcode}, @@ -10,7 +10,7 @@ use zksync_types::circuit::CircuitCycleStatistic; use super::circuits_capacity::*; use crate::{ - interface::{dyn_tracers::vm_1_4_1::DynTracer, tracer::TracerExecutionStatus}, + interface::{dyn_tracers::vm_1_5_0::DynTracer, tracer::TracerExecutionStatus}, vm_latest::{ bootloader_state::BootloaderState, old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, @@ -60,11 +60,23 @@ impl DynTracer> for Circuits self.statistics.log_demuxer_cycles += STORAGE_READ_LOG_DEMUXER_CYCLES; self.statistics.storage_sorter_cycles += STORAGE_READ_STORAGE_SORTER_CYCLES; } + Opcode::Log(LogOpcode::TransientStorageRead) => { + self.statistics.ram_permutation_cycles += TRANSIENT_STORAGE_READ_RAM_CYCLES; + self.statistics.log_demuxer_cycles += TRANSIENT_STORAGE_READ_LOG_DEMUXER_CYCLES; + self.statistics.transient_storage_checker_cycles += + TRANSIENT_STORAGE_READ_TRANSIENT_STORAGE_CHECKER_CYCLES; + } Opcode::Log(LogOpcode::StorageWrite) => { self.statistics.ram_permutation_cycles += STORAGE_WRITE_RAM_CYCLES; self.statistics.log_demuxer_cycles += STORAGE_WRITE_LOG_DEMUXER_CYCLES; self.statistics.storage_sorter_cycles += STORAGE_WRITE_STORAGE_SORTER_CYCLES; } + Opcode::Log(LogOpcode::TransientStorageWrite) => { + self.statistics.ram_permutation_cycles += TRANSIENT_STORAGE_WRITE_RAM_CYCLES; + self.statistics.log_demuxer_cycles += TRANSIENT_STORAGE_WRITE_LOG_DEMUXER_CYCLES; + self.statistics.transient_storage_checker_cycles += + TRANSIENT_STORAGE_WRITE_TRANSIENT_STORAGE_CHECKER_CYCLES; + } Opcode::Log(LogOpcode::ToL1Message) | Opcode::Log(LogOpcode::Event) => { self.statistics.ram_permutation_cycles += EVENT_RAM_CYCLES; self.statistics.log_demuxer_cycles += EVENT_LOG_DEMUXER_CYCLES; @@ -74,6 +86,12 @@ impl DynTracer> for Circuits self.statistics.ram_permutation_cycles += PRECOMPILE_RAM_CYCLES; self.statistics.log_demuxer_cycles += PRECOMPILE_LOG_DEMUXER_CYCLES; } + Opcode::Log(LogOpcode::Decommit) => { + // Note, that for decommit the log demuxer circuit is not used. + self.statistics.ram_permutation_cycles += LOG_DECOMMIT_RAM_CYCLES; + self.statistics.code_decommitter_sorter_cycles += + LOG_DECOMMIT_DECOMMITTER_SORTER_CYCLES; + } Opcode::FarCall(_) => { self.statistics.ram_permutation_cycles += FAR_CALL_RAM_CYCLES; self.statistics.code_decommitter_sorter_cycles += @@ -81,11 +99,16 @@ impl DynTracer> for Circuits self.statistics.storage_sorter_cycles += FAR_CALL_STORAGE_SORTER_CYCLES; self.statistics.log_demuxer_cycles += FAR_CALL_LOG_DEMUXER_CYCLES; } - Opcode::UMA(UMAOpcode::AuxHeapWrite | UMAOpcode::HeapWrite) => { + Opcode::UMA( + UMAOpcode::AuxHeapWrite | UMAOpcode::HeapWrite | UMAOpcode::StaticMemoryWrite, + ) => { self.statistics.ram_permutation_cycles += UMA_WRITE_RAM_CYCLES; } Opcode::UMA( - UMAOpcode::AuxHeapRead | UMAOpcode::HeapRead | UMAOpcode::FatPointerRead, + UMAOpcode::AuxHeapRead + | UMAOpcode::HeapRead + | UMAOpcode::FatPointerRead + | UMAOpcode::StaticMemoryRead, ) => { self.statistics.ram_permutation_cycles += UMA_READ_RAM_CYCLES; } @@ -105,9 +128,10 @@ impl VmTracer for CircuitsTracer { ); self.last_written_keys_history_entry_checked = - Some(state.storage.written_keys.history().len()); + Some(state.storage.written_storage_keys.history().len()); - self.last_read_keys_history_entry_checked = Some(state.storage.read_keys.history().len()); + self.last_read_keys_history_entry_checked = + Some(state.storage.read_storage_keys.history().len()); self.last_precompile_inner_entry_checked = Some( state @@ -175,7 +199,7 @@ impl CircuitsTracer { let last_writes_history_entry_checked = self .last_written_keys_history_entry_checked .expect("Value must be set during init"); - let history = state.storage.written_keys.history(); + let history = state.storage.written_storage_keys.history(); for (_, history_event) in &history[last_writes_history_entry_checked..] { // We assume that only insertions may happen during a single VM inspection. assert!(history_event.value.is_none()); @@ -189,7 +213,7 @@ impl CircuitsTracer { let last_reads_history_entry_checked = self .last_read_keys_history_entry_checked .expect("Value must be set during init"); - let history = state.storage.read_keys.history(); + let history = state.storage.read_storage_keys.history(); for (_, history_event) in &history[last_reads_history_entry_checked..] { // We assume that only insertions may happen during a single VM inspection. assert!(history_event.value.is_none()); @@ -197,7 +221,7 @@ impl CircuitsTracer { // If the slot is already written to, then we've already taken 2 cycles into account. if !state .storage - .written_keys + .written_storage_keys .inner() .contains_key(&history_event.key) { @@ -227,6 +251,9 @@ impl CircuitsTracer { PrecompileAddress::Keccak256 => { self.statistics.keccak256_cycles += *cycles as u32; } + PrecompileAddress::Secp256r1Verify => { + self.statistics.secp256k1_verify_cycles += *cycles as u32; + } }; } self.last_precompile_inner_entry_checked = Some(inner.len()); diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs b/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs index 020e78bbe16..9b70f159230 100755 --- a/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/default_tracers.rs @@ -3,7 +3,7 @@ use std::{ marker::PhantomData, }; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ aux_structures::Timestamp, tracing::{ AfterDecodingData, AfterExecutionData, BeforeExecutionData, Tracer, VmLocalStateData, @@ -18,8 +18,8 @@ use super::PubdataTracer; use crate::{ glue::GlueInto, interface::{ + dyn_tracers::vm_1_5_0::DynTracer, tracer::{TracerExecutionStopReason, VmExecutionStopReason}, - traits::tracers::dyn_tracers::vm_1_4_1::DynTracer, types::tracer::TracerExecutionStatus, Halt, VmExecutionMode, }, @@ -29,10 +29,7 @@ use crate::{ old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, tracers::{ dispatcher::TracerDispatcher, - utils::{ - computational_gas_price, gas_spent_on_bytecodes_and_long_messages_this_opcode, - print_debug_if_needed, VmHook, - }, + utils::{computational_gas_price, print_debug_if_needed, VmHook}, CircuitsTracer, RefundsTracer, ResultTracer, }, types::internals::ZkSyncVmState, @@ -45,7 +42,6 @@ pub(crate) struct DefaultExecutionTracer { tx_has_been_processed: bool, execution_mode: VmExecutionMode, - pub(crate) gas_spent_on_bytecodes_and_long_messages: u32, // Amount of gas used during account validation. pub(crate) computational_gas_used: u32, // Maximum number of gas that we're allowed to use during account validation. @@ -83,7 +79,6 @@ impl DefaultExecutionTracer { Self { tx_has_been_processed: false, execution_mode, - gas_spent_on_bytecodes_and_long_messages: 0, computational_gas_used: 0, tx_validation_gas_limit: computational_gas_limit, in_account_validation: false, @@ -107,10 +102,6 @@ impl DefaultExecutionTracer { self.computational_gas_used > self.tx_validation_gas_limit } - pub(crate) fn gas_spent_on_pubdata(&self, vm_local_state: &VmLocalState) -> u32 { - self.gas_spent_on_bytecodes_and_long_messages + vm_local_state.spent_pubdata_counter - } - fn set_fictive_l2_block( &mut self, state: &mut ZkSyncVmState, @@ -217,7 +208,12 @@ impl Tracer for DefaultExecutionTracer { } let hook = VmHook::from_opcode_memory(&state, &data); - print_debug_if_needed(&hook, &state, memory); + print_debug_if_needed( + &hook, + &state, + memory, + self.result_tracer.get_latest_result_ptr(), + ); match hook { VmHook::TxHasEnded => self.tx_has_been_processed = true, @@ -227,9 +223,6 @@ impl Tracer for DefaultExecutionTracer { _ => {} } - self.gas_spent_on_bytecodes_and_long_messages += - gas_spent_on_bytecodes_and_long_messages_this_opcode(&state, &data); - dispatch_tracers!(self.before_execution(state, data, memory, self.storage.clone())); } @@ -240,7 +233,7 @@ impl Tracer for DefaultExecutionTracer { memory: &Self::SupportedMemory, ) { if let VmExecutionMode::Bootloader = self.execution_mode { - let (next_opcode, _, _) = zk_evm_1_4_1::vm_state::read_and_decode( + let (next_opcode, _, _) = zk_evm_1_5_0::vm_state::read_and_decode( state.vm_local_state, memory, &mut DummyTracer, diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs b/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs index b6c779303a7..7949f73bc20 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/dispatcher.rs @@ -1,11 +1,11 @@ -use zk_evm_1_4_1::tracing::{ +use zk_evm_1_5_0::tracing::{ AfterDecodingData, AfterExecutionData, BeforeExecutionData, VmLocalStateData, }; use zksync_state::{StoragePtr, WriteStorage}; use crate::{ interface::{ - dyn_tracers::vm_1_4_1::DynTracer, + dyn_tracers::vm_1_5_0::DynTracer, tracer::{TracerExecutionStatus, VmExecutionStopReason}, }, vm_latest::{ diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs index aa913a7d510..afa9c7990ee 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/pubdata_tracer.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; -use circuit_sequencer_api_1_4_2::sort_storage_access::sort_storage_access_queries; -use zk_evm_1_4_1::{ +use circuit_sequencer_api_1_5_0::sort_storage_access::sort_storage_access_queries; +use zk_evm_1_5_0::{ aux_structures::Timestamp, tracing::{BeforeExecutionData, VmLocalStateData}, }; @@ -18,7 +18,7 @@ use zksync_utils::{h256_to_u256, u256_to_bytes_be, u256_to_h256}; use crate::{ interface::{ - dyn_tracers::vm_1_4_1::DynTracer, + dyn_tracers::vm_1_5_0::DynTracer, tracer::{TracerExecutionStatus, TracerExecutionStopReason}, types::inputs::L1BatchEnv, VmExecutionMode, diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs b/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs index b1046ee3c0e..2fcece67953 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/refunds.rs @@ -1,55 +1,52 @@ use std::marker::PhantomData; use vise::{Buckets, EncodeLabelSet, EncodeLabelValue, Family, Histogram, Metrics}; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ aux_structures::Timestamp, tracing::{BeforeExecutionData, VmLocalStateData}, - vm_state::VmLocalState, - zkevm_opcode_defs::system_params::L1_MESSAGE_PUBDATA_BYTES, }; use zksync_state::{StoragePtr, WriteStorage}; -use zksync_system_constants::{PUBLISH_BYTECODE_OVERHEAD, SYSTEM_CONTEXT_ADDRESS}; -use zksync_types::{ - event::{extract_long_l2_to_l1_messages, extract_published_bytecodes}, - l2_to_l1_log::L2ToL1Log, - L1BatchNumber, H256, U256, -}; -use zksync_utils::{bytecode::bytecode_len_in_bytes, ceil_div_u256, u256_to_h256}; +use zksync_types::{H256, U256}; +use zksync_utils::ceil_div_u256; use crate::{ interface::{ - traits::tracers::dyn_tracers::vm_1_4_1::DynTracer, types::tracer::TracerExecutionStatus, + traits::tracers::dyn_tracers::vm_1_5_0::DynTracer, types::tracer::TracerExecutionStatus, L1BatchEnv, Refunds, }, vm_latest::{ bootloader_state::BootloaderState, constants::{BOOTLOADER_HEAP_PAGE, OPERATOR_REFUNDS_OFFSET, TX_GAS_LIMIT_OFFSET}, - old_vm::{events::merge_events, history_recorder::HistoryMode, memory::SimpleMemory}, + old_vm::{history_recorder::HistoryMode, memory::SimpleMemory}, tracers::{ traits::VmTracer, - utils::{ - gas_spent_on_bytecodes_and_long_messages_this_opcode, get_vm_hook_params, VmHook, - }, + utils::{get_vm_hook_params, VmHook}, }, types::internals::ZkSyncVmState, utils::fee::get_batch_base_fee, }, }; +#[derive(Debug, Clone, Copy)] +struct RefundRequest { + refund: u64, + gas_spent_on_pubdata: u64, + used_gas_per_pubdata_byte: u32, +} + /// Tracer responsible for collecting information about refunds. #[derive(Debug, Clone)] pub(crate) struct RefundsTracer { // Some(x) means that the bootloader has asked the operator // to provide the refund the user, where `x` is the refund proposed // by the bootloader itself. - pending_operator_refund: Option, + pending_refund_request: Option, refund_gas: u64, operator_refund: Option, timestamp_initial: Timestamp, timestamp_before_cycle: Timestamp, computational_gas_remaining_before: u32, spent_pubdata_counter_before: u32, - gas_spent_on_bytecodes_and_long_messages: u32, l1_batch: L1BatchEnv, pubdata_published: u32, _phantom: PhantomData, @@ -58,14 +55,13 @@ pub(crate) struct RefundsTracer { impl RefundsTracer { pub(crate) fn new(l1_batch: L1BatchEnv) -> Self { Self { - pending_operator_refund: None, + pending_refund_request: None, refund_gas: 0, operator_refund: None, timestamp_initial: Timestamp(0), timestamp_before_cycle: Timestamp(0), computational_gas_remaining_before: 0, spent_pubdata_counter_before: 0, - gas_spent_on_bytecodes_and_long_messages: 0, l1_batch, pubdata_published: 0, _phantom: PhantomData, @@ -74,12 +70,12 @@ impl RefundsTracer { } impl RefundsTracer { - fn requested_refund(&self) -> Option { - self.pending_operator_refund + fn requested_refund(&self) -> Option { + self.pending_refund_request } fn set_refund_as_done(&mut self) { - self.pending_operator_refund = None; + self.pending_refund_request = None; } fn block_overhead_refund(&mut self) -> u64 { @@ -156,10 +152,6 @@ impl RefundsTracer { ceil_div_u256(refund_eth, effective_gas_price.into()).as_u64() } - pub(crate) fn gas_spent_on_pubdata(&self, vm_local_state: &VmLocalState) -> u32 { - self.gas_spent_on_bytecodes_and_long_messages + vm_local_state.spent_pubdata_counter - } - pub(crate) fn pubdata_published(&self) -> u32 { self.pubdata_published } @@ -178,13 +170,14 @@ impl DynTracer> for RefundsTracer { match hook { VmHook::NotifyAboutRefund => self.refund_gas = get_vm_hook_params(memory)[0].as_u64(), VmHook::AskOperatorForRefund => { - self.pending_operator_refund = Some(get_vm_hook_params(memory)[0].as_u64()) + self.pending_refund_request = Some(RefundRequest { + refund: get_vm_hook_params(memory)[0].as_u64(), + gas_spent_on_pubdata: get_vm_hook_params(memory)[1].as_u64(), + used_gas_per_pubdata_byte: get_vm_hook_params(memory)[2].as_u32(), + }) } _ => {} } - - self.gas_spent_on_bytecodes_and_long_messages += - gas_spent_on_bytecodes_and_long_messages_this_opcode(&state, &data); } } @@ -193,7 +186,7 @@ impl VmTracer for RefundsTracer { self.timestamp_initial = Timestamp(state.local_state.timestamp); self.computational_gas_remaining_before = state.local_state.callstack.current.ergs_remaining; - self.spent_pubdata_counter_before = state.local_state.spent_pubdata_counter; + self.spent_pubdata_counter_before = state.local_state.pubdata_revert_counter.0 as u32; } fn finish_cycle( @@ -231,8 +224,6 @@ impl VmTracer for RefundsTracer { self.operator_refund.is_none(), "Operator was asked for refund two times" ); - let gas_spent_on_pubdata = (self.gas_spent_on_pubdata(&state.local_state) - - self.spent_pubdata_counter_before) as u64; let current_tx_index = bootloader_state.current_tx(); let tx_description_offset = @@ -246,33 +237,30 @@ impl VmTracer for RefundsTracer { .value .as_u64(); - let used_published_storage_slots = state - .storage - .save_paid_changes(Timestamp(state.local_state.timestamp)); - - let pubdata_published = pubdata_published( - state, - used_published_storage_slots, - self.timestamp_initial, - self.l1_batch.number, + assert!( + state.local_state.pubdata_revert_counter.0 >= 0, + "Global counter is negative" ); + let current_counter = state.local_state.pubdata_revert_counter.0 as u32; - self.pubdata_published = pubdata_published; - let current_ergs_per_pubdata_byte = state.local_state.current_ergs_per_pubdata_byte; + self.pubdata_published = + current_counter.saturating_sub(self.spent_pubdata_counter_before); let tx_body_refund = self.tx_body_refund( - bootloader_refund, - gas_spent_on_pubdata, + bootloader_refund.refund, + bootloader_refund.gas_spent_on_pubdata, tx_gas_limit, - current_ergs_per_pubdata_byte, - pubdata_published, + bootloader_refund.used_gas_per_pubdata_byte, + self.pubdata_published, bootloader_state.last_l2_block().txs.last().unwrap().hash, ); - if tx_body_refund < bootloader_refund { + if tx_body_refund < bootloader_refund.refund { tracing::error!( - "Suggested tx body refund is less than bootloader refund. Tx body refund: {tx_body_refund}, \ - bootloader refund: {bootloader_refund}" + "Suggested tx body refund is less than bootloader refund. Tx body refund: {}, \ + bootloader refund: {}", + tx_body_refund, + bootloader_refund.refund ); } @@ -291,10 +279,12 @@ impl VmTracer for RefundsTracer { self.operator_refund = Some(refund_to_propose); self.set_refund_as_done(); - if tx_gas_limit < bootloader_refund { + if tx_gas_limit < bootloader_refund.refund { tracing::error!( - "Tx gas limit is less than bootloader refund. Tx gas limit: {tx_gas_limit}, \ - bootloader refund: {bootloader_refund}" + "Tx gas limit is less than bootloader refund. Tx gas limit: {}, \ + bootloader refund: {}", + tx_gas_limit, + bootloader_refund.refund ); } if tx_gas_limit < refund_to_propose { @@ -305,58 +295,14 @@ impl VmTracer for RefundsTracer { } METRICS.refund[&RefundType::Bootloader] - .observe(bootloader_refund as f64 / tx_gas_limit as f64 * 100.0); + .observe(bootloader_refund.refund as f64 / tx_gas_limit as f64 * 100.0); METRICS.refund[&RefundType::Operator] .observe(refund_to_propose as f64 / tx_gas_limit as f64 * 100.0); - let refund_diff = - (refund_to_propose as f64 - bootloader_refund as f64) / tx_gas_limit as f64 * 100.0; + let refund_diff = (refund_to_propose as f64 - bootloader_refund.refund as f64) + / tx_gas_limit as f64 + * 100.0; METRICS.refund_diff.observe(refund_diff); } TracerExecutionStatus::Continue } } - -/// Returns the given transactions' gas limit - by reading it directly from the VM memory. -pub(crate) fn pubdata_published( - state: &ZkSyncVmState, - storage_writes_pubdata_published: u32, - from_timestamp: Timestamp, - batch_number: L1BatchNumber, -) -> u32 { - let (raw_events, l1_messages) = state - .event_sink - .get_events_and_l2_l1_logs_after_timestamp(from_timestamp); - let events: Vec<_> = merge_events(raw_events) - .into_iter() - .map(|e| e.into_vm_event(batch_number)) - .collect(); - // For the first transaction in L1 batch there may be (it depends on the execution mode) an L2->L1 log - // that is sent by `SystemContext` in `setNewBlock`. It's a part of the L1 batch pubdata overhead and not the transaction itself. - let l2_l1_logs_bytes = (l1_messages - .into_iter() - .map(|log| L2ToL1Log { - shard_id: log.shard_id, - is_service: log.is_first, - tx_number_in_block: log.tx_number_in_block, - sender: log.address, - key: u256_to_h256(log.key), - value: u256_to_h256(log.value), - }) - .filter(|log| log.sender != SYSTEM_CONTEXT_ADDRESS) - .count() as u32) - * L1_MESSAGE_PUBDATA_BYTES; - let l2_l1_long_messages_bytes: u32 = extract_long_l2_to_l1_messages(&events) - .iter() - .map(|event| event.len() as u32) - .sum(); - - let published_bytecode_bytes: u32 = extract_published_bytecodes(&events) - .iter() - .map(|bytecodehash| bytecode_len_in_bytes(*bytecodehash) as u32 + PUBLISH_BYTECODE_OVERHEAD) - .sum(); - - storage_writes_pubdata_published - + l2_l1_logs_bytes - + l2_l1_long_messages_bytes - + published_bytecode_bytes -} diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs index 71a7dcb3738..0f0fa430eb0 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/result_tracer.rs @@ -1,16 +1,17 @@ use std::marker::PhantomData; -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ tracing::{AfterDecodingData, BeforeExecutionData, VmLocalStateData}, vm_state::{ErrorFlags, VmLocalState}, - zkevm_opcode_defs::FatPointer, + zkevm_opcode_defs::{FatPointer, Opcode, RET_IMPLICIT_RETURNDATA_PARAMS_REGISTER}, }; use zksync_state::{StoragePtr, WriteStorage}; +use zksync_system_constants::BOOTLOADER_ADDRESS; use zksync_types::U256; use crate::{ interface::{ - tracer::VmExecutionStopReason, traits::tracers::dyn_tracers::vm_1_4_1::DynTracer, + tracer::VmExecutionStopReason, traits::tracers::dyn_tracers::vm_1_5_0::DynTracer, types::tracer::TracerExecutionStopReason, ExecutionResult, Halt, TxRevertReason, VmExecutionMode, VmRevertReason, }, @@ -33,12 +34,74 @@ enum Result { Halt { reason: Halt }, } +/// Responsible for tracing the far calls from the bootloader. +#[derive(Debug, Copy, Clone, Default)] +#[allow(clippy::enum_variant_names)] +enum FarCallTracker { + #[default] + NoFarCallObserved, + FarCallObserved(usize), + ReturndataObserved(FatPointer), +} + +impl FarCallTracker { + // Should be called before opcode is executed + fn far_call_observed(&mut self, local_state: &VmLocalStateData<'_>) { + match &self { + FarCallTracker::NoFarCallObserved => { + *self = FarCallTracker::FarCallObserved( + local_state.vm_local_state.callstack.inner.len(), + ); + } + FarCallTracker::FarCallObserved(_) => { + panic!("Two far calls from bootloader in a row is not possible") + } + FarCallTracker::ReturndataObserved(_) => { + // Now we forget about the load returndata + *self = FarCallTracker::FarCallObserved( + local_state.vm_local_state.callstack.inner.len(), + ); + } + } + } + + // should be called after opcode is executed + fn return_observed(&mut self, local_state: &VmLocalStateData<'_>) { + if let FarCallTracker::FarCallObserved(x) = &self { + if *x != local_state.vm_local_state.callstack.inner.len() { + return; + } + + let returndata_pointer = local_state.vm_local_state.registers + [RET_IMPLICIT_RETURNDATA_PARAMS_REGISTER as usize]; + + assert!( + returndata_pointer.is_pointer, + "Returndata pointer is not a pointer" + ); + + *self = + FarCallTracker::ReturndataObserved(FatPointer::from_u256(returndata_pointer.value)); + } + } + + fn get_latest_returndata(self) -> Option { + match self { + FarCallTracker::ReturndataObserved(x) => Some(x), + _ => None, + } + } +} + /// Tracer responsible for handling the VM execution result. #[derive(Debug, Clone)] pub(crate) struct ResultTracer { result: Option, bootloader_out_of_gas: bool, execution_mode: VmExecutionMode, + + far_call_tracker: FarCallTracker, + _phantom: PhantomData, } @@ -48,6 +111,7 @@ impl ResultTracer { result: None, bootloader_out_of_gas: false, execution_mode, + far_call_tracker: Default::default(), _phantom: PhantomData, } } @@ -88,8 +152,11 @@ impl DynTracer> for ResultTracer { if let VmHook::ExecutionResult = hook { let vm_hook_params = get_vm_hook_params(memory); let success = vm_hook_params[0]; - let returndata_ptr = FatPointer::from_u256(vm_hook_params[1]); - let returndata = read_pointer(memory, returndata_ptr); + let returndata = self + .far_call_tracker + .get_latest_returndata() + .map(|ptr| read_pointer(memory, ptr)) + .unwrap_or_default(); if success == U256::zero() { self.result = Some(Result::Error { // Tx has reverted, without bootloader error, we can simply parse the revert reason @@ -101,6 +168,29 @@ impl DynTracer> for ResultTracer { }); } } + + if state.vm_local_state.callstack.current.this_address == BOOTLOADER_ADDRESS { + let opcode_variant = data.opcode.variant; + if let Opcode::FarCall(_) = opcode_variant.opcode { + self.far_call_tracker.far_call_observed(&state); + } + } + } + + fn after_execution( + &mut self, + state: VmLocalStateData<'_>, + data: zk_evm_1_5_0::tracing::AfterExecutionData, + _memory: &SimpleMemory, + _storage: StoragePtr, + ) { + let opcode_variant = data.opcode.variant; + + if state.vm_local_state.callstack.current.this_address == BOOTLOADER_ADDRESS { + if let Opcode::Ret(_) = opcode_variant.opcode { + self.far_call_tracker.return_observed(&state); + } + } } } @@ -230,6 +320,10 @@ impl ResultTracer { Result::Halt { reason } => ExecutionResult::Halt { reason }, } } + + pub(crate) fn get_latest_result_ptr(&self) -> Option { + self.far_call_tracker.get_latest_returndata() + } } pub(crate) fn tx_has_failed( diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs b/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs index 49cdc0b2839..5800ff4a3bc 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/traits.rs @@ -2,7 +2,7 @@ use zksync_state::WriteStorage; use crate::{ interface::{ - dyn_tracers::vm_1_4_1::DynTracer, + dyn_tracers::vm_1_5_0::DynTracer, tracer::{TracerExecutionStatus, VmExecutionStopReason}, }, vm_latest::{ diff --git a/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs b/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs index 78129790c44..4606386ed0a 100644 --- a/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs +++ b/core/lib/multivm/src/versions/vm_latest/tracers/utils.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::{ +use zk_evm_1_5_0::{ aux_structures::MemoryPage, tracing::{BeforeExecutionData, VmLocalStateData}, zkevm_opcode_defs::{ @@ -6,8 +6,7 @@ use zk_evm_1_4_1::{ }, }; use zksync_system_constants::{ - ECRECOVER_PRECOMPILE_ADDRESS, KECCAK256_PRECOMPILE_ADDRESS, KNOWN_CODES_STORAGE_ADDRESS, - L1_MESSENGER_ADDRESS, SHA256_PRECOMPILE_ADDRESS, + ECRECOVER_PRECOMPILE_ADDRESS, KECCAK256_PRECOMPILE_ADDRESS, SHA256_PRECOMPILE_ADDRESS, }; use zksync_types::U256; use zksync_utils::u256_to_h256; @@ -138,10 +137,15 @@ pub(crate) fn read_pointer( /// Outputs the returndata for the latest call. /// This is usually used to output the revert reason. -pub(crate) fn get_debug_returndata(memory: &SimpleMemory) -> String { - let vm_hook_params: Vec<_> = get_vm_hook_params(memory); - let returndata_ptr = FatPointer::from_u256(vm_hook_params[0]); - let returndata = read_pointer(memory, returndata_ptr); +pub(crate) fn get_debug_returndata( + memory: &SimpleMemory, + latest_returndata_ptr: Option, +) -> String { + let returndata = if let Some(ptr) = latest_returndata_ptr { + read_pointer(memory, ptr) + } else { + vec![] + }; format!("0x{}", hex::encode(returndata)) } @@ -151,10 +155,11 @@ pub(crate) fn print_debug_if_needed( hook: &VmHook, state: &VmLocalStateData<'_>, memory: &SimpleMemory, + latest_returndata_ptr: Option, ) { let log = match hook { VmHook::DebugLog => get_debug_log(state, memory), - VmHook::DebugReturnData => get_debug_returndata(memory), + VmHook::DebugReturnData => get_debug_returndata(memory, latest_returndata_ptr), _ => return, }; @@ -188,26 +193,6 @@ pub(crate) fn computational_gas_price( base_price + precompile_price } -pub(crate) fn gas_spent_on_bytecodes_and_long_messages_this_opcode( - state: &VmLocalStateData<'_>, - data: &BeforeExecutionData, -) -> u32 { - if data.opcode.variant.opcode == Opcode::Log(LogOpcode::PrecompileCall) { - let current_stack = state.vm_local_state.callstack.get_current_stack(); - // Trace for precompile calls from `KNOWN_CODES_STORAGE_ADDRESS` and `L1_MESSENGER_ADDRESS` that burn some gas. - // Note, that if there is less gas left than requested to burn it will be burnt anyway. - if current_stack.this_address == KNOWN_CODES_STORAGE_ADDRESS - || current_stack.this_address == L1_MESSENGER_ADDRESS - { - std::cmp::min(data.src1_value.value.as_u32(), current_stack.ergs_remaining) - } else { - 0 - } - } else { - 0 - } -} - pub(crate) fn get_calldata_page_via_abi(far_call_abi: &FarCallABI, base_page: MemoryPage) -> u32 { match far_call_abi.forwarding_mode { FarCallForwardPageType::ForwardFatPointer => { diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs b/core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs index 0633cf61cda..76b466fd605 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/internals/snapshot.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::vm_state::VmLocalState; +use zk_evm_1_5_0::vm_state::VmLocalState; use crate::vm_latest::bootloader_state::BootloaderStateSnapshot; diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs b/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs index 2445e1bdb72..a59d248bde3 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/internals/transaction_data.rs @@ -12,7 +12,7 @@ use zksync_types::{ use zksync_utils::{address_to_h256, bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256}; use crate::vm_latest::{ - constants::{L1_TX_TYPE, MAX_GAS_PER_PUBDATA_BYTE, PRIORITY_TX_MAX_GAS_LIMIT}, + constants::{MAX_GAS_PER_PUBDATA_BYTE, TX_MAX_COMPUTE_GAS_LIMIT}, utils::overhead::derive_overhead, }; @@ -215,14 +215,8 @@ impl TransactionData { } pub(crate) fn trusted_ergs_limit(&self) -> U256 { - if self.tx_type == L1_TX_TYPE { - // In case we get a users' transactions with unexpected gas limit, we do not let it have more than - // a certain limit - return U256::from(PRIORITY_TX_MAX_GAS_LIMIT).min(self.gas_limit); - } - - // TODO (EVM-66): correctly calculate the trusted gas limit for a transaction - self.gas_limit + // No transaction is allowed to spend more than `TX_MAX_COMPUTE_GAS_LIMIT` gas on compute. + U256::from(TX_MAX_COMPUTE_GAS_LIMIT).min(self.gas_limit) } pub(crate) fn tx_hash(&self, chain_id: L2ChainId) -> H256 { diff --git a/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs b/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs index 0d414eca64a..2207f640fcc 100644 --- a/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs +++ b/core/lib/multivm/src/versions/vm_latest/types/internals/vm_state.rs @@ -1,6 +1,6 @@ use circuit_sequencer_api_1_3_3::INITIAL_MONOTONIC_CYCLE_COUNTER; -use zk_evm_1_4_1::{ - aux_structures::{MemoryPage, Timestamp}, +use zk_evm_1_5_0::{ + aux_structures::{MemoryPage, PubdataCost, Timestamp}, block_properties::BlockProperties, vm_state::{CallStackEntry, PrimitiveValue, VmState}, witness_trace::DummyTracer, @@ -128,6 +128,11 @@ pub(crate) fn new_vm_state( default_aa_code_hash: h256_to_u256( system_env.base_system_smart_contracts.default_aa.hash, ), + // For now, the default account hash is used as the code hash for the EVM simulator. + // In the 1.5.0 version, it is not possible to instantiate EVM bytecode. + evm_simulator_code_hash: h256_to_u256( + system_env.base_system_smart_contracts.default_aa.hash, + ), zkporter_is_available: system_env.zk_porter_available, }, ); @@ -154,6 +159,8 @@ pub(crate) fn new_vm_state( is_static: false, is_local_frame: false, context_u128_value: 0, + total_pubdata_spent: PubdataCost(0), + stipend: 0, }; // We consider the contract that is being run as a bootloader @@ -161,7 +168,6 @@ pub(crate) fn new_vm_state( vm.local_state.timestamp = STARTING_TIMESTAMP; vm.local_state.memory_page_counter = STARTING_BASE_PAGE; vm.local_state.monotonic_cycle_counter = INITIAL_MONOTONIC_CYCLE_COUNTER; - vm.local_state.current_ergs_per_pubdata_byte = 0; vm.local_state.registers[0] = formal_calldata_abi(); // Deleting all the historical records brought by the initial diff --git a/core/lib/multivm/src/versions/vm_latest/utils/logs.rs b/core/lib/multivm/src/versions/vm_latest/utils/logs.rs index 27effbdbbea..4deea36f09f 100644 --- a/core/lib/multivm/src/versions/vm_latest/utils/logs.rs +++ b/core/lib/multivm/src/versions/vm_latest/utils/logs.rs @@ -1,4 +1,4 @@ -use zk_evm_1_4_1::aux_structures::{LogQuery, Timestamp}; +use zk_evm_1_5_0::aux_structures::{LogQuery, Timestamp}; use zksync_state::WriteStorage; use zksync_types::{l2_to_l1_log::L2ToL1Log, StorageLogQueryType, VmEvent}; diff --git a/core/lib/multivm/src/versions/vm_latest/vm.rs b/core/lib/multivm/src/versions/vm_latest/vm.rs index 8850a7a9d21..a2e4f68a49b 100644 --- a/core/lib/multivm/src/versions/vm_latest/vm.rs +++ b/core/lib/multivm/src/versions/vm_latest/vm.rs @@ -1,4 +1,4 @@ -use circuit_sequencer_api_1_4_2::sort_storage_access::sort_storage_access_queries; +use circuit_sequencer_api_1_5_0::sort_storage_access::sort_storage_access_queries; use zksync_state::{StoragePtr, WriteStorage}; use zksync_types::{ event::extract_l2tol1logs_from_l1_messenger, @@ -29,7 +29,7 @@ use crate::{ pub struct Vm { pub(crate) bootloader_state: BootloaderState, // Current state and oracles of virtual machine - pub(crate) state: ZkSyncVmState, + pub(crate) state: ZkSyncVmState, pub(crate) storage: StoragePtr, pub(crate) system_env: SystemEnv, pub(crate) batch_env: L1BatchEnv, @@ -39,7 +39,7 @@ pub struct Vm { } impl VmInterface for Vm { - type TracerDispatcher = TracerDispatcher; + type TracerDispatcher = TracerDispatcher; fn new(batch_env: L1BatchEnv, system_env: SystemEnv, storage: StoragePtr) -> Self { let (state, bootloader_state) = new_vm_state(storage.clone(), &system_env, &batch_env); @@ -106,7 +106,6 @@ impl VmInterface for Vm { + self.state.storage.get_final_log_queries().len(); let storage_log_queries = self.state.storage.get_final_log_queries(); - let deduped_storage_log_queries = sort_storage_access_queries(storage_log_queries.iter().map(|log| &log.log_query)).1; @@ -132,9 +131,8 @@ impl VmInterface for Vm { .into_iter() .map(GlueInto::glue_into) .collect(), - storage_refunds: self.state.storage.returned_refunds.inner().clone(), - // TODO: fix this line as soon as v1.5.0 is supported - pubdata_costs: Vec::new(), + storage_refunds: self.state.storage.returned_io_refunds.inner().clone(), + pubdata_costs: self.state.storage.returned_pubdata_costs.inner().clone(), } } diff --git a/core/lib/multivm/src/versions/vm_m6/vm.rs b/core/lib/multivm/src/versions/vm_m6/vm.rs index f7b65a2de4c..dfc39ae1229 100644 --- a/core/lib/multivm/src/versions/vm_m6/vm.rs +++ b/core/lib/multivm/src/versions/vm_m6/vm.rs @@ -208,8 +208,8 @@ impl VmInterface for Vm { // It's not applicable for `vm6` deduplicated_events_logs: vec![], storage_refunds: vec![], - pubdata_costs: vec![], user_l2_to_l1_logs: l2_to_l1_logs, + pubdata_costs: vec![], } } diff --git a/core/lib/multivm/src/vm_instance.rs b/core/lib/multivm/src/vm_instance.rs index 6af94b251d5..93e4d76ef6c 100644 --- a/core/lib/multivm/src/vm_instance.rs +++ b/core/lib/multivm/src/vm_instance.rs @@ -22,6 +22,7 @@ pub enum VmInstance { VmBoojumIntegration(crate::vm_boojum_integration::Vm), Vm1_4_1(crate::vm_1_4_1::Vm), Vm1_4_2(crate::vm_1_4_2::Vm), + Vm1_5_0(crate::vm_latest::Vm), } macro_rules! dispatch_vm { @@ -35,6 +36,7 @@ macro_rules! dispatch_vm { VmInstance::VmBoojumIntegration(vm) => vm.$function($($params)*), VmInstance::Vm1_4_1(vm) => vm.$function($($params)*), VmInstance::Vm1_4_2(vm) => vm.$function($($params)*), + VmInstance::Vm1_5_0(vm) => vm.$function($($params)*), } }; } @@ -214,6 +216,10 @@ impl VmInstance { let vm = crate::vm_1_4_2::Vm::new(l1_batch_env, system_env, storage_view); VmInstance::Vm1_4_2(vm) } + VmVersion::Vm1_5_0 => { + let vm = crate::vm_latest::Vm::new(l1_batch_env, system_env, storage_view); + VmInstance::Vm1_5_0(vm) + } } } } diff --git a/core/lib/protobuf_config/src/eth.rs b/core/lib/protobuf_config/src/eth.rs index f0106de3433..5fac5aa1638 100644 --- a/core/lib/protobuf_config/src/eth.rs +++ b/core/lib/protobuf_config/src/eth.rs @@ -183,6 +183,7 @@ impl ProtoRepr for proto::GasAdjuster { internal_l1_pricing_multiplier: *required(&self.internal_l1_pricing_multiplier) .context("internal_l1_pricing_multiplier")?, internal_enforced_l1_gas_price: self.internal_enforced_l1_gas_price, + internal_enforced_pubdata_price: self.internal_enforced_pubdata_price, poll_period: *required(&self.poll_period).context("poll_period")?, max_l1_gas_price: self.max_l1_gas_price, num_samples_for_blob_base_fee_estimate: required( @@ -206,6 +207,7 @@ impl ProtoRepr for proto::GasAdjuster { pricing_formula_parameter_b: Some(this.pricing_formula_parameter_b), internal_l1_pricing_multiplier: Some(this.internal_l1_pricing_multiplier), internal_enforced_l1_gas_price: this.internal_enforced_l1_gas_price, + internal_enforced_pubdata_price: this.internal_enforced_pubdata_price, poll_period: Some(this.poll_period), max_l1_gas_price: this.max_l1_gas_price, num_samples_for_blob_base_fee_estimate: Some( diff --git a/core/lib/protobuf_config/src/proto/eth_sender.proto b/core/lib/protobuf_config/src/proto/eth_sender.proto index 16e936fc32d..d4db3068203 100644 --- a/core/lib/protobuf_config/src/proto/eth_sender.proto +++ b/core/lib/protobuf_config/src/proto/eth_sender.proto @@ -52,7 +52,8 @@ message GasAdjuster { optional double pricing_formula_parameter_a = 3; // required optional double pricing_formula_parameter_b = 4; // required optional double internal_l1_pricing_multiplier = 5; // required - optional uint64 internal_enforced_l1_gas_price = 6; // optional; wei? + optional uint64 internal_enforced_l1_gas_price = 6; // optional; wei + optional uint64 internal_enforced_pubdata_price = 12; // optional; wei optional uint64 poll_period = 7; // required; s optional uint64 max_l1_gas_price = 8; // optional; wei? optional uint64 num_samples_for_blob_base_fee_estimate = 9; // required; diff --git a/core/lib/types/src/circuit.rs b/core/lib/types/src/circuit.rs index dacb34c813b..2aeb226e165 100644 --- a/core/lib/types/src/circuit.rs +++ b/core/lib/types/src/circuit.rs @@ -17,6 +17,7 @@ pub struct CircuitCycleStatistic { pub ecrecover_cycles: u32, pub sha256_cycles: u32, pub secp256k1_verify_cycles: u32, + pub transient_storage_checker_cycles: u32, } impl CircuitCycleStatistic { @@ -41,6 +42,8 @@ pub struct CircuitStatistic { pub sha256: f32, #[serde(default)] pub secp256k1_verify: f32, + #[serde(default)] + pub transient_storage_checker: f32, } impl CircuitStatistic { @@ -58,6 +61,7 @@ impl CircuitStatistic { + self.ecrecover.ceil() as usize + self.sha256.ceil() as usize + self.secp256k1_verify.ceil() as usize + + self.transient_storage_checker.ceil() as usize } /// Adds numbers. @@ -74,6 +78,7 @@ impl CircuitStatistic { + self.ecrecover + self.sha256 + self.secp256k1_verify + + self.transient_storage_checker } } @@ -94,6 +99,8 @@ impl Add for CircuitStatistic { ecrecover: self.ecrecover + other.ecrecover, sha256: self.sha256 + other.sha256, secp256k1_verify: self.secp256k1_verify + other.secp256k1_verify, + transient_storage_checker: self.transient_storage_checker + + other.transient_storage_checker, } } } diff --git a/core/lib/types/src/commitment/mod.rs b/core/lib/types/src/commitment/mod.rs index bb1caa71f4d..e03d91b4cbf 100644 --- a/core/lib/types/src/commitment/mod.rs +++ b/core/lib/types/src/commitment/mod.rs @@ -387,6 +387,12 @@ impl L1BatchAuxiliaryOutput { ); } + assert_eq!( + blob_linear_hashes.len(), + blob_commitments.len(), + "Blob linear hashes and commitments have different lengths" + ); + Self::PostBoojum { common: common_output, system_logs_linear_hash, diff --git a/core/lib/types/src/commitment/tests/mod.rs b/core/lib/types/src/commitment/tests/mod.rs index 4a9253c3933..34e308cfd0a 100644 --- a/core/lib/types/src/commitment/tests/mod.rs +++ b/core/lib/types/src/commitment/tests/mod.rs @@ -45,3 +45,8 @@ fn post_boojum_1_4_1() { fn post_boojum_1_4_2() { run_test("post_boojum_1_4_2_test"); } + +#[test] +fn post_boojum_1_5_0() { + run_test("post_boojum_1_5_0_test"); +} diff --git a/core/lib/types/src/commitment/tests/post_boojum_1_5_0_test.json b/core/lib/types/src/commitment/tests/post_boojum_1_5_0_test.json new file mode 100644 index 00000000000..506110c6bcc --- /dev/null +++ b/core/lib/types/src/commitment/tests/post_boojum_1_5_0_test.json @@ -0,0 +1,880 @@ +{ + "input": { + "PostBoojum": { + "common": { + "l2_to_l1_logs": [ + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 0, + "sender": "0x0000000000000000000000000000000000008001", + "key": "0x7814f203b8e02f6a676b8f7faefcf732d8b4368bab25239ea4525010aa85d5ee", + "value": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + ], + "rollup_last_leaf_index": 89, + "rollup_root_hash": "0xe47f013d1ecd4ce53b6872f6b762670b393815e7ddacdf2b0886af9c7f3a555b", + "bootloader_code_hash": "0x010007ed0e328b940e241f7666a6303b7ffd4e3fd7e8c154d6e7556befe6cd6d", + "default_aa_code_hash": "0x0100055b7a8be90522251be8be1a186464d056462973502ac8a0437c85e4d2a9", + "protocol_version": "Version23" + }, + "system_logs": [ + { + "shard_id": 0, + "is_service": false, + "tx_number_in_block": 0, + "sender": "0x000000000000000000000000000000000000800b", + "key": "0x0000000000000000000000000000000000000000000000000000000000000004", + "value": "0x55618db5ff24aee4d236921b6f4272101161137115a3b4c4a65f8677b124c01c" + }, + { + "shard_id": 0, + "is_service": false, + "tx_number_in_block": 1, + "sender": "0x000000000000000000000000000000000000800b", + "key": "0x0000000000000000000000000000000000000000000000000000000000000003", + "value": "0x00000000000000000000000065c22f8000000000000000000000000065c22f81" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008001", + "key": "0x0000000000000000000000000000000000000000000000000000000000000005", + "value": "0x155c82febe94e07df0065c153e8ed403b5351fd64d657c8dffbfbee8ec3d2ba3" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008001", + "key": "0x0000000000000000000000000000000000000000000000000000000000000006", + "value": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008008", + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x30ba728b1aac22b122de4f32589dd2711da264412cb90e35bf7b1f735dd357ff" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008008", + "key": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x85a7fb853512ba6575c99ee121dd560559523a4587a2cd7e83cd359cd9ea2aed" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008008", + "key": "0x0000000000000000000000000000000000000000000000000000000000000002", + "value": "0xb18f72a4a5b4b8ce1b7e41095fb1332a211a140376bcc2607910875d236708e0" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008011", + "key": "0x0000000000000000000000000000000000000000000000000000000000000007", + "value": "0x0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008011", + "key": "0x0000000000000000000000000000000000000000000000000000000000000008", + "value": "0x0000000000000000000000000000000000000000000000000000000000000004" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008011", + "key": "0x0000000000000000000000000000000000000000000000000000000000000008", + "value": "0x0000000000000000000000000000000000000000000000000000000000000005" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008011", + "key": "0x0000000000000000000000000000000000000000000000000000000000000008", + "value": "0x0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008011", + "key": "0x0000000000000000000000000000000000000000000000000000000000000008", + "value": "0x0000000000000000000000000000000000000000000000000000000000000007" + }, + { + "shard_id": 0, + "is_service": true, + "tx_number_in_block": 1, + "sender": "0x0000000000000000000000000000000000008011", + "key": "0x0000000000000000000000000000000000000000000000000000000000000008", + "value": "0x0000000000000000000000000000000000000000000000000000000000000008" + } + ], + "state_diffs": [ + { + "address": "0x000000000000000000000000000000000000800a", + "key": "0x1", + "derived_key": [ + 113, + 233, + 23, + 33, + 249, + 145, + 133, + 118, + 215, + 96, + 240, + 47, + 3, + 202, + 196, + 124, + 111, + 64, + 3, + 49, + 96, + 49, + 132, + 142, + 60, + 29, + 153, + 230, + 232, + 58, + 71, + 67 + ], + "enumeration_index": 49, + "initial_value": "0x18776f28c303800", + "final_value": "0x708da482cab20760" + }, + { + "address": "0x000000000000000000000000000000000000800a", + "key": "0x294a00337abeee2b3cd948ffeed92231e2a3acc2eb11210400e0aa9557f23e26", + "derived_key": [ + 45, + 90, + 105, + 98, + 204, + 206, + 229, + 212, + 173, + 180, + 138, + 54, + 187, + 191, + 68, + 58, + 83, + 23, + 33, + 72, + 67, + 129, + 18, + 89, + 55, + 243, + 0, + 26, + 197, + 255, + 135, + 91 + ], + "enumeration_index": 50, + "initial_value": "0xf5559e28fd66c0", + "final_value": "0xf5a19b324caf80" + }, + { + "address": "0x000000000000000000000000000000000000800a", + "key": "0xeaa2b2fbf0b42c559059e5e9510edc15755f1c1883f0e41d5ba5f9aea4ac201a", + "derived_key": [ + 141, + 97, + 126, + 192, + 90, + 203, + 191, + 95, + 226, + 69, + 41, + 166, + 75, + 35, + 133, + 169, + 106, + 173, + 67, + 240, + 155, + 225, + 173, + 169, + 44, + 112, + 64, + 49, + 220, + 193, + 72, + 27 + ], + "enumeration_index": 0, + "initial_value": "0x0", + "final_value": "0x6f05e193353286a0" + }, + { + "address": "0x000000000000000000000000000000000000800b", + "key": "0x7", + "derived_key": [ + 18, + 59, + 175, + 197, + 134, + 247, + 119, + 100, + 72, + 140, + 210, + 76, + 106, + 119, + 84, + 110, + 90, + 15, + 232, + 189, + 251, + 79, + 162, + 3, + 207, + 175, + 252, + 54, + 204, + 228, + 221, + 91 + ], + "enumeration_index": 53, + "initial_value": "0x100000000000000000000000065c22e3e", + "final_value": "0x200000000000000000000000065c22f80" + }, + { + "address": "0x000000000000000000000000000000000000800b", + "key": "0x9", + "derived_key": [ + 142, + 125, + 208, + 106, + 197, + 183, + 59, + 71, + 59, + 230, + 188, + 90, + 81, + 3, + 15, + 76, + 116, + 55, + 101, + 124, + 183, + 178, + 155, + 243, + 118, + 197, + 100, + 184, + 209, + 103, + 90, + 94 + ], + "enumeration_index": 54, + "initial_value": "0x200000000000000000000000065c22e3f", + "final_value": "0x400000000000000000000000065c22f81" + }, + { + "address": "0x000000000000000000000000000000000000800b", + "key": "0xd", + "derived_key": [ + 235, + 221, + 239, + 221, + 164, + 142, + 178, + 170, + 127, + 102, + 236, + 247, + 148, + 10, + 40, + 14, + 158, + 243, + 251, + 46, + 149, + 219, + 9, + 149, + 83, + 132, + 64, + 166, + 42, + 247, + 152, + 97 + ], + "enumeration_index": 0, + "initial_value": "0x0", + "final_value": "0xebbe609cd3ccd11f273eb94374d6d3a2f7856c5f1039dc4877c6a334188ac7c1" + }, + { + "address": "0x000000000000000000000000000000000000800b", + "key": "0xe", + "derived_key": [ + 70, + 64, + 215, + 56, + 69, + 54, + 78, + 198, + 145, + 246, + 222, + 251, + 96, + 106, + 58, + 114, + 253, + 165, + 215, + 173, + 51, + 209, + 125, + 4, + 153, + 90, + 142, + 37, + 44, + 74, + 6, + 216 + ], + "enumeration_index": 0, + "initial_value": "0x0", + "final_value": "0x708e7fcf68ebab6c87322686cac4bcdb5f2bd4c71f337b18d147fd9a6c44ad13" + }, + { + "address": "0x000000000000000000000000000000000000800b", + "key": "0x10c", + "derived_key": [ + 121, + 9, + 53, + 136, + 208, + 232, + 71, + 239, + 167, + 58, + 16, + 206, + 32, + 228, + 121, + 159, + 177, + 228, + 102, + 66, + 214, + 86, + 23, + 199, + 229, + 33, + 63, + 160, + 73, + 137, + 217, + 45 + ], + "enumeration_index": 57, + "initial_value": "0x200000000000000000000000065c22e3f", + "final_value": "0x400000000000000000000000065c22f81" + }, + { + "address": "0x000000000000000000000000000000000000800b", + "key": "0xad67d757c34507f157cacfa2e3153e9f260a2244f30428821be7be64587ac55f", + "derived_key": [ + 12, + 194, + 74, + 180, + 47, + 190, + 197, + 49, + 125, + 155, + 26, + 44, + 164, + 124, + 169, + 185, + 59, + 158, + 195, + 109, + 121, + 142, + 253, + 124, + 218, + 167, + 57, + 36, + 22, + 48, + 203, + 70 + ], + "enumeration_index": 0, + "initial_value": "0x0", + "final_value": "0x55618db5ff24aee4d236921b6f4272101161137115a3b4c4a65f8677b124c01c" + } + ], + "aux_commitments": { + "events_queue_commitment": "0x6193a5098eb140796387bdf40700a3855eeb010474b5478f30bf917172c67883", + "bootloader_initial_content_commitment": "0xf031b4491c37f20516c4ebf428f4765156409f67089e64772f4106fd2d9f3351" + }, + "blob_commitments": ["0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000003", "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0000000000000000000000000000000000000000000000000000000000000005", "0x0000000000000000000000000000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000"] + } + }, + "pass_through_data": { + "shared_states": [ + { + "last_leaf_index": 89, + "root_hash": "0xe47f013d1ecd4ce53b6872f6b762670b393815e7ddacdf2b0886af9c7f3a555b" + }, + { + "last_leaf_index": 0, + "root_hash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ] + }, + "meta_parameters": { + "zkporter_is_available": false, + "bootloader_code_hash": "0x010007ed0e328b940e241f7666a6303b7ffd4e3fd7e8c154d6e7556befe6cd6d", + "default_aa_code_hash": "0x0100055b7a8be90522251be8be1a186464d056462973502ac8a0437c85e4d2a9", + "protocol_version": "Version23" + }, + "auxiliary_output": { + "PostBoojum": { + "common": { + "l2_l1_logs_merkle_root": "0x30ba728b1aac22b122de4f32589dd2711da264412cb90e35bf7b1f735dd357ff", + "protocol_version": "Version23" + }, + "system_logs_linear_hash": "0x602dacc0a26e3347f0679924c4ae151ff5200e7dd80902fe0fc11c806c4d3ffb", + "state_diffs_compressed": [ + 1, + 0, + 1, + 72, + 4, + 0, + 4, + 141, + 97, + 126, + 192, + 90, + 203, + 191, + 95, + 226, + 69, + 41, + 166, + 75, + 35, + 133, + 169, + 106, + 173, + 67, + 240, + 155, + 225, + 173, + 169, + 44, + 112, + 64, + 49, + 220, + 193, + 72, + 27, + 65, + 111, + 5, + 225, + 147, + 53, + 50, + 134, + 160, + 235, + 221, + 239, + 221, + 164, + 142, + 178, + 170, + 127, + 102, + 236, + 247, + 148, + 10, + 40, + 14, + 158, + 243, + 251, + 46, + 149, + 219, + 9, + 149, + 83, + 132, + 64, + 166, + 42, + 247, + 152, + 97, + 0, + 235, + 190, + 96, + 156, + 211, + 204, + 209, + 31, + 39, + 62, + 185, + 67, + 116, + 214, + 211, + 162, + 247, + 133, + 108, + 95, + 16, + 57, + 220, + 72, + 119, + 198, + 163, + 52, + 24, + 138, + 199, + 193, + 70, + 64, + 215, + 56, + 69, + 54, + 78, + 198, + 145, + 246, + 222, + 251, + 96, + 106, + 58, + 114, + 253, + 165, + 215, + 173, + 51, + 209, + 125, + 4, + 153, + 90, + 142, + 37, + 44, + 74, + 6, + 216, + 0, + 112, + 142, + 127, + 207, + 104, + 235, + 171, + 108, + 135, + 50, + 38, + 134, + 202, + 196, + 188, + 219, + 95, + 43, + 212, + 199, + 31, + 51, + 123, + 24, + 209, + 71, + 253, + 154, + 108, + 68, + 173, + 19, + 12, + 194, + 74, + 180, + 47, + 190, + 197, + 49, + 125, + 155, + 26, + 44, + 164, + 124, + 169, + 185, + 59, + 158, + 195, + 109, + 121, + 142, + 253, + 124, + 218, + 167, + 57, + 36, + 22, + 48, + 203, + 70, + 0, + 85, + 97, + 141, + 181, + 255, + 36, + 174, + 228, + 210, + 54, + 146, + 27, + 111, + 66, + 114, + 16, + 17, + 97, + 19, + 113, + 21, + 163, + 180, + 196, + 166, + 95, + 134, + 119, + 177, + 36, + 192, + 28, + 0, + 0, + 0, + 49, + 65, + 111, + 6, + 45, + 144, + 62, + 129, + 207, + 96, + 0, + 0, + 0, + 50, + 49, + 75, + 253, + 9, + 79, + 72, + 192, + 0, + 0, + 0, + 53, + 137, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 66, + 0, + 0, + 0, + 54, + 137, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 66, + 0, + 0, + 0, + 57, + 137, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 66 + ], + "state_diffs_hash": "0xb18f72a4a5b4b8ce1b7e41095fb1332a211a140376bcc2607910875d236708e0", + "aux_commitments": { + "events_queue_commitment": "0x6193a5098eb140796387bdf40700a3855eeb010474b5478f30bf917172c67883", + "bootloader_initial_content_commitment": "0xf031b4491c37f20516c4ebf428f4765156409f67089e64772f4106fd2d9f3351" + }, + "blob_linear_hashes": ["0x0000000000000000000000000000000000000000000000000000000000000003", "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0000000000000000000000000000000000000000000000000000000000000005", "0x0000000000000000000000000000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000000000000000000000000000007", "0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000"], + "blob_commitments": ["0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000003", "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0000000000000000000000000000000000000000000000000000000000000005", "0x0000000000000000000000000000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000"] + } + }, + "hashes": { + "pass_through_data": "0x6a3ffc0f55d4abce9498b8bcb01a3018bc2b83d96acb27e23772fe9347954725", + "aux_output": "0xadc63d9c45f85598f3e3c232970315d1f6ac96222e379e16ced7a204524a4061", + "meta_parameters": "0xffdee3e679310760e0320a3f9dea3fa863b0771e4424193752ed803fc2d53d20", + "commitment": "0xbbac3e74f007f28453294acb27e3b5c85e67be1208203bb31db9065fe4305dea" + } +} diff --git a/core/lib/types/src/system_contracts.rs b/core/lib/types/src/system_contracts.rs index 6d0e44e8577..936a4a19279 100644 --- a/core/lib/types/src/system_contracts.rs +++ b/core/lib/types/src/system_contracts.rs @@ -4,8 +4,8 @@ use once_cell::sync::Lazy; use zksync_basic_types::{AccountTreeId, Address, U256}; use zksync_contracts::{read_sys_contract_bytecode, ContractLanguage, SystemContractsRepo}; use zksync_system_constants::{ - BOOTLOADER_UTILITIES_ADDRESS, COMPRESSOR_ADDRESS, EVENT_WRITER_ADDRESS, - PUBDATA_CHUNK_PUBLISHER_ADDRESS, + BOOTLOADER_UTILITIES_ADDRESS, CODE_ORACLE_ADDRESS, COMPRESSOR_ADDRESS, EVENT_WRITER_ADDRESS, + GAS_BOUND_CALLER_ADDRESS, P256VERIFY_PRECOMPILE_ADDRESS, PUBDATA_CHUNK_PUBLISHER_ADDRESS, }; use crate::{ @@ -25,7 +25,7 @@ use crate::{ pub const TX_NONCE_INCREMENT: U256 = U256([1, 0, 0, 0]); // 1 pub const DEPLOYMENT_NONCE_INCREMENT: U256 = U256([0, 0, 1, 0]); // 2^128 -static SYSTEM_CONTRACT_LIST: [(&str, &str, Address, ContractLanguage); 21] = [ +static SYSTEM_CONTRACT_LIST: [(&str, &str, Address, ContractLanguage); 24] = [ ( "", "AccountCodeStorage", @@ -104,6 +104,18 @@ static SYSTEM_CONTRACT_LIST: [(&str, &str, Address, ContractLanguage); 21] = [ EC_MUL_PRECOMPILE_ADDRESS, ContractLanguage::Yul, ), + ( + "precompiles/", + "P256Verify", + P256VERIFY_PRECOMPILE_ADDRESS, + ContractLanguage::Yul, + ), + ( + "precompiles/", + "CodeOracle", + CODE_ORACLE_ADDRESS, + ContractLanguage::Yul, + ), ( "", "SystemContext", @@ -144,6 +156,12 @@ static SYSTEM_CONTRACT_LIST: [(&str, &str, Address, ContractLanguage); 21] = [ PUBDATA_CHUNK_PUBLISHER_ADDRESS, ContractLanguage::Sol, ), + ( + "", + "GasBoundCaller", + GAS_BOUND_CALLER_ADDRESS, + ContractLanguage::Sol, + ), ]; static SYSTEM_CONTRACTS: Lazy> = Lazy::new(|| { diff --git a/core/lib/zksync_core/src/api_server/execution_sandbox/mod.rs b/core/lib/zksync_core/src/api_server/execution_sandbox/mod.rs index 5408c7ac0c3..bad188a808b 100644 --- a/core/lib/zksync_core/src/api_server/execution_sandbox/mod.rs +++ b/core/lib/zksync_core/src/api_server/execution_sandbox/mod.rs @@ -2,14 +2,12 @@ use std::{sync::Arc, time::Duration}; use anyhow::Context as _; use tokio::runtime::Handle; -use zksync_dal::{Connection, ConnectionPool, Core, CoreDal}; -use zksync_state::{PostgresStorage, PostgresStorageCaches, ReadStorage, StorageView}; -use zksync_system_constants::PUBLISH_BYTECODE_OVERHEAD; +use zksync_dal::{Connection, Core, CoreDal}; +use zksync_state::PostgresStorageCaches; use zksync_types::{ api, fee_model::BatchFeeInput, AccountTreeId, Address, L1BatchNumber, L2ChainId, MiniblockNumber, }; -use zksync_utils::bytecode::{compress_bytecode, hash_bytecode}; use self::vm_metrics::SandboxStage; pub(super) use self::{ @@ -162,53 +160,6 @@ async fn get_pending_state( Ok((block_id, resolved_block_number)) } -/// Returns the number of the pubdata that the transaction will spend on factory deps. -pub(super) async fn get_pubdata_for_factory_deps( - _vm_permit: &VmPermit, - connection_pool: &ConnectionPool, - factory_deps: &[Vec], - storage_caches: PostgresStorageCaches, -) -> anyhow::Result { - if factory_deps.is_empty() { - return Ok(0); // Shortcut for the common case allowing to not acquire DB connections etc. - } - - let mut storage = connection_pool - .connection_tagged("api") - .await - .context("failed acquiring DB connection")?; - let (_, block_number) = get_pending_state(&mut storage).await?; - drop(storage); - - let rt_handle = Handle::current(); - let connection_pool = connection_pool.clone(); - let factory_deps = factory_deps.to_vec(); - tokio::task::spawn_blocking(move || { - let connection = rt_handle - .block_on(connection_pool.connection_tagged("api")) - .context("failed acquiring DB connection")?; - let storage = PostgresStorage::new(rt_handle, connection, block_number, false) - .with_caches(storage_caches); - let mut storage_view = StorageView::new(storage); - - let effective_lengths = factory_deps.iter().map(|bytecode| { - if storage_view.is_bytecode_known(&hash_bytecode(bytecode)) { - return 0; - } - - let length = if let Ok(compressed) = compress_bytecode(bytecode) { - compressed.len() - } else { - bytecode.len() - }; - length as u32 + PUBLISH_BYTECODE_OVERHEAD - }); - anyhow::Ok(effective_lengths.sum()) - }) - .await - .context("computing pubdata dependencies size panicked")? -} - /// Arguments for VM execution not specific to a particular transaction. #[derive(Debug, Clone)] pub(crate) struct TxSharedArgs { diff --git a/core/lib/zksync_core/src/api_server/execution_sandbox/tests.rs b/core/lib/zksync_core/src/api_server/execution_sandbox/tests.rs index c858145c7bf..c08c318d7f2 100644 --- a/core/lib/zksync_core/src/api_server/execution_sandbox/tests.rs +++ b/core/lib/zksync_core/src/api_server/execution_sandbox/tests.rs @@ -1,6 +1,7 @@ //! Tests for the VM execution sandbox. use assert_matches::assert_matches; +use zksync_dal::ConnectionPool; use super::*; use crate::{ diff --git a/core/lib/zksync_core/src/api_server/execution_sandbox/tracers.rs b/core/lib/zksync_core/src/api_server/execution_sandbox/tracers.rs index 443b35fb46e..2b969e380dd 100644 --- a/core/lib/zksync_core/src/api_server/execution_sandbox/tracers.rs +++ b/core/lib/zksync_core/src/api_server/execution_sandbox/tracers.rs @@ -14,7 +14,7 @@ pub(crate) enum ApiTracer { impl ApiTracer { pub fn into_boxed< S: WriteStorage, - H: HistoryMode + multivm::HistoryMode + 'static, + H: HistoryMode + multivm::HistoryMode + 'static, >( self, ) -> MultiVmTracerPointer { diff --git a/core/lib/zksync_core/src/api_server/tx_sender/mod.rs b/core/lib/zksync_core/src/api_server/tx_sender/mod.rs index e91197c6ff2..7a1237da2cf 100644 --- a/core/lib/zksync_core/src/api_server/tx_sender/mod.rs +++ b/core/lib/zksync_core/src/api_server/tx_sender/mod.rs @@ -36,9 +36,8 @@ use self::tx_sink::TxSink; use crate::{ api_server::{ execution_sandbox::{ - get_pubdata_for_factory_deps, BlockArgs, BlockStartInfo, SubmitTxStage, - TransactionExecutor, TxExecutionArgs, TxSharedArgs, VmConcurrencyLimiter, VmPermit, - SANDBOX_METRICS, + BlockArgs, BlockStartInfo, SubmitTxStage, TransactionExecutor, TxExecutionArgs, + TxSharedArgs, VmConcurrencyLimiter, VmPermit, SANDBOX_METRICS, }, tx_sender::result::ApiCallResult, }, @@ -70,6 +69,8 @@ pub struct MultiVMBaseSystemContracts { pub(crate) post_1_4_1: BaseSystemContracts, /// Contracts to be used after the 1.4.2 upgrade pub(crate) post_1_4_2: BaseSystemContracts, + /// Contracts to be used after the 1.5.0 upgrade + pub(crate) post_1_5_0: BaseSystemContracts, } impl MultiVMBaseSystemContracts { @@ -96,9 +97,8 @@ impl MultiVMBaseSystemContracts { ProtocolVersionId::Version18 => self.post_boojum, ProtocolVersionId::Version19 => self.post_allowlist_removal, ProtocolVersionId::Version20 => self.post_1_4_1, - ProtocolVersionId::Version21 - | ProtocolVersionId::Version22 - | ProtocolVersionId::Version23 => self.post_1_4_2, + ProtocolVersionId::Version21 | ProtocolVersionId::Version22 => self.post_1_4_2, + ProtocolVersionId::Version23 | ProtocolVersionId::Version24 => self.post_1_5_0, } } } @@ -132,6 +132,7 @@ impl ApiContracts { post_allowlist_removal: BaseSystemContracts::estimate_gas_post_allowlist_removal(), post_1_4_1: BaseSystemContracts::estimate_gas_post_1_4_1(), post_1_4_2: BaseSystemContracts::estimate_gas_post_1_4_2(), + post_1_5_0: BaseSystemContracts::estimate_gas_post_1_5_0(), }, eth_call: MultiVMBaseSystemContracts { pre_virtual_blocks: BaseSystemContracts::playground_pre_virtual_blocks(), @@ -142,6 +143,7 @@ impl ApiContracts { post_allowlist_removal: BaseSystemContracts::playground_post_allowlist_removal(), post_1_4_1: BaseSystemContracts::playground_post_1_4_1(), post_1_4_2: BaseSystemContracts::playground_post_1_4_2(), + post_1_5_0: BaseSystemContracts::playground_post_1_5_0(), }, } } @@ -679,6 +681,7 @@ impl TxSender { let protocol_version = pending_protocol_version(&mut connection) .await .context("failed getting pending protocol version")?; + let max_gas_limit = get_max_batch_gas_limit(protocol_version.into()); drop(connection); let fee_input = { @@ -756,26 +759,38 @@ impl TxSender { let vm_permit = self.0.vm_concurrency_limiter.acquire().await; let vm_permit = vm_permit.ok_or(SubmitTxError::ServerShuttingDown)?; - // We already know how many gas is needed to cover for the publishing of the bytecodes. - // For L1->L2 transactions all the bytecodes have been made available on L1, so no funds need to be - // spent on re-publishing those. - let gas_for_bytecodes_pubdata = if tx.is_l1() { - 0 + // When the pubdata cost grows very high, the total gas limit required may become very high as well. If + // we do binary search over any possible gas limit naively, we may end up with a very high number of iterations, + // which affects performance. + // + // To optimize for this case, we first calculate the amount of gas needed to cover for the pubdata. After that, we + // need to do a smaller binary search that is focused on computational gas limit only. + let additional_gas_for_pubdata = if tx.is_l1() { + // For L1 transactions the pubdata priced in such a way that the maximal computational + // gas limit should be enough to cover for the pubdata as well, so no additional gas is provided there. + 0u64 } else { - let pubdata_for_factory_deps = get_pubdata_for_factory_deps( - &vm_permit, - &self.0.replica_connection_pool, - tx.execute.factory_deps.as_deref().unwrap_or_default(), - self.storage_caches(), - ) - .await? as u64; + // For L2 transactions, we estimate the amount of gas needed to cover for the pubdata by creating a transaction with infinite gas limit. + // And getting how much pubdata it used. - if pubdata_for_factory_deps > self.0.sender_config.max_pubdata_per_batch { - return Err(SubmitTxError::Unexecutable( - "exceeds limit for published pubdata".to_string(), - )); - } - pubdata_for_factory_deps * gas_per_pubdata_byte + // In theory, if the transaction has failed with such large gas limit, we could have returned an API error here right away, + // but doing it later on keeps the code more lean. + let (result, _) = self + .estimate_gas_step( + vm_permit.clone(), + tx.clone(), + max_gas_limit, + gas_per_pubdata_byte as u32, + fee_input, + block_args, + base_fee, + protocol_version.into(), + ) + .await + .context("estimate_gas step failed")?; + + // It is assumed that there is no overflow here + (result.statistics.pubdata_published as u64) * gas_per_pubdata_byte }; // We are using binary search to find the minimal values of gas_limit under which @@ -800,7 +815,7 @@ impl TxSender { // or normal execution errors, so we just hope that increasing the // gas limit will make the transaction successful let iteration_started_at = Instant::now(); - let try_gas_limit = gas_for_bytecodes_pubdata + mid; + let try_gas_limit = additional_gas_for_pubdata + mid; let (result, _) = self .estimate_gas_step( vm_permit.clone(), @@ -840,7 +855,7 @@ impl TxSender { ((upper_bound as f64) * estimated_fee_scale_factor) as u64, ); - let suggested_gas_limit = tx_body_gas_limit + gas_for_bytecodes_pubdata; + let suggested_gas_limit = tx_body_gas_limit + additional_gas_for_pubdata; let (result, tx_metrics) = self .estimate_gas_step( vm_permit, @@ -883,9 +898,9 @@ impl TxSender { } as u64; let full_gas_limit = - match tx_body_gas_limit.overflowing_add(gas_for_bytecodes_pubdata + overhead) { + match tx_body_gas_limit.overflowing_add(additional_gas_for_pubdata + overhead) { (value, false) => { - if value > get_max_batch_gas_limit(protocol_version.into()) { + if value > max_gas_limit { return Err(SubmitTxError::ExecutionReverted( "exceeds block gas limit".to_string(), vec![], diff --git a/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/mod.rs b/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/mod.rs index b87bf65384b..be3462dedf4 100644 --- a/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/mod.rs +++ b/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/mod.rs @@ -171,6 +171,10 @@ impl GasAdjuster { } pub(crate) fn estimate_effective_pubdata_price(&self) -> u64 { + if let Some(price) = self.config.internal_enforced_pubdata_price { + return price; + } + match self.pubdata_sending_mode { PubdataSendingMode::Blobs => { const BLOB_GAS_PER_BYTE: u64 = 1; // `BYTES_PER_BLOB` = `GAS_PER_BLOB` = 2 ^ 17. diff --git a/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/tests.rs b/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/tests.rs index 7b7bfc25972..1707458d699 100644 --- a/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/tests.rs +++ b/core/lib/zksync_core/src/l1_gas_price/gas_adjuster/tests.rs @@ -65,6 +65,7 @@ async fn kept_updated(deployment_mode: DeploymentMode) { pricing_formula_parameter_b: 1.0005, internal_l1_pricing_multiplier: 0.8, internal_enforced_l1_gas_price: None, + internal_enforced_pubdata_price: None, poll_period: 5, max_l1_gas_price: None, num_samples_for_blob_base_fee_estimate: 3, diff --git a/core/lib/zksync_core/src/state_keeper/batch_executor/tests/mod.rs b/core/lib/zksync_core/src/state_keeper/batch_executor/tests/mod.rs index 7beccf8c0e9..829e2d66f8f 100644 --- a/core/lib/zksync_core/src/state_keeper/batch_executor/tests/mod.rs +++ b/core/lib/zksync_core/src/state_keeper/batch_executor/tests/mod.rs @@ -224,7 +224,8 @@ async fn reject_tx() { assert_rejected(&res); } -/// Checks that tx with too big gas limit is correctly rejected. +/// Checks that tx with too big gas limit is correctly processed. +/// When processed in the bootloader, no more than 80M gas can be used within the execution context. #[tokio::test] async fn too_big_gas_limit() { let connection_pool = ConnectionPool::::constrained_test_pool(1).await; @@ -237,38 +238,9 @@ async fn too_big_gas_limit() { .create_batch_executor(StorageType::AsyncRocksdbCache) .await; - let bad_tx = alice.execute_with_gas_limit(u32::MAX); + let big_gas_limit_tx = alice.execute_with_gas_limit(u32::MAX); - let res_old = executor.execute_tx(bad_tx.clone()).await; - assert_rejected(&res_old); - - executor.rollback_last_tx().await; - let res_new = executor.execute_tx(bad_tx).await; - assert_rejected(&res_new); - executor.rollback_last_tx().await; - - let ( - TxExecutionResult::RejectedByVm { - reason: rejection_reason_old, - .. - }, - TxExecutionResult::RejectedByVm { - reason: rejection_reason_new, - .. - }, - ) = (res_old, res_new) - else { - unreachable!(); - }; - assert_eq!( - rejection_reason_old, rejection_reason_new, - "Rejection reasons must be the same" - ); - - // Ensure that now we can execute a valid tx. - alice.nonce -= 1; // Reset the nonce. - - let res = executor.execute_tx(alice.execute()).await; + let res = executor.execute_tx(big_gas_limit_tx).await; assert_executed(&res); executor.finish_batch().await; } diff --git a/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs b/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs index 4bfab40f4fd..100e0221b34 100644 --- a/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs +++ b/core/lib/zksync_core/src/state_keeper/io/tests/tester.rs @@ -66,6 +66,7 @@ impl Tester { pricing_formula_parameter_b: 1.0, internal_l1_pricing_multiplier: 1.0, internal_enforced_l1_gas_price: None, + internal_enforced_pubdata_price: None, poll_period: 10, max_l1_gas_price: None, num_samples_for_blob_base_fee_estimate: 10, diff --git a/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs b/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs index 8e838dacab5..c7166de9102 100644 --- a/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs +++ b/core/lib/zksync_core/src/state_keeper/seal_criteria/criteria/geometry_seal_criteria.rs @@ -73,7 +73,7 @@ mod tests { use super::*; - const MAX_CIRCUITS_PER_BATCH: usize = 20_000; + const MAX_CIRCUITS_PER_BATCH: usize = 30_000; fn get_config() -> StateKeeperConfig { StateKeeperConfig { @@ -174,7 +174,7 @@ mod tests { let protocol_version = ProtocolVersionId::latest(); let block_execution_metrics = ExecutionMetrics { circuit_statistic: CircuitStatistic { - main_vm: (MAX_CIRCUITS_PER_BATCH / 2) as f32, + main_vm: (MAX_CIRCUITS_PER_BATCH / 4) as f32, ..CircuitStatistic::default() }, ..ExecutionMetrics::default() diff --git a/core/node/node_framework/examples/showcase.rs b/core/node/node_framework/examples/showcase.rs index 29e7ae1fba3..0a1552f3350 100644 --- a/core/node/node_framework/examples/showcase.rs +++ b/core/node/node_framework/examples/showcase.rs @@ -68,7 +68,7 @@ struct DatabaseResource(pub Arc); impl Resource for DatabaseResource { fn name() -> String { // The convention for resource names is `/`. In this case, the scope is `common`, but - // for anything that is component-specific it could've been e.g. `state_keeper` or `api`. + // for anything that is component-specific it could have been e.g. `state_keeper` or `api`. "common/database".into() } } diff --git a/core/tests/ts-integration/src/context-owner.ts b/core/tests/ts-integration/src/context-owner.ts index 4f71df548e9..b8e7c383c39 100644 --- a/core/tests/ts-integration/src/context-owner.ts +++ b/core/tests/ts-integration/src/context-owner.ts @@ -17,7 +17,10 @@ import { RetryProvider } from './retry-provider'; export const L1_DEFAULT_ETH_PER_ACCOUNT = ethers.utils.parseEther('0.08'); // Stress tests for L1->L2 transactions on localhost require a lot of upfront payment, but these are skipped during tests on normal environments export const L1_EXTENDED_TESTS_ETH_PER_ACCOUNT = ethers.utils.parseEther('0.5'); -export const L2_ETH_PER_ACCOUNT = ethers.utils.parseEther('0.5'); +export const L2_DEFAULT_ETH_PER_ACCOUNT = ethers.utils.parseEther('0.5'); + +// Stress tests on local host may require a lot of additiomal funds, but these are skipped during tests on normal environments +export const L2_EXTENDED_TESTS_ETH_PER_ACCOUNT = ethers.utils.parseEther('50'); export const ERC20_PER_ACCOUNT = ethers.utils.parseEther('10000.0'); /** @@ -88,6 +91,11 @@ export class TestContextOwner { return this.env.network === 'localhost' ? L1_EXTENDED_TESTS_ETH_PER_ACCOUNT : L1_DEFAULT_ETH_PER_ACCOUNT; } + // Returns the required amount of L2 ETH + requiredL2ETHPerAccount() { + return this.env.network === 'localhost' ? L2_EXTENDED_TESTS_ETH_PER_ACCOUNT : L2_DEFAULT_ETH_PER_ACCOUNT; + } + /** * Performs the test context initialization. * @@ -178,7 +186,7 @@ export class TestContextOwner { this.reporter.message(`Operator address is ${this.mainEthersWallet.address}`); - const requiredL2ETHAmount = L2_ETH_PER_ACCOUNT.mul(accountsAmount); + const requiredL2ETHAmount = this.requiredL2ETHPerAccount().mul(accountsAmount); const actualL2ETHAmount = await this.mainSyncWallet.getBalance(); this.reporter.message(`Operator balance on L2 is ${ethers.utils.formatEther(actualL2ETHAmount)} ETH`); @@ -353,7 +361,7 @@ export class TestContextOwner { zksync.utils.ETH_ADDRESS, this.mainSyncWallet, wallets, - L2_ETH_PER_ACCOUNT, + this.requiredL2ETHPerAccount(), l2startNonce, undefined, this.reporter diff --git a/core/tests/ts-integration/tests/custom-account.test.ts b/core/tests/ts-integration/tests/custom-account.test.ts index 29778ac4882..b427c00a4a2 100644 --- a/core/tests/ts-integration/tests/custom-account.test.ts +++ b/core/tests/ts-integration/tests/custom-account.test.ts @@ -8,7 +8,7 @@ import * as zksync from 'zksync-web3'; import { utils, types } from 'zksync-web3'; import * as ethers from 'ethers'; import { deployContract, getTestContract } from '../src/helpers'; -import { ERC20_PER_ACCOUNT, L2_ETH_PER_ACCOUNT } from '../src/context-owner'; +import { ERC20_PER_ACCOUNT, L2_DEFAULT_ETH_PER_ACCOUNT } from '../src/context-owner'; import { shouldChangeETHBalances, shouldChangeTokenBalances } from '../src/modifiers/balance-checker'; const contracts = { @@ -17,7 +17,7 @@ const contracts = { }; // We create multiple custom accounts and we need to fund them with ETH to pay for fees. -const ETH_PER_CUSTOM_ACCOUNT = L2_ETH_PER_ACCOUNT.div(8); +const ETH_PER_CUSTOM_ACCOUNT = L2_DEFAULT_ETH_PER_ACCOUNT.div(8); const TRANSFER_AMOUNT = 1; describe('Tests for the custom account behavior', () => { diff --git a/core/tests/ts-integration/tests/erc20.test.ts b/core/tests/ts-integration/tests/erc20.test.ts index f38b24c1d78..891f7118dc3 100644 --- a/core/tests/ts-integration/tests/erc20.test.ts +++ b/core/tests/ts-integration/tests/erc20.test.ts @@ -10,7 +10,7 @@ import * as zksync from 'zksync-web3'; import { BigNumber, utils as etherUtils } from 'ethers'; import * as ethers from 'ethers'; import { scaledGasPrice, waitUntilBlockFinalized } from '../src/helpers'; -import { L2_ETH_PER_ACCOUNT } from '../src/context-owner'; +import { L2_DEFAULT_ETH_PER_ACCOUNT } from '../src/context-owner'; describe('ERC20 contract checks', () => { let testMaster: TestMaster; @@ -128,7 +128,7 @@ describe('ERC20 contract checks', () => { // Fund bob's account to perform a transaction from it. await alice - .transfer({ to: bob.address, amount: L2_ETH_PER_ACCOUNT.div(8), token: zksync.utils.ETH_ADDRESS }) + .transfer({ to: bob.address, amount: L2_DEFAULT_ETH_PER_ACCOUNT.div(8), token: zksync.utils.ETH_ADDRESS }) .then((tx) => tx.wait()); await expect(aliceErc20.allowance(alice.address, bob.address)).resolves.bnToBeEq(0); diff --git a/core/tests/ts-integration/tests/fees.test.ts b/core/tests/ts-integration/tests/fees.test.ts index 6af6473bfa2..3bfa93ef59f 100644 --- a/core/tests/ts-integration/tests/fees.test.ts +++ b/core/tests/ts-integration/tests/fees.test.ts @@ -17,6 +17,8 @@ import * as zksync from 'zksync-web3'; import { BigNumber, ethers } from 'ethers'; import { Token } from '../src/types'; +const UINT32_MAX = BigNumber.from(2).pow(32).sub(1); + const logs = fs.createWriteStream('fees.log', { flags: 'a' }); // Unless `RUN_FEE_TEST` is provided, skip the test suit @@ -128,12 +130,66 @@ testFees('Test fees', () => { ); } - await setInternalL1GasPrice(alice._providerL2(), undefined, true); - console.log(`Full report: \n\n${reports.join('\n\n')}`); }); + test('Test gas consumption under large L1 gas price', async () => { + if (process.env.CHAIN_STATE_KEEPER_L1_BATCH_COMMIT_DATA_GENERATOR_MODE === 'Validium') { + // We skip this test for Validium mode, since L1 gas price has little impact on the gasLimit in this mode. + return; + } + + // In this test we check that the server works fine when the required gasLimit is over u32::MAX. + // Under normal server behavior, the maximal gas spent on pubdata is around 120kb * 2^20 gas/byte = ~120 * 10^9 gas. + + // In this test we will set gas per pubdata byte to its maximum value, while publishing a large L1->L2 message. + + const minimalL2GasPrice = ethers.BigNumber.from(process.env.CHAIN_STATE_KEEPER_MINIMAL_L2_GAS_PRICE!); + + // We want the total gas limit to be over u32::MAX, so we need the gas per pubdata to be 50k. + // + // Note, that in case, any sort of overhead is present in the l2 fair gas price calculation, the final + // gas per pubdata may be lower than 50_000. Here we assume that it is not the case, but we'll double check + // that the gasLimit is indeed over u32::MAX, which is the most important tested property. + const requiredPubdataPrice = minimalL2GasPrice.mul(100_000); + + await setInternalL1GasPrice( + alice._providerL2(), + requiredPubdataPrice.toString(), + requiredPubdataPrice.toString() + ); + + const l1Messenger = new ethers.Contract(zksync.utils.L1_MESSENGER_ADDRESS, zksync.utils.L1_MESSENGER, alice); + + // Firstly, let's test a successful transaction. + const largeData = ethers.utils.randomBytes(90_000); + const tx = await l1Messenger.sendToL1(largeData); + expect(tx.gasLimit.gt(UINT32_MAX)).toBeTruthy(); + const receipt = await tx.wait(); + expect(receipt.gasUsed.gt(UINT32_MAX)).toBeTruthy(); + + // Secondly, let's test an unsuccessful transaction with large refund. + + // The size of the data has increased, so the previous gas limit is not enough. + const largerData = ethers.utils.randomBytes(91_000); + const gasToPass = receipt.gasUsed; + const unsuccessfulTx = await l1Messenger.sendToL1(largerData, { + gasLimit: gasToPass + }); + + try { + await unsuccessfulTx.wait(); + throw new Error('The transaction should have reverted'); + } catch { + const receipt = await alice.provider.getTransactionReceipt(unsuccessfulTx.hash); + expect(gasToPass.sub(receipt.gasUsed).gt(UINT32_MAX)).toBeTruthy(); + } + }); + afterAll(async () => { + // Returning the pubdata price to the default one + await setInternalL1GasPrice(alice._providerL2(), undefined, undefined, true); + await testMaster.deinitialize(); }); }); @@ -145,7 +201,8 @@ async function appendResults( newL1GasPrice: number, reports: string[] ): Promise { - await setInternalL1GasPrice(sender._providerL2(), newL1GasPrice.toString()); + // For the sake of simplicity, we'll use the same pubdata price as the L1 gas price. + await setInternalL1GasPrice(sender._providerL2(), newL1GasPrice.toString(), newL1GasPrice.toString()); if (originalL1Receipts.length !== reports.length && originalL1Receipts.length !== transactionRequests.length) { throw new Error('The array of receipts and reports have different length'); @@ -216,7 +273,12 @@ async function killServerAndWaitForShutdown(provider: zksync.Provider) { throw new Error("Server didn't stop after a kill request"); } -async function setInternalL1GasPrice(provider: zksync.Provider, newPrice?: string, disconnect?: boolean) { +async function setInternalL1GasPrice( + provider: zksync.Provider, + newL1GasPrice?: string, + newPubdataPrice?: string, + disconnect?: boolean +) { // Make sure server isn't running. try { await killServerAndWaitForShutdown(provider); @@ -225,10 +287,22 @@ async function setInternalL1GasPrice(provider: zksync.Provider, newPrice?: strin // Run server in background. let command = 'zk server --components api,tree,eth,state_keeper'; command = `DATABASE_MERKLE_TREE_MODE=full ${command}`; - if (newPrice) { + + if (newPubdataPrice) { + command = `ETH_SENDER_GAS_ADJUSTER_INTERNAL_ENFORCED_PUBDATA_PRICE=${newPubdataPrice} ${command}`; + } + + if (newL1GasPrice) { + // We need to ensure that each transaction gets into its own batch for more fair comparison. + command = `ETH_SENDER_GAS_ADJUSTER_INTERNAL_ENFORCED_L1_GAS_PRICE=${newL1GasPrice} ${command}`; + } + + const testMode = newPubdataPrice || newL1GasPrice; + if (testMode) { // We need to ensure that each transaction gets into its own batch for more fair comparison. - command = `CHAIN_STATE_KEEPER_TRANSACTION_SLOTS=1 ETH_SENDER_GAS_ADJUSTER_INTERNAL_ENFORCED_L1_GAS_PRICE=${newPrice} ${command}`; + command = `CHAIN_STATE_KEEPER_TRANSACTION_SLOTS=1 ${command}`; } + const zkSyncServer = utils.background(command, [null, logs, logs]); if (disconnect) { @@ -249,4 +323,6 @@ async function setInternalL1GasPrice(provider: zksync.Provider, newPrice?: strin if (!mainContract) { throw new Error('Server did not start'); } + + await utils.sleep(10); } diff --git a/core/tests/ts-integration/tests/l2-weth.test.ts b/core/tests/ts-integration/tests/l2-weth.test.ts index 849f956b6cd..186d0b5988a 100644 --- a/core/tests/ts-integration/tests/l2-weth.test.ts +++ b/core/tests/ts-integration/tests/l2-weth.test.ts @@ -13,7 +13,7 @@ import { shouldChangeTokenBalances, shouldOnlyTakeFee } from '../src/modifiers/balance-checker'; -import { L2_ETH_PER_ACCOUNT } from '../src/context-owner'; +import { L2_DEFAULT_ETH_PER_ACCOUNT } from '../src/context-owner'; describe('Tests for the WETH bridge/token behavior', () => { let testMaster: TestMaster; @@ -95,7 +95,7 @@ describe('Tests for the WETH bridge/token behavior', () => { // Fund bob's account to perform a transaction from it. await alice - .transfer({ to: bob.address, amount: L2_ETH_PER_ACCOUNT.div(8), token: zksync.utils.ETH_ADDRESS }) + .transfer({ to: bob.address, amount: L2_DEFAULT_ETH_PER_ACCOUNT.div(8), token: zksync.utils.ETH_ADDRESS }) .then((tx) => tx.wait()); const bobTokenBalanceChange = await shouldChangeTokenBalances(aliceL2Weth.address, [ diff --git a/core/tests/ts-integration/tests/paymaster.test.ts b/core/tests/ts-integration/tests/paymaster.test.ts index 573e4f9adcb..f7144949bb4 100644 --- a/core/tests/ts-integration/tests/paymaster.test.ts +++ b/core/tests/ts-integration/tests/paymaster.test.ts @@ -6,7 +6,7 @@ import * as zksync from 'zksync-web3'; import { Provider, Wallet, utils, Contract } from 'zksync-web3'; import * as ethers from 'ethers'; import { deployContract, getTestContract } from '../src/helpers'; -import { L2_ETH_PER_ACCOUNT } from '../src/context-owner'; +import { L2_DEFAULT_ETH_PER_ACCOUNT } from '../src/context-owner'; import { checkReceipt } from '../src/modifiers/receipt-check'; import { extractFee } from '../src/modifiers/balance-checker'; import { TestMessage } from '../src/matchers/matcher-helpers'; @@ -49,7 +49,9 @@ describe('Paymaster tests', () => { test('Should deploy a paymaster', async () => { paymaster = await deployContract(alice, contracts.customPaymaster, []); // Supplying paymaster with ETH it would need to cover the fees for the user - await alice.transfer({ to: paymaster.address, amount: L2_ETH_PER_ACCOUNT.div(4) }).then((tx) => tx.wait()); + await alice + .transfer({ to: paymaster.address, amount: L2_DEFAULT_ETH_PER_ACCOUNT.div(4) }) + .then((tx) => tx.wait()); }); test('Should pay fee with paymaster', async () => { @@ -144,7 +146,9 @@ describe('Paymaster tests', () => { expect(testnetPaymaster).toBeTruthy(); // Supplying paymaster with ETH it would need to cover the fees for the user - await alice.transfer({ to: testnetPaymaster, amount: L2_ETH_PER_ACCOUNT.div(4) }).then((tx) => tx.wait()); + await alice + .transfer({ to: testnetPaymaster, amount: L2_DEFAULT_ETH_PER_ACCOUNT.div(4) }) + .then((tx) => tx.wait()); const tx = await erc20.populateTransaction.transfer(alice.address, AMOUNT); const gasPrice = await alice.provider.getGasPrice(); diff --git a/core/tests/ts-integration/tests/system.test.ts b/core/tests/ts-integration/tests/system.test.ts index 7903934a335..ffcc5fb4bf7 100644 --- a/core/tests/ts-integration/tests/system.test.ts +++ b/core/tests/ts-integration/tests/system.test.ts @@ -7,7 +7,7 @@ import { TestMaster } from '../src/index'; import { shouldChangeTokenBalances } from '../src/modifiers/balance-checker'; -import { L2_ETH_PER_ACCOUNT } from '../src/context-owner'; +import { L2_DEFAULT_ETH_PER_ACCOUNT } from '../src/context-owner'; import * as zksync from 'zksync-web3'; import * as ethers from 'ethers'; @@ -16,6 +16,15 @@ import { serialize, hashBytecode } from 'zksync-web3/build/src/utils'; import { deployOnAnyLocalAddress, ForceDeployment } from '../src/system'; import { getTestContract } from '../src/helpers'; +import { + GasBoundCaller, + GasBoundCallerFactory, + L1Messenger, + L1MessengerFactory, + SystemContext, + SystemContextFactory +} from 'system-contracts/typechain'; + const contracts = { counter: getTestContract('Counter'), events: getTestContract('Emitter') @@ -50,6 +59,44 @@ describe('System behavior checks', () => { expect(result_b).toEqual('0x'); }); + test('GasBoundCaller should be deployed and works correctly', async () => { + const gasBoundCallerAddress = '0x0000000000000000000000000000000000010000'; + const l1MessengerAddress = '0x0000000000000000000000000000000000008008'; + const systemContextAddress = '0x000000000000000000000000000000000000800b'; + const systemContext: SystemContext = SystemContextFactory.connect(systemContextAddress, alice._signerL2()); + const l1Messenger: L1Messenger = L1MessengerFactory.connect(l1MessengerAddress, alice._signerL2()); + const gasBoundCaller: GasBoundCaller = GasBoundCallerFactory.connect(gasBoundCallerAddress, alice._signerL2()); + + const pubdataToSend = 5000; + const gasSpentOnPubdata = (await systemContext.gasPerPubdataByte()).mul(pubdataToSend); + + const pubdata = ethers.utils.hexlify(ethers.utils.randomBytes(pubdataToSend)); + + await expect( + ( + await gasBoundCaller.gasBoundCall( + l1MessengerAddress, + gasSpentOnPubdata, + l1Messenger.interface.encodeFunctionData('sendToL1', [pubdata]), + { + gasLimit: 80_000_000 + } + ) + ).wait() + ).toBeRejected(); + + await ( + await gasBoundCaller.gasBoundCall( + l1MessengerAddress, + 80_000_000, + l1Messenger.interface.encodeFunctionData('sendToL1', [pubdata]), + { + gasLimit: 80_000_000 + } + ) + ).wait(); + }); + test('Should check that system contracts and SDK create same CREATE/CREATE2 addresses', async () => { const deployerContract = new zksync.Contract( zksync.utils.CONTRACT_DEPLOYER_ADDRESS, @@ -223,7 +270,7 @@ describe('System behavior checks', () => { await alice.transfer({ amount, to: bob.address, token: l2Token }).then((tx) => tx.wait()); testMaster.reporter.debug('Sent L2 token to Bob'); await alice - .transfer({ amount: L2_ETH_PER_ACCOUNT.div(8), to: bob.address, token: zksync.utils.ETH_ADDRESS }) + .transfer({ amount: L2_DEFAULT_ETH_PER_ACCOUNT.div(8), to: bob.address, token: zksync.utils.ETH_ADDRESS }) .then((tx) => tx.wait()); testMaster.reporter.debug('Sent ethereum on L2 to Bob'); @@ -377,7 +424,7 @@ describe('System behavior checks', () => { it('should reject transaction with huge gas limit', async () => { await expect( - alice.sendTransaction({ to: alice.address, gasLimit: ethers.BigNumber.from(2).pow(32) }) + alice.sendTransaction({ to: alice.address, gasLimit: ethers.BigNumber.from(2).pow(51) }) ).toBeRejected('exceeds block gas limit'); }); diff --git a/core/tests/vm-benchmark/harness/src/instruction_counter.rs b/core/tests/vm-benchmark/harness/src/instruction_counter.rs index b52678ca20d..8ab861c56ae 100644 --- a/core/tests/vm-benchmark/harness/src/instruction_counter.rs +++ b/core/tests/vm-benchmark/harness/src/instruction_counter.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use multivm::{ - interface::{dyn_tracers::vm_1_4_1::DynTracer, tracer::TracerExecutionStatus}, + interface::{dyn_tracers::vm_1_5_0::DynTracer, tracer::TracerExecutionStatus}, vm_latest::{BootloaderState, HistoryMode, SimpleMemory, VmTracer, ZkSyncVmState}, }; use zksync_state::WriteStorage; diff --git a/etc/contracts-test-data/contracts/precompiles/precompiles.sol b/etc/contracts-test-data/contracts/precompiles/precompiles.sol index d9e23c46a6f..7ba10166275 100644 --- a/etc/contracts-test-data/contracts/precompiles/precompiles.sol +++ b/etc/contracts-test-data/contracts/precompiles/precompiles.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.0; +address constant CODE_ORACLE_ADDR = 0x0000000000000000000000000000000000008012; + contract Precompiles { function doKeccak(uint256 iters) public pure returns (uint256) { uint256 sum = 0; @@ -18,4 +20,20 @@ contract Precompiles { } return sum; } + + uint256 lastCodeOracleCost; + function callCodeOracle(bytes32 _versionedHash, bytes32 _expectedBytecodeHash) public { + uint256 gasBefore = gasleft(); + + // Call the code oracle + (bool success, bytes memory returnedBytecode) = CODE_ORACLE_ADDR.staticcall(abi.encodePacked(_versionedHash)); + + lastCodeOracleCost = gasBefore - gasleft(); + + // Check the result + require(success, "CodeOracle call failed"); + + // Check the returned bytecode + require(keccak256(returnedBytecode) == _expectedBytecodeHash, "Returned bytecode does not match the expected hash"); + } } diff --git a/etc/contracts-test-data/contracts/storage/storage.sol b/etc/contracts-test-data/contracts/storage/storage.sol new file mode 100644 index 00000000000..2f386f5c732 --- /dev/null +++ b/etc/contracts-test-data/contracts/storage/storage.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +contract StorageTester { + uint256 public value; + + // Will cause 65-byte pubdata to be published + uint256 constant BIG_VALUE = 0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + // Will cause 34-byte pubdata to be published + uint256 constant SMALL_VALUE = 1; + + function simpleWrite() external { + value = BIG_VALUE; + } + + function resettingWrite() external { + value = BIG_VALUE; + value = SMALL_VALUE; + } + + // We have to use a variable since otherwise the function will be optimized + // to never do the write in the first place + function writeAndRevert(bool shouldRevert) external { + value = BIG_VALUE; + + if(shouldRevert) { + revert("This method always reverts"); + } + } + + function resettingWriteViaRevert() external { + value = SMALL_VALUE; + (bool success ,) = (address(this)).call(abi.encodeWithSignature("writeAndRevert(bool)", (true))); + value = SMALL_VALUE; + } + + // This test aims to check that the tstore/sstore are writing into separate spaces. + function testTrasientAndNonTransientStore() external { + value = 100; + + uint256 x; + + uint256 storedVal; + uint256 tStoredVal; + assembly { + tstore(0, 1) + + storedVal := sload(0) + tStoredVal := tload(0) + } + + require(storedVal == 100, "Stored value should be 100"); + require(tStoredVal == 1, "Transient stored value should be 1"); + } + + + uint256 constant TSTORE_TEST_KEY = 0xff; + + // We have to use a variable since otherwise the function will be optimized + // to never do the write in the first place + function tStoreAndRevert(uint256 value, bool shouldRevert) external { + uint256 key = TSTORE_TEST_KEY; + assembly { + tstore(key, value) + } + + if(shouldRevert) { + revert("This method always reverts"); + } + } + + // We have to use a variable since otherwise the function will be optimized + // to never do the write in the first place + function assertTValue(uint256 expectedValue) external { + uint256 key = TSTORE_TEST_KEY; + uint256 realValue; + assembly { + realValue := tload(key) + } + + require(realValue == expectedValue, "The value in transient storage is not what was expected"); + } + + function testTstoreRollback() external { + // Firstly, write the 1000 to the transient storage + this.tStoreAndRevert(1000, false); + + // Now call and ignore the error + (bool success, ) = (address(this)).call(abi.encodeWithSignature("tStoreAndRevert(uint256,bool)", uint256(500), true)); + require(!success, "The call should have failed"); + + this.assertTValue(1000); + } + + function testTransientStore() external { + this.testTrasientAndNonTransientStore(); + this.testTstoreRollback(); + } + + fallback() external {} +} diff --git a/etc/contracts-test-data/contracts/transfer/transfer.sol b/etc/contracts-test-data/contracts/transfer/transfer.sol new file mode 100644 index 00000000000..4c63a2e9c7d --- /dev/null +++ b/etc/contracts-test-data/contracts/transfer/transfer.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.0; + +contract TransferTest { + function transfer(address payable to, uint256 amount) public payable { + to.transfer(amount); + } + + function send(address payable to, uint256 amount) public payable { + bool success = to.send(amount); + + require(success, "Transaction failed"); + } + + receive() external payable { + + } +} + +contract Recipient { + event Received(address indexed sender, uint256 amount); + + receive() external payable { + require(gasleft() >= 2100, "Not enough gas"); + require(gasleft() <= 2300, "Too much gas"); + emit Received(msg.sender, msg.value); + } +} + +contract ReentrantRecipient { + uint256 x; + + event Received(uint256 tmp, uint256 amount); + + function setX() external payable { + x = 1; + } + + receive() external payable { + require(gasleft() >= 15000); + x = 1; + } +} diff --git a/etc/contracts-test-data/hardhat.config.ts b/etc/contracts-test-data/hardhat.config.ts index 1828a5547c7..1883c1f6cd4 100644 --- a/etc/contracts-test-data/hardhat.config.ts +++ b/etc/contracts-test-data/hardhat.config.ts @@ -1,10 +1,23 @@ import '@matterlabs/hardhat-zksync-solc'; +const COMPILER_VERSION = '1.5.0'; +const PRE_RELEASE_VERSION = 'prerelease-a167aa3-code4rena'; +function getZksolcUrl(): string { + // @ts-ignore + const platform = { darwin: 'macosx', linux: 'linux', win32: 'windows' }[process.platform]; + // @ts-ignore + const toolchain = { linux: '-musl', win32: '-gnu', darwin: '' }[process.platform]; + const arch = process.arch === 'x64' ? 'amd64' : process.arch; + const ext = process.platform === 'win32' ? '.exe' : ''; + + return `https://github.com/matter-labs/era-compiler-solidity/releases/download/${PRE_RELEASE_VERSION}/zksolc-${platform}-${arch}${toolchain}-v${COMPILER_VERSION}${ext}`; +} + export default { zksolc: { - version: '1.3.7', compilerSource: 'binary', settings: { + compilerPath: getZksolcUrl(), isSystem: true } }, @@ -14,6 +27,9 @@ export default { } }, solidity: { - version: '0.8.16' + version: '0.8.24', + settings: { + evmVersion: 'cancun' + } } }; diff --git a/etc/env/base/chain.toml b/etc/env/base/chain.toml index e82565e2b1e..48263a0d12c 100644 --- a/etc/env/base/chain.toml +++ b/etc/env/base/chain.toml @@ -16,7 +16,8 @@ fee_account_addr = "0xde03a0B5963f75f1C8485B355fF6D30f3093BDE7" # Denotes the amount of slots for transactions in the block. transaction_slots = 250 -max_allowed_l2_tx_gas_limit = 4000000000 +# 2^50, the maximal allowed gas limit in a batch. +max_allowed_l2_tx_gas_limit = 1125899906842624 block_commit_deadline_ms = 2500 miniblock_commit_deadline_ms = 1000 miniblock_seal_queue_capacity = 10 @@ -75,7 +76,7 @@ max_gas_per_batch = 200000000 # Note, the max at this moment is 252 kb max_pubdata_per_batch = 100000 -# The version of the fee model to use. +# The version of the fee model to use. # - `V1`, the first model that was used in zkSync Era. In this fee model, the pubdata price must be pegged to the L1 gas price. # Also, the fair L2 gas price is expected to only include the proving/computation price for the operator and not the costs that come from # processing the batch on L1. @@ -91,8 +92,8 @@ save_call_traces = true virtual_blocks_interval = 1 virtual_blocks_per_miniblock = 1 -bootloader_hash = "0x010007ede999d096c84553fb514d3d6ca76fbf39789dda76bfeda9f3ae06236e" -default_aa_hash = "0x0100055b041eb28aff6e3a6e0f37c31fd053fc9ef142683b05e5f0aee6934066" +bootloader_hash = "0x0100089d6cf001d52b9e64572fa9b3555211f56a2ad66784f495c4192a88b477" +default_aa_hash = "0x01000563ee5d0abdf9fc65f748a700d2c377aadebc16d5bf21105f8a94eff028" [chain.operations_manager] # Sleep time when there is no new input data diff --git a/etc/env/base/contracts.toml b/etc/env/base/contracts.toml index 5acf3f7e0fd..700b6017af9 100644 --- a/etc/env/base/contracts.toml +++ b/etc/env/base/contracts.toml @@ -26,13 +26,12 @@ RECURSION_NODE_LEVEL_VK_HASH = "0x1186ec268d49f1905f8d9c1e9d39fc33e98c74f91d91a2 RECURSION_LEAF_LEVEL_VK_HASH = "0x101e08b00193e529145ee09823378ef51a3bc8966504064f1f6ba3f1ba863210" RECURSION_CIRCUITS_SET_VKS_HASH = "0x18c1639094f58177409186e8c48d9f577c9410901d2f1d486b3e7d6cf553ae4c" GENESIS_TX_HASH = "0xb99ebfea46cbe05a21cd80fe5597d97b204befc52a16303f579c607dc1ac2e2e" -GENESIS_ROOT = "0x1f50e4eda06a68d96a0272ba4581a342df2227ad12c23759ab7d78157950e69a" +GENESIS_ROOT = "0x04dac60f5d2ed063336847f360cbc3f332d1688d2a39c1f87c0b3180b2200174" PRIORITY_TX_MAX_GAS_LIMIT = 72000000 DEPLOY_L2_BRIDGE_COUNTERPART_GAS_LIMIT = 10000000 -GENESIS_BATCH_COMMITMENT = "0x84d7b576b9374729e0b38439c97aaf5074335e2a8d1c7a2e4581c1c1ec611631" -# Current rollup leaf index after genesis -GENESIS_ROLLUP_LEAF_INDEX = "46" -GENESIS_PROTOCOL_VERSION = "22" +GENESIS_BATCH_COMMITMENT = "0x1b5c65b84ba99d11b2ae25fa9ec0302d15bc81805363934c782eccf1f9690fba" +GENESIS_ROLLUP_LEAF_INDEX = "52" +GENESIS_PROTOCOL_VERSION = "23" L1_WETH_BRIDGE_IMPL_ADDR = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" L1_WETH_BRIDGE_PROXY_ADDR = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" L1_WETH_TOKEN_ADDR = "0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9" diff --git a/etc/multivm_bootloaders/vm_1_5_0/fee_estimate.yul/fee_estimate.yul.zbin b/etc/multivm_bootloaders/vm_1_5_0/fee_estimate.yul/fee_estimate.yul.zbin new file mode 100644 index 00000000000..595c2b4e184 Binary files /dev/null and b/etc/multivm_bootloaders/vm_1_5_0/fee_estimate.yul/fee_estimate.yul.zbin differ diff --git a/etc/multivm_bootloaders/vm_1_5_0/gas_test.yul/gas_test.yul.zbin b/etc/multivm_bootloaders/vm_1_5_0/gas_test.yul/gas_test.yul.zbin new file mode 100644 index 00000000000..cdd1fcc13cd Binary files /dev/null and b/etc/multivm_bootloaders/vm_1_5_0/gas_test.yul/gas_test.yul.zbin differ diff --git a/etc/multivm_bootloaders/vm_1_5_0/playground_batch.yul/playground_batch.yul.zbin b/etc/multivm_bootloaders/vm_1_5_0/playground_batch.yul/playground_batch.yul.zbin new file mode 100644 index 00000000000..1e65c822aeb Binary files /dev/null and b/etc/multivm_bootloaders/vm_1_5_0/playground_batch.yul/playground_batch.yul.zbin differ diff --git a/etc/multivm_bootloaders/vm_1_5_0/proved_batch.yul/proved_batch.yul.zbin b/etc/multivm_bootloaders/vm_1_5_0/proved_batch.yul/proved_batch.yul.zbin new file mode 100644 index 00000000000..f5654815d5c Binary files /dev/null and b/etc/multivm_bootloaders/vm_1_5_0/proved_batch.yul/proved_batch.yul.zbin differ diff --git a/infrastructure/zk/src/init.ts b/infrastructure/zk/src/init.ts index a87e957b1cf..29156f08f1f 100644 --- a/infrastructure/zk/src/init.ts +++ b/infrastructure/zk/src/init.ts @@ -151,7 +151,7 @@ export async function submoduleUpdate() { } export async function validiumSubmoduleCheckout() { - await utils.exec(`cd contracts && git checkout origin/feat_validium_mode`); + await utils.exec(`cd contracts && git checkout origin/feat-validium-1-5-0-integration`); } // clone dockprom and zksync-era dashboards diff --git a/prover/Cargo.lock b/prover/Cargo.lock index a51a839c39a..ca77f4c3844 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -883,6 +883,17 @@ dependencies = [ "zkevm_circuits 1.4.1", ] +[[package]] +name = "circuit_encodings" +version = "0.1.50" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.5.0#e9a20de5f2a2f960e22e3b66e3ea2782128403f7" +dependencies = [ + "derivative", + "serde", + "zk_evm 1.5.0", + "zkevm_circuits 1.5.0", +] + [[package]] name = "circuit_sequencer_api" version = "0.1.0" @@ -934,6 +945,19 @@ dependencies = [ "zk_evm 1.4.1", ] +[[package]] +name = "circuit_sequencer_api" +version = "0.1.50" +source = "git+https://github.com/matter-labs/era-zkevm_test_harness.git?branch=v1.5.0#e9a20de5f2a2f960e22e3b66e3ea2782128403f7" +dependencies = [ + "bellman_ce 0.3.2 (git+https://github.com/matter-labs/bellman?branch=dev)", + "circuit_encodings 0.1.50", + "derivative", + "rayon", + "serde", + "zk_evm 1.5.0", +] + [[package]] name = "circuit_testing" version = "0.1.0" @@ -1676,6 +1700,7 @@ dependencies = [ "ff 0.13.0", "generic-array", "group 0.13.0", + "pem-rfc7468", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", @@ -3259,6 +3284,7 @@ dependencies = [ "circuit_sequencer_api 0.1.40", "circuit_sequencer_api 0.1.41", "circuit_sequencer_api 0.1.42", + "circuit_sequencer_api 0.1.50", "hex", "itertools 0.10.5", "once_cell", @@ -3269,6 +3295,7 @@ dependencies = [ "zk_evm 1.3.3 (git+https://github.com/matter-labs/era-zk_evm.git?tag=v1.3.3-rc2)", "zk_evm 1.4.0", "zk_evm 1.4.1", + "zk_evm 1.5.0", "zksync_contracts", "zksync_state", "zksync_system_constants", @@ -3757,6 +3784,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "packed_simd" version = "0.3.9" @@ -4002,6 +4041,15 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -7149,6 +7197,20 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zk_evm" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zk_evm.git?branch=v1.5.0#06deb82ef51666b21ac5ebb543366906db04916b" +dependencies = [ + "anyhow", + "lazy_static", + "num", + "serde", + "serde_json", + "static_assertions", + "zk_evm_abstractions 1.5.0", +] + [[package]] name = "zk_evm_abstractions" version = "0.1.0" @@ -7173,6 +7235,18 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zk_evm_abstractions" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.5.0#e464b2cf2b146d883be80e7d690c752bf670ff05" +dependencies = [ + "anyhow", + "num_enum 0.6.1", + "serde", + "static_assertions", + "zkevm_opcode_defs 1.5.0", +] + [[package]] name = "zkevm-assembly" version = "1.3.2" @@ -7253,6 +7327,25 @@ dependencies = [ "zkevm_opcode_defs 1.4.1", ] +[[package]] +name = "zkevm_circuits" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_circuits.git?branch=v1.5.0#ff60cee4e0b1cae5e920f6d041e84851377327ca" +dependencies = [ + "arrayvec 0.7.4", + "boojum", + "cs_derive 0.1.0 (git+https://github.com/matter-labs/era-boojum?branch=main)", + "derivative", + "hex", + "itertools 0.10.5", + "rand 0.4.6", + "rand 0.8.5", + "seq-macro", + "serde", + "smallvec", + "zkevm_opcode_defs 1.5.0", +] + [[package]] name = "zkevm_opcode_defs" version = "1.3.1" @@ -7292,6 +7385,22 @@ dependencies = [ "sha3 0.10.8", ] +[[package]] +name = "zkevm_opcode_defs" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.5.0#dc74cf4e1167a9636e14725da09c8020a8bfa26b" +dependencies = [ + "bitflags 2.4.2", + "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ethereum-types", + "k256 0.13.3", + "lazy_static", + "p256", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", +] + [[package]] name = "zkevm_test_harness" version = "1.3.3" diff --git a/prover/witness_generator/src/basic_circuits.rs b/prover/witness_generator/src/basic_circuits.rs index 6f424c2127f..af1c17966c1 100644 --- a/prover/witness_generator/src/basic_circuits.rs +++ b/prover/witness_generator/src/basic_circuits.rs @@ -15,7 +15,8 @@ use circuit_definitions::{ encodings::recursion_request::RecursionQueueSimulator, zkevm_circuits::fsm_input_output::ClosedFormInputCompactFormWitness, }; -use multivm::vm_latest::{ +// TODO: Switch to `vm_latest` once the prover supports v1.5.0 +use multivm::vm_1_4_2::{ constants::MAX_CYCLES_FOR_TX, HistoryDisabled, StorageOracle as VmStorageOracle, }; use prover_dal::{