From 9a888902b8ae11097e3563038b40b9f5bd95476b Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 11:34:24 +0300 Subject: [PATCH 01/18] Start generalizing VM unit tests --- core/lib/multivm/src/versions/mod.rs | 2 - .../src/versions/testonly/block_tip.rs | 390 +++++++++++++++++ .../src/versions/testonly/bootloader.rs | 44 ++ .../versions/testonly/bytecode_publishing.rs | 36 ++ .../multivm/src/versions/testonly/circuits.rs | 74 ++++ .../src/versions/testonly/code_oracle.rs | 242 +++++++++++ .../src/versions/testonly/default_aa.rs | 64 +++ .../src/versions/testonly/gas_limit.rs | 35 ++ .../versions/testonly/get_used_contracts.rs | 231 +++++++++++ .../src/versions/testonly/is_write_initial.rs | 39 ++ .../src/versions/testonly/l1_tx_execution.rs | 183 ++++++++ .../versions/{testonly.rs => testonly/mod.rs} | 75 +++- .../versions/{tests.rs => testonly/shadow.rs} | 4 +- .../vm_tester.rs => testonly/tester/mod.rs} | 125 ++++-- .../tester/transaction_test_info.rs | 0 .../src/versions/vm_fast/tests/block_tip.rs | 392 +----------------- .../src/versions/vm_fast/tests/bootloader.rs | 50 +-- .../vm_fast/tests/bytecode_publishing.rs | 38 +- .../src/versions/vm_fast/tests/circuits.rs | 74 +--- .../src/versions/vm_fast/tests/code_oracle.rs | 247 +---------- .../src/versions/vm_fast/tests/default_aa.rs | 81 +--- .../src/versions/vm_fast/tests/gas_limit.rs | 39 +- .../vm_fast/tests/get_used_contracts.rs | 235 +---------- .../vm_fast/tests/invalid_bytecode.rs | 120 ------ .../vm_fast/tests/is_write_initial.rs | 46 +- .../versions/vm_fast/tests/l1_tx_execution.rs | 197 +-------- .../multivm/src/versions/vm_fast/tests/mod.rs | 88 +++- .../src/versions/vm_fast/tests/tester/mod.rs | 6 - .../src/versions/vm_fast/tests/utils.rs | 4 - core/lib/vm_interface/src/storage/view.rs | 10 + 30 files changed, 1634 insertions(+), 1537 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/block_tip.rs create mode 100644 core/lib/multivm/src/versions/testonly/bootloader.rs create mode 100644 core/lib/multivm/src/versions/testonly/bytecode_publishing.rs create mode 100644 core/lib/multivm/src/versions/testonly/circuits.rs create mode 100644 core/lib/multivm/src/versions/testonly/code_oracle.rs create mode 100644 core/lib/multivm/src/versions/testonly/default_aa.rs create mode 100644 core/lib/multivm/src/versions/testonly/gas_limit.rs create mode 100644 core/lib/multivm/src/versions/testonly/get_used_contracts.rs create mode 100644 core/lib/multivm/src/versions/testonly/is_write_initial.rs create mode 100644 core/lib/multivm/src/versions/testonly/l1_tx_execution.rs rename core/lib/multivm/src/versions/{testonly.rs => testonly/mod.rs} (53%) rename core/lib/multivm/src/versions/{tests.rs => testonly/shadow.rs} (99%) rename core/lib/multivm/src/versions/{vm_fast/tests/tester/vm_tester.rs => testonly/tester/mod.rs} (65%) rename core/lib/multivm/src/versions/{vm_fast/tests => testonly}/tester/transaction_test_info.rs (100%) delete mode 100644 core/lib/multivm/src/versions/vm_fast/tests/invalid_bytecode.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/tests/tester/mod.rs diff --git a/core/lib/multivm/src/versions/mod.rs b/core/lib/multivm/src/versions/mod.rs index bcb246cece4..1df706a6cce 100644 --- a/core/lib/multivm/src/versions/mod.rs +++ b/core/lib/multivm/src/versions/mod.rs @@ -1,8 +1,6 @@ mod shared; #[cfg(test)] mod testonly; -#[cfg(test)] -mod tests; pub mod vm_1_3_2; pub mod vm_1_4_1; pub mod vm_1_4_2; diff --git a/core/lib/multivm/src/versions/testonly/block_tip.rs b/core/lib/multivm/src/versions/testonly/block_tip.rs new file mode 100644 index 00000000000..dee563dc582 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/block_tip.rs @@ -0,0 +1,390 @@ +use ethabi::Token; +use itertools::Itertools; +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, fee_model::BatchFeeInput, get_code_key, + l2_to_l1_log::L2ToL1Log, writes::StateDiffRecord, Address, Execute, H256, U256, +}; +use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; + +use super::{ + get_complex_upgrade_abi, get_empty_storage, read_complex_upgrade, + tester::{TestedVm, VmTesterBuilder}, +}; +use crate::{ + interface::{L1BatchEnv, TxExecutionMode, VmExecutionMode, VmInterfaceExt}, + versions::testonly::default_l1_batch, + vm_latest::constants::{ + BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD, + BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD, BOOTLOADER_BATCH_TIP_OVERHEAD, + MAX_VM_PUBDATA_PER_BATCH, + }, +}; + +#[derive(Debug, Clone, Default)] +struct L1MessengerTestData { + l2_to_l1_logs: usize, + messages: Vec>, + bytecodes: Vec>, + state_diffs: Vec, +} + +struct MimicCallInfo { + to: Address, + who_to_mimic: Address, + data: 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"); + + let logs_mimic_calls = (0..data.l2_to_l1_logs).map(|_| MimicCallInfo { + to: L1_MESSENGER_ADDRESS, + who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, + data: l1_messenger + .function("sendL2ToL1Log") + .unwrap() + .encode_input(&[ + Token::Bool(false), + Token::FixedBytes(H256::random().0.to_vec()), + Token::FixedBytes(H256::random().0.to_vec()), + ]) + .unwrap(), + }); + let messages_mimic_calls = data.messages.iter().map(|message| MimicCallInfo { + to: L1_MESSENGER_ADDRESS, + who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, + data: l1_messenger + .function("sendToL1") + .unwrap() + .encode_input(&[Token::Bytes(message.clone())]) + .unwrap(), + }); + let bytecodes_mimic_calls = data.bytecodes.iter().map(|bytecode| MimicCallInfo { + to: L1_MESSENGER_ADDRESS, + who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, + data: l1_messenger + .function("requestBytecodeL1Publication") + .unwrap() + .encode_input(&[Token::FixedBytes(hash_bytecode(bytecode).0.to_vec())]) + .unwrap(), + }); + + let encoded_calls = logs_mimic_calls + .chain(messages_mimic_calls) + .chain(bytecodes_mimic_calls) + .map(|call| { + Token::Tuple(vec![ + Token::Address(call.to), + Token::Address(call.who_to_mimic), + Token::Bytes(call.data), + ]) + }) + .chunks(CALLS_PER_TX) + .into_iter() + .map(|chunk| { + complex_upgrade + .function("mimicCalls") + .unwrap() + .encode_input(&[Token::Array(chunk.collect_vec())]) + .unwrap() + }) + .collect_vec(); + + encoded_calls +} + +struct TestStatistics { + pub max_used_gas: u32, + pub circuit_statistics: u64, + pub execution_metrics_size: u64, +} + +struct StatisticsTagged { + pub statistics: TestStatistics, + pub tag: String, +} + +fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { + let mut storage = get_empty_storage(); + let complex_upgrade_code = read_complex_upgrade(); + + // For this test we'll just put the bytecode onto the force deployer address + storage.set_value( + get_code_key(&CONTRACT_FORCE_DEPLOYER_ADDRESS), + hash_bytecode(&complex_upgrade_code), + ); + storage.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() + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_l1_batch_env(batch_env) + .build::(); + + let bytecodes: Vec<_> = test_data.bytecodes.iter().map(Vec::as_slice).collect(); + vm.vm.insert_bytecodes(&bytecodes); + + let txs_data = populate_mimic_calls(test_data.clone()); + let account = &mut vm.rich_accounts[0]; + + for (i, data) in txs_data.into_iter().enumerate() { + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(CONTRACT_FORCE_DEPLOYER_ADDRESS), + calldata: data, + value: U256::zero(), + factory_deps: vec![], + }, + 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 + let gas_before = vm.vm.gas_remaining(); + let result = vm + .vm + .execute_with_state_diffs(test_data.state_diffs.clone(), VmExecutionMode::Batch); + assert!( + !result.result.is_failed(), + "Batch wasn't successful for input: {test_data:?}" + ); + let gas_after = vm.vm.gas_remaining(); + assert_eq!((gas_before - gas_after) as u64, result.statistics.gas_used); + + TestStatistics { + max_used_gas: gas_before - gas_after, + circuit_statistics: result.statistics.circuit_statistic.total() as u64, + execution_metrics_size: result.get_execution_metrics(None).size() as u64, + } +} + +fn generate_state_diffs( + repeated_writes: bool, + small_diff: bool, + number_of_state_diffs: usize, +) -> Vec { + (0..number_of_state_diffs) + .map(|i| { + let address = Address::from_low_u64_be(i as u64); + let key = U256::from(i); + let enumeration_index = if repeated_writes { i + 1 } else { 0 }; + + let (initial_value, final_value) = if small_diff { + // As small as it gets, one byte to denote zeroing out the value + (U256::from(1), U256::from(0)) + } else { + // As large as it gets + (U256::from(0), U256::from(2).pow(255.into())) + }; + + StateDiffRecord { + address, + key, + derived_key: u256_to_h256(i.into()).0, + enumeration_index: enumeration_index as u64, + initial_value, + final_value, + } + }) + .collect() +} + +// A valid zkEVM bytecode has odd number of 32 byte words +fn get_valid_bytecode_length(length: usize) -> usize { + // Firstly ensure that the length is divisible by 32 + let length_padded_to_32 = if length % 32 == 0 { + length + } else { + length + 32 - (length % 32) + }; + + // Then we ensure that the number returned by division by 32 is odd + if length_padded_to_32 % 64 == 0 { + length_padded_to_32 + 32 + } else { + length_padded_to_32 + } +} + +pub(crate) 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` + // is sufficient with a very large margin, so it is okay to ignore 1% of possible pubdata. + const MAX_EFFECTIVE_PUBDATA_PER_BATCH: usize = + (MAX_VM_PUBDATA_PER_BATCH as f64 * 0.99) as usize; + + // We are re-using the `ComplexUpgrade` contract as it already has the `mimicCall` functionality. + // To get the upper bound, we'll try to do the following: + // 1. Max number of logs. + // 2. Lots of small L2->L1 messages / one large L2->L1 message. + // 3. Lots of small bytecodes / one large bytecode. + // 4. Lots of storage slot updates. + + 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 + .iter() + .map(|s| (s.statistics.max_used_gas, s.tag.clone())) + .max() + .unwrap(); + assert!( + 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, + BOOTLOADER_BATCH_TIP_OVERHEAD + ); + + let circuit_statistics = statistics + .iter() + .map(|s| (s.statistics.circuit_statistics, s.tag.clone())) + .max() + .unwrap(); + assert!( + 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, + BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD + ); + + let execution_metrics_size = statistics + .iter() + .map(|s| (s.statistics.execution_metrics_size, s.tag.clone())) + .max() + .unwrap(); + assert!( + 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, + BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD + ); +} diff --git a/core/lib/multivm/src/versions/testonly/bootloader.rs b/core/lib/multivm/src/versions/testonly/bootloader.rs new file mode 100644 index 00000000000..4aa42c39aa4 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/bootloader.rs @@ -0,0 +1,44 @@ +use assert_matches::assert_matches; +use zksync_types::U256; + +use super::{get_bootloader, tester::VmTesterBuilder, TestedVm, BASE_SYSTEM_CONTRACTS}; +use crate::interface::{ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +pub(crate) fn test_dummy_bootloader() { + let mut base_system_contracts = BASE_SYSTEM_CONTRACTS.clone(); + base_system_contracts.bootloader = get_bootloader("dummy"); + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_base_system_smart_contracts(base_system_contracts) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build::(); + + let result = vm.vm.execute(VmExecutionMode::Batch); + assert!(!result.result.is_failed()); + + let correct_first_cell = U256::from_str_radix("123123123", 16).unwrap(); + vm.vm + .verify_required_bootloader_memory(&[(0, correct_first_cell)]); +} + +pub(crate) fn test_bootloader_out_of_gas() { + let mut base_system_contracts = BASE_SYSTEM_CONTRACTS.clone(); + base_system_contracts.bootloader = get_bootloader("dummy"); + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_base_system_smart_contracts(base_system_contracts) + .with_bootloader_gas_limit(10) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build::(); + + let res = vm.vm.execute(VmExecutionMode::Batch); + + assert_matches!( + res.result, + ExecutionResult::Halt { + reason: Halt::BootloaderOutOfGas + } + ); +} diff --git a/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs b/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs new file mode 100644 index 00000000000..97cf0db35d1 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs @@ -0,0 +1,36 @@ +use zksync_test_account::{DeployContractsTx, TxType}; + +use super::{read_test_contract, tester::VmTesterBuilder, TestedVm}; +use crate::{ + interface::{TxExecutionMode, VmEvent, VmExecutionMode, VmInterfaceExt}, + utils::bytecode, +}; + +pub(crate) fn test_bytecode_publishing() { + // In this test, we aim to ensure that the contents of the compressed bytecodes + // are included as part of the L2->L1 long messages + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let counter = read_test_contract(); + let account = &mut vm.rich_accounts[0]; + + let compressed_bytecode = bytecode::compress(counter.clone()).unwrap().compressed; + + let DeployContractsTx { tx, .. } = account.get_deploy_tx(&counter, None, TxType::L2); + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Transaction wasn't successful"); + + vm.vm.execute(VmExecutionMode::Batch); + + let state = vm.vm.get_current_execution_state(); + let long_messages = VmEvent::extract_long_l2_to_l1_messages(&state.events); + assert!( + long_messages.contains(&compressed_bytecode), + "Bytecode not published" + ); +} diff --git a/core/lib/multivm/src/versions/testonly/circuits.rs b/core/lib/multivm/src/versions/testonly/circuits.rs new file mode 100644 index 00000000000..26259247013 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/circuits.rs @@ -0,0 +1,74 @@ +use zksync_types::{Address, Execute, U256}; + +use super::tester::VmTesterBuilder; +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}, + versions::testonly::TestedVm, + vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, +}; + +/// Checks that estimated number of circuits for simple transfer doesn't differ much +/// from hardcoded expected value. +pub(crate) fn test_circuits() { + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build::(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(Address::random()), + calldata: Vec::new(), + value: U256::from(1u8), + factory_deps: vec![], + }, + None, + ); + vm.vm.push_transaction(tx); + let res = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!res.result.is_failed(), "{res:#?}"); + + let s = res.statistics.circuit_statistic; + // Check `circuit_statistic`. + 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"), + (s.ram_permutation, "ram_permutation"), + (s.storage_application, "storage_application"), + (s.storage_sorter, "storage_sorter"), + (s.code_decommitter, "code_decommitter"), + (s.code_decommitter_sorter, "code_decommitter_sorter"), + (s.log_demuxer, "log_demuxer"), + (s.events_sorter, "events_sorter"), + (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 { + assert_eq!( + *actual, expected, + "Check failed for {}, expected {}, actual {}", + name, expected, actual + ); + } else { + let diff = (actual - expected) / expected; + assert!( + diff.abs() < 0.1, + "Check failed for {}, expected {}, actual {}", + name, + expected, + actual + ); + } + } +} diff --git a/core/lib/multivm/src/versions/testonly/code_oracle.rs b/core/lib/multivm/src/versions/testonly/code_oracle.rs new file mode 100644 index 00000000000..0a709be3c88 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/code_oracle.rs @@ -0,0 +1,242 @@ +use ethabi::Token; +use zksync_types::{ + get_known_code_key, web3::keccak256, Address, Execute, StorageLogWithPreviousValue, U256, +}; +use zksync_utils::{bytecode::hash_bytecode, h256_to_u256, u256_to_h256}; + +use super::{ + get_empty_storage, load_precompiles_contract, read_precompiles_contract, read_test_contract, + tester::VmTesterBuilder, TestedVm, +}; +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}, + versions::testonly::ContractToDeploy, +}; + +fn generate_large_bytecode() -> Vec { + // This is the maximal possible size of a zkEVM bytecode + vec![2u8; ((1 << 16) - 1) * 32] +} + +pub(crate) 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() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ContractToDeploy::new( + precompile_contract_bytecode, + precompiles_contract_address, + )]) + .with_storage(storage) + .build::(); + + let precompile_contract = load_precompiles_contract(); + let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); + + vm.vm.insert_bytecodes(&[normal_zkevm_bytecode.as_slice()]); + 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: Some(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: vec![], + }, + None, + ); + + vm.vm.push_transaction(tx1); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful: {result:#?}" + ); + + // 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: Some(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: vec![], + }, + None, + ); + vm.vm.push_transaction(tx2); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful: {result:#?}" + ); +} + +fn find_code_oracle_cost_log( + precompiles_contract_address: Address, + logs: &[StorageLogWithPreviousValue], +) -> &StorageLogWithPreviousValue { + logs.iter() + .find(|log| { + *log.log.key.address() == precompiles_contract_address && log.log.key.key().is_zero() + }) + .expect("no code oracle cost log") +} + +pub(crate) 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() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ContractToDeploy::new( + precompile_contract_bytecode, + precompiles_contract_address, + )]) + .with_storage(storage) + .build::(); + + let precompile_contract = load_precompiles_contract(); + let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); + + vm.vm.insert_bytecodes(&[big_zkevm_bytecode.as_slice()]); + + 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: Some(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: vec![], + }, + None, + ); + + vm.vm.push_transaction(tx1); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful: {result:#?}" + ); +} + +pub(crate) fn test_refunds_in_code_oracle() { + let precompiles_contract_address = Address::random(); + let precompile_contract_bytecode = read_precompiles_contract(); + + 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()), + ); + + let precompile_contract = load_precompiles_contract(); + let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); + + // Execute code oracle twice with identical VM state that only differs in that the queried bytecode + // is already decommitted the second time. The second call must consume less gas (`decommit` doesn't charge additional gas + // for already decommitted codes). + let mut oracle_costs = vec![]; + for decommit in [false, true] { + let mut vm = VmTesterBuilder::new() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ContractToDeploy::new( + precompile_contract_bytecode.clone(), + precompiles_contract_address, + )]) + .with_storage(storage.clone()) + .build::(); + + vm.vm.insert_bytecodes(&[normal_zkevm_bytecode.as_slice()]); + + let account = &mut vm.rich_accounts[0]; + if decommit { + let is_fresh = vm.vm.manually_decommit(normal_zkevm_bytecode_hash); + assert!(is_fresh); + } + + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(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: vec![], + }, + None, + ); + + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful: {result:#?}" + ); + let log = + find_code_oracle_cost_log(precompiles_contract_address, &result.logs.storage_logs); + oracle_costs.push(log.log.value); + } + + // The refund is equal to `gasCost` parameter passed to the `decommit` opcode, which is defined as `4 * contract_length_in_words` + // in `CodeOracle.yul`. + let code_oracle_refund = h256_to_u256(oracle_costs[0]) - h256_to_u256(oracle_costs[1]); + assert_eq!( + code_oracle_refund, + (4 * (normal_zkevm_bytecode.len() / 32)).into() + ); +} diff --git a/core/lib/multivm/src/versions/testonly/default_aa.rs b/core/lib/multivm/src/versions/testonly/default_aa.rs new file mode 100644 index 00000000000..92b3b3910a9 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/default_aa.rs @@ -0,0 +1,64 @@ +use zksync_test_account::{DeployContractsTx, TxType}; +use zksync_types::{ + get_code_key, get_known_code_key, get_nonce_key, + system_contracts::{DEPLOYMENT_NONCE_INCREMENT, TX_NONCE_INCREMENT}, + utils::storage_key_for_eth_balance, + H256, U256, +}; +use zksync_utils::u256_to_h256; + +use super::{read_test_contract, tester::VmTesterBuilder, TestedVm}; +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}, + vm_latest::utils::fee::get_batch_base_fee, +}; + +pub(crate) fn test_default_aa_interaction() { + // 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() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let counter = read_test_contract(); + let account = &mut vm.rich_accounts[0]; + let DeployContractsTx { + tx, + bytecode_hash, + address, + } = account.get_deploy_tx(&counter, None, TxType::L2); + let maximal_fee = tx.gas_limit() * get_batch_base_fee(&vm.l1_batch_env); + + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + 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. + let account_nonce_key = get_nonce_key(&account.address); + let expected_nonce = TX_NONCE_INCREMENT + DEPLOYMENT_NONCE_INCREMENT; + + // The code hash of the deployed contract should be marked as republished. + let known_codes_key = get_known_code_key(&bytecode_hash); + + // The contract should be deployed successfully. + let account_code_key = get_code_key(&address); + + let operator_balance_key = storage_key_for_eth_balance(&vm.l1_batch_env.fee_account); + let expected_fee = maximal_fee + - U256::from(result.refunds.gas_refunded) + * U256::from(get_batch_base_fee(&vm.l1_batch_env)); + + let expected_slots = [ + (account_nonce_key, u256_to_h256(expected_nonce)), + (known_codes_key, H256::from_low_u64_be(1)), + (account_code_key, bytecode_hash), + (operator_balance_key, u256_to_h256(expected_fee)), + ]; + vm.vm.verify_required_storage(&expected_slots); +} diff --git a/core/lib/multivm/src/versions/testonly/gas_limit.rs b/core/lib/multivm/src/versions/testonly/gas_limit.rs new file mode 100644 index 00000000000..fb2be649688 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/gas_limit.rs @@ -0,0 +1,35 @@ +use zksync_test_account::Account; +use zksync_types::{fee::Fee, Execute}; + +use super::{tester::VmTesterBuilder, TestedVm}; +use crate::{ + interface::TxExecutionMode, + vm_latest::constants::{TX_DESCRIPTION_OFFSET, TX_GAS_LIMIT_OFFSET}, +}; + +/// Checks that `TX_GAS_LIMIT_OFFSET` constant is correct. +pub(crate) fn test_tx_gas_limit_offset() { + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let gas_limit = 9999.into(); + let tx = vm.rich_accounts[0].get_l2_tx_for_execute( + Execute { + contract_address: Some(Default::default()), + ..Default::default() + }, + Some(Fee { + gas_limit, + ..Account::default_fee() + }), + ); + + vm.vm.push_transaction(tx); + + let slot = (TX_DESCRIPTION_OFFSET + TX_GAS_LIMIT_OFFSET) as u32; + vm.vm + .verify_required_bootloader_memory(&[(slot, gas_limit)]); +} diff --git a/core/lib/multivm/src/versions/testonly/get_used_contracts.rs b/core/lib/multivm/src/versions/testonly/get_used_contracts.rs new file mode 100644 index 00000000000..be74d89c400 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/get_used_contracts.rs @@ -0,0 +1,231 @@ +use std::{collections::HashSet, iter}; + +use assert_matches::assert_matches; +use ethabi::Token; +use zk_evm_1_3_1::zkevm_opcode_defs::decoding::{EncodingModeProduction, VmEncodingMode}; +use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; +use zksync_test_account::{Account, TxType}; +use zksync_types::{AccountTreeId, Address, Execute, StorageKey, H256, U256}; +use zksync_utils::{bytecode::hash_bytecode, h256_to_u256}; + +use super::{ + read_proxy_counter_contract, read_test_contract, + tester::{VmTester, VmTesterBuilder}, + TestedVm, BASE_SYSTEM_CONTRACTS, +}; +use crate::{ + interface::{ + ExecutionResult, TxExecutionMode, VmExecutionMode, VmExecutionResultAndLogs, VmInterfaceExt, + }, + versions::testonly::ContractToDeploy, +}; + +pub(crate) fn test_get_used_contracts() { + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build::(); + + assert!(known_bytecodes_without_base_system_contracts(&vm.vm).is_empty()); + + // create and push and execute some not-empty factory deps transaction with success status + // to check that `get_decommitted_hashes()` updates + let contract_code = read_test_contract(); + let mut account = Account::random(); + let tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 0 }); + vm.vm.push_transaction(tx.tx.clone()); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed()); + + assert!(vm + .vm + .decommitted_hashes() + .contains(&h256_to_u256(tx.bytecode_hash))); + + // Note: `Default_AA` will be in the list of used contracts if L2 tx is used + assert_eq!( + vm.vm.decommitted_hashes(), + known_bytecodes_without_base_system_contracts(&vm.vm) + ); + + // create push and execute some non-empty factory deps transaction that fails + // (`known_bytecodes` will be updated but we expect `get_decommitted_hashes()` to not be updated) + + let calldata = [1, 2, 3]; + let big_calldata: Vec = calldata + .iter() + .cycle() + .take(calldata.len() * 1024) + .cloned() + .collect(); + let account2 = Account::random(); + let tx2 = account2.get_l1_tx( + Execute { + contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), + calldata: big_calldata, + value: Default::default(), + factory_deps: vec![vec![1; 32]], + }, + 1, + ); + + vm.vm.push_transaction(tx2.clone()); + + let res2 = vm.vm.execute(VmExecutionMode::OneTx); + + assert!(res2.result.is_failed()); + + for factory_dep in tx2.execute.factory_deps { + let hash = hash_bytecode(&factory_dep); + let hash_to_u256 = h256_to_u256(hash); + assert!(known_bytecodes_without_base_system_contracts(&vm.vm).contains(&hash_to_u256)); + assert!(!vm.vm.decommitted_hashes().contains(&hash_to_u256)); + } +} + +fn known_bytecodes_without_base_system_contracts(vm: &impl TestedVm) -> HashSet { + let mut known_bytecodes_without_base_system_contracts = vm.known_bytecode_hashes(); + known_bytecodes_without_base_system_contracts + .remove(&h256_to_u256(BASE_SYSTEM_CONTRACTS.default_aa.hash)); + if let Some(evm_emulator) = &BASE_SYSTEM_CONTRACTS.evm_emulator { + let was_removed = + known_bytecodes_without_base_system_contracts.remove(&h256_to_u256(evm_emulator.hash)); + assert!(was_removed); + } + known_bytecodes_without_base_system_contracts +} + +/// Counter test contract bytecode inflated by appending lots of `NOP` opcodes at the end. This leads to non-trivial +/// decommitment cost (>10,000 gas). +fn inflated_counter_bytecode() -> Vec { + let mut counter_bytecode = read_test_contract(); + counter_bytecode.extend( + iter::repeat(EncodingModeProduction::nop_encoding().to_be_bytes()) + .take(10_000) + .flatten(), + ); + counter_bytecode +} + +#[derive(Debug)] +struct ProxyCounterData { + proxy_counter_address: Address, + counter_bytecode_hash: U256, +} + +fn execute_proxy_counter( + gas: u32, +) -> (VmTester, ProxyCounterData, VmExecutionResultAndLogs) { + let counter_bytecode = inflated_counter_bytecode(); + let counter_bytecode_hash = h256_to_u256(hash_bytecode(&counter_bytecode)); + let counter_address = Address::repeat_byte(0x23); + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_custom_contracts(vec![ContractToDeploy::new( + counter_bytecode, + counter_address, + )]) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let (proxy_counter_bytecode, proxy_counter_abi) = read_proxy_counter_contract(); + let account = &mut vm.rich_accounts[0]; + let deploy_tx = account.get_deploy_tx( + &proxy_counter_bytecode, + Some(&[Token::Address(counter_address)]), + TxType::L2, + ); + let (compression_result, exec_result) = vm + .vm + .execute_transaction_with_bytecode_compression(deploy_tx.tx, true); + compression_result.unwrap(); + assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); + + let decommitted_hashes = vm.vm.decommitted_hashes(); + assert!( + !decommitted_hashes.contains(&counter_bytecode_hash), + "{decommitted_hashes:?}" + ); + + let increment = proxy_counter_abi.function("increment").unwrap(); + let increment_tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(deploy_tx.address), + calldata: increment + .encode_input(&[Token::Uint(1.into()), Token::Uint(gas.into())]) + .unwrap(), + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + let (compression_result, exec_result) = vm + .vm + .execute_transaction_with_bytecode_compression(increment_tx, true); + compression_result.unwrap(); + let data = ProxyCounterData { + proxy_counter_address: deploy_tx.address, + counter_bytecode_hash, + }; + (vm, data, exec_result) +} + +pub(crate) fn test_get_used_contracts_with_far_call() { + let (vm, data, exec_result) = execute_proxy_counter::(100_000); + assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); + let decommitted_hashes = vm.vm.decommitted_hashes(); + assert!( + decommitted_hashes.contains(&data.counter_bytecode_hash), + "{decommitted_hashes:?}" + ); +} + +pub(crate) fn test_get_used_contracts_with_out_of_gas_far_call() { + let (mut vm, data, exec_result) = execute_proxy_counter::(10_000); + assert_matches!(exec_result.result, ExecutionResult::Revert { .. }); + let decommitted_hashes = vm.vm.decommitted_hashes(); + assert!( + decommitted_hashes.contains(&data.counter_bytecode_hash), + "{decommitted_hashes:?}" + ); + + // Execute another transaction with a successful far call and check that it's still charged for decommitment. + let account = &mut vm.rich_accounts[0]; + let (_, proxy_counter_abi) = read_proxy_counter_contract(); + let increment = proxy_counter_abi.function("increment").unwrap(); + let increment_tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(data.proxy_counter_address), + calldata: increment + .encode_input(&[Token::Uint(1.into()), Token::Uint(u64::MAX.into())]) + .unwrap(), + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + let (compression_result, exec_result) = vm + .vm + .execute_transaction_with_bytecode_compression(increment_tx, true); + compression_result.unwrap(); + assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); + + let proxy_counter_cost_key = StorageKey::new( + AccountTreeId::new(data.proxy_counter_address), + H256::from_low_u64_be(1), + ); + let far_call_cost_log = exec_result + .logs + .storage_logs + .iter() + .find(|log| log.log.key == proxy_counter_cost_key) + .expect("no cost log"); + assert!( + far_call_cost_log.previous_value.is_zero(), + "{far_call_cost_log:?}" + ); + let far_call_cost = h256_to_u256(far_call_cost_log.log.value); + assert!(far_call_cost > 10_000.into(), "{far_call_cost}"); +} diff --git a/core/lib/multivm/src/versions/testonly/is_write_initial.rs b/core/lib/multivm/src/versions/testonly/is_write_initial.rs new file mode 100644 index 00000000000..7ba29b2810b --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/is_write_initial.rs @@ -0,0 +1,39 @@ +use zksync_test_account::{Account, TxType}; +use zksync_types::get_nonce_key; + +use super::{read_test_contract, tester::VmTesterBuilder, TestedVm}; +use crate::interface::{storage::ReadStorage, TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +pub(crate) fn test_is_write_initial_behaviour() { + // In this test, we check result of `is_write_initial` at different stages. + // The main idea is to check that `is_write_initial` storage uses the correct cache for initial_writes and doesn't + // messed up it with the repeated writes during the one batch execution. + + let mut account = Account::random(); + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(vec![account.clone()]) + .build::(); + + let nonce_key = get_nonce_key(&account.address); + // Check that the next write to the nonce key will be initial. + assert!(vm + .storage + .as_ref() + .borrow_mut() + .is_write_initial(&nonce_key)); + + let contract_code = read_test_contract(); + let tx = account.get_deploy_tx(&contract_code, None, TxType::L2).tx; + + vm.vm.push_transaction(tx); + vm.vm.execute(VmExecutionMode::OneTx); + + // Check that `is_write_initial` still returns true for the nonce key. + assert!(vm + .storage + .as_ref() + .borrow_mut() + .is_write_initial(&nonce_key)); +} diff --git a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs new file mode 100644 index 00000000000..b83e118eaa8 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs @@ -0,0 +1,183 @@ +use ethabi::Token; +use zksync_contracts::l1_messenger_contract; +use zksync_system_constants::{BOOTLOADER_ADDRESS, L1_MESSENGER_ADDRESS}; +use zksync_test_account::TxType; +use zksync_types::{ + get_code_key, get_known_code_key, + l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, + Execute, ExecuteTransactionCommon, H256, U256, +}; +use zksync_utils::u256_to_h256; + +use super::{read_test_contract, tester::VmTesterBuilder, TestedVm, BASE_SYSTEM_CONTRACTS}; +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}, + utils::StorageWritesDeduplicator, +}; + +pub(crate) fn test_l1_tx_execution() { + // In this test, we try to execute a contract deployment from L1 + // 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 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 5 slots instead of 9 due to 0 fee for transaction. + let basic_initial_writes = 5; + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let contract_code = read_test_contract(); + let account = &mut vm.rich_accounts[0]; + let deploy_tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 1 }); + let tx_hash = deploy_tx.tx.hash(); + + let required_l2_to_l1_logs: Vec<_> = vec![L2ToL1Log { + shard_id: 0, + is_service: true, + tx_number_in_block: 0, + sender: BOOTLOADER_ADDRESS, + key: tx_hash, + value: u256_to_h256(U256::from(1u32)), + }] + .into_iter() + .map(UserL2ToL1Log) + .collect(); + + vm.vm.push_transaction(deploy_tx.tx.clone()); + + let res = vm.vm.execute(VmExecutionMode::OneTx); + + // The code hash of the deployed contract should be marked as republished. + let known_codes_key = get_known_code_key(&deploy_tx.bytecode_hash); + + // The contract should be deployed successfully. + let account_code_key = get_code_key(&deploy_tx.address); + + assert!(!res.result.is_failed()); + + vm.vm.verify_required_storage(&[ + (known_codes_key, H256::from_low_u64_be(1)), + (account_code_key, deploy_tx.bytecode_hash), + ]); + assert_eq!(res.logs.user_l2_to_l1_logs, required_l2_to_l1_logs); + + let tx = account.get_test_contract_transaction( + deploy_tx.address, + true, + None, + false, + TxType::L1 { serial_id: 0 }, + ); + vm.vm.push_transaction(tx); + let res = vm.vm.execute(VmExecutionMode::OneTx); + let storage_logs = res.logs.storage_logs; + let res = StorageWritesDeduplicator::apply_on_empty_state(&storage_logs); + + // Tx panicked + assert_eq!(res.initial_storage_writes, basic_initial_writes); + + let tx = account.get_test_contract_transaction( + deploy_tx.address, + false, + None, + false, + TxType::L1 { serial_id: 0 }, + ); + vm.vm.push_transaction(tx.clone()); + 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. 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); + + // No repeated writes + let repeated_writes = res.repeated_storage_writes; + assert_eq!(res.repeated_storage_writes, 0); + + 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. + // 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); + + let tx = account.get_test_contract_transaction( + deploy_tx.address, + false, + Some(10.into()), + false, + TxType::L1 { serial_id: 1 }, + ); + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + // Method is not payable tx should fail + assert!(result.result.is_failed(), "The transaction should fail"); + + let res = StorageWritesDeduplicator::apply_on_empty_state(&result.logs.storage_logs); + assert_eq!(res.initial_storage_writes, basic_initial_writes); + assert_eq!(res.repeated_storage_writes, 1); +} + +pub(crate) fn test_l1_tx_execution_high_gas_limit() { + // In this test, we try to execute an L1->L2 transaction with a high gas limit. + // Usually priority transactions with dangerously gas limit should even pass the checks on the L1, + // however, they might pass during the transition period to the new fee model, so we check that we can safely process those. + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let account = &mut vm.rich_accounts[0]; + + let l1_messenger = l1_messenger_contract(); + + let contract_function = l1_messenger.function("sendToL1").unwrap(); + let params = [ + // Even a message of size 100k should not be able to be sent by a priority transaction + Token::Bytes(vec![0u8; 100_000]), + ]; + let calldata = contract_function.encode_input(¶ms).unwrap(); + + let mut tx = account.get_l1_tx( + Execute { + contract_address: Some(L1_MESSENGER_ADDRESS), + value: 0.into(), + factory_deps: vec![], + calldata, + }, + 0, + ); + + if let ExecuteTransactionCommon::L1(data) = &mut tx.common_data { + // Using some large gas limit + data.gas_limit = 300_000_000.into(); + } else { + unreachable!() + }; + + vm.vm.push_transaction(tx); + + let res = vm.vm.execute(VmExecutionMode::OneTx); + + assert!(res.result.is_failed(), "The transaction should've failed"); +} diff --git a/core/lib/multivm/src/versions/testonly.rs b/core/lib/multivm/src/versions/testonly/mod.rs similarity index 53% rename from core/lib/multivm/src/versions/testonly.rs rename to core/lib/multivm/src/versions/testonly/mod.rs index 51a4d0842d9..21755fc6fca 100644 --- a/core/lib/multivm/src/versions/testonly.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -1,17 +1,84 @@ -use zksync_contracts::BaseSystemContracts; +use ethabi::Contract; +use once_cell::sync::Lazy; +use zksync_contracts::{ + load_contract, read_bytecode, read_yul_bytecode, BaseSystemContracts, SystemContractCode, +}; use zksync_test_account::Account; use zksync_types::{ block::L2BlockHasher, fee_model::BatchFeeInput, get_code_key, get_is_account_key, helpers::unix_timestamp_ms, utils::storage_key_for_eth_balance, Address, L1BatchNumber, L2BlockNumber, L2ChainId, ProtocolVersionId, U256, }; -use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; +use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, u256_to_h256}; +use zksync_vm_interface::{L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode}; +pub(super) use self::tester::TestedVm; use crate::{ - interface::{storage::InMemoryStorage, L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode}, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, + interface::storage::InMemoryStorage, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; +pub(super) mod block_tip; +pub(super) mod bootloader; +pub(super) mod bytecode_publishing; +pub(super) mod circuits; +pub(super) mod code_oracle; +pub(super) mod default_aa; +pub(super) mod gas_limit; +pub(super) mod get_used_contracts; +pub(super) mod is_write_initial; +pub(super) mod l1_tx_execution; +mod shadow; +mod tester; + +static BASE_SYSTEM_CONTRACTS: Lazy = + Lazy::new(BaseSystemContracts::load_from_disk); + +fn get_empty_storage() -> InMemoryStorage { + InMemoryStorage::with_system_contracts(hash_bytecode) +} + +fn read_test_contract() -> Vec { + read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/counter/counter.sol/Counter.json") +} + +fn get_complex_upgrade_abi() -> Contract { + load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json" + ) +} + +fn read_complex_upgrade() -> Vec { + read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json") +} + +fn read_precompiles_contract() -> Vec { + read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", + ) +} + +fn load_precompiles_contract() -> Contract { + load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", + ) +} + +fn read_proxy_counter_contract() -> (Vec, Contract) { + const PATH: &str = "etc/contracts-test-data/artifacts-zk/contracts/counter/proxy_counter.sol/ProxyCounter.json"; + (read_bytecode(PATH), load_contract(PATH)) +} + +pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { + let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; + let bootloader_code = read_yul_bytecode(artifacts_path, test); + + let bootloader_hash = hash_bytecode(&bootloader_code); + SystemContractCode { + code: bytes_to_be_words(bootloader_code), + hash: bootloader_hash, + } +} + pub(super) fn default_system_env() -> SystemEnv { SystemEnv { zk_porter_available: false, diff --git a/core/lib/multivm/src/versions/tests.rs b/core/lib/multivm/src/versions/testonly/shadow.rs similarity index 99% rename from core/lib/multivm/src/versions/tests.rs rename to core/lib/multivm/src/versions/testonly/shadow.rs index c2a04c155fe..de21b93c522 100644 --- a/core/lib/multivm/src/versions/tests.rs +++ b/core/lib/multivm/src/versions/testonly/shadow.rs @@ -24,8 +24,8 @@ use crate::{ versions::testonly::{ default_l1_batch, default_system_env, make_account_rich, ContractToDeploy, }, - vm_fast, - vm_latest::{self, HistoryEnabled}, + vm_fast, vm_latest, + vm_latest::HistoryEnabled, }; type ReferenceVm = vm_latest::Vm, HistoryEnabled>; diff --git a/core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs similarity index 65% rename from core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs rename to core/lib/multivm/src/versions/testonly/tester/mod.rs index 9549b32c4f1..46007f02272 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/tester/vm_tester.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -1,37 +1,47 @@ -use std::{cell::RefCell, rc::Rc}; +use std::collections::HashSet; use zksync_contracts::BaseSystemContracts; use zksync_test_account::{Account, TxType}; use zksync_types::{ - block::L2BlockHasher, utils::deployed_address_create, AccountTreeId, Address, L1BatchNumber, - L2BlockNumber, Nonce, StorageKey, + block::L2BlockHasher, utils::deployed_address_create, writes::StateDiffRecord, Address, + L1BatchNumber, L2BlockNumber, Nonce, StorageKey, H256, U256, +}; +use zksync_vm_interface::{ + CurrentExecutionState, VmExecutionResultAndLogs, VmInterfaceHistoryEnabled, }; -use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; -use zksync_vm2::{interface::Tracer, WorldDiff}; +use super::{get_empty_storage, read_test_contract}; use crate::{ interface::{ - storage::{InMemoryStorage, StoragePtr}, - L1BatchEnv, L2Block, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmInterface, + storage::{InMemoryStorage, StoragePtr, StorageView}, + L1BatchEnv, L2Block, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmFactory, + VmInterfaceExt, }, - versions::{ - testonly::{default_l1_batch, default_system_env, make_account_rich, ContractToDeploy}, - vm_fast::{tests::utils::read_test_contract, vm::Vm}, + versions::testonly::{ + default_l1_batch, default_system_env, make_account_rich, ContractToDeploy, }, vm_latest::utils::l2_blocks::load_last_l2_block, }; -pub(crate) struct VmTester { - pub(crate) vm: Vm, Tr>, - pub(crate) storage: StoragePtr, +//mod transaction_test_info; FIXME + +// FIXME: revise fields +#[derive(Debug)] +pub(crate) struct VmTester { + pub(crate) vm: VM, + pub(crate) system_env: SystemEnv, + pub(crate) l1_batch_env: L1BatchEnv, + pub(crate) storage: StoragePtr>, pub(crate) deployer: Option, pub(crate) test_contract: Option
, - pub(crate) fee_account: Address, pub(crate) rich_accounts: Vec, pub(crate) custom_contracts: Vec, } -impl VmTester { +impl VmTester +where + VM: VmFactory>, +{ pub(crate) fn deploy_test_contract(&mut self) { let contract = read_test_contract(); let tx = self @@ -42,15 +52,14 @@ impl VmTester { .tx; let nonce = tx.nonce().unwrap().0.into(); self.vm.push_transaction(tx); - self.vm.inspect(&mut Tr::default(), VmExecutionMode::OneTx); + self.vm.execute(VmExecutionMode::OneTx); let deployed_address = deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); self.test_contract = Some(deployed_address); } pub(crate) fn reset_with_empty_storage(&mut self) { - self.storage = Rc::new(RefCell::new(get_empty_storage())); - *self.vm.inner.world_diff_mut() = WorldDiff::default(); + self.storage = StorageView::new(get_empty_storage()).to_rc_ptr(); self.reset_state(false); } @@ -60,10 +69,10 @@ impl VmTester { pub(crate) fn reset_state(&mut self, use_latest_l2_block: bool) { for account in self.rich_accounts.iter_mut() { account.nonce = Nonce(0); - make_account_rich(&mut self.storage.borrow_mut(), account); + make_account_rich(self.storage.borrow_mut().inner_mut(), account); } if let Some(deployer) = &self.deployer { - make_account_rich(&mut self.storage.borrow_mut(), deployer); + make_account_rich(self.storage.borrow_mut().inner_mut(), deployer); } if !self.custom_contracts.is_empty() { @@ -71,19 +80,9 @@ impl VmTester { // `insert_contracts(&mut self.storage, &self.custom_contracts);` } - let storage = self.storage.clone(); - { - let mut storage = storage.borrow_mut(); - // Commit pending storage changes (old VM versions commit them on successful execution) - for (&(address, slot), &value) in self.vm.inner.world_diff().get_storage_state() { - let key = StorageKey::new(AccountTreeId::new(address), u256_to_h256(slot)); - storage.set_value(key, u256_to_h256(value)); - } - } - - let mut l1_batch = self.vm.batch_env.clone(); + let l1_batch = &mut self.l1_batch_env; if use_latest_l2_block { - let last_l2_block = load_last_l2_block(&storage).unwrap_or(L2Block { + let last_l2_block = load_last_l2_block(&self.storage).unwrap_or(L2Block { number: 0, timestamp: 0, hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), @@ -96,8 +95,11 @@ impl VmTester { }; } - let vm = Vm::custom(l1_batch, self.vm.system_env.clone(), storage); - + let vm = VM::new( + l1_batch.clone(), + self.system_env.clone(), + self.storage.clone(), + ); if self.test_contract.is_some() { self.deploy_test_contract(); } @@ -105,6 +107,7 @@ impl VmTester { } } +#[derive(Debug)] pub(crate) struct VmTesterBuilder { storage: Option, l1_batch_env: Option, @@ -196,36 +199,68 @@ impl VmTesterBuilder { self } - pub(crate) fn build(self) -> VmTester<()> { + pub(crate) fn build(self) -> VmTester + where + VM: VmFactory>, + { let l1_batch_env = self .l1_batch_env .unwrap_or_else(|| default_l1_batch(L1BatchNumber(1))); let mut raw_storage = self.storage.unwrap_or_else(get_empty_storage); ContractToDeploy::insert_all(&self.custom_contracts, &mut raw_storage); - let storage_ptr = Rc::new(RefCell::new(raw_storage)); + let storage = StorageView::new(raw_storage).to_rc_ptr(); for account in self.rich_accounts.iter() { - make_account_rich(&mut storage_ptr.borrow_mut(), account); + make_account_rich(storage.borrow_mut().inner_mut(), account); } if let Some(deployer) = &self.deployer { - make_account_rich(&mut storage_ptr.borrow_mut(), deployer); + make_account_rich(storage.borrow_mut().inner_mut(), deployer); } - let fee_account = l1_batch_env.fee_account; - let vm = Vm::custom(l1_batch_env, self.system_env, storage_ptr.clone()); - + let vm = VM::new( + l1_batch_env.clone(), + self.system_env.clone(), + storage.clone(), + ); VmTester { vm, - storage: storage_ptr, + system_env: self.system_env, + l1_batch_env, + storage, deployer: self.deployer, test_contract: None, - fee_account, rich_accounts: self.rich_accounts.clone(), custom_contracts: self.custom_contracts.clone(), } } } -pub(crate) fn get_empty_storage() -> InMemoryStorage { - InMemoryStorage::with_system_contracts(hash_bytecode) +/// Test extensions for VM. +pub(crate) trait TestedVm: + VmFactory> + VmInterfaceHistoryEnabled +{ + fn gas_remaining(&mut self) -> u32; + + fn get_current_execution_state(&self) -> CurrentExecutionState; + + /// Unlike [`Self::known_bytecode_hashes()`], the output should only include successfully decommitted bytecodes. + fn decommitted_hashes(&self) -> HashSet; + + fn execute_with_state_diffs( + &mut self, + diffs: Vec, + mode: VmExecutionMode, + ) -> VmExecutionResultAndLogs; + + fn insert_bytecodes(&mut self, bytecodes: &[&[u8]]); + + /// Includes bytecodes that have failed to decommit. + fn known_bytecode_hashes(&self) -> HashSet; + + /// Returns `true` iff the decommit is fresh. + fn manually_decommit(&mut self, code_hash: H256) -> bool; + + fn verify_required_bootloader_memory(&self, cells: &[(u32, U256)]); + + fn verify_required_storage(&mut self, cells: &[(StorageKey, H256)]); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/tester/transaction_test_info.rs b/core/lib/multivm/src/versions/testonly/tester/transaction_test_info.rs similarity index 100% rename from core/lib/multivm/src/versions/vm_fast/tests/tester/transaction_test_info.rs rename to core/lib/multivm/src/versions/testonly/tester/transaction_test_info.rs diff --git a/core/lib/multivm/src/versions/vm_fast/tests/block_tip.rs b/core/lib/multivm/src/versions/vm_fast/tests/block_tip.rs index dd407c61668..bb66eb2f770 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/block_tip.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/block_tip.rs @@ -1,392 +1,6 @@ -use std::borrow::BorrowMut; - -use ethabi::Token; -use itertools::Itertools; -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, fee_model::BatchFeeInput, get_code_key, - l2_to_l1_log::L2ToL1Log, writes::StateDiffRecord, Address, Execute, H256, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; - -use super::{ - tester::{get_empty_storage, VmTesterBuilder}, - utils::{get_complex_upgrade_abi, read_complex_upgrade}, -}; -use crate::{ - interface::{L1BatchEnv, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - versions::testonly::default_l1_batch, - vm_latest::constants::{ - BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD, - BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD, BOOTLOADER_BATCH_TIP_OVERHEAD, - MAX_VM_PUBDATA_PER_BATCH, - }, -}; - -#[derive(Debug, Clone, Default)] -struct L1MessengerTestData { - l2_to_l1_logs: usize, - messages: Vec>, - bytecodes: Vec>, - state_diffs: Vec, -} - -struct MimicCallInfo { - to: Address, - who_to_mimic: Address, - data: 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"); - - let logs_mimic_calls = (0..data.l2_to_l1_logs).map(|_| MimicCallInfo { - to: L1_MESSENGER_ADDRESS, - who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, - data: l1_messenger - .function("sendL2ToL1Log") - .unwrap() - .encode_input(&[ - Token::Bool(false), - Token::FixedBytes(H256::random().0.to_vec()), - Token::FixedBytes(H256::random().0.to_vec()), - ]) - .unwrap(), - }); - let messages_mimic_calls = data.messages.iter().map(|message| MimicCallInfo { - to: L1_MESSENGER_ADDRESS, - who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, - data: l1_messenger - .function("sendToL1") - .unwrap() - .encode_input(&[Token::Bytes(message.clone())]) - .unwrap(), - }); - let bytecodes_mimic_calls = data.bytecodes.iter().map(|bytecode| MimicCallInfo { - to: L1_MESSENGER_ADDRESS, - who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, - data: l1_messenger - .function("requestBytecodeL1Publication") - .unwrap() - .encode_input(&[Token::FixedBytes(hash_bytecode(bytecode).0.to_vec())]) - .unwrap(), - }); - - let encoded_calls = logs_mimic_calls - .chain(messages_mimic_calls) - .chain(bytecodes_mimic_calls) - .map(|call| { - Token::Tuple(vec![ - Token::Address(call.to), - Token::Address(call.who_to_mimic), - Token::Bytes(call.data), - ]) - }) - .chunks(CALLS_PER_TX) - .into_iter() - .map(|chunk| { - complex_upgrade - .function("mimicCalls") - .unwrap() - .encode_input(&[Token::Array(chunk.collect_vec())]) - .unwrap() - }) - .collect_vec(); - - encoded_calls -} - -struct TestStatistics { - pub max_used_gas: u32, - pub circuit_statistics: u64, - pub execution_metrics_size: u64, -} - -struct StatisticsTagged { - pub statistics: TestStatistics, - pub tag: String, -} - -fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { - let mut storage = get_empty_storage(); - let complex_upgrade_code = read_complex_upgrade(); - - // For this test we'll just put the bytecode onto the force deployer address - storage.borrow_mut().set_value( - get_code_key(&CONTRACT_FORCE_DEPLOYER_ADDRESS), - hash_bytecode(&complex_upgrade_code), - ); - storage - .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() - .with_storage(storage) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .with_l1_batch_env(batch_env) - .build(); - - let bytecodes = test_data.bytecodes.iter().map(Vec::as_slice); - vm.vm.insert_bytecodes(bytecodes); - - let txs_data = populate_mimic_calls(test_data.clone()); - let account = &mut vm.rich_accounts[0]; - - for (i, data) in txs_data.into_iter().enumerate() { - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(CONTRACT_FORCE_DEPLOYER_ADDRESS), - calldata: data, - value: U256::zero(), - factory_deps: vec![], - }, - 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 gas was spent at the end of the batch - // It is assumed that the top level frame is the bootloader - vm.vm.enforce_state_diffs(test_data.state_diffs.clone()); - let gas_before = vm.vm.gas_remaining(); - - let result = vm.vm.execute(VmExecutionMode::Batch); - assert!( - !result.result.is_failed(), - "Batch wasn't successful for input: {test_data:?}" - ); - let gas_after = vm.vm.gas_remaining(); - assert_eq!((gas_before - gas_after) as u64, result.statistics.gas_used); - - TestStatistics { - max_used_gas: gas_before - gas_after, - circuit_statistics: result.statistics.circuit_statistic.total() as u64, - execution_metrics_size: result.get_execution_metrics(None).size() as u64, - } -} - -fn generate_state_diffs( - repeated_writes: bool, - small_diff: bool, - number_of_state_diffs: usize, -) -> Vec { - (0..number_of_state_diffs) - .map(|i| { - let address = Address::from_low_u64_be(i as u64); - let key = U256::from(i); - let enumeration_index = if repeated_writes { i + 1 } else { 0 }; - - let (initial_value, final_value) = if small_diff { - // As small as it gets, one byte to denote zeroing out the value - (U256::from(1), U256::from(0)) - } else { - // As large as it gets - (U256::from(0), U256::from(2).pow(255.into())) - }; - - StateDiffRecord { - address, - key, - derived_key: u256_to_h256(i.into()).0, - enumeration_index: enumeration_index as u64, - initial_value, - final_value, - } - }) - .collect() -} - -// A valid zkEVM bytecode has odd number of 32 byte words -fn get_valid_bytecode_length(length: usize) -> usize { - // Firstly ensure that the length is divisible by 32 - let length_padded_to_32 = if length % 32 == 0 { - length - } else { - length + 32 - (length % 32) - }; - - // Then we ensure that the number returned by division by 32 is odd - if length_padded_to_32 % 64 == 0 { - length_padded_to_32 + 32 - } else { - length_padded_to_32 - } -} +use crate::{versions::testonly::block_tip::test_dry_run_upper_bound, vm_fast::Vm}; #[test] -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` - // is sufficient with a very large margin, so it is okay to ignore 1% of possible pubdata. - const MAX_EFFECTIVE_PUBDATA_PER_BATCH: usize = - (MAX_VM_PUBDATA_PER_BATCH as f64 * 0.99) as usize; - - // We are re-using the `ComplexUpgrade` contract as it already has the `mimicCall` functionality. - // To get the upper bound, we'll try to do the following: - // 1. Max number of logs. - // 2. Lots of small L2->L1 messages / one large L2->L1 message. - // 3. Lots of small bytecodes / one large bytecode. - // 4. Lots of storage slot updates. - - 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 - .iter() - .map(|s| (s.statistics.max_used_gas, s.tag.clone())) - .max() - .unwrap(); - assert!( - 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, - BOOTLOADER_BATCH_TIP_OVERHEAD - ); - - let circuit_statistics = statistics - .iter() - .map(|s| (s.statistics.circuit_statistics, s.tag.clone())) - .max() - .unwrap(); - assert!( - 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, - BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD - ); - - let execution_metrics_size = statistics - .iter() - .map(|s| (s.statistics.execution_metrics_size, s.tag.clone())) - .max() - .unwrap(); - assert!( - 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, - BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD - ); +fn dry_run_upper_bound() { + test_dry_run_upper_bound::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/bootloader.rs b/core/lib/multivm/src/versions/vm_fast/tests/bootloader.rs index 48e1b10de44..6075aea0989 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/bootloader.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/bootloader.rs @@ -1,52 +1,14 @@ -use assert_matches::assert_matches; -use zksync_types::U256; -use zksync_vm2::interface::HeapId; - use crate::{ - interface::{ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterfaceExt}, - versions::vm_fast::tests::{ - tester::VmTesterBuilder, - utils::{get_bootloader, verify_required_memory, BASE_SYSTEM_CONTRACTS}, - }, + versions::testonly::bootloader::{test_bootloader_out_of_gas, test_dummy_bootloader}, + vm_fast::Vm, }; #[test] -fn test_dummy_bootloader() { - let mut base_system_contracts = BASE_SYSTEM_CONTRACTS.clone(); - base_system_contracts.bootloader = get_bootloader("dummy"); - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(base_system_contracts) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let result = vm.vm.execute(VmExecutionMode::Batch); - assert!(!result.result.is_failed()); - - let correct_first_cell = U256::from_str_radix("123123123", 16).unwrap(); - - verify_required_memory(&vm.vm.inner, vec![(correct_first_cell, HeapId::FIRST, 0)]); +fn dummy_bootloader() { + test_dummy_bootloader::>(); } #[test] -fn test_bootloader_out_of_gas() { - let mut base_system_contracts = BASE_SYSTEM_CONTRACTS.clone(); - base_system_contracts.bootloader = get_bootloader("dummy"); - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(base_system_contracts) - .with_bootloader_gas_limit(10) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let res = vm.vm.execute(VmExecutionMode::Batch); - - assert_matches!( - res.result, - ExecutionResult::Halt { - reason: Halt::BootloaderOutOfGas - } - ); +fn bootloader_out_of_gas() { + test_bootloader_out_of_gas::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/bytecode_publishing.rs b/core/lib/multivm/src/versions/vm_fast/tests/bytecode_publishing.rs index 3070140c00b..8a662c38827 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/bytecode_publishing.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/bytecode_publishing.rs @@ -1,38 +1,6 @@ -use crate::{ - interface::{TxExecutionMode, VmEvent, VmExecutionMode, VmInterface, VmInterfaceExt}, - utils::bytecode, - vm_fast::tests::{ - tester::{DeployContractsTx, TxType, VmTesterBuilder}, - utils::read_test_contract, - }, -}; +use crate::{versions::testonly::bytecode_publishing::test_bytecode_publishing, vm_fast::Vm}; #[test] -fn test_bytecode_publishing() { - // In this test, we aim to ensure that the contents of the compressed bytecodes - // are included as part of the L2->L1 long messages - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let counter = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - - let compressed_bytecode = bytecode::compress(counter.clone()).unwrap().compressed; - - let DeployContractsTx { tx, .. } = account.get_deploy_tx(&counter, None, TxType::L2); - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "Transaction wasn't successful"); - - vm.vm.execute(VmExecutionMode::Batch); - - let state = vm.vm.get_current_execution_state(); - let long_messages = VmEvent::extract_long_l2_to_l1_messages(&state.events); - assert!( - long_messages.contains(&compressed_bytecode), - "Bytecode not published" - ); +fn bytecode_publishing() { + test_bytecode_publishing::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs b/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs index f40e5336eb3..e7521d87c1c 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/circuits.rs @@ -1,74 +1,6 @@ -use zksync_types::{Address, Execute, U256}; +use crate::{versions::testonly::circuits::test_circuits, vm_fast::Vm}; -use super::tester::VmTesterBuilder; -use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, -}; - -// Checks that estimated number of circuits for simple transfer doesn't differ much -// from hardcoded expected value. #[test] -fn test_circuits() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(Address::random()), - calldata: Vec::new(), - value: U256::from(1u8), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - let res = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!res.result.is_failed(), "{res:#?}"); - - let s = res.statistics.circuit_statistic; - // Check `circuit_statistic`. - 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"), - (s.ram_permutation, "ram_permutation"), - (s.storage_application, "storage_application"), - (s.storage_sorter, "storage_sorter"), - (s.code_decommitter, "code_decommitter"), - (s.code_decommitter_sorter, "code_decommitter_sorter"), - (s.log_demuxer, "log_demuxer"), - (s.events_sorter, "events_sorter"), - (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 { - assert_eq!( - *actual, expected, - "Check failed for {}, expected {}, actual {}", - name, expected, actual - ); - } else { - let diff = (actual - expected) / expected; - assert!( - diff.abs() < 0.1, - "Check failed for {}, expected {}, actual {}", - name, - expected, - actual - ); - } - } +fn circuits() { + test_circuits::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs b/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs index 34342d7f3b8..4ef86128734 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/code_oracle.rs @@ -1,252 +1,21 @@ -use ethabi::Token; -use zksync_types::{ - get_known_code_key, web3::keccak256, Address, Execute, StorageLogWithPreviousValue, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, h256_to_u256, u256_to_h256}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - versions::testonly::ContractToDeploy, - vm_fast::{ - tests::{ - tester::{get_empty_storage, VmTesterBuilder}, - utils::{load_precompiles_contract, read_precompiles_contract, read_test_contract}, - }, - CircuitsTracer, + versions::testonly::code_oracle::{ + test_code_oracle, test_code_oracle_big_bytecode, test_refunds_in_code_oracle, }, + vm_fast::Vm, }; -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() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ContractToDeploy::new( - precompile_contract_bytecode, - precompiles_contract_address, - )]) - .with_storage(storage) - .build(); - - let precompile_contract = load_precompiles_contract(); - let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); - - vm.vm.insert_bytecodes([normal_zkevm_bytecode.as_slice()]); - 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: Some(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: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx1); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - - // 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: Some(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: vec![], - }, - None, - ); - vm.vm.push_transaction(tx2); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); -} - -fn find_code_oracle_cost_log( - precompiles_contract_address: Address, - logs: &[StorageLogWithPreviousValue], -) -> &StorageLogWithPreviousValue { - logs.iter() - .find(|log| { - *log.log.key.address() == precompiles_contract_address && log.log.key.key().is_zero() - }) - .expect("no code oracle cost log") +fn code_oracle() { + test_code_oracle::>(); } #[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() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ContractToDeploy::new( - precompile_contract_bytecode, - precompiles_contract_address, - )]) - .with_storage(storage) - .build(); - - let precompile_contract = load_precompiles_contract(); - let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); - - vm.vm.insert_bytecodes([big_zkevm_bytecode.as_slice()]); - - 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: Some(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: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx1); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); +fn code_oracle_big_bytecode() { + test_code_oracle_big_bytecode::>(); } #[test] fn refunds_in_code_oracle() { - let precompiles_contract_address = Address::random(); - let precompile_contract_bytecode = read_precompiles_contract(); - - 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()), - ); - - let precompile_contract = load_precompiles_contract(); - let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); - - // Execute code oracle twice with identical VM state that only differs in that the queried bytecode - // is already decommitted the second time. The second call must consume less gas (`decommit` doesn't charge additional gas - // for already decommitted codes). - let mut oracle_costs = vec![]; - for decommit in [false, true] { - let mut vm = VmTesterBuilder::new() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ContractToDeploy::new( - precompile_contract_bytecode.clone(), - precompiles_contract_address, - )]) - .with_storage(storage.clone()) - .build(); - - vm.vm.insert_bytecodes([normal_zkevm_bytecode.as_slice()]); - - let account = &mut vm.rich_accounts[0]; - if decommit { - let (_, is_fresh) = vm.vm.inner.world_diff_mut().decommit_opcode( - &mut vm.vm.world, - &mut ((), CircuitsTracer::default()), - h256_to_u256(normal_zkevm_bytecode_hash), - ); - assert!(is_fresh); - } - - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(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: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - let log = - find_code_oracle_cost_log(precompiles_contract_address, &result.logs.storage_logs); - oracle_costs.push(log.log.value); - } - - // The refund is equal to `gasCost` parameter passed to the `decommit` opcode, which is defined as `4 * contract_length_in_words` - // in `CodeOracle.yul`. - let code_oracle_refund = h256_to_u256(oracle_costs[0]) - h256_to_u256(oracle_costs[1]); - assert_eq!( - code_oracle_refund, - (4 * (normal_zkevm_bytecode.len() / 32)).into() - ); + test_refunds_in_code_oracle::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs b/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs index c2ce02d39fe..c3cfd8b29f3 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/default_aa.rs @@ -1,81 +1,6 @@ -use zksync_system_constants::L2_BASE_TOKEN_ADDRESS; -use zksync_types::{ - get_code_key, get_known_code_key, get_nonce_key, - system_contracts::{DEPLOYMENT_NONCE_INCREMENT, TX_NONCE_INCREMENT}, - AccountTreeId, U256, -}; -use zksync_utils::u256_to_h256; - -use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_fast::tests::{ - tester::{DeployContractsTx, TxType, VmTesterBuilder}, - utils::{get_balance, read_test_contract, verify_required_storage}, - }, - vm_latest::utils::fee::get_batch_base_fee, -}; +use crate::{versions::testonly::default_aa::test_default_aa_interaction, vm_fast::Vm}; #[test] -fn test_default_aa_interaction() { - // 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() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let counter = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - let DeployContractsTx { - tx, - bytecode_hash, - address, - } = account.get_deploy_tx(&counter, None, TxType::L2); - let maximal_fee = tx.gas_limit() * get_batch_base_fee(&vm.vm.batch_env); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - 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. - let account_nonce_key = get_nonce_key(&account.address); - let expected_nonce = TX_NONCE_INCREMENT + DEPLOYMENT_NONCE_INCREMENT; - - // The code hash of the deployed contract should be marked as republished. - let known_codes_key = get_known_code_key(&bytecode_hash); - - // The contract should be deployed successfully. - let account_code_key = get_code_key(&address); - - let expected_slots = [ - (u256_to_h256(expected_nonce), account_nonce_key), - (u256_to_h256(U256::from(1u32)), known_codes_key), - (bytecode_hash, account_code_key), - ]; - - verify_required_storage( - &expected_slots, - &mut vm.vm.world.storage, - vm.vm.inner.world_diff().get_storage_state(), - ); - - let expected_fee = maximal_fee - - U256::from(result.refunds.gas_refunded) - * U256::from(get_batch_base_fee(&vm.vm.batch_env)); - let operator_balance = get_balance( - AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), - &vm.fee_account, - &mut vm.vm.world.storage, - vm.vm.inner.world_diff().get_storage_state(), - ); - - assert_eq!( - operator_balance, expected_fee, - "Operator did not receive his fee" - ); +fn default_aa_interaction() { + test_default_aa_interaction::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/gas_limit.rs b/core/lib/multivm/src/versions/vm_fast/tests/gas_limit.rs index 3f0a47b980e..6ba55f8e1f8 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/gas_limit.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/gas_limit.rs @@ -1,39 +1,6 @@ -use zksync_test_account::Account; -use zksync_types::{fee::Fee, Execute}; +use crate::{versions::testonly::gas_limit::test_tx_gas_limit_offset, vm_fast::Vm}; -use crate::{ - interface::{TxExecutionMode, VmInterface}, - vm_fast::tests::tester::VmTesterBuilder, - vm_latest::constants::{TX_DESCRIPTION_OFFSET, TX_GAS_LIMIT_OFFSET}, -}; - -/// Checks that `TX_GAS_LIMIT_OFFSET` constant is correct. #[test] -fn test_tx_gas_limit_offset() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let gas_limit = 9999.into(); - let tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Some(Default::default()), - ..Default::default() - }, - Some(Fee { - gas_limit, - ..Account::default_fee() - }), - ); - - vm.vm.push_transaction(tx); - - assert!(!vm.vm.has_previous_far_calls()); - let gas_limit_from_memory = vm - .vm - .read_word_from_bootloader_heap(TX_DESCRIPTION_OFFSET + TX_GAS_LIMIT_OFFSET); - - assert_eq!(gas_limit_from_memory, gas_limit); +fn tx_gas_limit_offset() { + test_tx_gas_limit_offset::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs b/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs index 0447304f69f..5ec30907ed5 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/get_used_contracts.rs @@ -1,241 +1,22 @@ -use std::{collections::HashSet, iter}; - -use assert_matches::assert_matches; -use ethabi::Token; -use itertools::Itertools; -use zk_evm_1_3_1::zkevm_opcode_defs::decoding::{EncodingModeProduction, VmEncodingMode}; -use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; -use zksync_test_account::Account; -use zksync_types::{AccountTreeId, Address, Execute, StorageKey, H256, U256}; -use zksync_utils::{bytecode::hash_bytecode, h256_to_u256}; - use crate::{ - interface::{ - storage::ReadStorage, ExecutionResult, TxExecutionMode, VmExecutionMode, - VmExecutionResultAndLogs, VmInterface, VmInterfaceExt, - }, - versions::testonly::ContractToDeploy, - vm_fast::{ - tests::{ - tester::{TxType, VmTester, VmTesterBuilder}, - utils::{read_proxy_counter_contract, read_test_contract, BASE_SYSTEM_CONTRACTS}, - }, - vm::Vm, + versions::testonly::get_used_contracts::{ + test_get_used_contracts, test_get_used_contracts_with_far_call, + test_get_used_contracts_with_out_of_gas_far_call, }, + vm_fast::Vm, }; #[test] -fn test_get_used_contracts() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - assert!(known_bytecodes_without_base_system_contracts(&vm.vm).is_empty()); - - // create and push and execute some not-empty factory deps transaction with success status - // to check that `get_decommitted_hashes()` updates - let contract_code = read_test_contract(); - let mut account = Account::random(); - let tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 0 }); - vm.vm.push_transaction(tx.tx.clone()); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed()); - - assert!(vm - .vm - .decommitted_hashes() - .contains(&h256_to_u256(tx.bytecode_hash))); - - // Note: `Default_AA` will be in the list of used contracts if L2 tx is used - assert_eq!( - vm.vm.decommitted_hashes().collect::>(), - known_bytecodes_without_base_system_contracts(&vm.vm) - ); - - // create push and execute some non-empty factory deps transaction that fails - // (`known_bytecodes` will be updated but we expect `get_decommitted_hashes()` to not be updated) - - let calldata = [1, 2, 3]; - let big_calldata: Vec = calldata - .iter() - .cycle() - .take(calldata.len() * 1024) - .cloned() - .collect(); - let account2 = Account::random(); - let tx2 = account2.get_l1_tx( - Execute { - contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), - calldata: big_calldata, - value: Default::default(), - factory_deps: vec![vec![1; 32]], - }, - 1, - ); - - vm.vm.push_transaction(tx2.clone()); - - let res2 = vm.vm.execute(VmExecutionMode::OneTx); - - assert!(res2.result.is_failed()); - - for factory_dep in tx2.execute.factory_deps { - let hash = hash_bytecode(&factory_dep); - let hash_to_u256 = h256_to_u256(hash); - assert!(known_bytecodes_without_base_system_contracts(&vm.vm).contains(&hash_to_u256)); - assert!(!vm.vm.decommitted_hashes().contains(&hash_to_u256)); - } -} - -fn known_bytecodes_without_base_system_contracts(vm: &Vm) -> HashSet { - let mut known_bytecodes_without_base_system_contracts = vm - .world - .bytecode_cache - .keys() - .cloned() - .collect::>(); - known_bytecodes_without_base_system_contracts - .remove(&h256_to_u256(BASE_SYSTEM_CONTRACTS.default_aa.hash)); - if let Some(evm_emulator) = &BASE_SYSTEM_CONTRACTS.evm_emulator { - let was_removed = - known_bytecodes_without_base_system_contracts.remove(&h256_to_u256(evm_emulator.hash)); - assert!(was_removed); - } - known_bytecodes_without_base_system_contracts -} - -/// Counter test contract bytecode inflated by appending lots of `NOP` opcodes at the end. This leads to non-trivial -/// decommitment cost (>10,000 gas). -fn inflated_counter_bytecode() -> Vec { - let mut counter_bytecode = read_test_contract(); - counter_bytecode.extend( - iter::repeat(EncodingModeProduction::nop_encoding().to_be_bytes()) - .take(10_000) - .flatten(), - ); - counter_bytecode -} - -#[derive(Debug)] -struct ProxyCounterData { - proxy_counter_address: Address, - counter_bytecode_hash: U256, -} - -fn execute_proxy_counter(gas: u32) -> (VmTester<()>, ProxyCounterData, VmExecutionResultAndLogs) { - let counter_bytecode = inflated_counter_bytecode(); - let counter_bytecode_hash = h256_to_u256(hash_bytecode(&counter_bytecode)); - let counter_address = Address::repeat_byte(0x23); - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_custom_contracts(vec![ContractToDeploy::new( - counter_bytecode, - counter_address, - )]) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let (proxy_counter_bytecode, proxy_counter_abi) = read_proxy_counter_contract(); - let account = &mut vm.rich_accounts[0]; - let deploy_tx = account.get_deploy_tx( - &proxy_counter_bytecode, - Some(&[Token::Address(counter_address)]), - TxType::L2, - ); - let (compression_result, exec_result) = vm - .vm - .execute_transaction_with_bytecode_compression(deploy_tx.tx, true); - compression_result.unwrap(); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - - let decommitted_hashes = vm.vm.decommitted_hashes().collect::>(); - assert!( - !decommitted_hashes.contains(&counter_bytecode_hash), - "{decommitted_hashes:?}" - ); - - let increment = proxy_counter_abi.function("increment").unwrap(); - let increment_tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(deploy_tx.address), - calldata: increment - .encode_input(&[Token::Uint(1.into()), Token::Uint(gas.into())]) - .unwrap(), - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - let (compression_result, exec_result) = vm - .vm - .execute_transaction_with_bytecode_compression(increment_tx, true); - compression_result.unwrap(); - let data = ProxyCounterData { - proxy_counter_address: deploy_tx.address, - counter_bytecode_hash, - }; - (vm, data, exec_result) +fn get_used_contracts() { + test_get_used_contracts::>(); } #[test] fn get_used_contracts_with_far_call() { - let (vm, data, exec_result) = execute_proxy_counter(100_000); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - let decommitted_hashes = vm.vm.decommitted_hashes().collect::>(); - assert!( - decommitted_hashes.contains(&data.counter_bytecode_hash), - "{decommitted_hashes:?}" - ); + test_get_used_contracts_with_far_call::>(); } #[test] fn get_used_contracts_with_out_of_gas_far_call() { - let (mut vm, data, exec_result) = execute_proxy_counter(10_000); - assert_matches!(exec_result.result, ExecutionResult::Revert { .. }); - let decommitted_hashes = vm.vm.decommitted_hashes().collect::>(); - assert!( - decommitted_hashes.contains(&data.counter_bytecode_hash), - "{decommitted_hashes:?}" - ); - - // Execute another transaction with a successful far call and check that it's still charged for decommitment. - let account = &mut vm.rich_accounts[0]; - let (_, proxy_counter_abi) = read_proxy_counter_contract(); - let increment = proxy_counter_abi.function("increment").unwrap(); - let increment_tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(data.proxy_counter_address), - calldata: increment - .encode_input(&[Token::Uint(1.into()), Token::Uint(u64::MAX.into())]) - .unwrap(), - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - let (compression_result, exec_result) = vm - .vm - .execute_transaction_with_bytecode_compression(increment_tx, true); - compression_result.unwrap(); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - - let proxy_counter_cost_key = StorageKey::new( - AccountTreeId::new(data.proxy_counter_address), - H256::from_low_u64_be(1), - ); - let far_call_cost_log = exec_result - .logs - .storage_logs - .iter() - .find(|log| log.log.key == proxy_counter_cost_key) - .expect("no cost log"); - assert!( - far_call_cost_log.previous_value.is_zero(), - "{far_call_cost_log:?}" - ); - let far_call_cost = h256_to_u256(far_call_cost_log.log.value); - assert!(far_call_cost > 10_000.into(), "{far_call_cost}"); + test_get_used_contracts_with_out_of_gas_far_call::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/invalid_bytecode.rs b/core/lib/multivm/src/versions/vm_fast/tests/invalid_bytecode.rs deleted file mode 100644 index dde83d8a9f3..00000000000 --- a/core/lib/multivm/src/versions/vm_fast/tests/invalid_bytecode.rs +++ /dev/null @@ -1,120 +0,0 @@ -use zksync_types::H256; -use zksync_utils::h256_to_u256; - -use crate::vm_latest::tests::tester::VmTesterBuilder; -use crate::vm_latest::types::inputs::system_env::TxExecutionMode; -use crate::vm_latest::{HistoryEnabled, TxRevertReason}; - -// TODO this test requires a lot of hacks for bypassing the bytecode checks in the VM. -// Port it later, it's not significant. for now - -#[test] -fn test_invalid_bytecode() { - let mut vm_builder = VmTesterBuilder::new(HistoryEnabled) - .with_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1); - let mut storage = vm_builder.take_storage(); - let mut vm = vm_builder.build(&mut storage); - - let block_gas_per_pubdata = vm_test_env - .block_context - .context - .block_gas_price_per_pubdata(); - - let mut test_vm_with_custom_bytecode_hash = - |bytecode_hash: H256, expected_revert_reason: Option| { - let mut oracle_tools = - OracleTools::new(vm_test_env.storage_ptr.as_mut(), HistoryEnabled); - - let (encoded_tx, predefined_overhead) = get_l1_tx_with_custom_bytecode_hash( - h256_to_u256(bytecode_hash), - block_gas_per_pubdata as u32, - ); - - run_vm_with_custom_factory_deps( - &mut oracle_tools, - vm_test_env.block_context.context, - &vm_test_env.block_properties, - encoded_tx, - predefined_overhead, - expected_revert_reason, - ); - }; - - let failed_to_mark_factory_deps = |msg: &str, data: Vec| { - TxRevertReason::FailedToMarkFactoryDependencies(VmRevertReason::General { - msg: msg.to_string(), - data, - }) - }; - - // Here we provide the correctly-formatted bytecode hash of - // odd length, so it should work. - test_vm_with_custom_bytecode_hash( - H256([ - 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]), - None, - ); - - // Here we provide correctly formatted bytecode of even length, so - // it should fail. - test_vm_with_custom_bytecode_hash( - H256([ - 1, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]), - Some(failed_to_mark_factory_deps( - "Code length in words must be odd", - vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 67, 111, 100, 101, 32, 108, 101, 110, - 103, 116, 104, 32, 105, 110, 32, 119, 111, 114, 100, 115, 32, 109, 117, 115, 116, - 32, 98, 101, 32, 111, 100, 100, - ], - )), - ); - - // Here we provide incorrectly formatted bytecode of odd length, so - // it should fail. - test_vm_with_custom_bytecode_hash( - H256([ - 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]), - Some(failed_to_mark_factory_deps( - "Incorrectly formatted bytecodeHash", - vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 73, 110, 99, 111, 114, 114, 101, 99, - 116, 108, 121, 32, 102, 111, 114, 109, 97, 116, 116, 101, 100, 32, 98, 121, 116, - 101, 99, 111, 100, 101, 72, 97, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - )), - ); - - // Here we provide incorrectly formatted bytecode of odd length, so - // it should fail. - test_vm_with_custom_bytecode_hash( - H256([ - 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, - ]), - Some(failed_to_mark_factory_deps( - "Incorrectly formatted bytecodeHash", - vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 73, 110, 99, 111, 114, 114, 101, 99, - 116, 108, 121, 32, 102, 111, 114, 109, 97, 116, 116, 101, 100, 32, 98, 121, 116, - 101, 99, 111, 100, 101, 72, 97, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - )), - ); -} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/is_write_initial.rs b/core/lib/multivm/src/versions/vm_fast/tests/is_write_initial.rs index df8d992f02f..522aa2413f6 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/is_write_initial.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/is_write_initial.rs @@ -1,46 +1,6 @@ -use zksync_types::get_nonce_key; - -use crate::{ - interface::{ - storage::ReadStorage, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, - }, - vm_fast::tests::{ - tester::{Account, TxType, VmTesterBuilder}, - utils::read_test_contract, - }, -}; +use crate::{versions::testonly::is_write_initial::test_is_write_initial_behaviour, vm_fast::Vm}; #[test] -fn test_is_write_initial_behaviour() { - // In this test, we check result of `is_write_initial` at different stages. - // The main idea is to check that `is_write_initial` storage uses the correct cache for initial_writes and doesn't - // messed up it with the repeated writes during the one batch execution. - - let mut account = Account::random(); - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let nonce_key = get_nonce_key(&account.address); - // Check that the next write to the nonce key will be initial. - assert!(vm - .storage - .as_ref() - .borrow_mut() - .is_write_initial(&nonce_key)); - - let contract_code = read_test_contract(); - let tx = account.get_deploy_tx(&contract_code, None, TxType::L2).tx; - - vm.vm.push_transaction(tx); - vm.vm.execute(VmExecutionMode::OneTx); - - // Check that `is_write_initial` still returns true for the nonce key. - assert!(vm - .storage - .as_ref() - .borrow_mut() - .is_write_initial(&nonce_key)); +fn is_write_initial_behaviour() { + test_is_write_initial_behaviour::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/l1_tx_execution.rs b/core/lib/multivm/src/versions/vm_fast/tests/l1_tx_execution.rs index 1abb1e39e19..0174eeffd7e 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/l1_tx_execution.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/l1_tx_execution.rs @@ -1,199 +1,16 @@ -use ethabi::Token; -use zksync_contracts::l1_messenger_contract; -use zksync_system_constants::{BOOTLOADER_ADDRESS, L1_MESSENGER_ADDRESS}; -use zksync_types::{ - get_code_key, get_known_code_key, - l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, - Execute, ExecuteTransactionCommon, U256, -}; -use zksync_utils::{h256_to_u256, u256_to_h256}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - utils::StorageWritesDeduplicator, - vm_fast::{ - tests::{ - tester::{TxType, VmTesterBuilder}, - utils::{read_test_contract, BASE_SYSTEM_CONTRACTS}, - }, - transaction_data::TransactionData, + versions::testonly::l1_tx_execution::{ + test_l1_tx_execution, test_l1_tx_execution_high_gas_limit, }, + vm_fast::Vm, }; #[test] -fn test_l1_tx_execution() { - // In this test, we try to execute a contract deployment from L1 - // 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 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 5 slots instead of 9 due to 0 fee for transaction. - let basic_initial_writes = 5; - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let contract_code = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - let deploy_tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 1 }); - let tx_data: TransactionData = deploy_tx.tx.clone().into(); - - let required_l2_to_l1_logs: Vec<_> = vec![L2ToL1Log { - shard_id: 0, - is_service: true, - tx_number_in_block: 0, - sender: BOOTLOADER_ADDRESS, - key: tx_data.tx_hash(0.into()), - value: u256_to_h256(U256::from(1u32)), - }] - .into_iter() - .map(UserL2ToL1Log) - .collect(); - - vm.vm.push_transaction(deploy_tx.tx.clone()); - - let res = vm.vm.execute(VmExecutionMode::OneTx); - - // The code hash of the deployed contract should be marked as republished. - let known_codes_key = get_known_code_key(&deploy_tx.bytecode_hash); - - // The contract should be deployed successfully. - let account_code_key = get_code_key(&deploy_tx.address); - - assert!(!res.result.is_failed()); - - for (expected_value, storage_location) in [ - (U256::from(1u32), known_codes_key), - (h256_to_u256(deploy_tx.bytecode_hash), account_code_key), - ] { - assert_eq!( - expected_value, - vm.vm.inner.world_diff().get_storage_state()[&( - *storage_location.address(), - h256_to_u256(*storage_location.key()) - )] - ); - } - - assert_eq!(res.logs.user_l2_to_l1_logs, required_l2_to_l1_logs); - - let tx = account.get_test_contract_transaction( - deploy_tx.address, - true, - None, - false, - TxType::L1 { serial_id: 0 }, - ); - vm.vm.push_transaction(tx); - let res = vm.vm.execute(VmExecutionMode::OneTx); - let storage_logs = res.logs.storage_logs; - let res = StorageWritesDeduplicator::apply_on_empty_state(&storage_logs); - - // Tx panicked - assert_eq!(res.initial_storage_writes, basic_initial_writes); - - let tx = account.get_test_contract_transaction( - deploy_tx.address, - false, - None, - false, - TxType::L1 { serial_id: 0 }, - ); - vm.vm.push_transaction(tx.clone()); - 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. 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); - - // No repeated writes - let repeated_writes = res.repeated_storage_writes; - assert_eq!(res.repeated_storage_writes, 0); - - 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. - // 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); - - let tx = account.get_test_contract_transaction( - deploy_tx.address, - false, - Some(10.into()), - false, - TxType::L1 { serial_id: 1 }, - ); - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - // Method is not payable tx should fail - assert!(result.result.is_failed(), "The transaction should fail"); - - let res = StorageWritesDeduplicator::apply_on_empty_state(&result.logs.storage_logs); - assert_eq!(res.initial_storage_writes, basic_initial_writes); - assert_eq!(res.repeated_storage_writes, 1); +fn l1_tx_execution() { + test_l1_tx_execution::>(); } #[test] -fn test_l1_tx_execution_high_gas_limit() { - // In this test, we try to execute an L1->L2 transaction with a high gas limit. - // Usually priority transactions with dangerously gas limit should even pass the checks on the L1, - // however, they might pass during the transition period to the new fee model, so we check that we can safely process those. - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let account = &mut vm.rich_accounts[0]; - - let l1_messenger = l1_messenger_contract(); - - let contract_function = l1_messenger.function("sendToL1").unwrap(); - let params = [ - // Even a message of size 100k should not be able to be sent by a priority transaction - Token::Bytes(vec![0u8; 100_000]), - ]; - let calldata = contract_function.encode_input(¶ms).unwrap(); - - let mut tx = account.get_l1_tx( - Execute { - contract_address: Some(L1_MESSENGER_ADDRESS), - value: 0.into(), - factory_deps: vec![], - calldata, - }, - 0, - ); - - if let ExecuteTransactionCommon::L1(data) = &mut tx.common_data { - // Using some large gas limit - data.gas_limit = 300_000_000.into(); - } else { - unreachable!() - }; - - vm.vm.push_transaction(tx); - - let res = vm.vm.execute(VmExecutionMode::OneTx); - - assert!(res.result.is_failed(), "The transaction should've failed"); +fn l1_tx_execution_high_gas_limit() { + test_l1_tx_execution_high_gas_limit::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index 730c573cdcf..436df210006 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -1,14 +1,32 @@ +use std::collections::HashSet; + +use zksync_types::{writes::StateDiffRecord, StorageKey, H256, U256}; +use zksync_utils::{h256_to_u256, u256_to_h256}; +use zksync_vm2::interface::{HeapId, StateInterface}; +use zksync_vm_interface::{ + storage::ReadStorage, CurrentExecutionState, VmExecutionMode, VmExecutionResultAndLogs, + VmInterfaceExt, +}; + +use super::Vm; +use crate::{ + interface::storage::{ImmutableStorageView, InMemoryStorage}, + versions::testonly::TestedVm, + vm_fast::CircuitsTracer, +}; + mod block_tip; mod bootloader; mod bytecode_publishing; -mod default_aa; -// mod call_tracer; FIXME: requires tracers mod circuits; mod code_oracle; +mod default_aa; mod gas_limit; mod get_used_contracts; mod is_write_initial; mod l1_tx_execution; +/* +// mod call_tracer; FIXME: requires tracers mod l2_blocks; mod nonce_holder; mod precompiles; @@ -24,3 +42,69 @@ mod tracing_execution_error; mod transfer; mod upgrade; mod utils; +*/ + +impl TestedVm for Vm> { + fn gas_remaining(&mut self) -> u32 { + self.gas_remaining() + } + + fn get_current_execution_state(&self) -> CurrentExecutionState { + self.get_current_execution_state() + } + + fn decommitted_hashes(&self) -> HashSet { + self.decommitted_hashes().collect() + } + + fn execute_with_state_diffs( + &mut self, + diffs: Vec, + mode: VmExecutionMode, + ) -> VmExecutionResultAndLogs { + self.enforce_state_diffs(diffs); + self.execute(mode) + } + + fn insert_bytecodes(&mut self, bytecodes: &[&[u8]]) { + self.insert_bytecodes(bytecodes.iter().copied()) + } + + fn known_bytecode_hashes(&self) -> HashSet { + self.world.bytecode_cache.keys().copied().collect() + } + + fn manually_decommit(&mut self, code_hash: H256) -> bool { + let (_, is_fresh) = self.inner.world_diff_mut().decommit_opcode( + &mut self.world, + &mut ((), CircuitsTracer::default()), + h256_to_u256(code_hash), + ); + is_fresh + } + + fn verify_required_bootloader_memory(&self, required_values: &[(u32, U256)]) { + for &(slot, expected_value) in required_values { + let current_value = self.inner.read_heap_u256(HeapId::FIRST, slot * 32); + assert_eq!(current_value, expected_value); + } + } + + fn verify_required_storage(&mut self, cells: &[(StorageKey, H256)]) { + let storage_changes = self.inner.world_diff().get_storage_state(); + let main_storage = &mut self.world.storage; + + for &(key, required_value) in cells { + let current_value = storage_changes + .get(&(*key.account().address(), h256_to_u256(*key.key()))) + .copied() + .unwrap_or_else(|| h256_to_u256(main_storage.read_value(&key))); + + assert_eq!( + u256_to_h256(current_value), + required_value, + "Invalid value at key {key:?}" + ); + } + } +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/tester/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/tester/mod.rs deleted file mode 100644 index 212e569d510..00000000000 --- a/core/lib/multivm/src/versions/vm_fast/tests/tester/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub(crate) use transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier}; -pub(crate) use vm_tester::{get_empty_storage, VmTester, VmTesterBuilder}; -pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType}; - -mod transaction_test_info; -mod vm_tester; diff --git a/core/lib/multivm/src/versions/vm_fast/tests/utils.rs b/core/lib/multivm/src/versions/vm_fast/tests/utils.rs index 76ca9bc5dd3..b089276406c 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/utils.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/utils.rs @@ -113,10 +113,6 @@ pub(crate) fn read_nonce_holder_tester() -> Vec { read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/custom-account/nonce-holder-test.sol/NonceHolderTest.json") } -pub(crate) fn read_complex_upgrade() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json") -} - pub(crate) fn get_complex_upgrade_abi() -> Contract { load_contract( "etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json" diff --git a/core/lib/vm_interface/src/storage/view.rs b/core/lib/vm_interface/src/storage/view.rs index ec9267609e2..249d584c9f6 100644 --- a/core/lib/vm_interface/src/storage/view.rs +++ b/core/lib/vm_interface/src/storage/view.rs @@ -102,6 +102,16 @@ impl StorageView { pub fn cache(&self) -> StorageViewCache { self.cache.clone() } + + /// Provides mutable access to the underlying storage. + /// + /// # Warning + /// + /// Mutating the underlying storage directly can easily break implied `StorageView` invariants, so use with care. + #[doc(hidden)] + pub fn inner_mut(&mut self) -> &mut S { + &mut self.storage_handle + } } impl ReadStorage for Box From fe8d5ba9ad9f0158005aaf4af72212aaf53e19ee Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 11:52:06 +0300 Subject: [PATCH 02/18] Use generic unit tests for old VM --- .../src/versions/vm_latest/tests/block_tip.rs | 427 +----------------- .../versions/vm_latest/tests/bootloader.rs | 55 +-- .../vm_latest/tests/bytecode_publishing.rs | 40 +- .../src/versions/vm_latest/tests/circuits.rs | 75 +-- .../versions/vm_latest/tests/code_oracle.rs | 277 +----------- .../versions/vm_latest/tests/default_aa.rs | 78 +--- .../src/versions/vm_latest/tests/gas_limit.rs | 45 +- .../vm_latest/tests/get_used_contracts.rs | 246 +--------- .../vm_latest/tests/is_write_initial.rs | 48 +- .../vm_latest/tests/l1_tx_execution.rs | 194 +------- .../src/versions/vm_latest/tests/mod.rs | 108 +++++ 11 files changed, 161 insertions(+), 1432 deletions(-) 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 9909ca24937..df4a36f2d3d 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,428 +1,9 @@ -use std::borrow::BorrowMut; - -use ethabi::Token; -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, 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}; - -use super::utils::{get_complex_upgrade_abi, read_complex_upgrade}; use crate::{ - interface::{L1BatchEnv, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - constants::{ - BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD, - BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD, BOOTLOADER_BATCH_TIP_OVERHEAD, - MAX_VM_PUBDATA_PER_BATCH, - }, - tests::tester::{ - default_l1_batch, get_empty_storage, InMemoryStorageView, VmTesterBuilder, - }, - tracers::PubdataTracer, - HistoryEnabled, TracerDispatcher, - }, + versions::testonly::block_tip::test_dry_run_upper_bound, + vm_latest::{HistoryEnabled, Vm}, }; -#[derive(Debug, Clone, Default)] -struct L1MessengerTestData { - l2_to_l1_logs: usize, - messages: Vec>, - bytecodes: Vec>, - state_diffs: Vec, -} - -struct MimicCallInfo { - to: Address, - who_to_mimic: Address, - data: 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"); - - let logs_mimic_calls = (0..data.l2_to_l1_logs).map(|_| MimicCallInfo { - to: L1_MESSENGER_ADDRESS, - who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, - data: l1_messenger - .function("sendL2ToL1Log") - .unwrap() - .encode_input(&[ - Token::Bool(false), - Token::FixedBytes(H256::random().0.to_vec()), - Token::FixedBytes(H256::random().0.to_vec()), - ]) - .unwrap(), - }); - let messages_mimic_calls = data.messages.iter().map(|message| MimicCallInfo { - to: L1_MESSENGER_ADDRESS, - who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, - data: l1_messenger - .function("sendToL1") - .unwrap() - .encode_input(&[Token::Bytes(message.clone())]) - .unwrap(), - }); - let bytecodes_mimic_calls = data.bytecodes.iter().map(|bytecode| MimicCallInfo { - to: L1_MESSENGER_ADDRESS, - who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, - data: l1_messenger - .function("requestBytecodeL1Publication") - .unwrap() - .encode_input(&[Token::FixedBytes(hash_bytecode(bytecode).0.to_vec())]) - .unwrap(), - }); - - let encoded_calls = logs_mimic_calls - .chain(messages_mimic_calls) - .chain(bytecodes_mimic_calls) - .map(|call| { - Token::Tuple(vec![ - Token::Address(call.to), - Token::Address(call.who_to_mimic), - Token::Bytes(call.data), - ]) - }) - .chunks(CALLS_PER_TX) - .into_iter() - .map(|chunk| { - complex_upgrade - .function("mimicCalls") - .unwrap() - .encode_input(&[Token::Array(chunk.collect_vec())]) - .unwrap() - }) - .collect_vec(); - - encoded_calls -} - -struct TestStatistics { - pub max_used_gas: u32, - pub circuit_statistics: u64, - pub execution_metrics_size: u64, -} - -struct StatisticsTagged { - pub statistics: TestStatistics, - pub tag: String, -} - -fn execute_test(test_data: L1MessengerTestData) -> TestStatistics { - let mut storage = get_empty_storage(); - let complex_upgrade_code = read_complex_upgrade(); - - // For this test we'll just put the bytecode onto the force deployer address - storage.borrow_mut().set_value( - get_code_key(&CONTRACT_FORCE_DEPLOYER_ADDRESS), - hash_bytecode(&complex_upgrade_code), - ); - storage - .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 - .bytecodes - .iter() - .map(|bytecode| { - let hash = hash_bytecode(bytecode); - let words = bytes_to_be_words(bytecode.clone()); - (h256_to_u256(hash), words) - }) - .collect(); - vm.vm - .state - .decommittment_processor - .populate(bytecodes, Timestamp(0)); - - let txs_data = populate_mimic_calls(test_data.clone()); - let account = &mut vm.rich_accounts[0]; - - for (i, data) in txs_data.into_iter().enumerate() { - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(CONTRACT_FORCE_DEPLOYER_ADDRESS), - calldata: data, - value: U256::zero(), - factory_deps: vec![], - }, - 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 - - let ergs_before = vm.vm.state.local_state.callstack.current.ergs_remaining; - - // We ensure that indeed the provided state diffs are used - let pubdata_tracer = PubdataTracer::::new_with_forced_state_diffs( - vm.vm.batch_env.clone(), - VmExecutionMode::Batch, - test_data.state_diffs.clone(), - crate::vm_latest::MultiVMSubversion::latest(), - ); - - let result = vm.vm.inspect_inner( - &mut TracerDispatcher::default(), - VmExecutionMode::Batch, - Some(pubdata_tracer), - ); - - assert!( - !result.result.is_failed(), - "Batch wasn't successful for input: {:?}", - test_data - ); - - let ergs_after = vm.vm.state.local_state.callstack.current.ergs_remaining; - - assert_eq!( - (ergs_before - ergs_after) as u64, - result.statistics.gas_used - ); - - TestStatistics { - max_used_gas: ergs_before - ergs_after, - circuit_statistics: result.statistics.circuit_statistic.total() as u64, - execution_metrics_size: result.get_execution_metrics(None).size() as u64, - } -} - -fn generate_state_diffs( - repeated_writes: bool, - small_diff: bool, - number_of_state_diffs: usize, -) -> Vec { - (0..number_of_state_diffs) - .map(|i| { - let address = Address::from_low_u64_be(i as u64); - let key = U256::from(i); - let enumeration_index = if repeated_writes { i + 1 } else { 0 }; - - let (initial_value, final_value) = if small_diff { - // As small as it gets, one byte to denote zeroing out the value - (U256::from(1), U256::from(0)) - } else { - // As large as it gets - (U256::from(0), U256::from(2).pow(255.into())) - }; - - StateDiffRecord { - address, - key, - derived_key: u256_to_h256(i.into()).0, - enumeration_index: enumeration_index as u64, - initial_value, - final_value, - } - }) - .collect() -} - -// A valid zkEVM bytecode has odd number of 32 byte words -fn get_valid_bytecode_length(length: usize) -> usize { - // Firstly ensure that the length is divisible by 32 - let length_padded_to_32 = if length % 32 == 0 { - length - } else { - length + 32 - (length % 32) - }; - - // Then we ensure that the number returned by division by 32 is odd - if length_padded_to_32 % 64 == 0 { - length_padded_to_32 + 32 - } else { - length_padded_to_32 - } -} - #[test] -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` - // is sufficient with a very large margin, so it is okay to ignore 1% of possible pubdata. - const MAX_EFFECTIVE_PUBDATA_PER_BATCH: usize = - (MAX_VM_PUBDATA_PER_BATCH as f64 * 0.99) as usize; - - // We are re-using the `ComplexUpgrade` contract as it already has the `mimicCall` functionality. - // To get the upper bound, we'll try to do the following: - // 1. Max number of logs. - // 2. Lots of small L2->L1 messages / one large L2->L1 message. - // 3. Lots of small bytecodes / one large bytecode. - // 4. Lots of storage slot updates. - - 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 - .iter() - .map(|s| (s.statistics.max_used_gas, s.tag.clone())) - .max() - .unwrap(); - assert!( - 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, - BOOTLOADER_BATCH_TIP_OVERHEAD - ); - - let circuit_statistics = statistics - .iter() - .map(|s| (s.statistics.circuit_statistics, s.tag.clone())) - .max() - .unwrap(); - assert!( - 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, - BOOTLOADER_BATCH_TIP_CIRCUIT_STATISTICS_OVERHEAD - ); - - let execution_metrics_size = statistics - .iter() - .map(|s| (s.statistics.execution_metrics_size, s.tag.clone())) - .max() - .unwrap(); - assert!( - 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, - BOOTLOADER_BATCH_TIP_METRICS_SIZE_OVERHEAD - ); +fn dry_run_upper_bound() { + test_dry_run_upper_bound::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/bootloader.rs b/core/lib/multivm/src/versions/vm_latest/tests/bootloader.rs index 9d23f658cb8..22239a6c1e3 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/bootloader.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/bootloader.rs @@ -1,57 +1,14 @@ -use assert_matches::assert_matches; -use zksync_types::U256; - use crate::{ - interface::{ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterfaceExt}, - vm_latest::{ - constants::BOOTLOADER_HEAP_PAGE, - tests::{ - tester::VmTesterBuilder, - utils::{get_bootloader, verify_required_memory, BASE_SYSTEM_CONTRACTS}, - }, - HistoryEnabled, - }, + versions::testonly::bootloader::{test_bootloader_out_of_gas, test_dummy_bootloader}, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_dummy_bootloader() { - let mut base_system_contracts = BASE_SYSTEM_CONTRACTS.clone(); - base_system_contracts.bootloader = get_bootloader("dummy"); - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(base_system_contracts) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let result = vm.vm.execute(VmExecutionMode::Batch); - assert!(!result.result.is_failed()); - - let correct_first_cell = U256::from_str_radix("123123123", 16).unwrap(); - verify_required_memory( - &vm.vm.state, - vec![(correct_first_cell, BOOTLOADER_HEAP_PAGE, 0)], - ); +fn dummy_bootloader() { + test_dummy_bootloader::>(); } #[test] -fn test_bootloader_out_of_gas() { - let mut base_system_contracts = BASE_SYSTEM_CONTRACTS.clone(); - base_system_contracts.bootloader = get_bootloader("dummy"); - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(base_system_contracts) - .with_bootloader_gas_limit(10) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let res = vm.vm.execute(VmExecutionMode::Batch); - - assert_matches!( - res.result, - ExecutionResult::Halt { - reason: Halt::BootloaderOutOfGas - } - ); +fn bootloader_out_of_gas() { + test_bootloader_out_of_gas::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/bytecode_publishing.rs b/core/lib/multivm/src/versions/vm_latest/tests/bytecode_publishing.rs index 2ed9948af81..e0727fbed89 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/bytecode_publishing.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/bytecode_publishing.rs @@ -1,41 +1,9 @@ use crate::{ - interface::{TxExecutionMode, VmEvent, VmExecutionMode, VmInterface, VmInterfaceExt}, - utils::bytecode, - vm_latest::{ - tests::{ - tester::{DeployContractsTx, TxType, VmTesterBuilder}, - utils::read_test_contract, - }, - HistoryEnabled, - }, + versions::testonly::bytecode_publishing::test_bytecode_publishing, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_bytecode_publishing() { - // In this test, we aim to ensure that the contents of the compressed bytecodes - // are included as part of the L2->L1 long messages - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let counter = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - - let compressed_bytecode = bytecode::compress(counter.clone()).unwrap().compressed; - - let DeployContractsTx { tx, .. } = account.get_deploy_tx(&counter, None, TxType::L2); - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "Transaction wasn't successful"); - - vm.vm.execute(VmExecutionMode::Batch); - - let state = vm.vm.get_current_execution_state(); - let long_messages = VmEvent::extract_long_l2_to_l1_messages(&state.events); - assert!( - long_messages.contains(&compressed_bytecode), - "Bytecode not published" - ); +fn bytecode_publishing() { + test_bytecode_publishing::>(); } 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 c3c6816cbd8..690af7d2a35 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/circuits.rs @@ -1,76 +1,9 @@ -use zksync_types::{Address, Execute, U256}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface}, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, tests::tester::VmTesterBuilder, HistoryEnabled, - }, + versions::testonly::circuits::test_circuits, + vm_latest::{HistoryEnabled, Vm}, }; -// Checks that estimated number of circuits for simple transfer doesn't differ much -// from hardcoded expected value. #[test] -fn test_circuits() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(Address::random()), - calldata: Vec::new(), - value: U256::from(1u8), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - let res = vm - .vm - .inspect(&mut Default::default(), VmExecutionMode::OneTx); - - let s = res.statistics.circuit_statistic; - // Check `circuit_statistic`. - 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"), - (s.ram_permutation, "ram_permutation"), - (s.storage_application, "storage_application"), - (s.storage_sorter, "storage_sorter"), - (s.code_decommitter, "code_decommitter"), - (s.code_decommitter_sorter, "code_decommitter_sorter"), - (s.log_demuxer, "log_demuxer"), - (s.events_sorter, "events_sorter"), - (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 { - assert_eq!( - *actual, expected, - "Check failed for {}, expected {}, actual {}", - name, expected, actual - ); - } else { - let diff = (actual - expected) / expected; - assert!( - diff.abs() < 0.1, - "Check failed for {}, expected {}, actual {}", - name, - expected, - actual - ); - } - } +fn circuits() { + test_circuits::>(); } 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 index b15ef7fde2b..e50e2aafcbf 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/code_oracle.rs @@ -1,282 +1,21 @@ -use ethabi::Token; -use zk_evm_1_5_0::{ - aux_structures::{MemoryPage, Timestamp}, - zkevm_opcode_defs::{ContractCodeSha256Format, VersionedHashLen32}, -}; -use zksync_types::{ - get_known_code_key, web3::keccak256, Address, Execute, StorageLogWithPreviousValue, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - tests::{ - tester::{get_empty_storage, VmTesterBuilder}, - utils::{load_precompiles_contract, read_precompiles_contract, read_test_contract}, - }, - HistoryEnabled, + versions::testonly::code_oracle::{ + test_code_oracle, test_code_oracle_big_bytecode, test_refunds_in_code_oracle, }, + vm_latest::{HistoryEnabled, Vm}, }; -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: Some(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: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx1); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - - // 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: Some(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: vec![], - }, - None, - ); - vm.vm.push_transaction(tx2); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); -} - -fn find_code_oracle_cost_log( - precompiles_contract_address: Address, - logs: &[StorageLogWithPreviousValue], -) -> &StorageLogWithPreviousValue { - logs.iter() - .find(|log| { - *log.log.key.address() == precompiles_contract_address && log.log.key.key().is_zero() - }) - .expect("no code oracle cost log") +fn code_oracle() { + test_code_oracle::>(); } #[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: Some(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: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx1); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "Transaction wasn't successful"); +fn code_oracle_big_bytecode() { + test_code_oracle_big_bytecode::>(); } #[test] fn refunds_in_code_oracle() { - let precompiles_contract_address = Address::random(); - let precompile_contract_bytecode = read_precompiles_contract(); - - 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 normal_zkevm_bytecode_words = bytes_to_be_words(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()), - ); - - let precompile_contract = load_precompiles_contract(); - let call_code_oracle_function = precompile_contract.function("callCodeOracle").unwrap(); - - // Execute code oracle twice with identical VM state that only differs in that the queried bytecode - // is already decommitted the second time. The second call must consume less gas (`decommit` doesn't charge additional gas - // for already decommitted codes). - let mut oracle_costs = vec![]; - for decommit in [false, true] { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .with_custom_contracts(vec![( - precompile_contract_bytecode.clone(), - precompiles_contract_address, - false, - )]) - .with_storage(storage.clone()) - .build(); - - vm.vm.state.decommittment_processor.populate( - vec![( - h256_to_u256(normal_zkevm_bytecode_hash), - normal_zkevm_bytecode_words.clone(), - )], - Timestamp(0), - ); - - let account = &mut vm.rich_accounts[0]; - if decommit { - let (header, normalized_preimage) = - ContractCodeSha256Format::normalize_for_decommitment(&normal_zkevm_bytecode_hash.0); - let query = vm - .vm - .state - .prepare_to_decommit( - 0, - header, - normalized_preimage, - MemoryPage(123), - Timestamp(0), - ) - .unwrap(); - - assert!(query.is_fresh); - vm.vm.state.execute_decommit(0, query).unwrap(); - } - - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(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: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "Transaction wasn't successful"); - let log = - find_code_oracle_cost_log(precompiles_contract_address, &result.logs.storage_logs); - oracle_costs.push(log.log.value); - } - - // The refund is equal to `gasCost` parameter passed to the `decommit` opcode, which is defined as `4 * contract_length_in_words` - // in `CodeOracle.yul`. - let code_oracle_refund = h256_to_u256(oracle_costs[0]) - h256_to_u256(oracle_costs[1]); - assert_eq!( - code_oracle_refund, - (4 * normal_zkevm_bytecode_words.len()).into() - ); + test_refunds_in_code_oracle::>(); } 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 aa3eb5e752c..3d0e21c2466 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 @@ -1,79 +1,9 @@ -use zksync_system_constants::L2_BASE_TOKEN_ADDRESS; -use zksync_types::{ - get_code_key, get_known_code_key, get_nonce_key, - system_contracts::{DEPLOYMENT_NONCE_INCREMENT, TX_NONCE_INCREMENT}, - AccountTreeId, U256, -}; -use zksync_utils::u256_to_h256; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - tests::{ - tester::{DeployContractsTx, TxType, VmTesterBuilder}, - utils::{get_balance, read_test_contract, verify_required_storage}, - }, - utils::fee::get_batch_base_fee, - HistoryEnabled, - }, + versions::testonly::default_aa::test_default_aa_interaction, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_default_aa_interaction() { - // 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) - .build(); - - let counter = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - let DeployContractsTx { - tx, - bytecode_hash, - address, - } = account.get_deploy_tx(&counter, None, TxType::L2); - let maximal_fee = tx.gas_limit() * get_batch_base_fee(&vm.vm.batch_env); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - 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. - let account_nonce_key = get_nonce_key(&account.address); - let expected_nonce = TX_NONCE_INCREMENT + DEPLOYMENT_NONCE_INCREMENT; - - // The code hash of the deployed contract should be marked as republished. - let known_codes_key = get_known_code_key(&bytecode_hash); - - // The contract should be deployed successfully. - let account_code_key = get_code_key(&address); - - let expected_slots = vec![ - (u256_to_h256(expected_nonce), account_nonce_key), - (u256_to_h256(U256::from(1u32)), known_codes_key), - (bytecode_hash, account_code_key), - ]; - - verify_required_storage(&vm.vm.state, expected_slots); - - let expected_fee = maximal_fee - - U256::from(result.refunds.gas_refunded) - * U256::from(get_batch_base_fee(&vm.vm.batch_env)); - let operator_balance = get_balance( - AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), - &vm.fee_account, - vm.vm.state.storage.storage.get_ptr(), - ); - - assert_eq!( - operator_balance, expected_fee, - "Operator did not receive his fee" - ); +fn default_aa_interaction() { + test_default_aa_interaction::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs b/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs index cc9aac5bb91..5aa7ab9e9c7 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/gas_limit.rs @@ -1,46 +1,9 @@ -use zksync_test_account::Account; -use zksync_types::{fee::Fee, Execute}; - use crate::{ - interface::{TxExecutionMode, VmInterface}, - vm_latest::{ - constants::{BOOTLOADER_HEAP_PAGE, TX_DESCRIPTION_OFFSET, TX_GAS_LIMIT_OFFSET}, - tests::tester::VmTesterBuilder, - HistoryDisabled, - }, + versions::testonly::gas_limit::test_tx_gas_limit_offset, + vm_latest::{HistoryEnabled, Vm}, }; -/// Checks that `TX_GAS_LIMIT_OFFSET` constant is correct. #[test] -fn test_tx_gas_limit_offset() { - let mut vm = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let gas_limit = 9999.into(); - let tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Some(Default::default()), - ..Default::default() - }, - Some(Fee { - gas_limit, - ..Account::default_fee() - }), - ); - - vm.vm.push_transaction(tx); - - let gas_limit_from_memory = vm - .vm - .state - .memory - .read_slot( - BOOTLOADER_HEAP_PAGE as usize, - TX_DESCRIPTION_OFFSET + TX_GAS_LIMIT_OFFSET, - ) - .value; - assert_eq!(gas_limit_from_memory, gas_limit); +fn tx_gas_limit_offset() { + test_tx_gas_limit_offset::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs b/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs index d7cadc54b44..7f39915f2b6 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/get_used_contracts.rs @@ -1,252 +1,22 @@ -use std::{ - collections::{HashMap, HashSet}, - iter, - str::FromStr, -}; - -use assert_matches::assert_matches; -use ethabi::Token; -use itertools::Itertools; -use zk_evm_1_3_1::zkevm_opcode_defs::decoding::{EncodingModeProduction, VmEncodingMode}; -use zk_evm_1_5_0::{ - abstractions::DecommittmentProcessor, - aux_structures::{DecommittmentQuery, MemoryPage, Timestamp}, - zkevm_opcode_defs::{VersionedHashHeader, VersionedHashNormalizedPreimage}, -}; -use zksync_system_constants::CONTRACT_DEPLOYER_ADDRESS; -use zksync_test_account::Account; -use zksync_types::{Address, Execute, U256}; -use zksync_utils::{bytecode::hash_bytecode, h256_to_u256}; -use zksync_vm_interface::VmExecutionResultAndLogs; - use crate::{ - interface::{ - storage::WriteStorage, ExecutionResult, TxExecutionMode, VmExecutionMode, VmInterface, - VmInterfaceExt, + versions::testonly::get_used_contracts::{ + test_get_used_contracts, test_get_used_contracts_with_far_call, + test_get_used_contracts_with_out_of_gas_far_call, }, - vm_latest::{ - tests::{ - tester::{TxType, VmTester, VmTesterBuilder}, - utils::{read_proxy_counter_contract, read_test_contract, BASE_SYSTEM_CONTRACTS}, - }, - HistoryDisabled, Vm, - }, - HistoryMode, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_get_used_contracts() { - let mut vm = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - assert!(known_bytecodes_without_base_system_contracts(&vm.vm).is_empty()); - - // create and push and execute some not-empty factory deps transaction with success status - // to check that `get_used_contracts()` updates - let contract_code = read_test_contract(); - let mut account = Account::random(); - let tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 0 }); - vm.vm.push_transaction(tx.tx.clone()); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed()); - - assert!(vm - .vm - .get_used_contracts() - .contains(&h256_to_u256(tx.bytecode_hash))); - - // Note: `Default_AA` will be in the list of used contracts if L2 tx is used - assert_eq!( - vm.vm - .get_used_contracts() - .into_iter() - .collect::>(), - known_bytecodes_without_base_system_contracts(&vm.vm) - .keys() - .cloned() - .collect::>() - ); - - // create push and execute some non-empty factory deps transaction that fails - // (`known_bytecodes` will be updated but we expect `get_used_contracts()` to not be updated) - - let calldata = [1, 2, 3]; - let big_calldata: Vec = calldata - .iter() - .cycle() - .take(calldata.len() * 1024) - .cloned() - .collect(); - let account2 = Account::random(); - let tx2 = account2.get_l1_tx( - Execute { - contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), - calldata: big_calldata, - value: Default::default(), - factory_deps: vec![vec![1; 32]], - }, - 1, - ); - - vm.vm.push_transaction(tx2.clone()); - - let res2 = vm.vm.execute(VmExecutionMode::OneTx); - - assert!(res2.result.is_failed()); - - for factory_dep in tx2.execute.factory_deps { - let hash = hash_bytecode(&factory_dep); - let hash_to_u256 = h256_to_u256(hash); - assert!(known_bytecodes_without_base_system_contracts(&vm.vm) - .keys() - .contains(&hash_to_u256)); - assert!(!vm.vm.get_used_contracts().contains(&hash_to_u256)); - } -} - -#[test] -fn test_contract_is_used_right_after_prepare_to_decommit() { - let mut vm = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - assert!(vm.vm.get_used_contracts().is_empty()); - - let bytecode_hash = - U256::from_str("0x100067ff3124f394104ab03481f7923f0bc4029a2aa9d41cc1d848c81257185") - .unwrap(); - vm.vm - .state - .decommittment_processor - .populate(vec![(bytecode_hash, vec![])], Timestamp(0)); - - let header = hex::decode("0100067f").unwrap(); - let normalized_preimage = - hex::decode("f3124f394104ab03481f7923f0bc4029a2aa9d41cc1d848c81257185").unwrap(); - vm.vm - .state - .decommittment_processor - .prepare_to_decommit( - 0, - DecommittmentQuery { - header: VersionedHashHeader(header.try_into().unwrap()), - normalized_preimage: VersionedHashNormalizedPreimage( - normalized_preimage.try_into().unwrap(), - ), - timestamp: Timestamp(0), - memory_page: MemoryPage(0), - decommitted_length: 0, - is_fresh: false, - }, - ) - .unwrap(); - - assert_eq!(vm.vm.get_used_contracts(), vec![bytecode_hash]); -} - -fn known_bytecodes_without_base_system_contracts( - vm: &Vm, -) -> HashMap> { - let mut known_bytecodes_without_base_system_contracts = vm - .state - .decommittment_processor - .known_bytecodes - .inner() - .clone(); - known_bytecodes_without_base_system_contracts - .remove(&h256_to_u256(BASE_SYSTEM_CONTRACTS.default_aa.hash)) - .unwrap(); - if let Some(evm_emulator) = &BASE_SYSTEM_CONTRACTS.evm_emulator { - known_bytecodes_without_base_system_contracts - .remove(&h256_to_u256(evm_emulator.hash)) - .unwrap(); - } - known_bytecodes_without_base_system_contracts -} - -/// Counter test contract bytecode inflated by appending lots of `NOP` opcodes at the end. This leads to non-trivial -/// decommitment cost (>10,000 gas). -fn inflated_counter_bytecode() -> Vec { - let mut counter_bytecode = read_test_contract(); - counter_bytecode.extend( - iter::repeat(EncodingModeProduction::nop_encoding().to_be_bytes()) - .take(10_000) - .flatten(), - ); - counter_bytecode -} - -fn execute_proxy_counter(gas: u32) -> (VmTester, U256, VmExecutionResultAndLogs) { - let counter_bytecode = inflated_counter_bytecode(); - let counter_bytecode_hash = h256_to_u256(hash_bytecode(&counter_bytecode)); - let counter_address = Address::repeat_byte(0x23); - - let mut vm = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_custom_contracts(vec![(counter_bytecode, counter_address, false)]) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let (proxy_counter_bytecode, proxy_counter_abi) = read_proxy_counter_contract(); - let account = &mut vm.rich_accounts[0]; - let deploy_tx = account.get_deploy_tx( - &proxy_counter_bytecode, - Some(&[Token::Address(counter_address)]), - TxType::L2, - ); - let (compression_result, exec_result) = vm - .vm - .execute_transaction_with_bytecode_compression(deploy_tx.tx, true); - compression_result.unwrap(); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - - let decommitted_hashes = vm.vm.get_used_contracts(); - assert!( - !decommitted_hashes.contains(&counter_bytecode_hash), - "{decommitted_hashes:?}" - ); - - let increment = proxy_counter_abi.function("increment").unwrap(); - let increment_tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(deploy_tx.address), - calldata: increment - .encode_input(&[Token::Uint(1.into()), Token::Uint(gas.into())]) - .unwrap(), - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - let (compression_result, exec_result) = vm - .vm - .execute_transaction_with_bytecode_compression(increment_tx, true); - compression_result.unwrap(); - (vm, counter_bytecode_hash, exec_result) +fn get_used_contracts() { + test_get_used_contracts::>(); } #[test] fn get_used_contracts_with_far_call() { - let (vm, counter_bytecode_hash, exec_result) = execute_proxy_counter(100_000); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - let decommitted_hashes = vm.vm.get_used_contracts(); - assert!( - decommitted_hashes.contains(&counter_bytecode_hash), - "{decommitted_hashes:?}" - ); + test_get_used_contracts_with_far_call::>(); } #[test] fn get_used_contracts_with_out_of_gas_far_call() { - let (vm, counter_bytecode_hash, exec_result) = execute_proxy_counter(10_000); - assert_matches!(exec_result.result, ExecutionResult::Revert { .. }); - let decommitted_hashes = vm.vm.get_used_contracts(); - assert!( - decommitted_hashes.contains(&counter_bytecode_hash), - "{decommitted_hashes:?}" - ); + test_get_used_contracts_with_out_of_gas_far_call::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/is_write_initial.rs b/core/lib/multivm/src/versions/vm_latest/tests/is_write_initial.rs index 8206cfa9be6..193fc586079 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/is_write_initial.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/is_write_initial.rs @@ -1,49 +1,9 @@ -use zksync_types::get_nonce_key; - use crate::{ - interface::{ - storage::ReadStorage, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, - }, - vm_latest::{ - tests::{ - tester::{Account, TxType, VmTesterBuilder}, - utils::read_test_contract, - }, - HistoryDisabled, - }, + versions::testonly::is_write_initial::test_is_write_initial_behaviour, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_is_write_initial_behaviour() { - // In this test, we check result of `is_write_initial` at different stages. - // The main idea is to check that `is_write_initial` storage uses the correct cache for initial_writes and doesn't - // messed up it with the repeated writes during the one batch execution. - - let mut account = Account::random(); - let mut vm = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let nonce_key = get_nonce_key(&account.address); - // Check that the next write to the nonce key will be initial. - assert!(vm - .storage - .as_ref() - .borrow_mut() - .is_write_initial(&nonce_key)); - - let contract_code = read_test_contract(); - let tx = account.get_deploy_tx(&contract_code, None, TxType::L2).tx; - - vm.vm.push_transaction(tx); - vm.vm.execute(VmExecutionMode::OneTx); - - // Check that `is_write_initial` still returns true for the nonce key. - assert!(vm - .storage - .as_ref() - .borrow_mut() - .is_write_initial(&nonce_key)); +fn is_write_initial_behaviour() { + test_is_write_initial_behaviour::>(); } 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 4bb32cdf7ae..4b7429c2829 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 @@ -1,196 +1,16 @@ -use ethabi::Token; -use zksync_contracts::l1_messenger_contract; -use zksync_system_constants::{BOOTLOADER_ADDRESS, L1_MESSENGER_ADDRESS}; -use zksync_test_account::Account; -use zksync_types::{ - get_code_key, get_known_code_key, - l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, - Execute, ExecuteTransactionCommon, K256PrivateKey, U256, -}; -use zksync_utils::u256_to_h256; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - utils::StorageWritesDeduplicator, - vm_latest::{ - tests::{ - tester::{TxType, VmTesterBuilder}, - utils::{read_test_contract, verify_required_storage, BASE_SYSTEM_CONTRACTS}, - }, - types::internals::TransactionData, - HistoryEnabled, + versions::testonly::l1_tx_execution::{ + test_l1_tx_execution, test_l1_tx_execution_high_gas_limit, }, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_l1_tx_execution() { - // In this test, we try to execute a contract deployment from L1 - // 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 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 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() - .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let contract_code = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - let deploy_tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 1 }); - let tx_data = TransactionData::new(deploy_tx.tx.clone(), false); - - let required_l2_to_l1_logs: Vec<_> = vec![L2ToL1Log { - shard_id: 0, - is_service: true, - tx_number_in_block: 0, - sender: BOOTLOADER_ADDRESS, - key: tx_data.tx_hash(0.into()), - value: u256_to_h256(U256::from(1u32)), - }] - .into_iter() - .map(UserL2ToL1Log) - .collect(); - - vm.vm.push_transaction(deploy_tx.tx.clone()); - - let res = vm.vm.execute(VmExecutionMode::OneTx); - - // The code hash of the deployed contract should be marked as republished. - let known_codes_key = get_known_code_key(&deploy_tx.bytecode_hash); - - // The contract should be deployed successfully. - let account_code_key = get_code_key(&deploy_tx.address); - - let expected_slots = vec![ - (u256_to_h256(U256::from(1u32)), known_codes_key), - (deploy_tx.bytecode_hash, account_code_key), - ]; - assert!(!res.result.is_failed()); - - verify_required_storage(&vm.vm.state, expected_slots); - - assert_eq!(res.logs.user_l2_to_l1_logs, required_l2_to_l1_logs); - - let tx = account.get_test_contract_transaction( - deploy_tx.address, - true, - None, - false, - TxType::L1 { serial_id: 0 }, - ); - vm.vm.push_transaction(tx); - let res = vm.vm.execute(VmExecutionMode::OneTx); - let storage_logs = res.logs.storage_logs; - let res = StorageWritesDeduplicator::apply_on_empty_state(&storage_logs); - - // Tx panicked - assert_eq!(res.initial_storage_writes, basic_initial_writes); - - let tx = account.get_test_contract_transaction( - deploy_tx.address, - false, - None, - false, - TxType::L1 { serial_id: 0 }, - ); - vm.vm.push_transaction(tx.clone()); - 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. 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; - assert_eq!(res.repeated_storage_writes, 0); - - 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. - // 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); - - let tx = account.get_test_contract_transaction( - deploy_tx.address, - false, - Some(10.into()), - false, - TxType::L1 { serial_id: 1 }, - ); - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - // Method is not payable tx should fail - assert!(result.result.is_failed(), "The transaction should fail"); - - 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, 1); +fn l1_tx_execution() { + test_l1_tx_execution::>(); } #[test] -fn test_l1_tx_execution_high_gas_limit() { - // In this test, we try to execute an L1->L2 transaction with a high gas limit. - // Usually priority transactions with dangerously gas limit should even pass the checks on the L1, - // however, they might pass during the transition period to the new fee model, so we check that we can safely process those. - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![Account::new( - K256PrivateKey::from_bytes([0xad; 32].into()).unwrap(), - )]) - .build(); - - let account = &mut vm.rich_accounts[0]; - - let l1_messenger = l1_messenger_contract(); - - let contract_function = l1_messenger.function("sendToL1").unwrap(); - let params = [ - // Even a message of size 100k should not be able to be sent by a priority transaction - Token::Bytes(vec![0u8; 100_000]), - ]; - let calldata = contract_function.encode_input(¶ms).unwrap(); - - let mut tx = account.get_l1_tx( - Execute { - contract_address: Some(L1_MESSENGER_ADDRESS), - value: 0.into(), - factory_deps: vec![], - calldata, - }, - 0, - ); - - if let ExecuteTransactionCommon::L1(data) = &mut tx.common_data { - // Using some large gas limit - data.gas_limit = 300_000_000.into(); - } else { - unreachable!() - }; - - vm.vm.push_transaction(tx); - - let res = vm.vm.execute(VmExecutionMode::OneTx); - - assert!(res.result.is_failed(), "The transaction should've failed"); +fn l1_tx_execution_high_gas_limit() { + test_l1_tx_execution_high_gas_limit::>(); } 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 112be637fe0..44e272caca8 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -1,3 +1,20 @@ +use std::collections::HashSet; + +use zk_evm_1_5_0::{ + aux_structures::{MemoryPage, Timestamp}, + zkevm_opcode_defs::{ContractCodeSha256Format, VersionedHashLen32}, +}; +use zksync_types::{writes::StateDiffRecord, StorageKey, H256, U256}; +use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; +use zksync_vm_interface::{CurrentExecutionState, VmExecutionMode, VmExecutionResultAndLogs}; + +use super::{HistoryEnabled, Vm}; +use crate::{ + interface::storage::{InMemoryStorage, StorageView}, + versions::testonly::TestedVm, + vm_latest::{constants::BOOTLOADER_HEAP_PAGE, tracers::PubdataTracer, TracerDispatcher}, +}; + mod bootloader; mod default_aa; // TODO - fix this test @@ -29,3 +46,94 @@ mod tracing_execution_error; mod transfer; mod upgrade; mod utils; + +impl TestedVm for Vm, HistoryEnabled> { + fn gas_remaining(&mut self) -> u32 { + self.state.local_state.callstack.current.ergs_remaining + } + + fn get_current_execution_state(&self) -> CurrentExecutionState { + self.get_current_execution_state() + } + + fn decommitted_hashes(&self) -> HashSet { + self.get_used_contracts().into_iter().collect() + } + + fn execute_with_state_diffs( + &mut self, + diffs: Vec, + mode: VmExecutionMode, + ) -> VmExecutionResultAndLogs { + let pubdata_tracer = PubdataTracer::new_with_forced_state_diffs( + self.batch_env.clone(), + VmExecutionMode::Batch, + diffs, + crate::vm_latest::MultiVMSubversion::latest(), + ); + self.inspect_inner(&mut TracerDispatcher::default(), mode, Some(pubdata_tracer)) + } + + fn insert_bytecodes(&mut self, bytecodes: &[&[u8]]) { + let bytecodes = bytecodes + .iter() + .map(|&bytecode| { + let hash = hash_bytecode(bytecode); + let words = bytes_to_be_words(bytecode.to_vec()); + (h256_to_u256(hash), words) + }) + .collect(); + self.state + .decommittment_processor + .populate(bytecodes, Timestamp(0)); + } + + fn known_bytecode_hashes(&self) -> HashSet { + self.state + .decommittment_processor + .known_bytecodes + .inner() + .keys() + .copied() + .collect() + } + + fn manually_decommit(&mut self, code_hash: H256) -> bool { + let (header, normalized_preimage) = + ContractCodeSha256Format::normalize_for_decommitment(&code_hash.0); + let query = self + .state + .prepare_to_decommit( + 0, + header, + normalized_preimage, + MemoryPage(123), + Timestamp(0), + ) + .unwrap(); + self.state.execute_decommit(0, query).unwrap(); + query.is_fresh + } + + fn verify_required_bootloader_memory(&self, cells: &[(u32, U256)]) { + for &(slot, required_value) in cells { + let current_value = self + .state + .memory + .read_slot(BOOTLOADER_HEAP_PAGE as usize, slot as usize) + .value; + assert_eq!(current_value, required_value); + } + } + + fn verify_required_storage(&mut self, cells: &[(StorageKey, H256)]) { + for &(key, required_value) in cells { + let current_value = self.state.storage.storage.read_from_storage(&key); + assert_eq!( + u256_to_h256(current_value), + required_value, + "Invalid value at key {key:?}" + ); + } + } +} From aebc0e62415c2725d012ee498d5136f78b9dc163 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 12:42:04 +0300 Subject: [PATCH 03/18] Unify more unit tests --- .../src/versions/testonly/bootloader.rs | 2 +- .../src/versions/testonly/default_aa.rs | 12 +- .../src/versions/testonly/gas_limit.rs | 3 +- .../src/versions/testonly/l1_tx_execution.rs | 8 +- .../src/versions/testonly/l2_blocks.rs | 415 +++++++++++++++++ core/lib/multivm/src/versions/testonly/mod.rs | 23 + .../src/versions/testonly/nonce_holder.rs | 198 ++++++++ .../src/versions/testonly/precompiles.rs | 113 +++++ .../multivm/src/versions/testonly/refunds.rs | 213 +++++++++ .../src/versions/testonly/require_eip712.rs | 150 ++++++ .../sekp256r1.rs => testonly/secp256r1.rs} | 11 +- .../src/versions/testonly/simple_execution.rs | 77 ++++ .../multivm/src/versions/testonly/storage.rs | 126 +++++ .../src/versions/testonly/tester/mod.rs | 88 ++-- .../src/versions/vm_fast/tests/l2_blocks.rs | 421 +---------------- .../multivm/src/versions/vm_fast/tests/mod.rs | 53 ++- .../versions/vm_fast/tests/nonce_holder.rs | 179 +------- .../src/versions/vm_fast/tests/precompiles.rs | 113 +---- .../versions/vm_fast/tests/prestate_tracer.rs | 143 ------ .../src/versions/vm_fast/tests/refunds.rs | 217 +-------- .../versions/vm_fast/tests/require_eip712.rs | 175 +------ .../src/versions/vm_fast/tests/secp256r1.rs | 6 + .../vm_fast/tests/simple_execution.rs | 74 +-- .../src/versions/vm_fast/tests/storage.rs | 131 +----- .../src/versions/vm_latest/tests/l2_blocks.rs | 430 +----------------- .../src/versions/vm_latest/tests/mod.rs | 49 +- .../versions/vm_latest/tests/nonce_holder.rs | 185 +------- .../versions/vm_latest/tests/precompiles.rs | 139 +----- .../src/versions/vm_latest/tests/refunds.rs | 224 +-------- .../vm_latest/tests/require_eip712.rs | 165 +------ .../src/versions/vm_latest/tests/secp256r1.rs | 9 + .../src/versions/vm_latest/tests/sekp256r1.rs | 74 --- .../vm_latest/tests/simple_execution.rs | 77 +--- .../src/versions/vm_latest/tests/storage.rs | 186 +------- .../versions/vm_latest/tests/tester/mod.rs | 4 +- 35 files changed, 1535 insertions(+), 2958 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/l2_blocks.rs create mode 100644 core/lib/multivm/src/versions/testonly/nonce_holder.rs create mode 100644 core/lib/multivm/src/versions/testonly/precompiles.rs create mode 100644 core/lib/multivm/src/versions/testonly/refunds.rs create mode 100644 core/lib/multivm/src/versions/testonly/require_eip712.rs rename core/lib/multivm/src/versions/{vm_fast/tests/sekp256r1.rs => testonly/secp256r1.rs} (92%) create mode 100644 core/lib/multivm/src/versions/testonly/simple_execution.rs create mode 100644 core/lib/multivm/src/versions/testonly/storage.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/tests/prestate_tracer.rs create mode 100644 core/lib/multivm/src/versions/vm_fast/tests/secp256r1.rs create mode 100644 core/lib/multivm/src/versions/vm_latest/tests/secp256r1.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs diff --git a/core/lib/multivm/src/versions/testonly/bootloader.rs b/core/lib/multivm/src/versions/testonly/bootloader.rs index 4aa42c39aa4..e3177e07851 100644 --- a/core/lib/multivm/src/versions/testonly/bootloader.rs +++ b/core/lib/multivm/src/versions/testonly/bootloader.rs @@ -19,7 +19,7 @@ pub(crate) fn test_dummy_bootloader() { let correct_first_cell = U256::from_str_radix("123123123", 16).unwrap(); vm.vm - .verify_required_bootloader_memory(&[(0, correct_first_cell)]); + .verify_required_bootloader_heap(&[(0, correct_first_cell)]); } pub(crate) fn test_bootloader_out_of_gas() { diff --git a/core/lib/multivm/src/versions/testonly/default_aa.rs b/core/lib/multivm/src/versions/testonly/default_aa.rs index 92b3b3910a9..422855a5dbe 100644 --- a/core/lib/multivm/src/versions/testonly/default_aa.rs +++ b/core/lib/multivm/src/versions/testonly/default_aa.rs @@ -3,9 +3,9 @@ use zksync_types::{ get_code_key, get_known_code_key, get_nonce_key, system_contracts::{DEPLOYMENT_NONCE_INCREMENT, TX_NONCE_INCREMENT}, utils::storage_key_for_eth_balance, - H256, U256, + U256, }; -use zksync_utils::u256_to_h256; +use zksync_utils::h256_to_u256; use super::{read_test_contract, tester::VmTesterBuilder, TestedVm}; use crate::{ @@ -55,10 +55,10 @@ pub(crate) fn test_default_aa_interaction() { * U256::from(get_batch_base_fee(&vm.l1_batch_env)); let expected_slots = [ - (account_nonce_key, u256_to_h256(expected_nonce)), - (known_codes_key, H256::from_low_u64_be(1)), - (account_code_key, bytecode_hash), - (operator_balance_key, u256_to_h256(expected_fee)), + (account_nonce_key, expected_nonce), + (known_codes_key, 1.into()), + (account_code_key, h256_to_u256(bytecode_hash)), + (operator_balance_key, expected_fee), ]; vm.vm.verify_required_storage(&expected_slots); } diff --git a/core/lib/multivm/src/versions/testonly/gas_limit.rs b/core/lib/multivm/src/versions/testonly/gas_limit.rs index fb2be649688..b9957f0d8c0 100644 --- a/core/lib/multivm/src/versions/testonly/gas_limit.rs +++ b/core/lib/multivm/src/versions/testonly/gas_limit.rs @@ -30,6 +30,5 @@ pub(crate) fn test_tx_gas_limit_offset() { vm.vm.push_transaction(tx); let slot = (TX_DESCRIPTION_OFFSET + TX_GAS_LIMIT_OFFSET) as u32; - vm.vm - .verify_required_bootloader_memory(&[(slot, gas_limit)]); + vm.vm.verify_required_bootloader_heap(&[(slot, gas_limit)]); } diff --git a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs index b83e118eaa8..e614eb03468 100644 --- a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs +++ b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs @@ -5,9 +5,9 @@ use zksync_test_account::TxType; use zksync_types::{ get_code_key, get_known_code_key, l2_to_l1_log::{L2ToL1Log, UserL2ToL1Log}, - Execute, ExecuteTransactionCommon, H256, U256, + Execute, ExecuteTransactionCommon, U256, }; -use zksync_utils::u256_to_h256; +use zksync_utils::{h256_to_u256, u256_to_h256}; use super::{read_test_contract, tester::VmTesterBuilder, TestedVm, BASE_SYSTEM_CONTRACTS}; use crate::{ @@ -71,8 +71,8 @@ pub(crate) fn test_l1_tx_execution() { assert!(!res.result.is_failed()); vm.vm.verify_required_storage(&[ - (known_codes_key, H256::from_low_u64_be(1)), - (account_code_key, deploy_tx.bytecode_hash), + (known_codes_key, U256::from(1)), + (account_code_key, h256_to_u256(deploy_tx.bytecode_hash)), ]); assert_eq!(res.logs.user_l2_to_l1_logs, required_l2_to_l1_logs); diff --git a/core/lib/multivm/src/versions/testonly/l2_blocks.rs b/core/lib/multivm/src/versions/testonly/l2_blocks.rs new file mode 100644 index 00000000000..8df75761c9b --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/l2_blocks.rs @@ -0,0 +1,415 @@ +//! +//! Tests for the bootloader +//! The description for each of the tests can be found in the corresponding `.yul` file. +//! + +use assert_matches::assert_matches; +use zksync_system_constants::REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE; +use zksync_types::{ + block::{pack_block_info, L2BlockHasher}, + AccountTreeId, Execute, ExecuteTransactionCommon, L1BatchNumber, L1TxCommonData, L2BlockNumber, + ProtocolVersionId, StorageKey, Transaction, H160, H256, SYSTEM_CONTEXT_ADDRESS, + SYSTEM_CONTEXT_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, + SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, U256, +}; +use zksync_utils::{h256_to_u256, u256_to_h256}; + +use super::{default_l1_batch, tester::VmTesterBuilder, TestedVm}; +use crate::{ + interface::{ + ExecutionResult, Halt, L2BlockEnv, TxExecutionMode, VmExecutionMode, VmInterfaceExt, + }, + vm_latest::{ + constants::{TX_OPERATOR_L2_BLOCK_INFO_OFFSET, TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO}, + utils::l2_blocks::get_l2_block_hash_key, + }, +}; + +fn get_l1_noop() -> Transaction { + Transaction { + common_data: ExecuteTransactionCommon::L1(L1TxCommonData { + sender: H160::random(), + gas_limit: U256::from(2000000u32), + gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), + ..Default::default() + }), + execute: Execute { + contract_address: Some(H160::zero()), + calldata: vec![], + value: U256::zero(), + factory_deps: vec![], + }, + received_timestamp_ms: 0, + raw_bytes: None, + } +} + +pub(crate) fn test_l2_block_initialization_timestamp() { + // This test checks that the L2 block initialization works correctly. + // Here we check that that the first block must have timestamp that is greater or equal to the timestamp + // of the current batch. + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + // Override the timestamp of the current L2 block to be 0. + vm.vm.push_l2_block_unchecked(L2BlockEnv { + number: 1, + timestamp: 0, + prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), + max_virtual_blocks_to_create: 1, + }); + let l1_tx = get_l1_noop(); + + vm.vm.push_transaction(l1_tx); + let res = vm.vm.execute(VmExecutionMode::OneTx); + + assert_matches!( + res.result, + ExecutionResult::Halt { reason: Halt::FailedToSetL2Block(msg) } + if msg.contains("timestamp") + ); +} + +pub(crate) fn test_l2_block_initialization_number_non_zero() { + // This test checks that the L2 block initialization works correctly. + // Here we check that the first L2 block number can not be zero. + + let l1_batch = default_l1_batch(L1BatchNumber(1)); + let first_l2_block = L2BlockEnv { + number: 0, + timestamp: l1_batch.timestamp, + prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), + max_virtual_blocks_to_create: 1, + }; + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_l1_batch_env(l1_batch) + .with_random_rich_accounts(1) + .build::(); + + let l1_tx = get_l1_noop(); + + vm.vm.push_transaction(l1_tx); + + set_manual_l2_block_info(&mut vm.vm, 0, first_l2_block); + + let res = vm.vm.execute(VmExecutionMode::OneTx); + + assert_eq!( + res.result, + ExecutionResult::Halt { + reason: Halt::FailedToSetL2Block( + "L2 block number is never expected to be zero".to_string() + ) + } + ); +} + +fn test_same_l2_block( + expected_error: Option, + override_timestamp: Option, + override_prev_block_hash: Option, +) { + let mut l1_batch = default_l1_batch(L1BatchNumber(1)); + l1_batch.timestamp = 1; + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_l1_batch_env(l1_batch) + .with_random_rich_accounts(1) + .build::(); + + let l1_tx = get_l1_noop(); + vm.vm.push_transaction(l1_tx.clone()); + let res = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!res.result.is_failed()); + + let mut current_l2_block = vm.l1_batch_env.first_l2_block; + + if let Some(timestamp) = override_timestamp { + current_l2_block.timestamp = timestamp; + } + if let Some(prev_block_hash) = override_prev_block_hash { + current_l2_block.prev_block_hash = prev_block_hash; + } + + if (None, None) == (override_timestamp, override_prev_block_hash) { + current_l2_block.max_virtual_blocks_to_create = 0; + } + + vm.vm.push_transaction(l1_tx); + set_manual_l2_block_info(&mut vm.vm, 1, current_l2_block); + + let result = vm.vm.execute(VmExecutionMode::OneTx); + + if let Some(err) = expected_error { + assert_eq!(result.result, ExecutionResult::Halt { reason: err }); + } else { + assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); + } +} + +pub(crate) fn test_l2_block_same_l2_block() { + // This test aims to test the case when there are multiple transactions inside the same L2 block. + + // Case 1: Incorrect timestamp + test_same_l2_block::( + Some(Halt::FailedToSetL2Block( + "The timestamp of the same L2 block must be same".to_string(), + )), + Some(0), + None, + ); + + // Case 2: Incorrect previous block hash + test_same_l2_block::( + Some(Halt::FailedToSetL2Block( + "The previous hash of the same L2 block must be same".to_string(), + )), + None, + Some(H256::zero()), + ); + + // Case 3: Correct continuation of the same L2 block + test_same_l2_block::(None, None, None); +} + +fn test_new_l2_block( + first_l2_block: L2BlockEnv, + overriden_second_block_number: Option, + overriden_second_block_timestamp: Option, + overriden_second_block_prev_block_hash: Option, + expected_error: Option, +) { + let mut l1_batch = default_l1_batch(L1BatchNumber(1)); + l1_batch.timestamp = 1; + l1_batch.first_l2_block = first_l2_block; + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_l1_batch_env(l1_batch) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let l1_tx = get_l1_noop(); + + // Firstly we execute the first transaction + vm.vm.push_transaction(l1_tx.clone()); + vm.vm.execute(VmExecutionMode::OneTx); + + let mut second_l2_block = vm.l1_batch_env.first_l2_block; + second_l2_block.number += 1; + second_l2_block.timestamp += 1; + second_l2_block.prev_block_hash = vm.vm.last_l2_block_hash(); + + if let Some(block_number) = overriden_second_block_number { + second_l2_block.number = block_number; + } + if let Some(timestamp) = overriden_second_block_timestamp { + second_l2_block.timestamp = timestamp; + } + if let Some(prev_block_hash) = overriden_second_block_prev_block_hash { + second_l2_block.prev_block_hash = prev_block_hash; + } + + vm.vm.push_l2_block_unchecked(second_l2_block); + vm.vm.push_transaction(l1_tx); + + let result = vm.vm.execute(VmExecutionMode::OneTx); + if let Some(err) = expected_error { + assert_eq!(result.result, ExecutionResult::Halt { reason: err }); + } else { + assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); + } +} + +pub(crate) fn test_l2_block_new_l2_block() { + // This test is aimed to cover potential issue + + let correct_first_block = L2BlockEnv { + number: 1, + timestamp: 1, + prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), + max_virtual_blocks_to_create: 1, + }; + + // Case 1: Block number increasing by more than 1 + test_new_l2_block::( + correct_first_block, + Some(3), + None, + None, + Some(Halt::FailedToSetL2Block( + "Invalid new L2 block number".to_string(), + )), + ); + + // Case 2: Timestamp not increasing + test_new_l2_block::( + correct_first_block, + None, + Some(1), + None, + Some(Halt::FailedToSetL2Block("The timestamp of the new L2 block must be greater than the timestamp of the previous L2 block".to_string())), + ); + + // Case 3: Incorrect previous block hash + test_new_l2_block::( + correct_first_block, + None, + None, + Some(H256::zero()), + Some(Halt::FailedToSetL2Block( + "The current L2 block hash is incorrect".to_string(), + )), + ); + + // Case 4: Correct new block + test_new_l2_block::(correct_first_block, None, None, None, None); +} + +#[allow(clippy::too_many_arguments)] +fn test_first_in_batch( + miniblock_timestamp: u64, + miniblock_number: u32, + pending_txs_hash: H256, + batch_timestamp: u64, + new_batch_timestamp: u64, + batch_number: u32, + proposed_block: L2BlockEnv, + expected_error: Option, +) { + let mut l1_batch = default_l1_batch(L1BatchNumber(1)); + l1_batch.number += 1; + l1_batch.timestamp = new_batch_timestamp; + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_l1_batch_env(l1_batch) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + let l1_tx = get_l1_noop(); + + // Setting the values provided. + let mut storage_ptr = vm.storage.borrow_mut(); + let miniblock_info_slot = StorageKey::new( + AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), + SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, + ); + let pending_txs_hash_slot = StorageKey::new( + AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), + SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, + ); + let batch_info_slot = StorageKey::new( + AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), + SYSTEM_CONTEXT_BLOCK_INFO_POSITION, + ); + let prev_block_hash_position = get_l2_block_hash_key(miniblock_number - 1); + + // FIXME: doesn't work for vm_latest + let storage = storage_ptr.inner_mut(); + storage.set_value( + miniblock_info_slot, + u256_to_h256(pack_block_info( + miniblock_number as u64, + miniblock_timestamp, + )), + ); + storage.set_value(pending_txs_hash_slot, pending_txs_hash); + storage.set_value( + batch_info_slot, + u256_to_h256(pack_block_info(batch_number as u64, batch_timestamp)), + ); + storage.set_value( + prev_block_hash_position, + L2BlockHasher::legacy_hash(L2BlockNumber(miniblock_number - 1)), + ); + drop(storage_ptr); + + // In order to skip checks from the Rust side of the VM, we firstly use some definitely correct L2 block info. + // And then override it with the user-provided value + + let last_l2_block = vm.l1_batch_env.first_l2_block; + let new_l2_block = L2BlockEnv { + number: last_l2_block.number + 1, + timestamp: last_l2_block.timestamp + 1, + prev_block_hash: vm.vm.last_l2_block_hash(), + max_virtual_blocks_to_create: last_l2_block.max_virtual_blocks_to_create, + }; + + vm.vm.push_l2_block_unchecked(new_l2_block); + vm.vm.push_transaction(l1_tx); + set_manual_l2_block_info(&mut vm.vm, 0, proposed_block); + + let result = vm.vm.execute(VmExecutionMode::OneTx); + if let Some(err) = expected_error { + assert_eq!(result.result, ExecutionResult::Halt { reason: err }); + } else { + assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); + } +} + +pub(crate) fn test_l2_block_first_in_batch() { + let prev_block_hash = L2BlockHasher::legacy_hash(L2BlockNumber(0)); + let prev_block_hash = L2BlockHasher::new(L2BlockNumber(1), 1, prev_block_hash) + .finalize(ProtocolVersionId::latest()); + test_first_in_batch::( + 1, + 1, + H256::zero(), + 1, + 2, + 1, + L2BlockEnv { + number: 2, + timestamp: 2, + prev_block_hash, + max_virtual_blocks_to_create: 1, + }, + None, + ); + + let prev_block_hash = L2BlockHasher::legacy_hash(L2BlockNumber(0)); + let prev_block_hash = L2BlockHasher::new(L2BlockNumber(1), 8, prev_block_hash) + .finalize(ProtocolVersionId::latest()); + test_first_in_batch::( + 8, + 1, + H256::zero(), + 5, + 12, + 1, + L2BlockEnv { + number: 2, + timestamp: 9, + prev_block_hash, + max_virtual_blocks_to_create: 1, + }, + Some(Halt::FailedToSetL2Block("The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch".to_string())), + ); +} + +fn set_manual_l2_block_info(vm: &mut impl TestedVm, tx_number: usize, block_info: L2BlockEnv) { + let fictive_miniblock_position = + TX_OPERATOR_L2_BLOCK_INFO_OFFSET + TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO * tx_number; + vm.write_to_bootloader_heap(&[ + (fictive_miniblock_position, block_info.number.into()), + (fictive_miniblock_position + 1, block_info.timestamp.into()), + ( + fictive_miniblock_position + 2, + h256_to_u256(block_info.prev_block_hash), + ), + ( + fictive_miniblock_position + 3, + block_info.max_virtual_blocks_to_create.into(), + ), + ]) +} diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 21755fc6fca..aaeadc34be5 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -27,7 +27,15 @@ pub(super) mod gas_limit; pub(super) mod get_used_contracts; pub(super) mod is_write_initial; pub(super) mod l1_tx_execution; +pub(super) mod l2_blocks; +pub(super) mod nonce_holder; +pub(super) mod precompiles; +pub(super) mod refunds; +pub(super) mod require_eip712; +pub(super) mod secp256r1; mod shadow; +pub(super) mod simple_execution; +pub(super) mod storage; mod tester; static BASE_SYSTEM_CONTRACTS: Lazy = @@ -68,6 +76,21 @@ fn read_proxy_counter_contract() -> (Vec, Contract) { (read_bytecode(PATH), load_contract(PATH)) } +fn read_nonce_holder_tester() -> Vec { + read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/custom-account/nonce-holder-test.sol/NonceHolderTest.json") +} + +fn read_expensive_contract() -> (Vec, Contract) { + const PATH: &str = + "etc/contracts-test-data/artifacts-zk/contracts/expensive/expensive.sol/Expensive.json"; + (read_bytecode(PATH), load_contract(PATH)) +} + +fn read_many_owners_custom_account_contract() -> (Vec, Contract) { + let path = "etc/contracts-test-data/artifacts-zk/contracts/custom-account/many-owners-custom-account.sol/ManyOwnersCustomAccount.json"; + (read_bytecode(path), load_contract(path)) +} + pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; let bootloader_code = read_yul_bytecode(artifacts_path, test); diff --git a/core/lib/multivm/src/versions/testonly/nonce_holder.rs b/core/lib/multivm/src/versions/testonly/nonce_holder.rs new file mode 100644 index 00000000000..2d46c293237 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/nonce_holder.rs @@ -0,0 +1,198 @@ +use zksync_test_account::Account; +use zksync_types::{Execute, ExecuteTransactionCommon, Nonce}; + +use super::{read_nonce_holder_tester, tester::VmTesterBuilder, ContractToDeploy, TestedVm}; +use crate::interface::{ + ExecutionResult, Halt, TxExecutionMode, TxRevertReason, VmExecutionMode, VmInterfaceExt, + VmRevertReason, +}; + +pub enum NonceHolderTestMode { + SetValueUnderNonce, + IncreaseMinNonceBy5, + IncreaseMinNonceTooMuch, + LeaveNonceUnused, + IncreaseMinNonceBy1, + SwitchToArbitraryOrdering, +} + +impl From for u8 { + fn from(mode: NonceHolderTestMode) -> u8 { + match mode { + NonceHolderTestMode::SetValueUnderNonce => 0, + NonceHolderTestMode::IncreaseMinNonceBy5 => 1, + NonceHolderTestMode::IncreaseMinNonceTooMuch => 2, + NonceHolderTestMode::LeaveNonceUnused => 3, + NonceHolderTestMode::IncreaseMinNonceBy1 => 4, + NonceHolderTestMode::SwitchToArbitraryOrdering => 5, + } + } +} + +fn run_nonce_test( + vm: &mut impl TestedVm, + account: &mut Account, + nonce: u32, + test_mode: NonceHolderTestMode, + error_message: Option, + comment: &'static str, +) { + vm.make_snapshot(); + let mut transaction = account.get_l2_tx_for_execute_with_nonce( + Execute { + contract_address: Some(account.address), + calldata: vec![12], + value: Default::default(), + factory_deps: vec![], + }, + None, + Nonce(nonce), + ); + let ExecuteTransactionCommon::L2(tx_data) = &mut transaction.common_data else { + unreachable!(); + }; + tx_data.signature = vec![test_mode.into()]; + vm.push_transaction(transaction); + let result = vm.execute(VmExecutionMode::OneTx); + + if let Some(msg) = error_message { + let expected_error = + TxRevertReason::Halt(Halt::ValidationFailed(VmRevertReason::General { + msg, + data: vec![], + })); + let ExecutionResult::Halt { reason } = &result.result else { + panic!("Expected revert, got {:?}", result.result); + }; + assert_eq!(reason.to_string(), expected_error.to_string(), "{comment}"); + vm.rollback_to_the_latest_snapshot(); + } else { + assert!(!result.result.is_failed(), "{}", comment); + vm.pop_snapshot_no_rollback(); + } +} + +pub(crate) fn test_nonce_holder() { + let mut account = Account::random(); + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_custom_contracts(vec![ContractToDeploy::account( + read_nonce_holder_tester(), + account.address, + )]) + .with_rich_accounts(vec![account.clone()]) + .build::(); + + // Test 1: trying to set value under non sequential nonce value. + run_nonce_test( + &mut vm.vm, + &mut account, + 1u32, + NonceHolderTestMode::SetValueUnderNonce, + Some("Previous nonce has not been used".to_string()), + "Allowed to set value under non sequential value", + ); + + // Test 2: increase min nonce by 1 with sequential nonce ordering: + run_nonce_test( + &mut vm.vm, + &mut account, + 0u32, + NonceHolderTestMode::IncreaseMinNonceBy1, + None, + "Failed to increment nonce by 1 for sequential account", + ); + + // Test 3: correctly set value under nonce with sequential nonce ordering: + run_nonce_test( + &mut vm.vm, + &mut account, + 1u32, + NonceHolderTestMode::SetValueUnderNonce, + None, + "Failed to set value under nonce sequential value", + ); + + // Test 5: migrate to the arbitrary nonce ordering: + run_nonce_test( + &mut vm.vm, + &mut account, + 2u32, + NonceHolderTestMode::SwitchToArbitraryOrdering, + None, + "Failed to switch to arbitrary ordering", + ); + + // Test 6: increase min nonce by 5 + run_nonce_test( + &mut vm.vm, + &mut account, + 6u32, + NonceHolderTestMode::IncreaseMinNonceBy5, + None, + "Failed to increase min nonce by 5", + ); + + // Test 7: since the nonces in range [6,10] are no longer allowed, the + // tx with nonce 10 should not be allowed + run_nonce_test( + &mut vm.vm, + &mut account, + 10u32, + NonceHolderTestMode::IncreaseMinNonceBy5, + Some("Reusing the same nonce twice".to_string()), + "Allowed to reuse nonce below the minimal one", + ); + + // Test 8: we should be able to use nonce 13 + run_nonce_test( + &mut vm.vm, + &mut account, + 13u32, + NonceHolderTestMode::SetValueUnderNonce, + None, + "Did not allow to use unused nonce 10", + ); + + // Test 9: we should not be able to reuse nonce 13 + run_nonce_test( + &mut vm.vm, + &mut account, + 13u32, + NonceHolderTestMode::IncreaseMinNonceBy5, + Some("Reusing the same nonce twice".to_string()), + "Allowed to reuse the same nonce twice", + ); + + // Test 10: we should be able to simply use nonce 14, while bumping the minimal nonce by 5 + run_nonce_test( + &mut vm.vm, + &mut account, + 14u32, + NonceHolderTestMode::IncreaseMinNonceBy5, + None, + "Did not allow to use a bumped nonce", + ); + + // Test 11: Do not allow bumping nonce by too much + run_nonce_test( + &mut vm.vm, + &mut account, + 16u32, + NonceHolderTestMode::IncreaseMinNonceTooMuch, + Some("The value for incrementing the nonce is too high".to_string()), + "Allowed for incrementing min nonce too much", + ); + + // Test 12: Do not allow not setting a nonce as used + run_nonce_test( + &mut vm.vm, + &mut account, + 16u32, + NonceHolderTestMode::LeaveNonceUnused, + Some("The nonce was not set as used".to_string()), + "Allowed to leave nonce as unused", + ); +} diff --git a/core/lib/multivm/src/versions/testonly/precompiles.rs b/core/lib/multivm/src/versions/testonly/precompiles.rs new file mode 100644 index 00000000000..4ab7d49ee88 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/precompiles.rs @@ -0,0 +1,113 @@ +use circuit_sequencer_api_1_5_0::geometry_config::get_geometry_config; +use zksync_types::{Address, Execute}; + +use super::{read_precompiles_contract, tester::VmTesterBuilder, TestedVm}; +use crate::{ + interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}, + versions::testonly::ContractToDeploy, + vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, +}; + +pub(crate) fn test_keccak() { + // Execute special transaction and check that at least 1000 keccak calls were made. + let contract = read_precompiles_contract(); + let address = Address::random(); + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) + .build::(); + + // calldata for `doKeccak(1000)`. + let keccak1000_calldata = + "370f20ac00000000000000000000000000000000000000000000000000000000000003e8"; + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(address), + calldata: hex::decode(keccak1000_calldata).unwrap(), + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + vm.vm.push_transaction(tx); + + let exec_result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); + + let keccak_count = exec_result.statistics.circuit_statistic.keccak256 + * get_geometry_config().cycles_per_keccak256_circuit as f32; + assert!(keccak_count >= 1000.0, "{keccak_count}"); +} + +pub(crate) fn test_sha256() { + // Execute special transaction and check that at least 1000 `sha256` calls were made. + let contract = read_precompiles_contract(); + let address = Address::random(); + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) + .build::(); + + // calldata for `doSha256(1000)`. + let sha1000_calldata = + "5d0b4fb500000000000000000000000000000000000000000000000000000000000003e8"; + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(address), + calldata: hex::decode(sha1000_calldata).unwrap(), + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + vm.vm.push_transaction(tx); + + let exec_result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); + + let sha_count = exec_result.statistics.circuit_statistic.sha256 + * get_geometry_config().cycles_per_sha256_circuit as f32; + assert!(sha_count >= 1000.0, "{sha_count}"); +} + +pub(crate) fn test_ecrecover() { + // Execute simple transfer and check that exactly 1 `ecrecover` call was made (it's done during tx validation). + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_random_rich_accounts(1) + .with_deployer() + .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .build::(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(account.address), + calldata: vec![], + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + vm.vm.push_transaction(tx); + + let exec_result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); + + let ecrecover_count = exec_result.statistics.circuit_statistic.ecrecover + * get_geometry_config().cycles_per_ecrecover_circuit as f32; + assert!((ecrecover_count - 1.0).abs() < 1e-4, "{ecrecover_count}"); +} diff --git a/core/lib/multivm/src/versions/testonly/refunds.rs b/core/lib/multivm/src/versions/testonly/refunds.rs new file mode 100644 index 00000000000..7b1a473fa5d --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/refunds.rs @@ -0,0 +1,213 @@ +use ethabi::Token; +use zksync_test_account::TxType; +use zksync_types::{Address, Execute, U256}; + +use super::{ + read_expensive_contract, read_test_contract, tester::VmTesterBuilder, ContractToDeploy, + TestedVm, +}; +use crate::interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +pub(crate) fn test_predetermined_refunded_gas() { + // In this test, we compare the execution of the bootloader with the predefined + // refunded gas and without them + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + let l1_batch = vm.l1_batch_env.clone(); + + let counter = read_test_contract(); + let account = &mut vm.rich_accounts[0]; + + let tx = account.get_deploy_tx(&counter, None, TxType::L2).tx; + vm.vm.push_transaction(tx.clone()); + let result = vm.vm.execute(VmExecutionMode::OneTx); + + assert!(!result.result.is_failed()); + + // If the refund provided by the operator or the final refund are the 0 + // there is no impact of the operator's refund at all and so this test does not + // make much sense. + assert!( + result.refunds.operator_suggested_refund > 0, + "The operator's refund is 0" + ); + assert!(result.refunds.gas_refunded > 0, "The final refund is 0"); + + let result_without_predefined_refunds = vm.vm.execute(VmExecutionMode::Batch); + let mut current_state_without_predefined_refunds = vm.vm.get_current_execution_state(); + assert!(!result_without_predefined_refunds.result.is_failed(),); + + // Here we want to provide the same refund from the operator and check that it's the correct one. + // We execute the whole block without refund tracer, because refund tracer will eventually override the provided refund. + // But the overall result should be the same + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_l1_batch_env(l1_batch.clone()) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(vec![account.clone()]) + .build::(); + + vm.vm + .push_transaction_with_refund(tx.clone(), result.refunds.gas_refunded); + + let result_with_predefined_refunds = vm.vm.execute(VmExecutionMode::Batch); + let mut current_state_with_predefined_refunds = vm.vm.get_current_execution_state(); + + assert!(!result_with_predefined_refunds.result.is_failed()); + + // We need to sort these lists as those are flattened from HashMaps + current_state_with_predefined_refunds + .used_contract_hashes + .sort(); + current_state_without_predefined_refunds + .used_contract_hashes + .sort(); + + assert_eq!( + current_state_with_predefined_refunds.events, + current_state_without_predefined_refunds.events + ); + + assert_eq!( + current_state_with_predefined_refunds.user_l2_to_l1_logs, + current_state_without_predefined_refunds.user_l2_to_l1_logs + ); + + assert_eq!( + current_state_with_predefined_refunds.system_logs, + current_state_without_predefined_refunds.system_logs + ); + + assert_eq!( + current_state_with_predefined_refunds.deduplicated_storage_logs, + current_state_without_predefined_refunds.deduplicated_storage_logs + ); + assert_eq!( + current_state_with_predefined_refunds.used_contract_hashes, + current_state_without_predefined_refunds.used_contract_hashes + ); + + // In this test we put the different refund from the operator. + // We still can't use the refund tracer, because it will override the refund. + // But we can check that the logs and events have changed. + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_l1_batch_env(l1_batch) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(vec![account.clone()]) + .build::(); + + let changed_operator_suggested_refund = result.refunds.gas_refunded + 1000; + vm.vm + .push_transaction_with_refund(tx, changed_operator_suggested_refund); + let result = vm.vm.execute(VmExecutionMode::Batch); + let mut current_state_with_changed_predefined_refunds = vm.vm.get_current_execution_state(); + + assert!(!result.result.is_failed()); + current_state_with_changed_predefined_refunds + .used_contract_hashes + .sort(); + current_state_without_predefined_refunds + .used_contract_hashes + .sort(); + + assert_eq!( + current_state_with_changed_predefined_refunds.events.len(), + current_state_without_predefined_refunds.events.len() + ); + + assert_ne!( + current_state_with_changed_predefined_refunds.events, + current_state_without_predefined_refunds.events + ); + + assert_eq!( + current_state_with_changed_predefined_refunds.user_l2_to_l1_logs, + current_state_without_predefined_refunds.user_l2_to_l1_logs + ); + + assert_ne!( + current_state_with_changed_predefined_refunds.system_logs, + current_state_without_predefined_refunds.system_logs + ); + + assert_eq!( + current_state_with_changed_predefined_refunds + .deduplicated_storage_logs + .len(), + current_state_without_predefined_refunds + .deduplicated_storage_logs + .len() + ); + + assert_ne!( + current_state_with_changed_predefined_refunds.deduplicated_storage_logs, + current_state_without_predefined_refunds.deduplicated_storage_logs + ); + assert_eq!( + current_state_with_changed_predefined_refunds.used_contract_hashes, + current_state_without_predefined_refunds.used_contract_hashes + ); +} + +pub(crate) fn test_negative_pubdata_for_transaction() { + let expensive_contract_address = Address::random(); + let (expensive_contract_bytecode, expensive_contract) = read_expensive_contract(); + let expensive_function = expensive_contract.function("expensive").unwrap(); + let cleanup_function = expensive_contract.function("cleanUp").unwrap(); + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ContractToDeploy::new( + expensive_contract_bytecode, + expensive_contract_address, + )]) + .build::(); + + let expensive_tx = vm.rich_accounts[0].get_l2_tx_for_execute( + Execute { + contract_address: Some(expensive_contract_address), + calldata: expensive_function + .encode_input(&[Token::Uint(10.into())]) + .unwrap(), + value: U256::zero(), + factory_deps: vec![], + }, + None, + ); + vm.vm.push_transaction(expensive_tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful: {result:#?}" + ); + + // This transaction cleans all initial writes in the contract, thus having negative `pubdata` impact. + let clean_up_tx = vm.rich_accounts[0].get_l2_tx_for_execute( + Execute { + contract_address: Some(expensive_contract_address), + calldata: cleanup_function.encode_input(&[]).unwrap(), + value: U256::zero(), + factory_deps: vec![], + }, + None, + ); + vm.vm.push_transaction(clean_up_tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful: {result:#?}" + ); + assert!(result.refunds.operator_suggested_refund > 0); + assert_eq!( + result.refunds.gas_refunded, + result.refunds.operator_suggested_refund + ); +} diff --git a/core/lib/multivm/src/versions/testonly/require_eip712.rs b/core/lib/multivm/src/versions/testonly/require_eip712.rs new file mode 100644 index 00000000000..a9a4fdd0604 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/require_eip712.rs @@ -0,0 +1,150 @@ +use ethabi::Token; +use zksync_eth_signer::TransactionParameters; +use zksync_test_account::Account; +use zksync_types::{ + fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, Eip712Domain, Execute, L2ChainId, + Nonce, Transaction, U256, +}; + +use super::{ + read_many_owners_custom_account_contract, tester::VmTesterBuilder, ContractToDeploy, TestedVm, +}; +use crate::interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +/// This test deploys 'buggy' account abstraction code, and then tries accessing it both with legacy +/// and EIP712 transactions. +/// Currently we support both, but in the future, we should allow only EIP712 transactions to access the AA accounts. +pub(crate) fn test_require_eip712() { + // Use 3 accounts: + // - `private_address` - EOA account, where we have the key + // - `account_address` - AA account, where the contract is deployed + // - beneficiary - an EOA account, where we'll try to transfer the tokens. + let account_abstraction = Account::random(); + let mut private_account = Account::random(); + let beneficiary = Account::random(); + + let (bytecode, contract) = read_many_owners_custom_account_contract(); + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_custom_contracts(vec![ContractToDeploy::account( + bytecode, + account_abstraction.address, + )]) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(vec![account_abstraction.clone(), private_account.clone()]) + .build::(); + + assert_eq!(vm.get_eth_balance(beneficiary.address), U256::from(0)); + + let chain_id: u32 = 270; + + // First, let's set the owners of the AA account to the `private_address`. + // (so that messages signed by `private_address`, are authorized to act on behalf of the AA account). + let set_owners_function = contract.function("setOwners").unwrap(); + let encoded_input = set_owners_function + .encode_input(&[Token::Array(vec![Token::Address(private_account.address)])]) + .unwrap(); + + let tx = private_account.get_l2_tx_for_execute( + Execute { + contract_address: Some(account_abstraction.address), + calldata: encoded_input, + value: Default::default(), + factory_deps: vec![], + }, + None, + ); + + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed()); + + let private_account_balance = vm.get_eth_balance(private_account.address); + + // And now let's do the transfer from the 'account abstraction' to 'beneficiary' (using 'legacy' transaction). + // Normally this would not work - unless the operator is malicious. + let aa_raw_tx = TransactionParameters { + nonce: U256::from(0), + to: Some(beneficiary.address), + gas: U256::from(100000000), + gas_price: Some(U256::from(10000000)), + value: U256::from(888000088), + data: vec![], + chain_id: 270, + transaction_type: None, + access_list: None, + max_fee_per_gas: U256::from(1000000000), + max_priority_fee_per_gas: U256::from(1000000000), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + }; + + let aa_tx = private_account.sign_legacy_tx(aa_raw_tx); + let (tx_request, hash) = TransactionRequest::from_bytes(&aa_tx, L2ChainId::from(270)).unwrap(); + + let mut l2_tx: L2Tx = L2Tx::from_request(tx_request, 10000, false).unwrap(); + l2_tx.set_input(aa_tx, hash); + // Pretend that operator is malicious and sets the initiator to the AA account. + l2_tx.common_data.initiator_address = account_abstraction.address; + let transaction: Transaction = l2_tx.into(); + + 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) + ); + // Make sure that the tokens were transferred from the AA account. + assert_eq!( + private_account_balance, + vm.get_eth_balance(private_account.address) + ); + + // // Now send the 'classic' EIP712 transaction + let tx_712 = L2Tx::new( + Some(beneficiary.address), + vec![], + Nonce(1), + Fee { + gas_limit: U256::from(1000000000), + max_fee_per_gas: U256::from(1000000000), + max_priority_fee_per_gas: U256::from(1000000000), + gas_per_pubdata_limit: U256::from(1000000000), + }, + account_abstraction.address, + U256::from(28374938), + vec![], + Default::default(), + ); + + let mut transaction_request: TransactionRequest = tx_712.into(); + transaction_request.chain_id = Some(chain_id.into()); + + let domain = Eip712Domain::new(L2ChainId::from(chain_id)); + let signature = private_account + .get_pk_signer() + .sign_typed_data(&domain, &transaction_request) + .unwrap(); + let encoded_tx = transaction_request.get_signed_bytes(&signature).unwrap(); + + let (aa_txn_request, aa_hash) = + TransactionRequest::from_bytes(&encoded_tx, L2ChainId::from(chain_id)).unwrap(); + + let mut l2_tx = L2Tx::from_request(aa_txn_request, 100000, false).unwrap(); + l2_tx.set_input(encoded_tx, aa_hash); + + let transaction: Transaction = l2_tx.into(); + vm.vm.push_transaction(transaction); + vm.vm.execute(VmExecutionMode::OneTx); + + assert_eq!( + vm.get_eth_balance(beneficiary.address), + U256::from(916375026) + ); + assert_eq!( + private_account_balance, + vm.get_eth_balance(private_account.address) + ); +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/sekp256r1.rs b/core/lib/multivm/src/versions/testonly/secp256r1.rs similarity index 92% rename from core/lib/multivm/src/versions/vm_fast/tests/sekp256r1.rs rename to core/lib/multivm/src/versions/testonly/secp256r1.rs index 55ca372c4a9..2401b5b9dd9 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/sekp256r1.rs +++ b/core/lib/multivm/src/versions/testonly/secp256r1.rs @@ -3,13 +3,10 @@ use zksync_system_constants::P256VERIFY_PRECOMPILE_ADDRESS; use zksync_types::{web3::keccak256, Execute, H256, U256}; use zksync_utils::h256_to_u256; -use crate::{ - interface::{ExecutionResult, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_fast::tests::tester::VmTesterBuilder, -}; +use super::{tester::VmTesterBuilder, TestedVm}; +use crate::interface::{ExecutionResult, TxExecutionMode, VmExecutionMode, VmInterfaceExt}; -#[test] -fn test_sekp256r1() { +pub(crate) fn test_secp256r1() { // 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() @@ -17,7 +14,7 @@ fn test_sekp256r1() { .with_execution_mode(TxExecutionMode::VerifyExecute) .with_execution_mode(TxExecutionMode::EthCall) .with_random_rich_accounts(1) - .build(); + .build::(); let account = &mut vm.rich_accounts[0]; diff --git a/core/lib/multivm/src/versions/testonly/simple_execution.rs b/core/lib/multivm/src/versions/testonly/simple_execution.rs new file mode 100644 index 00000000000..933022380be --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/simple_execution.rs @@ -0,0 +1,77 @@ +use assert_matches::assert_matches; +use zksync_test_account::TxType; + +use super::{tester::VmTesterBuilder, TestedVm}; +use crate::interface::{ExecutionResult, VmExecutionMode, VmInterfaceExt}; + +pub(crate) fn test_estimate_fee() { + let mut vm_tester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_deployer() + .with_random_rich_accounts(1) + .build::(); + + vm_tester.deploy_test_contract(); + let account = &mut vm_tester.rich_accounts[0]; + + let tx = account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + false, + Default::default(), + false, + TxType::L2, + ); + + vm_tester.vm.push_transaction(tx); + + let result = vm_tester.vm.execute(VmExecutionMode::OneTx); + assert_matches!(result.result, ExecutionResult::Success { .. }); +} + +pub(crate) fn test_simple_execute() { + let mut vm_tester = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_deployer() + .with_random_rich_accounts(1) + .build::(); + + vm_tester.deploy_test_contract(); + + let account = &mut vm_tester.rich_accounts[0]; + + let tx1 = account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + false, + Default::default(), + false, + TxType::L1 { serial_id: 1 }, + ); + + let tx2 = account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + true, + Default::default(), + false, + TxType::L1 { serial_id: 1 }, + ); + + let tx3 = account.get_test_contract_transaction( + vm_tester.test_contract.unwrap(), + false, + Default::default(), + false, + TxType::L1 { serial_id: 1 }, + ); + let vm = &mut vm_tester.vm; + vm.push_transaction(tx1); + vm.push_transaction(tx2); + vm.push_transaction(tx3); + let tx = vm.execute(VmExecutionMode::OneTx); + assert_matches!(tx.result, ExecutionResult::Success { .. }); + let tx = vm.execute(VmExecutionMode::OneTx); + assert_matches!(tx.result, ExecutionResult::Revert { .. }); + let tx = vm.execute(VmExecutionMode::OneTx); + assert_matches!(tx.result, ExecutionResult::Success { .. }); + let block_tip = vm.execute(VmExecutionMode::Batch); + assert_matches!(block_tip.result, ExecutionResult::Success { .. }); +} diff --git a/core/lib/multivm/src/versions/testonly/storage.rs b/core/lib/multivm/src/versions/testonly/storage.rs new file mode 100644 index 00000000000..86218675b5e --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/storage.rs @@ -0,0 +1,126 @@ +use ethabi::Token; +use zksync_contracts::{load_contract, read_bytecode}; +use zksync_types::{Address, Execute, U256}; + +use super::{tester::VmTesterBuilder, ContractToDeploy, TestedVm}; +use crate::interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +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() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ContractToDeploy::new(bytecode, test_contract_address)]) + .build::(); + + let account = &mut vm.rich_accounts[0]; + + let tx1 = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(test_contract_address), + calldata: first_tx_calldata, + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + + let tx2 = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(test_contract_address), + calldata: second_tx_calldata, + value: 0.into(), + factory_deps: vec![], + }, + None, + ); + + vm.vm.make_snapshot(); + vm.vm.push_transaction(tx1); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "First tx failed"); + vm.vm.pop_snapshot_no_rollback(); + + // We rollback once because transient storage and rollbacks are a tricky combination. + vm.vm.make_snapshot(); + vm.vm.push_transaction(tx2.clone()); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Second tx failed"); + vm.vm.rollback_to_the_latest_snapshot(); + + vm.vm.make_snapshot(); + vm.vm.push_transaction(tx2); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed(), "Second tx failed on second run"); + + result.statistics.pubdata_published +} + +fn test_storage_one_tx(second_tx_calldata: Vec) -> u32 { + test_storage::(vec![], second_tx_calldata) +} + +pub(crate) 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); +} + +pub(crate) 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/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index 46007f02272..5270043dfe1 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -3,8 +3,9 @@ use std::collections::HashSet; use zksync_contracts::BaseSystemContracts; use zksync_test_account::{Account, TxType}; use zksync_types::{ - block::L2BlockHasher, utils::deployed_address_create, writes::StateDiffRecord, Address, - L1BatchNumber, L2BlockNumber, Nonce, StorageKey, H256, U256, + utils::{deployed_address_create, storage_key_for_eth_balance}, + writes::StateDiffRecord, + Address, L1BatchNumber, StorageKey, Transaction, H256, U256, }; use zksync_vm_interface::{ CurrentExecutionState, VmExecutionResultAndLogs, VmInterfaceHistoryEnabled, @@ -14,13 +15,12 @@ use super::{get_empty_storage, read_test_contract}; use crate::{ interface::{ storage::{InMemoryStorage, StoragePtr, StorageView}, - L1BatchEnv, L2Block, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmFactory, + L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmFactory, VmInterfaceExt, }, versions::testonly::{ default_l1_batch, default_system_env, make_account_rich, ContractToDeploy, }, - vm_latest::utils::l2_blocks::load_last_l2_block, }; //mod transaction_test_info; FIXME @@ -38,10 +38,7 @@ pub(crate) struct VmTester { pub(crate) custom_contracts: Vec, } -impl VmTester -where - VM: VmFactory>, -{ +impl VmTester { pub(crate) fn deploy_test_contract(&mut self) { let contract = read_test_contract(); let tx = self @@ -58,52 +55,8 @@ where self.test_contract = Some(deployed_address); } - pub(crate) fn reset_with_empty_storage(&mut self) { - self.storage = StorageView::new(get_empty_storage()).to_rc_ptr(); - self.reset_state(false); - } - - /// Reset the state of the VM to the initial state. - /// If `use_latest_l2_block` is true, then the VM will use the latest L2 block from storage, - /// otherwise it will use the first L2 block of l1 batch env - pub(crate) fn reset_state(&mut self, use_latest_l2_block: bool) { - for account in self.rich_accounts.iter_mut() { - account.nonce = Nonce(0); - make_account_rich(self.storage.borrow_mut().inner_mut(), account); - } - if let Some(deployer) = &self.deployer { - make_account_rich(self.storage.borrow_mut().inner_mut(), deployer); - } - - if !self.custom_contracts.is_empty() { - println!("Inserting custom contracts is not yet supported") - // `insert_contracts(&mut self.storage, &self.custom_contracts);` - } - - let l1_batch = &mut self.l1_batch_env; - if use_latest_l2_block { - let last_l2_block = load_last_l2_block(&self.storage).unwrap_or(L2Block { - number: 0, - timestamp: 0, - hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - }); - l1_batch.first_l2_block = L2BlockEnv { - number: last_l2_block.number + 1, - timestamp: std::cmp::max(last_l2_block.timestamp + 1, l1_batch.timestamp), - prev_block_hash: last_l2_block.hash, - max_virtual_blocks_to_create: 1, - }; - } - - let vm = VM::new( - l1_batch.clone(), - self.system_env.clone(), - self.storage.clone(), - ); - if self.test_contract.is_some() { - self.deploy_test_contract(); - } - self.vm = vm; + pub(crate) fn get_eth_balance(&mut self, address: Address) -> U256 { + self.vm.read_storage(storage_key_for_eth_balance(&address)) } } @@ -260,7 +213,30 @@ pub(crate) trait TestedVm: /// Returns `true` iff the decommit is fresh. fn manually_decommit(&mut self, code_hash: H256) -> bool; - fn verify_required_bootloader_memory(&self, cells: &[(u32, U256)]); + // FIXME: u32 -> usize + fn verify_required_bootloader_heap(&self, cells: &[(u32, U256)]); + + fn write_to_bootloader_heap(&mut self, cells: &[(usize, U256)]); + + /// Reads storage accounting for changes made during the VM run. + fn read_storage(&mut self, key: StorageKey) -> U256; + + fn verify_required_storage(&mut self, cells: &[(StorageKey, U256)]) { + for &(key, expected_value) in cells { + assert_eq!( + self.read_storage(key), + expected_value, + "Unexpected storage value at {key:?}" + ); + } + } + + /// Returns the current hash of the latest L2 block. + fn last_l2_block_hash(&self) -> H256; + + /// Same as `start_new_l2_block`, but should skip consistency checks (to verify they are performed by the bootloader). + fn push_l2_block_unchecked(&mut self, block: L2BlockEnv); - fn verify_required_storage(&mut self, cells: &[(StorageKey, H256)]); + /// Pushes a transaction with predefined refund value. + fn push_transaction_with_refund(&mut self, tx: Transaction, refund: u64); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/l2_blocks.rs b/core/lib/multivm/src/versions/vm_fast/tests/l2_blocks.rs index fde94d9da6c..0823bee6cc9 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/l2_blocks.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/l2_blocks.rs @@ -1,424 +1,33 @@ -//! -//! Tests for the bootloader -//! The description for each of the tests can be found in the corresponding `.yul` file. -//! - -use zksync_system_constants::REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE; -use zksync_types::{ - block::{pack_block_info, L2BlockHasher}, - AccountTreeId, Execute, ExecuteTransactionCommon, L1BatchNumber, L1TxCommonData, L2BlockNumber, - ProtocolVersionId, StorageKey, Transaction, H160, H256, SYSTEM_CONTEXT_ADDRESS, - SYSTEM_CONTEXT_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, U256, -}; -use zksync_utils::{h256_to_u256, u256_to_h256}; - use crate::{ - interface::{ - storage::ReadStorage, ExecutionResult, Halt, L2BlockEnv, TxExecutionMode, VmExecutionMode, - VmInterface, VmInterfaceExt, - }, - versions::testonly::default_l1_batch, - vm_fast::{tests::tester::VmTesterBuilder, vm::Vm}, - vm_latest::{ - constants::{TX_OPERATOR_L2_BLOCK_INFO_OFFSET, TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO}, - utils::l2_blocks::get_l2_block_hash_key, + versions::testonly::l2_blocks::{ + test_l2_block_first_in_batch, test_l2_block_initialization_number_non_zero, + test_l2_block_initialization_timestamp, test_l2_block_new_l2_block, + test_l2_block_same_l2_block, }, + vm_fast::Vm, }; -fn get_l1_noop() -> Transaction { - Transaction { - common_data: ExecuteTransactionCommon::L1(L1TxCommonData { - sender: H160::random(), - gas_limit: U256::from(2000000u32), - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - ..Default::default() - }), - execute: Execute { - contract_address: Some(H160::zero()), - calldata: vec![], - value: U256::zero(), - factory_deps: vec![], - }, - received_timestamp_ms: 0, - raw_bytes: None, - } -} - #[test] -fn test_l2_block_initialization_timestamp() { - // This test checks that the L2 block initialization works correctly. - // Here we check that that the first block must have timestamp that is greater or equal to the timestamp - // of the current batch. - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - // Override the timestamp of the current L2 block to be 0. - vm.vm.bootloader_state.push_l2_block(L2BlockEnv { - number: 1, - timestamp: 0, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - }); - let l1_tx = get_l1_noop(); - - vm.vm.push_transaction(l1_tx); - let res = vm.vm.execute(VmExecutionMode::OneTx); - - assert_eq!( - res.result, - ExecutionResult::Halt {reason: Halt::FailedToSetL2Block("The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch".to_string())} - ); +fn l2_block_initialization_timestamp() { + test_l2_block_initialization_timestamp::>(); } #[test] -fn test_l2_block_initialization_number_non_zero() { - // This test checks that the L2 block initialization works correctly. - // Here we check that the first L2 block number can not be zero. - - let l1_batch = default_l1_batch(L1BatchNumber(1)); - let first_l2_block = L2BlockEnv { - number: 0, - timestamp: l1_batch.timestamp, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - }; - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_l1_batch_env(l1_batch) - .with_random_rich_accounts(1) - .build(); - - let l1_tx = get_l1_noop(); - - vm.vm.push_transaction(l1_tx); - - set_manual_l2_block_info(&mut vm.vm, 0, first_l2_block); - - let res = vm.vm.execute(VmExecutionMode::OneTx); - - assert_eq!( - res.result, - ExecutionResult::Halt { - reason: Halt::FailedToSetL2Block( - "L2 block number is never expected to be zero".to_string() - ) - } - ); -} - -fn test_same_l2_block( - expected_error: Option, - override_timestamp: Option, - override_prev_block_hash: Option, -) { - let mut l1_batch = default_l1_batch(L1BatchNumber(1)); - l1_batch.timestamp = 1; - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_l1_batch_env(l1_batch) - .with_random_rich_accounts(1) - .build(); - - let l1_tx = get_l1_noop(); - vm.vm.push_transaction(l1_tx.clone()); - let res = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!res.result.is_failed()); - - let mut current_l2_block = vm.vm.batch_env.first_l2_block; - - if let Some(timestamp) = override_timestamp { - current_l2_block.timestamp = timestamp; - } - if let Some(prev_block_hash) = override_prev_block_hash { - current_l2_block.prev_block_hash = prev_block_hash; - } - - if (None, None) == (override_timestamp, override_prev_block_hash) { - current_l2_block.max_virtual_blocks_to_create = 0; - } - - vm.vm.push_transaction(l1_tx); - set_manual_l2_block_info(&mut vm.vm, 1, current_l2_block); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - - if let Some(err) = expected_error { - assert_eq!(result.result, ExecutionResult::Halt { reason: err }); - } else { - assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); - } +fn l2_block_initialization_number_non_zero() { + test_l2_block_initialization_number_non_zero::>(); } #[test] -fn test_l2_block_same_l2_block() { - // This test aims to test the case when there are multiple transactions inside the same L2 block. - - // Case 1: Incorrect timestamp - test_same_l2_block( - Some(Halt::FailedToSetL2Block( - "The timestamp of the same L2 block must be same".to_string(), - )), - Some(0), - None, - ); - - // Case 2: Incorrect previous block hash - test_same_l2_block( - Some(Halt::FailedToSetL2Block( - "The previous hash of the same L2 block must be same".to_string(), - )), - None, - Some(H256::zero()), - ); - - // Case 3: Correct continuation of the same L2 block - test_same_l2_block(None, None, None); -} - -fn test_new_l2_block( - first_l2_block: L2BlockEnv, - overriden_second_block_number: Option, - overriden_second_block_timestamp: Option, - overriden_second_block_prev_block_hash: Option, - expected_error: Option, -) { - let mut l1_batch = default_l1_batch(L1BatchNumber(1)); - l1_batch.timestamp = 1; - l1_batch.first_l2_block = first_l2_block; - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let l1_tx = get_l1_noop(); - - // Firstly we execute the first transaction - vm.vm.push_transaction(l1_tx.clone()); - vm.vm.execute(VmExecutionMode::OneTx); - - let mut second_l2_block = vm.vm.batch_env.first_l2_block; - second_l2_block.number += 1; - second_l2_block.timestamp += 1; - second_l2_block.prev_block_hash = vm.vm.bootloader_state.last_l2_block().get_hash(); - - if let Some(block_number) = overriden_second_block_number { - second_l2_block.number = block_number; - } - if let Some(timestamp) = overriden_second_block_timestamp { - second_l2_block.timestamp = timestamp; - } - if let Some(prev_block_hash) = overriden_second_block_prev_block_hash { - second_l2_block.prev_block_hash = prev_block_hash; - } - - vm.vm.bootloader_state.push_l2_block(second_l2_block); - - vm.vm.push_transaction(l1_tx); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - if let Some(err) = expected_error { - assert_eq!(result.result, ExecutionResult::Halt { reason: err }); - } else { - assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); - } +fn l2_block_same_l2_block() { + test_l2_block_same_l2_block::>(); } #[test] -fn test_l2_block_new_l2_block() { - // This test is aimed to cover potential issue - - let correct_first_block = L2BlockEnv { - number: 1, - timestamp: 1, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - }; - - // Case 1: Block number increasing by more than 1 - test_new_l2_block( - correct_first_block, - Some(3), - None, - None, - Some(Halt::FailedToSetL2Block( - "Invalid new L2 block number".to_string(), - )), - ); - - // Case 2: Timestamp not increasing - test_new_l2_block( - correct_first_block, - None, - Some(1), - None, - Some(Halt::FailedToSetL2Block("The timestamp of the new L2 block must be greater than the timestamp of the previous L2 block".to_string())), - ); - - // Case 3: Incorrect previous block hash - test_new_l2_block( - correct_first_block, - None, - None, - Some(H256::zero()), - Some(Halt::FailedToSetL2Block( - "The current L2 block hash is incorrect".to_string(), - )), - ); - - // Case 4: Correct new block - test_new_l2_block(correct_first_block, None, None, None, None); -} - -#[allow(clippy::too_many_arguments)] -fn test_first_in_batch( - miniblock_timestamp: u64, - miniblock_number: u32, - pending_txs_hash: H256, - batch_timestamp: u64, - new_batch_timestamp: u64, - batch_number: u32, - proposed_block: L2BlockEnv, - expected_error: Option, -) { - let mut l1_batch = default_l1_batch(L1BatchNumber(1)); - l1_batch.number += 1; - l1_batch.timestamp = new_batch_timestamp; - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - let l1_tx = get_l1_noop(); - - // Setting the values provided. - let mut storage_ptr = vm.vm.world.storage.borrow_mut(); - let miniblock_info_slot = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - ); - let pending_txs_hash_slot = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, - ); - let batch_info_slot = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_BLOCK_INFO_POSITION, - ); - let prev_block_hash_position = get_l2_block_hash_key(miniblock_number - 1); - - storage_ptr.set_value( - miniblock_info_slot, - u256_to_h256(pack_block_info( - miniblock_number as u64, - miniblock_timestamp, - )), - ); - storage_ptr.set_value(pending_txs_hash_slot, pending_txs_hash); - storage_ptr.set_value( - batch_info_slot, - u256_to_h256(pack_block_info(batch_number as u64, batch_timestamp)), - ); - storage_ptr.set_value( - prev_block_hash_position, - L2BlockHasher::legacy_hash(L2BlockNumber(miniblock_number - 1)), - ); - drop(storage_ptr); - - // In order to skip checks from the Rust side of the VM, we firstly use some definitely correct L2 block info. - // And then override it with the user-provided value - - let last_l2_block = vm.vm.bootloader_state.last_l2_block(); - let new_l2_block = L2BlockEnv { - number: last_l2_block.number + 1, - timestamp: last_l2_block.timestamp + 1, - prev_block_hash: last_l2_block.get_hash(), - max_virtual_blocks_to_create: last_l2_block.max_virtual_blocks_to_create, - }; - - vm.vm.bootloader_state.push_l2_block(new_l2_block); - vm.vm.push_transaction(l1_tx); - set_manual_l2_block_info(&mut vm.vm, 0, proposed_block); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - if let Some(err) = expected_error { - assert_eq!(result.result, ExecutionResult::Halt { reason: err }); - } else { - assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); - } +fn l2_block_new_l2_block() { + test_l2_block_new_l2_block::>(); } #[test] -fn test_l2_block_first_in_batch() { - let prev_block_hash = L2BlockHasher::legacy_hash(L2BlockNumber(0)); - let prev_block_hash = L2BlockHasher::new(L2BlockNumber(1), 1, prev_block_hash) - .finalize(ProtocolVersionId::latest()); - test_first_in_batch( - 1, - 1, - H256::zero(), - 1, - 2, - 1, - L2BlockEnv { - number: 2, - timestamp: 2, - prev_block_hash, - max_virtual_blocks_to_create: 1, - }, - None, - ); - - let prev_block_hash = L2BlockHasher::legacy_hash(L2BlockNumber(0)); - let prev_block_hash = L2BlockHasher::new(L2BlockNumber(1), 8, prev_block_hash) - .finalize(ProtocolVersionId::latest()); - test_first_in_batch( - 8, - 1, - H256::zero(), - 5, - 12, - 1, - L2BlockEnv { - number: 2, - timestamp: 9, - prev_block_hash, - max_virtual_blocks_to_create: 1, - }, - Some(Halt::FailedToSetL2Block("The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch".to_string())), - ); -} - -fn set_manual_l2_block_info( - vm: &mut Vm, - tx_number: usize, - block_info: L2BlockEnv, -) { - let fictive_miniblock_position = - TX_OPERATOR_L2_BLOCK_INFO_OFFSET + TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO * tx_number; - - vm.write_to_bootloader_heap([ - (fictive_miniblock_position, block_info.number.into()), - (fictive_miniblock_position + 1, block_info.timestamp.into()), - ( - fictive_miniblock_position + 2, - h256_to_u256(block_info.prev_block_hash), - ), - ( - fictive_miniblock_position + 3, - block_info.max_virtual_blocks_to_create.into(), - ), - ]) +fn l2_block_first_in_batch() { + test_l2_block_first_in_batch::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index 436df210006..6648b4ae94b 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -1,11 +1,11 @@ use std::collections::HashSet; -use zksync_types::{writes::StateDiffRecord, StorageKey, H256, U256}; -use zksync_utils::{h256_to_u256, u256_to_h256}; +use zksync_types::{writes::StateDiffRecord, StorageKey, Transaction, H256, U256}; +use zksync_utils::h256_to_u256; use zksync_vm2::interface::{HeapId, StateInterface}; use zksync_vm_interface::{ - storage::ReadStorage, CurrentExecutionState, VmExecutionMode, VmExecutionResultAndLogs, - VmInterfaceExt, + storage::ReadStorage, CurrentExecutionState, L2BlockEnv, VmExecutionMode, + VmExecutionResultAndLogs, VmInterfaceExt, }; use super::Vm; @@ -25,18 +25,18 @@ mod gas_limit; mod get_used_contracts; mod is_write_initial; mod l1_tx_execution; -/* -// mod call_tracer; FIXME: requires tracers mod l2_blocks; mod nonce_holder; mod precompiles; -// mod prestate_tracer; FIXME: is pre-state tracer still relevant? mod refunds; mod require_eip712; -mod rollbacks; -mod sekp256r1; +mod secp256r1; mod simple_execution; mod storage; +/* +// mod call_tracer; FIXME: requires tracers +// mod prestate_tracer; FIXME: is pre-state tracer still relevant? +mod rollbacks; mod tester; mod tracing_execution_error; mod transfer; @@ -83,28 +83,35 @@ impl TestedVm for Vm> { is_fresh } - fn verify_required_bootloader_memory(&self, required_values: &[(u32, U256)]) { + fn verify_required_bootloader_heap(&self, required_values: &[(u32, U256)]) { for &(slot, expected_value) in required_values { let current_value = self.inner.read_heap_u256(HeapId::FIRST, slot * 32); assert_eq!(current_value, expected_value); } } - fn verify_required_storage(&mut self, cells: &[(StorageKey, H256)]) { + fn write_to_bootloader_heap(&mut self, cells: &[(usize, U256)]) { + self.write_to_bootloader_heap(cells.iter().copied()); + } + + fn read_storage(&mut self, key: StorageKey) -> U256 { let storage_changes = self.inner.world_diff().get_storage_state(); let main_storage = &mut self.world.storage; + storage_changes + .get(&(*key.account().address(), h256_to_u256(*key.key()))) + .copied() + .unwrap_or_else(|| h256_to_u256(main_storage.read_value(&key))) + } - for &(key, required_value) in cells { - let current_value = storage_changes - .get(&(*key.account().address(), h256_to_u256(*key.key()))) - .copied() - .unwrap_or_else(|| h256_to_u256(main_storage.read_value(&key))); - - assert_eq!( - u256_to_h256(current_value), - required_value, - "Invalid value at key {key:?}" - ); - } + fn last_l2_block_hash(&self) -> H256 { + self.bootloader_state.last_l2_block().get_hash() + } + + fn push_l2_block_unchecked(&mut self, block: L2BlockEnv) { + self.bootloader_state.push_l2_block(block); + } + + fn push_transaction_with_refund(&mut self, tx: Transaction, refund: u64) { + self.push_transaction_inner(tx, refund, true); } } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/nonce_holder.rs b/core/lib/multivm/src/versions/vm_fast/tests/nonce_holder.rs index f72e95da9f8..438d6aabe55 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/nonce_holder.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/nonce_holder.rs @@ -1,179 +1,6 @@ -use zksync_types::{Execute, ExecuteTransactionCommon, Nonce}; - -use crate::{ - interface::{ - ExecutionResult, Halt, TxExecutionMode, TxRevertReason, VmExecutionMode, VmInterfaceExt, - VmRevertReason, - }, - versions::testonly::ContractToDeploy, - vm_fast::tests::{ - tester::{Account, VmTesterBuilder}, - utils::read_nonce_holder_tester, - }, -}; - -pub enum NonceHolderTestMode { - SetValueUnderNonce, - IncreaseMinNonceBy5, - IncreaseMinNonceTooMuch, - LeaveNonceUnused, - IncreaseMinNonceBy1, - SwitchToArbitraryOrdering, -} - -impl From for u8 { - fn from(mode: NonceHolderTestMode) -> u8 { - match mode { - NonceHolderTestMode::SetValueUnderNonce => 0, - NonceHolderTestMode::IncreaseMinNonceBy5 => 1, - NonceHolderTestMode::IncreaseMinNonceTooMuch => 2, - NonceHolderTestMode::LeaveNonceUnused => 3, - NonceHolderTestMode::IncreaseMinNonceBy1 => 4, - NonceHolderTestMode::SwitchToArbitraryOrdering => 5, - } - } -} +use crate::{versions::testonly::nonce_holder::test_nonce_holder, vm_fast::Vm}; #[test] -fn test_nonce_holder() { - let mut account = Account::random(); - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_custom_contracts(vec![ContractToDeploy::account( - read_nonce_holder_tester().to_vec(), - account.address, - )]) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let mut run_nonce_test = |nonce: u32, - test_mode: NonceHolderTestMode, - error_message: Option, - comment: &'static str| { - // In this test we have to reset VM state after each test case. Because once bootloader failed during the validation of the transaction, - // it will fail again and again. At the same time we have to keep the same storage, because we want to keep the nonce holder contract state. - // The easiest way in terms of lifetimes is to reuse `vm_builder` to achieve it. - vm.reset_state(true); - let mut transaction = account.get_l2_tx_for_execute_with_nonce( - Execute { - contract_address: Some(account.address), - calldata: vec![12], - value: Default::default(), - factory_deps: vec![], - }, - None, - Nonce(nonce), - ); - let ExecuteTransactionCommon::L2(tx_data) = &mut transaction.common_data else { - unreachable!(); - }; - tx_data.signature = vec![test_mode.into()]; - vm.vm.push_transaction_inner(transaction, 0, true); - let result = vm.vm.execute(VmExecutionMode::OneTx); - - if let Some(msg) = error_message { - let expected_error = - TxRevertReason::Halt(Halt::ValidationFailed(VmRevertReason::General { - msg, - data: vec![], - })); - let ExecutionResult::Halt { reason } = result.result else { - panic!("Expected revert, got {:?}", result.result); - }; - assert_eq!(reason.to_string(), expected_error.to_string(), "{comment}"); - } else { - assert!(!result.result.is_failed(), "{comment}: {result:?}"); - } - }; - // Test 1: trying to set value under non sequential nonce value. - run_nonce_test( - 1u32, - NonceHolderTestMode::SetValueUnderNonce, - Some("Previous nonce has not been used".to_string()), - "Allowed to set value under non sequential value", - ); - - // Test 2: increase min nonce by 1 with sequential nonce ordering: - run_nonce_test( - 0u32, - NonceHolderTestMode::IncreaseMinNonceBy1, - None, - "Failed to increment nonce by 1 for sequential account", - ); - - // Test 3: correctly set value under nonce with sequential nonce ordering: - run_nonce_test( - 1u32, - NonceHolderTestMode::SetValueUnderNonce, - None, - "Failed to set value under nonce sequential value", - ); - - // Test 5: migrate to the arbitrary nonce ordering: - run_nonce_test( - 2u32, - NonceHolderTestMode::SwitchToArbitraryOrdering, - None, - "Failed to switch to arbitrary ordering", - ); - - // Test 6: increase min nonce by 5 - run_nonce_test( - 6u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - None, - "Failed to increase min nonce by 5", - ); - - // Test 7: since the nonces in range [6,10] are no longer allowed, the - // tx with nonce 10 should not be allowed - run_nonce_test( - 10u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - Some("Reusing the same nonce twice".to_string()), - "Allowed to reuse nonce below the minimal one", - ); - - // Test 8: we should be able to use nonce 13 - run_nonce_test( - 13u32, - NonceHolderTestMode::SetValueUnderNonce, - None, - "Did not allow to use unused nonce 10", - ); - - // Test 9: we should not be able to reuse nonce 13 - run_nonce_test( - 13u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - Some("Reusing the same nonce twice".to_string()), - "Allowed to reuse the same nonce twice", - ); - - // Test 10: we should be able to simply use nonce 14, while bumping the minimal nonce by 5 - run_nonce_test( - 14u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - None, - "Did not allow to use a bumped nonce", - ); - - // Test 11: Do not allow bumping nonce by too much - run_nonce_test( - 16u32, - NonceHolderTestMode::IncreaseMinNonceTooMuch, - Some("The value for incrementing the nonce is too high".to_string()), - "Allowed for incrementing min nonce too much", - ); - - // Test 12: Do not allow not setting a nonce as used - run_nonce_test( - 16u32, - NonceHolderTestMode::LeaveNonceUnused, - Some("The nonce was not set as used".to_string()), - "Allowed to leave nonce as unused", - ); +fn nonce_holder() { + test_nonce_holder::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs b/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs index b3ca1596217..ccf1463979c 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/precompiles.rs @@ -1,116 +1,19 @@ -use circuit_sequencer_api_1_5_0::geometry_config::get_geometry_config; -use zksync_types::{Address, Execute}; - -use super::{tester::VmTesterBuilder, utils::read_precompiles_contract}; use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - versions::testonly::ContractToDeploy, - vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, + versions::testonly::precompiles::{test_ecrecover, test_keccak, test_sha256}, + vm_fast::Vm, }; #[test] -fn test_keccak() { - // Execute special transaction and check that at least 1000 keccak calls were made. - let contract = read_precompiles_contract(); - let address = Address::random(); - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) - .build(); - - // calldata for `doKeccak(1000)`. - let keccak1000_calldata = - "370f20ac00000000000000000000000000000000000000000000000000000000000003e8"; - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: hex::decode(keccak1000_calldata).unwrap(), - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - - let exec_result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - - let keccak_count = exec_result.statistics.circuit_statistic.keccak256 - * get_geometry_config().cycles_per_keccak256_circuit as f32; - assert!(keccak_count >= 1000.0, "{keccak_count}"); +fn keccak() { + test_keccak::>(); } #[test] -fn test_sha256() { - // Execute special transaction and check that at least 1000 `sha256` calls were made. - let contract = read_precompiles_contract(); - let address = Address::random(); - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) - .build(); - - // calldata for `doSha256(1000)`. - let sha1000_calldata = - "5d0b4fb500000000000000000000000000000000000000000000000000000000000003e8"; - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: hex::decode(sha1000_calldata).unwrap(), - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - - let exec_result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - - let sha_count = exec_result.statistics.circuit_statistic.sha256 - * get_geometry_config().cycles_per_sha256_circuit as f32; - assert!(sha_count >= 1000.0, "{sha_count}"); +fn sha256() { + test_sha256::>(); } #[test] -fn test_ecrecover() { - // Execute simple transfer and check that exactly 1 `ecrecover` call was made (it's done during tx validation). - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(account.address), - calldata: vec![], - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - - let exec_result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!exec_result.result.is_failed(), "{exec_result:#?}"); - - let ecrecover_count = exec_result.statistics.circuit_statistic.ecrecover - * get_geometry_config().cycles_per_ecrecover_circuit as f32; - assert!((ecrecover_count - 1.0).abs() < 1e-4, "{ecrecover_count}"); +fn ecrecover() { + test_ecrecover::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/prestate_tracer.rs b/core/lib/multivm/src/versions/vm_fast/tests/prestate_tracer.rs deleted file mode 100644 index 63620c7d9ff..00000000000 --- a/core/lib/multivm/src/versions/vm_fast/tests/prestate_tracer.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::sync::Arc; - -use once_cell::sync::OnceCell; -use zksync_test_account::TxType; -use zksync_types::{utils::deployed_address_create, Execute, U256}; - -use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface}, - tracers::PrestateTracer, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - tests::{tester::VmTesterBuilder, utils::read_simple_transfer_contract}, - HistoryEnabled, ToTracerPointer, - }, -}; - -#[test] -fn test_prestate_tracer() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - vm.deploy_test_contract(); - let account = &mut vm.rich_accounts[0]; - - let tx1 = account.get_test_contract_transaction( - vm.test_contract.unwrap(), - false, - Default::default(), - true, - TxType::L2, - ); - vm.vm.push_transaction(tx1); - - let contract_address = vm.test_contract.unwrap(); - let prestate_tracer_result = Arc::new(OnceCell::default()); - let prestate_tracer = PrestateTracer::new(false, prestate_tracer_result.clone()); - let tracer_ptr = prestate_tracer.into_tracer_pointer(); - vm.vm.inspect(tracer_ptr.into(), VmExecutionMode::Batch); - - let prestate_result = Arc::try_unwrap(prestate_tracer_result) - .unwrap() - .take() - .unwrap_or_default(); - - assert!(prestate_result.1.contains_key(&contract_address)); -} - -#[test] -fn test_prestate_tracer_diff_mode() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - let contract = read_simple_transfer_contract(); - let tx = vm - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; - let nonce = tx.nonce().unwrap().0.into(); - vm.vm.push_transaction(tx); - vm.vm.execute(VmExecutionMode::OneTx); - let deployed_address = deployed_address_create(vm.deployer.as_ref().unwrap().address, nonce); - vm.test_contract = Some(deployed_address); - - // Deploy a second copy of the contract to see its appearance in the pre-state - let tx2 = vm - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; - let nonce2 = tx2.nonce().unwrap().0.into(); - vm.vm.push_transaction(tx2); - vm.vm.execute(VmExecutionMode::OneTx); - let deployed_address2 = deployed_address_create(vm.deployer.as_ref().unwrap().address, nonce2); - - let account = &mut vm.rich_accounts[0]; - - //enter ether to contract to see difference in the balance post execution - let tx0 = Execute { - contract_address: vm.test_contract.unwrap(), - calldata: Default::default(), - value: U256::from(100000), - factory_deps: None, - }; - - vm.vm - .push_transaction(account.get_l2_tx_for_execute(tx0.clone(), None)); - - let tx1 = Execute { - contract_address: deployed_address2, - calldata: Default::default(), - value: U256::from(200000), - factory_deps: None, - }; - - vm.vm - .push_transaction(account.get_l2_tx_for_execute(tx1, None)); - let prestate_tracer_result = Arc::new(OnceCell::default()); - let prestate_tracer = PrestateTracer::new(true, prestate_tracer_result.clone()); - let tracer_ptr = prestate_tracer.into_tracer_pointer(); - vm.vm - .inspect(tracer_ptr.into(), VmExecutionMode::Bootloader); - - let prestate_result = Arc::try_unwrap(prestate_tracer_result) - .unwrap() - .take() - .unwrap_or_default(); - - //assert that the pre-state contains both deployed contracts with balance zero - assert!(prestate_result.0.contains_key(&deployed_address)); - assert!(prestate_result.0.contains_key(&deployed_address2)); - assert_eq!( - prestate_result.0[&deployed_address].balance, - Some(U256::zero()) - ); - assert_eq!( - prestate_result.0[&deployed_address2].balance, - Some(U256::zero()) - ); - - //assert that the post-state contains both deployed contracts with the correct balance - assert!(prestate_result.1.contains_key(&deployed_address)); - assert!(prestate_result.1.contains_key(&deployed_address2)); - assert_eq!( - prestate_result.1[&deployed_address].balance, - Some(U256::from(100000)) - ); - assert_eq!( - prestate_result.1[&deployed_address2].balance, - Some(U256::from(200000)) - ); -} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/refunds.rs b/core/lib/multivm/src/versions/vm_fast/tests/refunds.rs index 1856995149a..335cb4afb1c 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/refunds.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/refunds.rs @@ -1,221 +1,16 @@ -use ethabi::Token; -use zksync_types::{Address, Execute, U256}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - versions::testonly::ContractToDeploy, - vm_fast::tests::{ - tester::{DeployContractsTx, TxType, VmTesterBuilder}, - utils::{read_expensive_contract, read_test_contract}, + versions::testonly::refunds::{ + test_negative_pubdata_for_transaction, test_predetermined_refunded_gas, }, + vm_fast::Vm, }; #[test] -fn test_predetermined_refunded_gas() { - // In this test, we compare the execution of the bootloader with the predefined - // refunded gas and without them - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - let l1_batch = vm.vm.batch_env.clone(); - - let counter = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - - let DeployContractsTx { - tx, - bytecode_hash: _, - address: _, - } = account.get_deploy_tx(&counter, None, TxType::L2); - vm.vm.push_transaction(tx.clone()); - let result = vm.vm.execute(VmExecutionMode::OneTx); - - assert!(!result.result.is_failed()); - - // If the refund provided by the operator or the final refund are the 0 - // there is no impact of the operator's refund at all and so this test does not - // make much sense. - assert!( - result.refunds.operator_suggested_refund > 0, - "The operator's refund is 0" - ); - assert!(result.refunds.gas_refunded > 0, "The final refund is 0"); - - let result_without_predefined_refunds = vm.vm.execute(VmExecutionMode::Batch); - let mut current_state_without_predefined_refunds = vm.vm.get_current_execution_state(); - assert!(!result_without_predefined_refunds.result.is_failed(),); - - // Here we want to provide the same refund from the operator and check that it's the correct one. - // We execute the whole block without refund tracer, because refund tracer will eventually override the provided refund. - // But the overall result should be the same - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch.clone()) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) - .build(); - - vm.vm - .push_transaction_inner(tx.clone(), result.refunds.gas_refunded, true); - - let result_with_predefined_refunds = vm.vm.execute(VmExecutionMode::Batch); - let mut current_state_with_predefined_refunds = vm.vm.get_current_execution_state(); - - assert!(!result_with_predefined_refunds.result.is_failed()); - - // We need to sort these lists as those are flattened from HashMaps - current_state_with_predefined_refunds - .used_contract_hashes - .sort(); - current_state_without_predefined_refunds - .used_contract_hashes - .sort(); - - assert_eq!( - current_state_with_predefined_refunds.events, - current_state_without_predefined_refunds.events - ); - - assert_eq!( - current_state_with_predefined_refunds.user_l2_to_l1_logs, - current_state_without_predefined_refunds.user_l2_to_l1_logs - ); - - assert_eq!( - current_state_with_predefined_refunds.system_logs, - current_state_without_predefined_refunds.system_logs - ); - - assert_eq!( - current_state_with_predefined_refunds.deduplicated_storage_logs, - current_state_without_predefined_refunds.deduplicated_storage_logs - ); - assert_eq!( - current_state_with_predefined_refunds.used_contract_hashes, - current_state_without_predefined_refunds.used_contract_hashes - ); - - // In this test we put the different refund from the operator. - // We still can't use the refund tracer, because it will override the refund. - // But we can check that the logs and events have changed. - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let changed_operator_suggested_refund = result.refunds.gas_refunded + 1000; - vm.vm - .push_transaction_inner(tx, changed_operator_suggested_refund, true); - let result = vm.vm.execute(VmExecutionMode::Batch); - let mut current_state_with_changed_predefined_refunds = vm.vm.get_current_execution_state(); - - assert!(!result.result.is_failed()); - current_state_with_changed_predefined_refunds - .used_contract_hashes - .sort(); - current_state_without_predefined_refunds - .used_contract_hashes - .sort(); - - assert_eq!( - current_state_with_changed_predefined_refunds.events.len(), - current_state_without_predefined_refunds.events.len() - ); - - assert_ne!( - current_state_with_changed_predefined_refunds.events, - current_state_without_predefined_refunds.events - ); - - assert_eq!( - current_state_with_changed_predefined_refunds.user_l2_to_l1_logs, - current_state_without_predefined_refunds.user_l2_to_l1_logs - ); - - assert_ne!( - current_state_with_changed_predefined_refunds.system_logs, - current_state_without_predefined_refunds.system_logs - ); - - assert_eq!( - current_state_with_changed_predefined_refunds - .deduplicated_storage_logs - .len(), - current_state_without_predefined_refunds - .deduplicated_storage_logs - .len() - ); - - assert_ne!( - current_state_with_changed_predefined_refunds.deduplicated_storage_logs, - current_state_without_predefined_refunds.deduplicated_storage_logs - ); - assert_eq!( - current_state_with_changed_predefined_refunds.used_contract_hashes, - current_state_without_predefined_refunds.used_contract_hashes - ); +fn predetermined_refunded_gas() { + test_predetermined_refunded_gas::>(); } #[test] fn negative_pubdata_for_transaction() { - let expensive_contract_address = Address::random(); - let (expensive_contract_bytecode, expensive_contract) = read_expensive_contract(); - let expensive_function = expensive_contract.function("expensive").unwrap(); - let cleanup_function = expensive_contract.function("cleanUp").unwrap(); - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ContractToDeploy::new( - expensive_contract_bytecode, - expensive_contract_address, - )]) - .build(); - - let expensive_tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Some(expensive_contract_address), - calldata: expensive_function - .encode_input(&[Token::Uint(10.into())]) - .unwrap(), - value: U256::zero(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(expensive_tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - - // This transaction cleans all initial writes in the contract, thus having negative `pubdata` impact. - let clean_up_tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Some(expensive_contract_address), - calldata: cleanup_function.encode_input(&[]).unwrap(), - value: U256::zero(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(clean_up_tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - assert!(result.refunds.operator_suggested_refund > 0); - assert_eq!( - result.refunds.gas_refunded, - result.refunds.operator_suggested_refund - ); + test_negative_pubdata_for_transaction::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs b/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs index b4448683cf7..22e4ebf258c 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/require_eip712.rs @@ -1,175 +1,6 @@ -use ethabi::Token; -use zksync_eth_signer::TransactionParameters; -use zksync_system_constants::L2_BASE_TOKEN_ADDRESS; -use zksync_types::{ - fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, - utils::storage_key_for_standard_token_balance, AccountTreeId, Address, Eip712Domain, Execute, - L2ChainId, Nonce, Transaction, U256, -}; -use zksync_utils::h256_to_u256; +use crate::{versions::testonly::require_eip712::test_require_eip712, vm_fast::Vm}; -use crate::{ - interface::{ - storage::ReadStorage, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, - }, - versions::testonly::ContractToDeploy, - vm_fast::tests::{ - tester::{Account, VmTester, VmTesterBuilder}, - utils::read_many_owners_custom_account_contract, - }, -}; - -impl VmTester<()> { - pub(crate) fn get_eth_balance(&mut self, address: Address) -> U256 { - let key = storage_key_for_standard_token_balance( - AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), - &address, - ); - self.vm - .inner - .world_diff() - .get_storage_state() - .get(&(L2_BASE_TOKEN_ADDRESS, h256_to_u256(*key.key()))) - .copied() - .unwrap_or_else(|| h256_to_u256(self.vm.world.storage.read_value(&key))) - } -} - -/// This test deploys 'buggy' account abstraction code, and then tries accessing it both with legacy -/// and EIP712 transactions. -/// Currently we support both, but in the future, we should allow only EIP712 transactions to access the AA accounts. #[test] -fn test_require_eip712() { - // Use 3 accounts: - // - `private_address` - EOA account, where we have the key - // - `account_address` - AA account, where the contract is deployed - // - beneficiary - an EOA account, where we'll try to transfer the tokens. - let account_abstraction = Account::random(); - let mut private_account = Account::random(); - let beneficiary = Account::random(); - - let (bytecode, contract) = read_many_owners_custom_account_contract(); - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_custom_contracts(vec![ContractToDeploy::account( - bytecode, - account_abstraction.address, - )]) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account_abstraction.clone(), private_account.clone()]) - .build(); - - assert_eq!(vm.get_eth_balance(beneficiary.address), U256::from(0)); - - let chain_id: u32 = 270; - - // First, let's set the owners of the AA account to the `private_address`. - // (so that messages signed by `private_address`, are authorized to act on behalf of the AA account). - let set_owners_function = contract.function("setOwners").unwrap(); - let encoded_input = set_owners_function - .encode_input(&[Token::Array(vec![Token::Address(private_account.address)])]) - .unwrap(); - - let tx = private_account.get_l2_tx_for_execute( - Execute { - contract_address: Some(account_abstraction.address), - calldata: encoded_input, - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed()); - - let private_account_balance = vm.get_eth_balance(private_account.address); - - // And now let's do the transfer from the 'account abstraction' to 'beneficiary' (using 'legacy' transaction). - // Normally this would not work - unless the operator is malicious. - let aa_raw_tx = TransactionParameters { - nonce: U256::from(0), - to: Some(beneficiary.address), - gas: U256::from(100000000), - gas_price: Some(U256::from(10000000)), - value: U256::from(888000088), - data: vec![], - chain_id: 270, - transaction_type: None, - access_list: None, - max_fee_per_gas: U256::from(1000000000), - max_priority_fee_per_gas: U256::from(1000000000), - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - }; - - let aa_tx = private_account.sign_legacy_tx(aa_raw_tx); - let (tx_request, hash) = TransactionRequest::from_bytes(&aa_tx, L2ChainId::from(270)).unwrap(); - - let mut l2_tx: L2Tx = L2Tx::from_request(tx_request, 10000, false).unwrap(); - l2_tx.set_input(aa_tx, hash); - // Pretend that operator is malicious and sets the initiator to the AA account. - l2_tx.common_data.initiator_address = account_abstraction.address; - let transaction: Transaction = l2_tx.into(); - - 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) - ); - // Make sure that the tokens were transferred from the AA account. - assert_eq!( - private_account_balance, - vm.get_eth_balance(private_account.address) - ); - - // // Now send the 'classic' EIP712 transaction - let tx_712 = L2Tx::new( - Some(beneficiary.address), - vec![], - Nonce(1), - Fee { - gas_limit: U256::from(1000000000), - max_fee_per_gas: U256::from(1000000000), - max_priority_fee_per_gas: U256::from(1000000000), - gas_per_pubdata_limit: U256::from(1000000000), - }, - account_abstraction.address, - U256::from(28374938), - vec![], - Default::default(), - ); - - let mut transaction_request: TransactionRequest = tx_712.into(); - transaction_request.chain_id = Some(chain_id.into()); - - let domain = Eip712Domain::new(L2ChainId::from(chain_id)); - let signature = private_account - .get_pk_signer() - .sign_typed_data(&domain, &transaction_request) - .unwrap(); - let encoded_tx = transaction_request.get_signed_bytes(&signature).unwrap(); - - let (aa_txn_request, aa_hash) = - TransactionRequest::from_bytes(&encoded_tx, L2ChainId::from(chain_id)).unwrap(); - - let mut l2_tx = L2Tx::from_request(aa_txn_request, 100000, false).unwrap(); - l2_tx.set_input(encoded_tx, aa_hash); - - let transaction: Transaction = l2_tx.into(); - vm.vm.push_transaction(transaction); - vm.vm.execute(VmExecutionMode::OneTx); - - assert_eq!( - vm.get_eth_balance(beneficiary.address), - U256::from(916375026) - ); - assert_eq!( - private_account_balance, - vm.get_eth_balance(private_account.address) - ); +fn require_eip712() { + test_require_eip712::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/secp256r1.rs b/core/lib/multivm/src/versions/vm_fast/tests/secp256r1.rs new file mode 100644 index 00000000000..d9661c7f713 --- /dev/null +++ b/core/lib/multivm/src/versions/vm_fast/tests/secp256r1.rs @@ -0,0 +1,6 @@ +use crate::{versions::testonly::secp256r1::test_secp256r1, vm_fast::Vm}; + +#[test] +fn secp256r1() { + test_secp256r1::>(); +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs b/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs index 8c916a541e2..4fe33d237e9 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/simple_execution.rs @@ -1,80 +1,14 @@ -use assert_matches::assert_matches; - use crate::{ - interface::{ExecutionResult, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_fast::tests::tester::{TxType, VmTesterBuilder}, + versions::testonly::simple_execution::{test_estimate_fee, test_simple_execute}, + vm_fast::Vm, }; #[test] fn estimate_fee() { - let mut vm_tester = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_deployer() - .with_random_rich_accounts(1) - .build(); - - vm_tester.deploy_test_contract(); - let account = &mut vm_tester.rich_accounts[0]; - - let tx = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - false, - Default::default(), - false, - TxType::L2, - ); - - vm_tester.vm.push_transaction(tx); - - let result = vm_tester.vm.execute(VmExecutionMode::OneTx); - assert_matches!(result.result, ExecutionResult::Success { .. }); + test_estimate_fee::>(); } #[test] fn simple_execute() { - let mut vm_tester = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_deployer() - .with_random_rich_accounts(1) - .build(); - - vm_tester.deploy_test_contract(); - - let account = &mut vm_tester.rich_accounts[0]; - - let tx1 = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - false, - Default::default(), - false, - TxType::L1 { serial_id: 1 }, - ); - - let tx2 = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - true, - Default::default(), - false, - TxType::L1 { serial_id: 1 }, - ); - - let tx3 = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - false, - Default::default(), - false, - TxType::L1 { serial_id: 1 }, - ); - let vm = &mut vm_tester.vm; - vm.push_transaction(tx1); - vm.push_transaction(tx2); - vm.push_transaction(tx3); - let tx = vm.execute(VmExecutionMode::OneTx); - assert_matches!(tx.result, ExecutionResult::Success { .. }); - let tx = vm.execute(VmExecutionMode::OneTx); - assert_matches!(tx.result, ExecutionResult::Revert { .. }); - let tx = vm.execute(VmExecutionMode::OneTx); - assert_matches!(tx.result, ExecutionResult::Success { .. }); - let block_tip = vm.execute(VmExecutionMode::Batch); - assert_matches!(block_tip.result, ExecutionResult::Success { .. }); + test_simple_execute::>(); } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/storage.rs b/core/lib/multivm/src/versions/vm_fast/tests/storage.rs index 2cfadb640e7..54a38814d3b 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/storage.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/storage.rs @@ -1,133 +1,14 @@ -use ethabi::Token; -use zksync_contracts::{load_contract, read_bytecode}; -use zksync_types::{Address, Execute, U256}; - use crate::{ - interface::{ - TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, - }, - versions::testonly::ContractToDeploy, - vm_fast::tests::tester::VmTesterBuilder, + versions::testonly::storage::{test_storage_behavior, test_transient_storage_behavior}, + vm_fast::Vm, }; -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() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ContractToDeploy::new(bytecode, test_contract_address)]) - .build(); - - let account = &mut vm.rich_accounts[0]; - - let tx1 = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(test_contract_address), - calldata: first_tx_calldata, - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - - let tx2 = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(test_contract_address), - calldata: second_tx_calldata, - value: 0.into(), - factory_deps: vec![], - }, - None, - ); - - vm.vm.make_snapshot(); - vm.vm.push_transaction(tx1); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "First tx failed"); - vm.vm.pop_snapshot_no_rollback(); - - // We rollback once because transient storage and rollbacks are a tricky combination. - vm.vm.make_snapshot(); - vm.vm.push_transaction(tx2.clone()); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "Second tx failed"); - vm.vm.rollback_to_the_latest_snapshot(); - - vm.vm.make_snapshot(); - vm.vm.push_transaction(tx2); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed(), "Second tx failed on second run"); - - 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); +fn storage_behavior() { + test_storage_behavior::>(); } #[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); +fn transient_storage_behavior() { + test_transient_storage_behavior::>(); } 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 1b5c3db59f7..82003b4a6ab 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 @@ -1,433 +1,33 @@ -//! -//! Tests for the bootloader -//! The description for each of the tests can be found in the corresponding `.yul` file. -//! - -use zk_evm_1_5_0::aux_structures::Timestamp; -use zksync_system_constants::REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE; -use zksync_types::{ - block::{pack_block_info, L2BlockHasher}, - AccountTreeId, Execute, ExecuteTransactionCommon, L1BatchNumber, L1TxCommonData, L2BlockNumber, - ProtocolVersionId, StorageKey, Transaction, H160, H256, SYSTEM_CONTEXT_ADDRESS, - SYSTEM_CONTEXT_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, U256, -}; -use zksync_utils::{h256_to_u256, u256_to_h256}; - use crate::{ - interface::{ - storage::WriteStorage, ExecutionResult, Halt, L2BlockEnv, TxExecutionMode, VmExecutionMode, - VmInterface, VmInterfaceExt, - }, - vm_latest::{ - constants::{ - BOOTLOADER_HEAP_PAGE, TX_OPERATOR_L2_BLOCK_INFO_OFFSET, - TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO, - }, - tests::tester::{default_l1_batch, VmTesterBuilder}, - utils::l2_blocks::get_l2_block_hash_key, - HistoryEnabled, Vm, + versions::testonly::l2_blocks::{ + test_l2_block_first_in_batch, test_l2_block_initialization_number_non_zero, + test_l2_block_initialization_timestamp, test_l2_block_new_l2_block, + test_l2_block_same_l2_block, }, - HistoryMode, + vm_latest::{HistoryEnabled, Vm}, }; -fn get_l1_noop() -> Transaction { - Transaction { - common_data: ExecuteTransactionCommon::L1(L1TxCommonData { - sender: H160::random(), - gas_limit: U256::from(2000000u32), - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - ..Default::default() - }), - execute: Execute::default(), - received_timestamp_ms: 0, - raw_bytes: None, - } -} - #[test] -fn test_l2_block_initialization_timestamp() { - // This test checks that the L2 block initialization works correctly. - // Here we check that that the first block must have timestamp that is greater or equal to the timestamp - // of the current batch. - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - // Override the timestamp of the current miniblock to be 0. - vm.vm.bootloader_state.push_l2_block(L2BlockEnv { - number: 1, - timestamp: 0, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - }); - let l1_tx = get_l1_noop(); - - vm.vm.push_transaction(l1_tx); - let res = vm.vm.execute(VmExecutionMode::OneTx); - - assert_eq!( - res.result, - ExecutionResult::Halt {reason: Halt::FailedToSetL2Block("The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch".to_string())} - ); +fn l2_block_initialization_timestamp() { + test_l2_block_initialization_timestamp::>(); } #[test] -fn test_l2_block_initialization_number_non_zero() { - // This test checks that the L2 block initialization works correctly. - // Here we check that the first miniblock number can not be zero. - - let l1_batch = default_l1_batch(L1BatchNumber(1)); - let first_l2_block = L2BlockEnv { - number: 0, - timestamp: l1_batch.timestamp, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - }; - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_l1_batch_env(l1_batch) - .with_random_rich_accounts(1) - .build(); - - let l1_tx = get_l1_noop(); - - vm.vm.push_transaction(l1_tx); - - let timestamp = Timestamp(vm.vm.state.local_state.timestamp); - set_manual_l2_block_info(&mut vm.vm, 0, first_l2_block, timestamp); - - let res = vm.vm.execute(VmExecutionMode::OneTx); - - assert_eq!( - res.result, - ExecutionResult::Halt { - reason: Halt::FailedToSetL2Block( - "L2 block number is never expected to be zero".to_string() - ) - } - ); -} - -fn test_same_l2_block( - expected_error: Option, - override_timestamp: Option, - override_prev_block_hash: Option, -) { - let mut l1_batch = default_l1_batch(L1BatchNumber(1)); - l1_batch.timestamp = 1; - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_l1_batch_env(l1_batch) - .with_random_rich_accounts(1) - .build(); - - let l1_tx = get_l1_noop(); - vm.vm.push_transaction(l1_tx.clone()); - let res = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!res.result.is_failed()); - - let mut current_l2_block = vm.vm.batch_env.first_l2_block; - - if let Some(timestamp) = override_timestamp { - current_l2_block.timestamp = timestamp; - } - if let Some(prev_block_hash) = override_prev_block_hash { - current_l2_block.prev_block_hash = prev_block_hash; - } - - if (None, None) == (override_timestamp, override_prev_block_hash) { - current_l2_block.max_virtual_blocks_to_create = 0; - } - - vm.vm.push_transaction(l1_tx); - let timestamp = Timestamp(vm.vm.state.local_state.timestamp); - set_manual_l2_block_info(&mut vm.vm, 1, current_l2_block, timestamp); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - - if let Some(err) = expected_error { - assert_eq!(result.result, ExecutionResult::Halt { reason: err }); - } else { - assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); - } +fn l2_block_initialization_number_non_zero() { + test_l2_block_initialization_number_non_zero::>(); } #[test] -fn test_l2_block_same_l2_block() { - // This test aims to test the case when there are multiple transactions inside the same L2 block. - - // Case 1: Incorrect timestamp - test_same_l2_block( - Some(Halt::FailedToSetL2Block( - "The timestamp of the same L2 block must be same".to_string(), - )), - Some(0), - None, - ); - - // Case 2: Incorrect previous block hash - test_same_l2_block( - Some(Halt::FailedToSetL2Block( - "The previous hash of the same L2 block must be same".to_string(), - )), - None, - Some(H256::zero()), - ); - - // Case 3: Correct continuation of the same L2 block - test_same_l2_block(None, None, None); -} - -fn test_new_l2_block( - first_l2_block: L2BlockEnv, - overriden_second_block_number: Option, - overriden_second_block_timestamp: Option, - overriden_second_block_prev_block_hash: Option, - expected_error: Option, -) { - let mut l1_batch = default_l1_batch(L1BatchNumber(1)); - l1_batch.timestamp = 1; - l1_batch.first_l2_block = first_l2_block; - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let l1_tx = get_l1_noop(); - - // Firstly we execute the first transaction - vm.vm.push_transaction(l1_tx.clone()); - vm.vm.execute(VmExecutionMode::OneTx); - - let mut second_l2_block = vm.vm.batch_env.first_l2_block; - second_l2_block.number += 1; - second_l2_block.timestamp += 1; - second_l2_block.prev_block_hash = vm.vm.bootloader_state.last_l2_block().get_hash(); - - if let Some(block_number) = overriden_second_block_number { - second_l2_block.number = block_number; - } - if let Some(timestamp) = overriden_second_block_timestamp { - second_l2_block.timestamp = timestamp; - } - if let Some(prev_block_hash) = overriden_second_block_prev_block_hash { - second_l2_block.prev_block_hash = prev_block_hash; - } - - vm.vm.bootloader_state.push_l2_block(second_l2_block); - - vm.vm.push_transaction(l1_tx); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - if let Some(err) = expected_error { - assert_eq!(result.result, ExecutionResult::Halt { reason: err }); - } else { - assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); - } +fn l2_block_same_l2_block() { + test_l2_block_same_l2_block::>(); } #[test] -fn test_l2_block_new_l2_block() { - // This test is aimed to cover potential issue - - let correct_first_block = L2BlockEnv { - number: 1, - timestamp: 1, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 1, - }; - - // Case 1: Block number increasing by more than 1 - test_new_l2_block( - correct_first_block, - Some(3), - None, - None, - Some(Halt::FailedToSetL2Block( - "Invalid new L2 block number".to_string(), - )), - ); - - // Case 2: Timestamp not increasing - test_new_l2_block( - correct_first_block, - None, - Some(1), - None, - Some(Halt::FailedToSetL2Block("The timestamp of the new L2 block must be greater than the timestamp of the previous L2 block".to_string())), - ); - - // Case 3: Incorrect previous block hash - test_new_l2_block( - correct_first_block, - None, - None, - Some(H256::zero()), - Some(Halt::FailedToSetL2Block( - "The current L2 block hash is incorrect".to_string(), - )), - ); - - // Case 4: Correct new block - test_new_l2_block(correct_first_block, None, None, None, None); -} - -#[allow(clippy::too_many_arguments)] -fn test_first_in_batch( - miniblock_timestamp: u64, - miniblock_number: u32, - pending_txs_hash: H256, - batch_timestamp: u64, - new_batch_timestamp: u64, - batch_number: u32, - proposed_block: L2BlockEnv, - expected_error: Option, -) { - let mut l1_batch = default_l1_batch(L1BatchNumber(1)); - l1_batch.number += 1; - l1_batch.timestamp = new_batch_timestamp; - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - let l1_tx = get_l1_noop(); - - // Setting the values provided. - let storage_ptr = vm.vm.state.storage.storage.get_ptr(); - let miniblock_info_slot = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, - ); - let pending_txs_hash_slot = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, - ); - let batch_info_slot = StorageKey::new( - AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), - SYSTEM_CONTEXT_BLOCK_INFO_POSITION, - ); - let prev_block_hash_position = get_l2_block_hash_key(miniblock_number - 1); - - storage_ptr.borrow_mut().set_value( - miniblock_info_slot, - u256_to_h256(pack_block_info( - miniblock_number as u64, - miniblock_timestamp, - )), - ); - storage_ptr - .borrow_mut() - .set_value(pending_txs_hash_slot, pending_txs_hash); - storage_ptr.borrow_mut().set_value( - batch_info_slot, - u256_to_h256(pack_block_info(batch_number as u64, batch_timestamp)), - ); - storage_ptr.borrow_mut().set_value( - prev_block_hash_position, - L2BlockHasher::legacy_hash(L2BlockNumber(miniblock_number - 1)), - ); - - // In order to skip checks from the Rust side of the VM, we firstly use some definitely correct L2 block info. - // And then override it with the user-provided value - - let last_l2_block = vm.vm.bootloader_state.last_l2_block(); - let new_l2_block = L2BlockEnv { - number: last_l2_block.number + 1, - timestamp: last_l2_block.timestamp + 1, - prev_block_hash: last_l2_block.get_hash(), - max_virtual_blocks_to_create: last_l2_block.max_virtual_blocks_to_create, - }; - - vm.vm.bootloader_state.push_l2_block(new_l2_block); - vm.vm.push_transaction(l1_tx); - let timestamp = Timestamp(vm.vm.state.local_state.timestamp); - set_manual_l2_block_info(&mut vm.vm, 0, proposed_block, timestamp); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - if let Some(err) = expected_error { - assert_eq!(result.result, ExecutionResult::Halt { reason: err }); - } else { - assert_eq!(result.result, ExecutionResult::Success { output: vec![] }); - } +fn l2_block_new_l2_block() { + test_l2_block_new_l2_block::>(); } #[test] -fn test_l2_block_first_in_batch() { - let prev_block_hash = L2BlockHasher::legacy_hash(L2BlockNumber(0)); - let prev_block_hash = L2BlockHasher::new(L2BlockNumber(1), 1, prev_block_hash) - .finalize(ProtocolVersionId::latest()); - test_first_in_batch( - 1, - 1, - H256::zero(), - 1, - 2, - 1, - L2BlockEnv { - number: 2, - timestamp: 2, - prev_block_hash, - max_virtual_blocks_to_create: 1, - }, - None, - ); - - let prev_block_hash = L2BlockHasher::legacy_hash(L2BlockNumber(0)); - let prev_block_hash = L2BlockHasher::new(L2BlockNumber(1), 8, prev_block_hash) - .finalize(ProtocolVersionId::latest()); - test_first_in_batch( - 8, - 1, - H256::zero(), - 5, - 12, - 1, - L2BlockEnv { - number: 2, - timestamp: 9, - prev_block_hash, - max_virtual_blocks_to_create: 1, - }, - Some(Halt::FailedToSetL2Block("The timestamp of the L2 block must be greater than or equal to the timestamp of the current batch".to_string())), - ); -} - -fn set_manual_l2_block_info( - vm: &mut Vm, - tx_number: usize, - block_info: L2BlockEnv, - timestamp: Timestamp, -) { - let fictive_miniblock_position = - TX_OPERATOR_L2_BLOCK_INFO_OFFSET + TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO * tx_number; - - vm.state.memory.populate_page( - BOOTLOADER_HEAP_PAGE as usize, - vec![ - (fictive_miniblock_position, block_info.number.into()), - (fictive_miniblock_position + 1, block_info.timestamp.into()), - ( - fictive_miniblock_position + 2, - h256_to_u256(block_info.prev_block_hash), - ), - ( - fictive_miniblock_position + 3, - block_info.max_virtual_blocks_to_create.into(), - ), - ], - timestamp, - ) +fn l2_block_first_in_batch() { + test_l2_block_first_in_batch::>(); } 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 44e272caca8..f0e30ac3d8a 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -4,15 +4,20 @@ use zk_evm_1_5_0::{ aux_structures::{MemoryPage, Timestamp}, zkevm_opcode_defs::{ContractCodeSha256Format, VersionedHashLen32}, }; -use zksync_types::{writes::StateDiffRecord, StorageKey, H256, U256}; -use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; -use zksync_vm_interface::{CurrentExecutionState, VmExecutionMode, VmExecutionResultAndLogs}; +use zksync_types::{writes::StateDiffRecord, StorageKey, Transaction, H256, U256}; +use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256}; +use zksync_vm_interface::{ + CurrentExecutionState, L2BlockEnv, VmExecutionMode, VmExecutionResultAndLogs, +}; use super::{HistoryEnabled, Vm}; use crate::{ interface::storage::{InMemoryStorage, StorageView}, versions::testonly::TestedVm, - vm_latest::{constants::BOOTLOADER_HEAP_PAGE, tracers::PubdataTracer, TracerDispatcher}, + vm_latest::{ + constants::BOOTLOADER_HEAP_PAGE, tracers::PubdataTracer, types::internals::TransactionData, + TracerDispatcher, + }, }; mod bootloader; @@ -38,7 +43,7 @@ mod prestate_tracer; mod refunds; mod require_eip712; mod rollbacks; -mod sekp256r1; +mod secp256r1; mod simple_execution; mod storage; mod tester; @@ -115,7 +120,7 @@ impl TestedVm for Vm, HistoryEnabled> { query.is_fresh } - fn verify_required_bootloader_memory(&self, cells: &[(u32, U256)]) { + fn verify_required_bootloader_heap(&self, cells: &[(u32, U256)]) { for &(slot, required_value) in cells { let current_value = self .state @@ -126,14 +131,28 @@ impl TestedVm for Vm, HistoryEnabled> { } } - fn verify_required_storage(&mut self, cells: &[(StorageKey, H256)]) { - for &(key, required_value) in cells { - let current_value = self.state.storage.storage.read_from_storage(&key); - assert_eq!( - u256_to_h256(current_value), - required_value, - "Invalid value at key {key:?}" - ); - } + fn write_to_bootloader_heap(&mut self, cells: &[(usize, U256)]) { + let timestamp = Timestamp(self.state.local_state.timestamp); + self.state + .memory + .populate_page(BOOTLOADER_HEAP_PAGE as usize, cells.to_vec(), timestamp) + } + + fn read_storage(&mut self, key: StorageKey) -> U256 { + self.state.storage.storage.read_from_storage(&key) + } + + fn last_l2_block_hash(&self) -> H256 { + self.bootloader_state.last_l2_block().get_hash() + } + + fn push_l2_block_unchecked(&mut self, block: L2BlockEnv) { + self.bootloader_state.push_l2_block(block); + } + + fn push_transaction_with_refund(&mut self, tx: Transaction, refund: u64) { + let tx = TransactionData::new(tx, false); + let overhead = tx.overhead_gas(); + self.push_raw_transaction(tx, overhead, refund, true) } } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs b/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs index 6be49367d39..c7ea3242d4a 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/nonce_holder.rs @@ -1,186 +1,9 @@ -use zksync_types::{Execute, Nonce}; - use crate::{ - interface::{ - ExecutionResult, Halt, TxExecutionMode, TxRevertReason, VmExecutionMode, VmInterfaceExt, - VmRevertReason, - }, - vm_latest::{ - tests::{ - tester::{Account, VmTesterBuilder}, - utils::read_nonce_holder_tester, - }, - types::internals::TransactionData, - HistoryEnabled, - }, + versions::testonly::nonce_holder::test_nonce_holder, + vm_latest::{HistoryEnabled, Vm}, }; -pub enum NonceHolderTestMode { - SetValueUnderNonce, - IncreaseMinNonceBy5, - IncreaseMinNonceTooMuch, - LeaveNonceUnused, - IncreaseMinNonceBy1, - SwitchToArbitraryOrdering, -} - -impl From for u8 { - fn from(mode: NonceHolderTestMode) -> u8 { - match mode { - NonceHolderTestMode::SetValueUnderNonce => 0, - NonceHolderTestMode::IncreaseMinNonceBy5 => 1, - NonceHolderTestMode::IncreaseMinNonceTooMuch => 2, - NonceHolderTestMode::LeaveNonceUnused => 3, - NonceHolderTestMode::IncreaseMinNonceBy1 => 4, - NonceHolderTestMode::SwitchToArbitraryOrdering => 5, - } - } -} - #[test] -fn test_nonce_holder() { - let mut account = Account::random(); - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_custom_contracts(vec![( - read_nonce_holder_tester().to_vec(), - account.address, - true, - )]) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let mut run_nonce_test = |nonce: u32, - test_mode: NonceHolderTestMode, - error_message: Option, - comment: &'static str| { - // In this test we have to reset VM state after each test case. Because once bootloader failed during the validation of the transaction, - // it will fail again and again. At the same time we have to keep the same storage, because we want to keep the nonce holder contract state. - // The easiest way in terms of lifetimes is to reuse `vm_builder` to achieve it. - vm.reset_state(true); - let tx = account.get_l2_tx_for_execute_with_nonce( - Execute { - contract_address: Some(account.address), - calldata: vec![12], - value: Default::default(), - factory_deps: vec![], - }, - None, - Nonce(nonce), - ); - let mut transaction_data = TransactionData::new(tx, false); - transaction_data.signature = vec![test_mode.into()]; - vm.vm.push_raw_transaction(transaction_data, 0, 0, true); - let result = vm.vm.execute(VmExecutionMode::OneTx); - - if let Some(msg) = error_message { - let expected_error = - TxRevertReason::Halt(Halt::ValidationFailed(VmRevertReason::General { - msg, - data: vec![], - })); - let ExecutionResult::Halt { reason } = result.result else { - panic!("Expected revert, got {:?}", result.result); - }; - assert_eq!( - reason.to_string(), - expected_error.to_string(), - "{}", - comment - ); - } else { - assert!(!result.result.is_failed(), "{}", comment); - } - }; - // Test 1: trying to set value under non sequential nonce value. - run_nonce_test( - 1u32, - NonceHolderTestMode::SetValueUnderNonce, - Some("Previous nonce has not been used".to_string()), - "Allowed to set value under non sequential value", - ); - - // Test 2: increase min nonce by 1 with sequential nonce ordering: - run_nonce_test( - 0u32, - NonceHolderTestMode::IncreaseMinNonceBy1, - None, - "Failed to increment nonce by 1 for sequential account", - ); - - // Test 3: correctly set value under nonce with sequential nonce ordering: - run_nonce_test( - 1u32, - NonceHolderTestMode::SetValueUnderNonce, - None, - "Failed to set value under nonce sequential value", - ); - - // Test 5: migrate to the arbitrary nonce ordering: - run_nonce_test( - 2u32, - NonceHolderTestMode::SwitchToArbitraryOrdering, - None, - "Failed to switch to arbitrary ordering", - ); - - // Test 6: increase min nonce by 5 - run_nonce_test( - 6u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - None, - "Failed to increase min nonce by 5", - ); - - // Test 7: since the nonces in range [6,10] are no longer allowed, the - // tx with nonce 10 should not be allowed - run_nonce_test( - 10u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - Some("Reusing the same nonce twice".to_string()), - "Allowed to reuse nonce below the minimal one", - ); - - // Test 8: we should be able to use nonce 13 - run_nonce_test( - 13u32, - NonceHolderTestMode::SetValueUnderNonce, - None, - "Did not allow to use unused nonce 10", - ); - - // Test 9: we should not be able to reuse nonce 13 - run_nonce_test( - 13u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - Some("Reusing the same nonce twice".to_string()), - "Allowed to reuse the same nonce twice", - ); - - // Test 10: we should be able to simply use nonce 14, while bumping the minimal nonce by 5 - run_nonce_test( - 14u32, - NonceHolderTestMode::IncreaseMinNonceBy5, - None, - "Did not allow to use a bumped nonce", - ); - - // Test 11: Do not allow bumping nonce by too much - run_nonce_test( - 16u32, - NonceHolderTestMode::IncreaseMinNonceTooMuch, - Some("The value for incrementing the nonce is too high".to_string()), - "Allowed for incrementing min nonce too much", - ); - - // Test 12: Do not allow not setting a nonce as used - run_nonce_test( - 16u32, - NonceHolderTestMode::LeaveNonceUnused, - Some("The nonce was not set as used".to_string()), - "Allowed to leave nonce as unused", - ); +fn nonce_holder() { + test_nonce_holder::>(); } 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 110b14146c7..7ef45721ea5 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/precompiles.rs @@ -1,142 +1,19 @@ -use zk_evm_1_5_0::zk_evm_abstractions::precompiles::PrecompileAddress; -use zksync_types::{Address, Execute}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface}, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - tests::{tester::VmTesterBuilder, utils::read_precompiles_contract}, - HistoryEnabled, - }, + versions::testonly::precompiles::{test_ecrecover, test_keccak, test_sha256}, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_keccak() { - // Execute special transaction and check that at least 1000 keccak calls were made. - let contract = read_precompiles_contract(); - let address = Address::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![(contract, address, true)]) - .build(); - - // calldata for `doKeccak(1000)`. - let keccak1000_calldata = - "370f20ac00000000000000000000000000000000000000000000000000000000000003e8"; - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: hex::decode(keccak1000_calldata).unwrap(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - let _ = vm - .vm - .inspect(&mut Default::default(), VmExecutionMode::OneTx); - - let keccak_count = vm - .vm - .state - .precompiles_processor - .precompile_cycles_history - .inner() - .iter() - .filter(|(precompile, _)| precompile == &PrecompileAddress::Keccak256) - .count(); - - assert!(keccak_count >= 1000); +fn keccak() { + test_keccak::>(); } #[test] -fn test_sha256() { - // Execute special transaction and check that at least 1000 `sha256` calls were made. - let contract = read_precompiles_contract(); - let address = Address::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![(contract, address, true)]) - .build(); - - // calldata for `doSha256(1000)`. - let sha1000_calldata = - "5d0b4fb500000000000000000000000000000000000000000000000000000000000003e8"; - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: hex::decode(sha1000_calldata).unwrap(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - let _ = vm - .vm - .inspect(&mut Default::default(), VmExecutionMode::OneTx); - - let sha_count = vm - .vm - .state - .precompiles_processor - .precompile_cycles_history - .inner() - .iter() - .filter(|(precompile, _)| precompile == &PrecompileAddress::SHA256) - .count(); - - assert!(sha_count >= 1000); +fn sha256() { + test_sha256::>(); } #[test] -fn test_ecrecover() { - // Execute simple transfer and check that exactly 1 `ecrecover` call was made (it's done during tx validation). - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(account.address), - calldata: Vec::new(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(tx); - let _ = vm - .vm - .inspect(&mut Default::default(), VmExecutionMode::OneTx); - - let ecrecover_count = vm - .vm - .state - .precompiles_processor - .precompile_cycles_history - .inner() - .iter() - .filter(|(precompile, _)| precompile == &PrecompileAddress::Ecrecover) - .count(); - - assert_eq!(ecrecover_count, 1); +fn ecrecover() { + test_ecrecover::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/refunds.rs b/core/lib/multivm/src/versions/vm_latest/tests/refunds.rs index c00192aa8f1..dfbec170682 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/refunds.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/refunds.rs @@ -1,228 +1,16 @@ -use ethabi::Token; -use zksync_types::{Address, Execute, U256}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - tests::{ - tester::{DeployContractsTx, TxType, VmTesterBuilder}, - utils::{read_expensive_contract, read_test_contract}, - }, - types::internals::TransactionData, - HistoryEnabled, + versions::testonly::refunds::{ + test_negative_pubdata_for_transaction, test_predetermined_refunded_gas, }, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_predetermined_refunded_gas() { - // In this test, we compare the execution of the bootloader with the predefined - // refunded gas and without them - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - let l1_batch = vm.vm.batch_env.clone(); - - let counter = read_test_contract(); - let account = &mut vm.rich_accounts[0]; - - let DeployContractsTx { - tx, - bytecode_hash: _, - address: _, - } = account.get_deploy_tx(&counter, None, TxType::L2); - vm.vm.push_transaction(tx.clone()); - let result = vm.vm.execute(VmExecutionMode::OneTx); - - assert!(!result.result.is_failed()); - - // If the refund provided by the operator or the final refund are the 0 - // there is no impact of the operator's refund at all and so this test does not - // make much sense. - assert!( - result.refunds.operator_suggested_refund > 0, - "The operator's refund is 0" - ); - assert!(result.refunds.gas_refunded > 0, "The final refund is 0"); - - let result_without_predefined_refunds = vm.vm.execute(VmExecutionMode::Batch); - let mut current_state_without_predefined_refunds = vm.vm.get_current_execution_state(); - assert!(!result_without_predefined_refunds.result.is_failed(),); - - // Here we want to provide the same refund from the operator and check that it's the correct one. - // We execute the whole block without refund tracer, because refund tracer will eventually override the provided refund. - // But the overall result should be the same - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch.clone()) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let tx = TransactionData::new(tx, false); - // Overhead - let overhead = tx.overhead_gas(); - vm.vm - .push_raw_transaction(tx.clone(), overhead, result.refunds.gas_refunded, true); - - let result_with_predefined_refunds = vm.vm.execute(VmExecutionMode::Batch); - let mut current_state_with_predefined_refunds = vm.vm.get_current_execution_state(); - - assert!(!result_with_predefined_refunds.result.is_failed()); - - // We need to sort these lists as those are flattened from HashMaps - current_state_with_predefined_refunds - .used_contract_hashes - .sort(); - current_state_without_predefined_refunds - .used_contract_hashes - .sort(); - - assert_eq!( - current_state_with_predefined_refunds.events, - current_state_without_predefined_refunds.events - ); - - assert_eq!( - current_state_with_predefined_refunds.user_l2_to_l1_logs, - current_state_without_predefined_refunds.user_l2_to_l1_logs - ); - - assert_eq!( - current_state_with_predefined_refunds.system_logs, - current_state_without_predefined_refunds.system_logs - ); - - assert_eq!( - current_state_with_predefined_refunds.deduplicated_storage_logs, - current_state_without_predefined_refunds.deduplicated_storage_logs - ); - assert_eq!( - current_state_with_predefined_refunds.used_contract_hashes, - current_state_without_predefined_refunds.used_contract_hashes - ); - - // In this test we put the different refund from the operator. - // We still can't use the refund tracer, because it will override the refund. - // But we can check that the logs and events have changed. - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_l1_batch_env(l1_batch) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) - .build(); - - let changed_operator_suggested_refund = result.refunds.gas_refunded + 1000; - vm.vm - .push_raw_transaction(tx, overhead, changed_operator_suggested_refund, true); - let result = vm.vm.execute(VmExecutionMode::Batch); - let mut current_state_with_changed_predefined_refunds = vm.vm.get_current_execution_state(); - - assert!(!result.result.is_failed()); - current_state_with_changed_predefined_refunds - .used_contract_hashes - .sort(); - current_state_without_predefined_refunds - .used_contract_hashes - .sort(); - - assert_eq!( - current_state_with_changed_predefined_refunds.events.len(), - current_state_without_predefined_refunds.events.len() - ); - - assert_ne!( - current_state_with_changed_predefined_refunds.events, - current_state_without_predefined_refunds.events - ); - - assert_eq!( - current_state_with_changed_predefined_refunds.user_l2_to_l1_logs, - current_state_without_predefined_refunds.user_l2_to_l1_logs - ); - - assert_ne!( - current_state_with_changed_predefined_refunds.system_logs, - current_state_without_predefined_refunds.system_logs - ); - - assert_eq!( - current_state_with_changed_predefined_refunds - .deduplicated_storage_logs - .len(), - current_state_without_predefined_refunds - .deduplicated_storage_logs - .len() - ); - - assert_ne!( - current_state_with_changed_predefined_refunds.deduplicated_storage_logs, - current_state_without_predefined_refunds.deduplicated_storage_logs - ); - assert_eq!( - current_state_with_changed_predefined_refunds.used_contract_hashes, - current_state_without_predefined_refunds.used_contract_hashes - ); +fn predetermined_refunded_gas() { + test_predetermined_refunded_gas::>(); } #[test] fn negative_pubdata_for_transaction() { - let expensive_contract_address = Address::random(); - let (expensive_contract_bytecode, expensive_contract) = read_expensive_contract(); - let expensive_function = expensive_contract.function("expensive").unwrap(); - let cleanup_function = expensive_contract.function("cleanUp").unwrap(); - - 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![( - expensive_contract_bytecode, - expensive_contract_address, - false, - )]) - .build(); - - let expensive_tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Some(expensive_contract_address), - calldata: expensive_function - .encode_input(&[Token::Uint(10.into())]) - .unwrap(), - value: U256::zero(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(expensive_tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - - // This transaction cleans all initial writes in the contract, thus having negative `pubdata` impact. - let clean_up_tx = vm.rich_accounts[0].get_l2_tx_for_execute( - Execute { - contract_address: Some(expensive_contract_address), - calldata: cleanup_function.encode_input(&[]).unwrap(), - value: U256::zero(), - factory_deps: vec![], - }, - None, - ); - vm.vm.push_transaction(clean_up_tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful: {result:#?}" - ); - assert!(result.refunds.operator_suggested_refund > 0); - assert_eq!( - result.refunds.gas_refunded, - result.refunds.operator_suggested_refund - ); + test_negative_pubdata_for_transaction::>(); } 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 1f38c6f947e..470ddb28699 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 @@ -1,166 +1,9 @@ -use ethabi::Token; -use zksync_eth_signer::TransactionParameters; -use zksync_system_constants::L2_BASE_TOKEN_ADDRESS; -use zksync_types::{ - fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, - utils::storage_key_for_standard_token_balance, AccountTreeId, Address, Eip712Domain, Execute, - L2ChainId, Nonce, Transaction, U256, -}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - tests::{ - tester::{Account, VmTester, VmTesterBuilder}, - utils::read_many_owners_custom_account_contract, - }, - HistoryDisabled, - }, + versions::testonly::require_eip712::test_require_eip712, + vm_latest::{HistoryEnabled, Vm}, }; -impl VmTester { - pub(crate) fn get_eth_balance(&mut self, address: Address) -> U256 { - let key = storage_key_for_standard_token_balance( - AccountTreeId::new(L2_BASE_TOKEN_ADDRESS), - &address, - ); - self.vm.state.storage.storage.read_from_storage(&key) - } -} - -// TODO refactor this test it use too much internal details of the VM #[test] -/// This test deploys 'buggy' account abstraction code, and then tries accessing it both with legacy -/// and EIP712 transactions. -/// Currently we support both, but in the future, we should allow only EIP712 transactions to access the AA accounts. -fn test_require_eip712() { - // Use 3 accounts: - // - `private_address` - EOA account, where we have the key - // - `account_address` - AA account, where the contract is deployed - // - beneficiary - an EOA account, where we'll try to transfer the tokens. - let account_abstraction = Account::random(); - let mut private_account = Account::random(); - let beneficiary = Account::random(); - - let (bytecode, contract) = read_many_owners_custom_account_contract(); - let mut vm = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_custom_contracts(vec![(bytecode, account_abstraction.address, true)]) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account_abstraction.clone(), private_account.clone()]) - .build(); - - assert_eq!(vm.get_eth_balance(beneficiary.address), U256::from(0)); - - let chain_id: u32 = 270; - - // First, let's set the owners of the AA account to the `private_address`. - // (so that messages signed by `private_address`, are authorized to act on behalf of the AA account). - let set_owners_function = contract.function("setOwners").unwrap(); - let encoded_input = set_owners_function - .encode_input(&[Token::Array(vec![Token::Address(private_account.address)])]) - .unwrap(); - - let tx = private_account.get_l2_tx_for_execute( - Execute { - contract_address: Some(account_abstraction.address), - calldata: encoded_input, - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed()); - - let private_account_balance = vm.get_eth_balance(private_account.address); - - // And now let's do the transfer from the 'account abstraction' to 'beneficiary' (using 'legacy' transaction). - // Normally this would not work - unless the operator is malicious. - let aa_raw_tx = TransactionParameters { - nonce: U256::from(0), - to: Some(beneficiary.address), - gas: U256::from(100000000), - gas_price: Some(U256::from(10000000)), - value: U256::from(888000088), - data: vec![], - chain_id: 270, - transaction_type: None, - access_list: None, - max_fee_per_gas: U256::from(1000000000), - max_priority_fee_per_gas: U256::from(1000000000), - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - }; - - let aa_tx = private_account.sign_legacy_tx(aa_raw_tx); - let (tx_request, hash) = TransactionRequest::from_bytes(&aa_tx, L2ChainId::from(270)).unwrap(); - - let mut l2_tx: L2Tx = L2Tx::from_request(tx_request, 10000, false).unwrap(); - l2_tx.set_input(aa_tx, hash); - // Pretend that operator is malicious and sets the initiator to the AA account. - l2_tx.common_data.initiator_address = account_abstraction.address; - let transaction: Transaction = l2_tx.into(); - - 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) - ); - // Make sure that the tokens were transferred from the AA account. - assert_eq!( - private_account_balance, - vm.get_eth_balance(private_account.address) - ); - - // // Now send the 'classic' EIP712 transaction - let tx_712 = L2Tx::new( - Some(beneficiary.address), - vec![], - Nonce(1), - Fee { - gas_limit: U256::from(1000000000), - max_fee_per_gas: U256::from(1000000000), - max_priority_fee_per_gas: U256::from(1000000000), - gas_per_pubdata_limit: U256::from(1000000000), - }, - account_abstraction.address, - U256::from(28374938), - vec![], - Default::default(), - ); - - let mut transaction_request: TransactionRequest = tx_712.into(); - transaction_request.chain_id = Some(chain_id.into()); - - let domain = Eip712Domain::new(L2ChainId::from(chain_id)); - let signature = private_account - .get_pk_signer() - .sign_typed_data(&domain, &transaction_request) - .unwrap(); - let encoded_tx = transaction_request.get_signed_bytes(&signature).unwrap(); - - let (aa_txn_request, aa_hash) = - TransactionRequest::from_bytes(&encoded_tx, L2ChainId::from(chain_id)).unwrap(); - - let mut l2_tx = L2Tx::from_request(aa_txn_request, 100000, false).unwrap(); - l2_tx.set_input(encoded_tx, aa_hash); - - let transaction: Transaction = l2_tx.into(); - vm.vm.push_transaction(transaction); - vm.vm.execute(VmExecutionMode::OneTx); - - assert_eq!( - vm.get_eth_balance(beneficiary.address), - U256::from(916375026) - ); - assert_eq!( - private_account_balance, - vm.get_eth_balance(private_account.address) - ); +fn require_eip712() { + test_require_eip712::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/secp256r1.rs b/core/lib/multivm/src/versions/vm_latest/tests/secp256r1.rs new file mode 100644 index 00000000000..11534a26ded --- /dev/null +++ b/core/lib/multivm/src/versions/vm_latest/tests/secp256r1.rs @@ -0,0 +1,9 @@ +use crate::{ + versions::testonly::secp256r1::test_secp256r1, + vm_latest::{HistoryEnabled, Vm}, +}; + +#[test] +fn secp256r1() { + test_secp256r1::>(); +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs b/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs deleted file mode 100644 index 93be9506a3b..00000000000 --- a/core/lib/multivm/src/versions/vm_latest/tests/sekp256r1.rs +++ /dev/null @@ -1,74 +0,0 @@ -use zk_evm_1_5_0::zkevm_opcode_defs::p256; -use zksync_system_constants::P256VERIFY_PRECOMPILE_ADDRESS; -use zksync_types::{web3::keccak256, Execute, H256, U256}; -use zksync_utils::h256_to_u256; - -use crate::{ - interface::{ExecutionResult, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{tests::tester::VmTesterBuilder, 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: Some(P256VERIFY_PRECOMPILE_ADDRESS), - calldata: [digest, encoded_r, encoded_s, x, y].concat(), - value: U256::zero(), - factory_deps: vec![], - }, - 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/simple_execution.rs b/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs index cd020ee9f96..29072e66b1e 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/simple_execution.rs @@ -1,83 +1,14 @@ -use assert_matches::assert_matches; - use crate::{ - interface::{ExecutionResult, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - tests::tester::{TxType, VmTesterBuilder}, - HistoryDisabled, - }, + versions::testonly::simple_execution::{test_estimate_fee, test_simple_execute}, + vm_latest::{HistoryEnabled, Vm}, }; #[test] fn estimate_fee() { - let mut vm_tester = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_deployer() - .with_random_rich_accounts(1) - .build(); - - vm_tester.deploy_test_contract(); - let account = &mut vm_tester.rich_accounts[0]; - - let tx = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - false, - Default::default(), - false, - TxType::L2, - ); - - vm_tester.vm.push_transaction(tx); - - let result = vm_tester.vm.execute(VmExecutionMode::OneTx); - assert_matches!(result.result, ExecutionResult::Success { .. }); + test_estimate_fee::>(); } #[test] fn simple_execute() { - let mut vm_tester = VmTesterBuilder::new(HistoryDisabled) - .with_empty_in_memory_storage() - .with_deployer() - .with_random_rich_accounts(1) - .build(); - - vm_tester.deploy_test_contract(); - - let account = &mut vm_tester.rich_accounts[0]; - - let tx1 = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - false, - Default::default(), - false, - TxType::L1 { serial_id: 1 }, - ); - - let tx2 = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - true, - Default::default(), - false, - TxType::L1 { serial_id: 1 }, - ); - - let tx3 = account.get_test_contract_transaction( - vm_tester.test_contract.unwrap(), - false, - Default::default(), - false, - TxType::L1 { serial_id: 1 }, - ); - let vm = &mut vm_tester.vm; - vm.push_transaction(tx1); - vm.push_transaction(tx2); - vm.push_transaction(tx3); - let tx = vm.execute(VmExecutionMode::OneTx); - assert_matches!(tx.result, ExecutionResult::Success { .. }); - let tx = vm.execute(VmExecutionMode::OneTx); - assert_matches!(tx.result, ExecutionResult::Revert { .. }); - let tx = vm.execute(VmExecutionMode::OneTx); - assert_matches!(tx.result, ExecutionResult::Success { .. }); - let block_tip = vm.execute(VmExecutionMode::Batch); - assert_matches!(block_tip.result, ExecutionResult::Success { .. }); + test_simple_execute::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/storage.rs b/core/lib/multivm/src/versions/vm_latest/tests/storage.rs index 126d174a646..4cb03875a0f 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/storage.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/storage.rs @@ -1,188 +1,14 @@ -use ethabi::Token; -use zksync_contracts::{load_contract, read_bytecode}; -use zksync_test_account::Account; -use zksync_types::{fee::Fee, Address, Execute, U256}; - use crate::{ - interface::{ - TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, - }, - vm_latest::{tests::tester::VmTesterBuilder, HistoryEnabled}, + versions::testonly::storage::{test_storage_behavior, test_transient_storage_behavior}, + vm_latest::{HistoryEnabled, Vm}, }; -#[derive(Debug, Default)] - -struct TestTxInfo { - calldata: Vec, - fee_overrides: Option, - should_fail: bool, -} - -fn test_storage(txs: 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(txs.len() as u32) - .with_custom_contracts(vec![(bytecode, test_contract_address, false)]) - .build(); - - let mut last_result = None; - - for (id, tx) in txs.into_iter().enumerate() { - let TestTxInfo { - calldata, - fee_overrides, - should_fail, - } = tx; - - let account = &mut vm.rich_accounts[id]; - - vm.vm.make_snapshot(); - - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(test_contract_address), - calldata, - value: 0.into(), - factory_deps: vec![], - }, - fee_overrides, - ); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - if should_fail { - assert!(result.result.is_failed(), "Transaction should fail"); - vm.vm.rollback_to_the_latest_snapshot(); - } else { - assert!(!result.result.is_failed(), "Transaction should not fail"); - vm.vm.pop_snapshot_no_rollback(); - } - - last_result = Some(result); - } - - last_result.unwrap().statistics.pubdata_published -} - -fn test_storage_one_tx(second_tx_calldata: Vec) -> u32 { - test_storage(vec![ - TestTxInfo::default(), - TestTxInfo { - calldata: second_tx_calldata, - fee_overrides: None, - should_fail: false, - }, - ]) -} - -#[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(vec![ - TestTxInfo { - calldata: first_tstore_test, - ..TestTxInfo::default() - }, - TestTxInfo { - calldata: second_tstore_test, - ..TestTxInfo::default() - }, - ]); +fn storage_behavior() { + test_storage_behavior::>(); } #[test] -fn test_transient_storage_behavior_panic() { - let contract = load_contract( - "etc/contracts-test-data/artifacts-zk/contracts/storage/storage.sol/StorageTester.json", - ); - - let basic_tstore_test = contract - .function("tStoreAndRevert") - .unwrap() - .encode_input(&[Token::Uint(U256::one()), Token::Bool(false)]) - .unwrap(); - - let small_fee = Fee { - // Something very-very small to make the validation fail - gas_limit: 10_000.into(), - ..Account::default_fee() - }; - - test_storage(vec![ - TestTxInfo { - calldata: basic_tstore_test.clone(), - ..TestTxInfo::default() - }, - TestTxInfo { - fee_overrides: Some(small_fee), - should_fail: true, - ..TestTxInfo::default() - }, - TestTxInfo { - calldata: basic_tstore_test, - ..TestTxInfo::default() - }, - ]); +fn transient_storage_behavior() { + test_transient_storage_behavior::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs index c3cc5d8d980..0620d065e20 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs @@ -1,7 +1,5 @@ pub(crate) use transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier}; -pub(crate) use vm_tester::{ - default_l1_batch, get_empty_storage, InMemoryStorageView, VmTester, VmTesterBuilder, -}; +pub(crate) use vm_tester::{get_empty_storage, InMemoryStorageView, VmTester, VmTesterBuilder}; pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType}; mod inner_state; From 65dc7e7649bc641f59b21b0d850177c1855bcd6c Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 17:10:04 +0300 Subject: [PATCH 04/18] Unify execution error tests --- core/lib/multivm/src/versions/testonly/mod.rs | 7 ++ .../src/versions/testonly/tester/mod.rs | 9 +- .../testonly/tester/transaction_test_info.rs | 88 +++++++------------ .../testonly/tracing_execution_error.rs | 64 ++++++++++++++ .../multivm/src/versions/vm_fast/tests/mod.rs | 57 ++++++++++-- .../vm_fast/tests/tracing_execution_error.rs | 53 +---------- .../src/versions/vm_latest/tests/mod.rs | 10 ++- .../vm_latest/tests/tester/inner_state.rs | 45 +++++----- .../versions/vm_latest/tests/tester/mod.rs | 4 +- .../tests/tracing_execution_error.rs | 53 +---------- 10 files changed, 203 insertions(+), 187 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/tracing_execution_error.rs diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index aaeadc34be5..1697af0178c 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -37,6 +37,7 @@ mod shadow; pub(super) mod simple_execution; pub(super) mod storage; mod tester; +pub(super) mod tracing_execution_error; static BASE_SYSTEM_CONTRACTS: Lazy = Lazy::new(BaseSystemContracts::load_from_disk); @@ -91,6 +92,12 @@ fn read_many_owners_custom_account_contract() -> (Vec, Contract) { (read_bytecode(path), load_contract(path)) } +fn read_error_contract() -> Vec { + read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/error/error.sol/SimpleRequire.json", + ) +} + pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; let bootloader_code = read_yul_bytecode(artifacts_path, test); diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index 5270043dfe1..80b452e45c3 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::{collections::HashSet, fmt}; use zksync_contracts::BaseSystemContracts; use zksync_test_account::{Account, TxType}; @@ -11,6 +11,7 @@ use zksync_vm_interface::{ CurrentExecutionState, VmExecutionResultAndLogs, VmInterfaceHistoryEnabled, }; +pub(crate) use self::transaction_test_info::{ExpectedError, TransactionTestInfo}; use super::{get_empty_storage, read_test_contract}; use crate::{ interface::{ @@ -23,7 +24,7 @@ use crate::{ }, }; -//mod transaction_test_info; FIXME +mod transaction_test_info; // FIXME: revise fields #[derive(Debug)] @@ -192,6 +193,10 @@ impl VmTesterBuilder { pub(crate) trait TestedVm: VmFactory> + VmInterfaceHistoryEnabled { + type StateDump: fmt::Debug + PartialEq; + + fn dump_state(&self) -> Self::StateDump; + fn gas_remaining(&mut self) -> u32; fn get_current_execution_state(&self) -> CurrentExecutionState; diff --git a/core/lib/multivm/src/versions/testonly/tester/transaction_test_info.rs b/core/lib/multivm/src/versions/testonly/tester/transaction_test_info.rs index e6506ff225b..fe04652b8f8 100644 --- a/core/lib/multivm/src/versions/testonly/tester/transaction_test_info.rs +++ b/core/lib/multivm/src/versions/testonly/tester/transaction_test_info.rs @@ -1,16 +1,9 @@ -use std::fmt; - -use zksync_types::{ExecuteTransactionCommon, Transaction, H160, U256}; -use zksync_vm2::interface::{Event, StateInterface}; - -use super::VmTester; -use crate::{ - interface::{ - storage::ReadStorage, CurrentExecutionState, ExecutionResult, Halt, TxRevertReason, - VmExecutionMode, VmExecutionResultAndLogs, VmInterface, VmInterfaceExt, - VmInterfaceHistoryEnabled, VmRevertReason, - }, - vm_fast::Vm, +use zksync_types::{ExecuteTransactionCommon, Transaction}; + +use super::{TestedVm, VmTester}; +use crate::interface::{ + CurrentExecutionState, ExecutionResult, Halt, TxRevertReason, VmExecutionMode, + VmExecutionResultAndLogs, VmInterfaceExt, VmRevertReason, }; #[derive(Debug, Clone)] @@ -186,33 +179,7 @@ impl TransactionTestInfo { } } -// TODO this doesn't include all the state of ModifiedWorld -#[derive(Debug)] -struct VmStateDump { - state: S, - storage_writes: Vec<((H160, U256), U256)>, - events: Box<[Event]>, -} - -impl PartialEq for VmStateDump { - fn eq(&self, other: &Self) -> bool { - self.state == other.state - && self.storage_writes == other.storage_writes - && self.events == other.events - } -} - -impl Vm { - fn dump_state(&self) -> VmStateDump { - VmStateDump { - state: self.inner.dump_state(), - storage_writes: self.inner.get_storage_state().collect(), - events: self.inner.events().collect(), - } - } -} - -impl VmTester<()> { +impl VmTester { pub(crate) fn execute_and_verify_txs( &mut self, txs: &[TransactionTestInfo], @@ -230,22 +197,29 @@ impl VmTester<()> { &mut self, tx_test_info: TransactionTestInfo, ) -> VmExecutionResultAndLogs { - self.vm.make_snapshot(); - let inner_state_before = self.vm.dump_state(); - self.vm.push_transaction(tx_test_info.tx.clone()); - let result = self.vm.execute(VmExecutionMode::OneTx); - tx_test_info.verify_result(&result); - if tx_test_info.should_rollback() { - self.vm.rollback_to_the_latest_snapshot(); - let inner_state_after = self.vm.dump_state(); - pretty_assertions::assert_eq!( - inner_state_before, - inner_state_after, - "Inner state before and after rollback should be equal" - ); - } else { - self.vm.pop_snapshot_no_rollback(); - } - result + execute_tx_and_verify(&mut self.vm, tx_test_info) + } +} + +fn execute_tx_and_verify( + vm: &mut impl TestedVm, + tx_test_info: TransactionTestInfo, +) -> VmExecutionResultAndLogs { + let inner_state_before = vm.dump_state(); + vm.make_snapshot(); + vm.push_transaction(tx_test_info.tx.clone()); + let result = vm.execute(VmExecutionMode::OneTx); + tx_test_info.verify_result(&result); + if tx_test_info.should_rollback() { + vm.rollback_to_the_latest_snapshot(); + let inner_state_after = vm.dump_state(); + pretty_assertions::assert_eq!( + inner_state_before, + inner_state_after, + "Inner state before and after rollback should be equal" + ); + } else { + vm.pop_snapshot_no_rollback(); } + result } diff --git a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs new file mode 100644 index 00000000000..1072ae17333 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs @@ -0,0 +1,64 @@ +use zksync_contracts::load_contract; +use zksync_types::{Execute, H160}; + +use super::{ + read_error_contract, tester::VmTesterBuilder, ContractToDeploy, TestedVm, BASE_SYSTEM_CONTRACTS, +}; +use crate::{ + interface::{TxExecutionMode, TxRevertReason, VmRevertReason}, + versions::testonly::tester::{ExpectedError, TransactionTestInfo}, +}; + +fn get_execute_error_calldata() -> Vec { + let test_contract = load_contract( + "etc/contracts-test-data/artifacts-zk/contracts/error/error.sol/SimpleRequire.json", + ); + let function = test_contract.function("require_short").unwrap(); + function + .encode_input(&[]) + .expect("failed to encode parameters") +} + +pub(crate) fn test_tracing_of_execution_errors() { + let contract_address = H160::random(); + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) + .with_custom_contracts(vec![ContractToDeploy::new( + read_error_contract(), + contract_address, + )]) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .build::(); + + let account = &mut vm.rich_accounts[0]; + + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(contract_address), + calldata: get_execute_error_calldata(), + value: Default::default(), + factory_deps: vec![], + }, + None, + ); + + vm.execute_tx_and_verify(TransactionTestInfo::new_rejected( + tx, + ExpectedError { + revert_reason: TxRevertReason::TxReverted(VmRevertReason::General { + msg: "short".to_string(), + data: vec![ + 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 115, 104, 111, 114, 116, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + ], + }), + modifier: None, + }, + )); +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index 6648b4ae94b..7a600fcc009 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -1,8 +1,8 @@ -use std::collections::HashSet; +use std::{any::Any, collections::HashSet, fmt}; -use zksync_types::{writes::StateDiffRecord, StorageKey, Transaction, H256, U256}; +use zksync_types::{writes::StateDiffRecord, StorageKey, Transaction, H160, H256, U256}; use zksync_utils::h256_to_u256; -use zksync_vm2::interface::{HeapId, StateInterface}; +use zksync_vm2::interface::{Event, HeapId, StateInterface}; use zksync_vm_interface::{ storage::ReadStorage, CurrentExecutionState, L2BlockEnv, VmExecutionMode, VmExecutionResultAndLogs, VmInterfaceExt, @@ -33,18 +33,65 @@ mod require_eip712; mod secp256r1; mod simple_execution; mod storage; +mod tracing_execution_error; /* // mod call_tracer; FIXME: requires tracers // mod prestate_tracer; FIXME: is pre-state tracer still relevant? mod rollbacks; -mod tester; -mod tracing_execution_error; mod transfer; mod upgrade; mod utils; */ +trait ObjectSafeEq: fmt::Debug + AsRef { + fn eq(&self, other: &dyn ObjectSafeEq) -> bool; +} + +#[derive(Debug)] +struct BoxedEq(T); + +impl AsRef for BoxedEq { + fn as_ref(&self) -> &dyn Any { + &self.0 + } +} + +impl ObjectSafeEq for BoxedEq { + fn eq(&self, other: &dyn ObjectSafeEq) -> bool { + let Some(other) = other.as_ref().downcast_ref::() else { + return false; + }; + self.0 == *other + } +} + +// TODO this doesn't include all the state of ModifiedWorld +#[derive(Debug)] +pub(crate) struct VmStateDump { + state: Box, + storage_writes: Vec<((H160, U256), U256)>, + events: Box<[Event]>, +} + +impl PartialEq for VmStateDump { + fn eq(&self, other: &Self) -> bool { + self.state.as_ref().eq(other.state.as_ref()) + && self.storage_writes == other.storage_writes + && self.events == other.events + } +} + impl TestedVm for Vm> { + type StateDump = VmStateDump; + + fn dump_state(&self) -> Self::StateDump { + VmStateDump { + state: Box::new(BoxedEq(self.inner.dump_state())), + storage_writes: self.inner.get_storage_state().collect(), + events: self.inner.events().collect(), + } + } + fn gas_remaining(&mut self) -> u32 { self.gas_remaining() } diff --git a/core/lib/multivm/src/versions/vm_fast/tests/tracing_execution_error.rs b/core/lib/multivm/src/versions/vm_fast/tests/tracing_execution_error.rs index 89f0fa23620..b3f5b4b33bc 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/tracing_execution_error.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/tracing_execution_error.rs @@ -1,55 +1,8 @@ -use zksync_types::{Execute, H160}; - use crate::{ - interface::{TxExecutionMode, TxRevertReason, VmRevertReason}, - versions::testonly::ContractToDeploy, - vm_fast::tests::{ - tester::{ExpectedError, TransactionTestInfo, VmTesterBuilder}, - utils::{get_execute_error_calldata, read_error_contract, BASE_SYSTEM_CONTRACTS}, - }, + versions::testonly::tracing_execution_error::test_tracing_of_execution_errors, vm_fast::Vm, }; #[test] -fn test_tracing_of_execution_errors() { - let contract_address = H160::random(); - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) - .with_custom_contracts(vec![ContractToDeploy::new( - read_error_contract(), - contract_address, - )]) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_random_rich_accounts(1) - .build(); - - let account = &mut vm.rich_accounts[0]; - - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(contract_address), - calldata: get_execute_error_calldata(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - vm.execute_tx_and_verify(TransactionTestInfo::new_rejected( - tx, - ExpectedError { - revert_reason: TxRevertReason::TxReverted(VmRevertReason::General { - msg: "short".to_string(), - data: vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 115, 104, 111, 114, 116, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - }), - modifier: None, - }, - )); +fn tracing_of_execution_errors() { + test_tracing_of_execution_errors::>(); } 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 f0e30ac3d8a..1d72a5f8404 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -15,8 +15,8 @@ use crate::{ interface::storage::{InMemoryStorage, StorageView}, versions::testonly::TestedVm, vm_latest::{ - constants::BOOTLOADER_HEAP_PAGE, tracers::PubdataTracer, types::internals::TransactionData, - TracerDispatcher, + constants::BOOTLOADER_HEAP_PAGE, tests::tester::VmInstanceInnerState, + tracers::PubdataTracer, types::internals::TransactionData, TracerDispatcher, }, }; @@ -53,6 +53,12 @@ mod upgrade; mod utils; impl TestedVm for Vm, HistoryEnabled> { + type StateDump = VmInstanceInnerState; + + fn dump_state(&self) -> Self::StateDump { + self.dump_inner_state() + } + fn gas_remaining(&mut self) -> u32 { self.state.local_state.callstack.current.ergs_remaining } 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 c0ef52afaa5..3b17762469c 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 @@ -2,9 +2,10 @@ use std::collections::HashMap; use zk_evm_1_5_0::{aux_structures::Timestamp, vm_state::VmLocalState}; use zksync_types::{StorageKey, StorageValue, U256}; +use zksync_vm_interface::storage::StorageView; use crate::{ - interface::storage::WriteStorage, + interface::storage::{ReadStorage, WriteStorage}, vm_latest::{ old_vm::{ event_sink::InMemoryEventSink, @@ -19,17 +20,27 @@ use crate::{ #[derive(Clone, Debug)] pub(crate) struct ModifiedKeysMap(HashMap); +impl ModifiedKeysMap { + fn new(storage: &mut StorageView) -> Self { + let mut modified_keys = storage.modified_storage_keys().clone(); + let inner = storage.inner_mut(); + // Remove modified keys that were set to the same value (e.g., due to a rollback). + modified_keys.retain(|key, value| inner.read_value(key) != *value); + Self(modified_keys) + } +} + // We consider hashmaps to be equal even if there is a key // that is not present in one but has zero value in another. impl PartialEq for ModifiedKeysMap { fn eq(&self, other: &Self) -> bool { - for (key, value) in self.0.iter() { - if *value != other.0.get(key).cloned().unwrap_or_default() { + for (key, value) in &self.0 { + if *value != other.0.get(key).copied().unwrap_or_default() { return false; } } - for (key, value) in other.0.iter() { - if *value != self.0.get(key).cloned().unwrap_or_default() { + for (key, value) in &other.0 { + if *value != self.0.get(key).copied().unwrap_or_default() { return false; } } @@ -51,9 +62,7 @@ pub(crate) struct StorageOracleInnerState { /// There is no way to "truly" compare the storage pointer, /// so we just compare the modified keys. This is reasonable enough. pub(crate) modified_storage_keys: ModifiedKeysMap, - pub(crate) frames_stack: AppDataFrameManagerWithHistory, H>, - pub(crate) paid_changes: HistoryRecorder, H>, pub(crate) initial_values: HistoryRecorder, H>, pub(crate) returned_io_refunds: HistoryRecorder, H>, @@ -77,7 +86,7 @@ pub(crate) struct VmInstanceInnerState { local_state: VmLocalState, } -impl Vm { +impl Vm, H> { // Dump inner state of the VM. pub(crate) fn dump_inner_state(&self) -> VmInstanceInnerState { let event_sink = self.state.event_sink.clone(); @@ -86,13 +95,12 @@ impl Vm { }; let memory = self.state.memory.clone(); let decommitter_state = DecommitterTestInnerState { - modified_storage_keys: ModifiedKeysMap( - self.state + modified_storage_keys: ModifiedKeysMap::new( + &mut self + .state .decommittment_processor .get_storage() - .borrow() - .modified_storage_keys() - .clone(), + .borrow_mut(), ), known_bytecodes: self.state.decommittment_processor.known_bytecodes.clone(), decommitted_code_hashes: self @@ -101,15 +109,10 @@ impl Vm { .get_decommitted_code_hashes_with_history() .clone(), }; + let storage_oracle_state = StorageOracleInnerState { - modified_storage_keys: ModifiedKeysMap( - self.state - .storage - .storage - .get_ptr() - .borrow() - .modified_storage_keys() - .clone(), + modified_storage_keys: ModifiedKeysMap::new( + &mut self.state.storage.storage.get_ptr().borrow_mut(), ), frames_stack: self.state.storage.storage_frames_stack.clone(), paid_changes: self.state.storage.paid_changes.clone(), diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs index 0620d065e20..7282a8b897c 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs @@ -1,7 +1,9 @@ -pub(crate) use transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier}; +pub(crate) use transaction_test_info::{TransactionTestInfo, TxModifier}; pub(crate) use vm_tester::{get_empty_storage, InMemoryStorageView, VmTester, VmTesterBuilder}; pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType}; +pub(crate) use self::inner_state::VmInstanceInnerState; // FIXME + mod inner_state; mod transaction_test_info; mod vm_tester; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs b/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs index 2db37881352..a2cd6af6211 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tracing_execution_error.rs @@ -1,54 +1,9 @@ -use zksync_types::{Execute, H160}; - use crate::{ - interface::{TxExecutionMode, TxRevertReason, VmRevertReason}, - vm_latest::{ - tests::{ - tester::{ExpectedError, TransactionTestInfo, VmTesterBuilder}, - utils::{get_execute_error_calldata, read_error_contract, BASE_SYSTEM_CONTRACTS}, - }, - HistoryEnabled, - }, + versions::testonly::tracing_execution_error::test_tracing_of_execution_errors, + vm_latest::{HistoryEnabled, Vm}, }; #[test] -fn test_tracing_of_execution_errors() { - let contract_address = H160::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) - .with_custom_contracts(vec![(read_error_contract(), contract_address, false)]) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_random_rich_accounts(1) - .build(); - - let account = &mut vm.rich_accounts[0]; - - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(contract_address), - calldata: get_execute_error_calldata(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - vm.execute_tx_and_verify(TransactionTestInfo::new_rejected( - tx, - ExpectedError { - revert_reason: TxRevertReason::TxReverted(VmRevertReason::General { - msg: "short".to_string(), - data: vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 115, 104, 111, 114, 116, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, - ], - }), - modifier: None, - }, - )); +fn tracing_of_execution_errors() { + test_tracing_of_execution_errors::>(); } From 87169cd08ad6d7b9e1d84849cc6b0ef2bf4d2e13 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 17:16:46 +0300 Subject: [PATCH 05/18] Unify transfer tests --- core/lib/multivm/src/versions/testonly/mod.rs | 1 + .../multivm/src/versions/testonly/transfer.rs | 202 ++++++++++++++++ .../multivm/src/versions/vm_fast/tests/mod.rs | 2 +- .../src/versions/vm_fast/tests/transfer.rs | 213 +---------------- .../src/versions/vm_latest/tests/transfer.rs | 218 +----------------- 5 files changed, 218 insertions(+), 418 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/transfer.rs diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 1697af0178c..0fdd4cbb0b0 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -38,6 +38,7 @@ pub(super) mod simple_execution; pub(super) mod storage; mod tester; pub(super) mod tracing_execution_error; +pub(super) mod transfer; static BASE_SYSTEM_CONTRACTS: Lazy = Lazy::new(BaseSystemContracts::load_from_disk); diff --git a/core/lib/multivm/src/versions/testonly/transfer.rs b/core/lib/multivm/src/versions/testonly/transfer.rs new file mode 100644 index 00000000000..6e054ca977e --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/transfer.rs @@ -0,0 +1,202 @@ +use ethabi::Token; +use zksync_contracts::{load_contract, read_bytecode}; +use zksync_types::{utils::storage_key_for_eth_balance, Address, Execute, U256}; +use zksync_utils::u256_to_h256; + +use super::{get_empty_storage, tester::VmTesterBuilder, ContractToDeploy, TestedVm}; +use crate::interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +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 recipient_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() + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ + ContractToDeploy::new(test_bytecode, test_contract_address), + ContractToDeploy::new(recipient_bytecode, recipient_address), + ]) + .build::(); + + let account = &mut vm.rich_accounts[0]; + let tx = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(test_contract_address), + calldata, + value: U256::zero(), + factory_deps: vec![], + }, + 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 = vm.get_eth_balance(recipient_address); + assert_eq!(new_recipient_balance, value); +} + +pub(crate) 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_recipient_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_recipient_address = Address::random(); + + let (value, calldata) = match test_option { + TestOptions::Send(value) => ( + value, + test_abi + .function("send") + .unwrap() + .encode_input(&[ + Token::Address(reentrant_recipient_address), + Token::Uint(value), + ]) + .unwrap(), + ), + TestOptions::Transfer(value) => ( + value, + test_abi + .function("transfer") + .unwrap() + .encode_input(&[ + Token::Address(reentrant_recipient_address), + Token::Uint(value), + ]) + .unwrap(), + ), + }; + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_deployer() + .with_random_rich_accounts(1) + .with_custom_contracts(vec![ + ContractToDeploy::new(test_bytecode, test_contract_address), + ContractToDeploy::new(reentrant_recipient_bytecode, reentrant_recipient_address), + ]) + .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: Some(reentrant_recipient_address), + calldata: reentrant_recipient_abi + .function("setX") + .unwrap() + .encode_input(&[]) + .unwrap(), + value: U256::from(1), + factory_deps: vec![], + }, + 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: Some(test_contract_address), + calldata, + value, + factory_deps: vec![], + }, + 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"); +} + +pub(crate) 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_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index 7a600fcc009..81464401384 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -34,11 +34,11 @@ mod secp256r1; mod simple_execution; mod storage; mod tracing_execution_error; +mod transfer; /* // mod call_tracer; FIXME: requires tracers // mod prestate_tracer; FIXME: is pre-state tracer still relevant? mod rollbacks; -mod transfer; mod upgrade; mod utils; */ diff --git a/core/lib/multivm/src/versions/vm_fast/tests/transfer.rs b/core/lib/multivm/src/versions/vm_fast/tests/transfer.rs index ef510546f11..57c2c3e2c34 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/transfer.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/transfer.rs @@ -1,215 +1,16 @@ -use ethabi::Token; -use zksync_contracts::{load_contract, read_bytecode}; -use zksync_system_constants::L2_BASE_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, VmInterfaceExt}, - versions::testonly::ContractToDeploy, - vm_fast::tests::{ - tester::{get_empty_storage, VmTesterBuilder}, - utils::get_balance, + versions::testonly::transfer::{ + test_reentrancy_protection_send_and_transfer, test_send_and_transfer, }, + vm_fast::Vm, }; -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 recipient_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() - .with_storage(storage) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ - ContractToDeploy::new(test_bytecode, test_contract_address), - ContractToDeploy::new(recipient_bytecode, recipient_address), - ]) - .build(); - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(test_contract_address), - calldata, - value: U256::zero(), - factory_deps: vec![], - }, - 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_BASE_TOKEN_ADDRESS), - &recipient_address, - &mut vm.vm.world.storage, - vm.vm.inner.world_diff().get_storage_state(), - ); - - 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_recipient_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_recipient_address = Address::random(); - - let (value, calldata) = match test_option { - TestOptions::Send(value) => ( - value, - test_abi - .function("send") - .unwrap() - .encode_input(&[ - Token::Address(reentrant_recipient_address), - Token::Uint(value), - ]) - .unwrap(), - ), - TestOptions::Transfer(value) => ( - value, - test_abi - .function("transfer") - .unwrap() - .encode_input(&[ - Token::Address(reentrant_recipient_address), - Token::Uint(value), - ]) - .unwrap(), - ), - }; - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() - .with_random_rich_accounts(1) - .with_custom_contracts(vec![ - ContractToDeploy::new(test_bytecode, test_contract_address), - ContractToDeploy::new(reentrant_recipient_bytecode, reentrant_recipient_address), - ]) - .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: Some(reentrant_recipient_address), - calldata: reentrant_recipient_abi - .function("setX") - .unwrap() - .encode_input(&[]) - .unwrap(), - value: U256::from(1), - factory_deps: vec![], - }, - 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: Some(test_contract_address), - calldata, - value, - factory_deps: vec![], - }, - 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"); +fn send_and_transfer() { + test_send_and_transfer::>(); } #[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()), - )); +fn reentrancy_protection_send_and_transfer() { + test_reentrancy_protection_send_and_transfer::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs b/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs index 2c380623636..f37ebe6a3fb 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/transfer.rs @@ -1,220 +1,16 @@ -use ethabi::Token; -use zksync_contracts::{load_contract, read_bytecode}; -use zksync_system_constants::L2_BASE_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, VmInterfaceExt}, - vm_latest::{ - tests::{ - tester::{get_empty_storage, VmTesterBuilder}, - utils::get_balance, - }, - HistoryEnabled, + versions::testonly::transfer::{ + test_reentrancy_protection_send_and_transfer, test_send_and_transfer, }, + vm_latest::{HistoryEnabled, Vm}, }; -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: Some(test_contract_address), - calldata, - value: U256::zero(), - factory_deps: vec![], - }, - 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_BASE_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: Some(reentrant_recipeint_address), - calldata: reentrant_recipient_abi - .function("setX") - .unwrap() - .encode_input(&[]) - .unwrap(), - value: U256::from(1), - factory_deps: vec![], - }, - 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: Some(test_contract_address), - calldata, - value, - factory_deps: vec![], - }, - 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"); +fn send_and_transfer() { + test_send_and_transfer::>(); } #[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()), - )); +fn reentrancy_protection_send_and_transfer() { + test_reentrancy_protection_send_and_transfer::>(); } From 957d21348979457482e51313a274e00c78ff1c20 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 17:28:13 +0300 Subject: [PATCH 06/18] Unify upgrade tests --- core/lib/multivm/src/versions/testonly/mod.rs | 1 + .../multivm/src/versions/testonly/upgrade.rs | 322 ++++++++++++++++ .../multivm/src/versions/vm_fast/tests/mod.rs | 2 +- .../src/versions/vm_fast/tests/upgrade.rs | 340 +---------------- .../src/versions/vm_latest/tests/upgrade.rs | 351 +----------------- 5 files changed, 342 insertions(+), 674 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/upgrade.rs diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 0fdd4cbb0b0..dd361a906d0 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -39,6 +39,7 @@ pub(super) mod storage; mod tester; pub(super) mod tracing_execution_error; pub(super) mod transfer; +pub(super) mod upgrade; static BASE_SYSTEM_CONTRACTS: Lazy = Lazy::new(BaseSystemContracts::load_from_disk); diff --git a/core/lib/multivm/src/versions/testonly/upgrade.rs b/core/lib/multivm/src/versions/testonly/upgrade.rs new file mode 100644 index 00000000000..7fb6e6f4d01 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/upgrade.rs @@ -0,0 +1,322 @@ +use zksync_contracts::{deployer_contract, load_sys_contract, read_bytecode}; +use zksync_test_account::TxType; +use zksync_types::{ + ethabi::{Contract, Token}, + get_code_key, get_known_code_key, + protocol_upgrade::ProtocolUpgradeTxCommonData, + Address, Execute, ExecuteTransactionCommon, Transaction, COMPLEX_UPGRADER_ADDRESS, + CONTRACT_DEPLOYER_ADDRESS, CONTRACT_FORCE_DEPLOYER_ADDRESS, H160, H256, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, +}; +use zksync_utils::{bytecode::hash_bytecode, h256_to_u256, u256_to_h256}; + +use super::{ + get_complex_upgrade_abi, get_empty_storage, read_complex_upgrade, read_test_contract, + tester::VmTesterBuilder, TestedVm, +}; +use crate::interface::{ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +/// In this test we ensure that the requirements for protocol upgrade transactions are enforced by the bootloader: +/// - This transaction must be the only one in block +/// - If present, this transaction must be the first one in block +pub(crate) fn test_protocol_upgrade_is_first() { + let mut storage = get_empty_storage(); + let bytecode_hash = hash_bytecode(&read_test_contract()); + storage.set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); + + let mut vm = VmTesterBuilder::new() + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + // Here we just use some random transaction of protocol upgrade type: + let protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment { + // The bytecode hash to put on an address + bytecode_hash, + // The address on which to deploy the bytecode hash to + address: H160::random(), + // Whether to run the constructor on the force deployment + call_constructor: false, + // The value with which to initialize a contract + value: U256::zero(), + // The constructor calldata + input: vec![], + }]); + + // Another random upgrade transaction + let another_protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment { + // The bytecode hash to put on an address + bytecode_hash, + // The address on which to deploy the bytecode hash to + address: H160::random(), + // Whether to run the constructor on the force deployment + call_constructor: false, + // The value with which to initialize a contract + value: U256::zero(), + // The constructor calldata + input: vec![], + }]); + + let normal_l1_transaction = vm.rich_accounts[0] + .get_deploy_tx(&read_test_contract(), None, TxType::L1 { serial_id: 0 }) + .tx; + + let expected_error = + Halt::UnexpectedVMBehavior("Assertion error: Protocol upgrade tx not first".to_string()); + + vm.vm.make_snapshot(); + // Test 1: there must be only one system transaction in block + vm.vm.push_transaction(protocol_upgrade_transaction.clone()); + vm.vm.push_transaction(normal_l1_transaction.clone()); + vm.vm.push_transaction(another_protocol_upgrade_transaction); + + vm.vm.execute(VmExecutionMode::OneTx); + vm.vm.execute(VmExecutionMode::OneTx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert_eq!( + result.result, + ExecutionResult::Halt { + reason: expected_error.clone() + } + ); + + // Test 2: the protocol upgrade tx must be the first one in block + vm.vm.rollback_to_the_latest_snapshot(); + vm.vm.make_snapshot(); + vm.vm.push_transaction(normal_l1_transaction.clone()); + vm.vm.push_transaction(protocol_upgrade_transaction.clone()); + + vm.vm.execute(VmExecutionMode::OneTx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert_eq!( + result.result, + ExecutionResult::Halt { + reason: expected_error + } + ); + + vm.vm.rollback_to_the_latest_snapshot(); + vm.vm.make_snapshot(); + vm.vm.push_transaction(protocol_upgrade_transaction); + vm.vm.push_transaction(normal_l1_transaction); + + vm.vm.execute(VmExecutionMode::OneTx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!(!result.result.is_failed()); +} + +/// In this test we try to test how force deployments could be done via protocol upgrade transactions. +pub(crate) fn test_force_deploy_upgrade() { + let mut storage = get_empty_storage(); + let bytecode_hash = hash_bytecode(&read_test_contract()); + let known_code_key = get_known_code_key(&bytecode_hash); + // It is generally expected that all the keys will be set as known prior to the protocol upgrade. + storage.set_value(known_code_key, u256_to_h256(1.into())); + + let mut vm = VmTesterBuilder::new() + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let address_to_deploy = H160::random(); + // Here we just use some random transaction of protocol upgrade type: + let transaction = get_forced_deploy_tx(&[ForceDeployment { + // The bytecode hash to put on an address + bytecode_hash, + // The address on which to deploy the bytecode hash to + address: address_to_deploy, + // Whether to run the constructor on the force deployment + call_constructor: false, + // The value with which to initialize a contract + value: U256::zero(), + // The constructor calldata + input: vec![], + }]); + + vm.vm.push_transaction(transaction); + + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "The force upgrade was not successful" + ); + + let expected_slots = [( + get_code_key(&address_to_deploy), + h256_to_u256(bytecode_hash), + )]; + // Verify that the bytecode has been set correctly + vm.vm.verify_required_storage(&expected_slots); +} + +/// Here we show how the work with the complex upgrader could be done. +pub(crate) fn test_complex_upgrader() { + let mut storage = get_empty_storage(); + let bytecode_hash = hash_bytecode(&read_complex_upgrade()); + let msg_sender_test_hash = hash_bytecode(&read_msg_sender_test()); + // Let's assume that the bytecode for the implementation of the complex upgrade + // is already deployed in some address in user space + let upgrade_impl = H160::random(); + let account_code_key = get_code_key(&upgrade_impl); + storage.set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); + storage.set_value( + get_known_code_key(&msg_sender_test_hash), + u256_to_h256(1.into()), + ); + storage.set_value(account_code_key, bytecode_hash); + storage.store_factory_dep(bytecode_hash, read_complex_upgrade()); + storage.store_factory_dep(msg_sender_test_hash, read_msg_sender_test()); + + let mut vm = VmTesterBuilder::new() + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let address_to_deploy1 = H160::random(); + let address_to_deploy2 = H160::random(); + + let transaction = get_complex_upgrade_tx( + upgrade_impl, + address_to_deploy1, + address_to_deploy2, + bytecode_hash, + ); + + vm.vm.push_transaction(transaction); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "The force upgrade was not successful" + ); + + let expected_slots = [ + ( + get_code_key(&address_to_deploy1), + h256_to_u256(bytecode_hash), + ), + ( + get_code_key(&address_to_deploy2), + h256_to_u256(bytecode_hash), + ), + ]; + // Verify that the bytecode has been set correctly + vm.vm.verify_required_storage(&expected_slots); +} + +#[derive(Debug, Clone)] +struct ForceDeployment { + // The bytecode hash to put on an address + bytecode_hash: H256, + // The address on which to deploy the bytecode hash to + address: Address, + // Whether to run the constructor on the force deployment + call_constructor: bool, + // The value with which to initialize a contract + value: U256, + // The constructor calldata + input: Vec, +} + +fn get_forced_deploy_tx(deployment: &[ForceDeployment]) -> Transaction { + let deployer = deployer_contract(); + let contract_function = deployer.function("forceDeployOnAddresses").unwrap(); + + let encoded_deployments: Vec<_> = deployment + .iter() + .map(|deployment| { + Token::Tuple(vec![ + Token::FixedBytes(deployment.bytecode_hash.as_bytes().to_vec()), + Token::Address(deployment.address), + Token::Bool(deployment.call_constructor), + Token::Uint(deployment.value), + Token::Bytes(deployment.input.clone()), + ]) + }) + .collect(); + + let params = [Token::Array(encoded_deployments)]; + + let calldata = contract_function + .encode_input(¶ms) + .expect("failed to encode parameters"); + + let execute = Execute { + contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), + calldata, + factory_deps: vec![], + value: U256::zero(), + }; + + Transaction { + common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { + sender: CONTRACT_FORCE_DEPLOYER_ADDRESS, + gas_limit: U256::from(200_000_000u32), + gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), + ..Default::default() + }), + execute, + received_timestamp_ms: 0, + raw_bytes: None, + } +} + +// Returns the transaction that performs a complex protocol upgrade. +// The first param is the address of the implementation of the complex upgrade +// in user-space, while the next 3 params are params of the implementation itself +// For the explanation for the parameters, please refer to: +// etc/contracts-test-data/complex-upgrade/complex-upgrade.sol +fn get_complex_upgrade_tx( + implementation_address: Address, + address1: Address, + address2: Address, + bytecode_hash: H256, +) -> Transaction { + let impl_contract = get_complex_upgrade_abi(); + let impl_function = impl_contract.function("someComplexUpgrade").unwrap(); + let impl_calldata = impl_function + .encode_input(&[ + Token::Address(address1), + Token::Address(address2), + Token::FixedBytes(bytecode_hash.as_bytes().to_vec()), + ]) + .unwrap(); + + let complex_upgrader = get_complex_upgrader_abi(); + let upgrade_function = complex_upgrader.function("upgrade").unwrap(); + let complex_upgrader_calldata = upgrade_function + .encode_input(&[ + Token::Address(implementation_address), + Token::Bytes(impl_calldata), + ]) + .unwrap(); + + let execute = Execute { + contract_address: Some(COMPLEX_UPGRADER_ADDRESS), + calldata: complex_upgrader_calldata, + factory_deps: vec![], + value: U256::zero(), + }; + + Transaction { + common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { + sender: CONTRACT_FORCE_DEPLOYER_ADDRESS, + gas_limit: U256::from(200_000_000u32), + gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), + ..Default::default() + }), + execute, + received_timestamp_ms: 0, + raw_bytes: None, + } +} + +fn read_msg_sender_test() -> Vec { + read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/msg-sender.sol/MsgSenderTest.json") +} + +fn get_complex_upgrader_abi() -> Contract { + load_sys_contract("ComplexUpgrader") +} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index 81464401384..a1e61e41814 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -35,11 +35,11 @@ mod simple_execution; mod storage; mod tracing_execution_error; mod transfer; +mod upgrade; /* // mod call_tracer; FIXME: requires tracers // mod prestate_tracer; FIXME: is pre-state tracer still relevant? mod rollbacks; -mod upgrade; mod utils; */ diff --git a/core/lib/multivm/src/versions/vm_fast/tests/upgrade.rs b/core/lib/multivm/src/versions/vm_fast/tests/upgrade.rs index ba4863f7c45..4e4533c6868 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/upgrade.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/upgrade.rs @@ -1,343 +1,21 @@ -use zksync_contracts::{deployer_contract, load_sys_contract, read_bytecode}; -use zksync_test_account::TxType; -use zksync_types::{ - ethabi::{Contract, Token}, - get_code_key, get_known_code_key, - protocol_upgrade::ProtocolUpgradeTxCommonData, - Address, Execute, ExecuteTransactionCommon, Transaction, COMPLEX_UPGRADER_ADDRESS, - CONTRACT_DEPLOYER_ADDRESS, CONTRACT_FORCE_DEPLOYER_ADDRESS, H160, H256, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; - use crate::{ - interface::{ - ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, - VmInterfaceHistoryEnabled, - }, - vm_fast::tests::{ - tester::VmTesterBuilder, - utils::{ - get_complex_upgrade_abi, read_complex_upgrade, read_test_contract, - verify_required_storage, - }, + versions::testonly::upgrade::{ + test_complex_upgrader, test_force_deploy_upgrade, test_protocol_upgrade_is_first, }, + vm_fast::Vm, }; -/// In this test we ensure that the requirements for protocol upgrade transactions are enforced by the bootloader: -/// - This transaction must be the only one in block -/// - If present, this transaction must be the first one in block #[test] -fn test_protocol_upgrade_is_first() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let bytecode_hash = hash_bytecode(&read_test_contract()); - vm.storage - .borrow_mut() - .set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); - - // Here we just use some random transaction of protocol upgrade type: - let protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash, - // The address on which to deploy the bytecode hash to - address: H160::random(), - // Whether to run the constructor on the force deployment - call_constructor: false, - // The value with which to initialize a contract - value: U256::zero(), - // The constructor calldata - input: vec![], - }]); - - // Another random upgrade transaction - let another_protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash, - // The address on which to deploy the bytecode hash to - address: H160::random(), - // Whether to run the constructor on the force deployment - call_constructor: false, - // The value with which to initialize a contract - value: U256::zero(), - // The constructor calldata - input: vec![], - }]); - - let normal_l1_transaction = vm.rich_accounts[0] - .get_deploy_tx(&read_test_contract(), None, TxType::L1 { serial_id: 0 }) - .tx; - - let expected_error = - Halt::UnexpectedVMBehavior("Assertion error: Protocol upgrade tx not first".to_string()); - - vm.vm.make_snapshot(); - // Test 1: there must be only one system transaction in block - vm.vm.push_transaction(protocol_upgrade_transaction.clone()); - vm.vm.push_transaction(normal_l1_transaction.clone()); - vm.vm.push_transaction(another_protocol_upgrade_transaction); - - vm.vm.execute(VmExecutionMode::OneTx); - vm.vm.execute(VmExecutionMode::OneTx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert_eq!( - result.result, - ExecutionResult::Halt { - reason: expected_error.clone() - } - ); - - // Test 2: the protocol upgrade tx must be the first one in block - vm.vm.rollback_to_the_latest_snapshot(); - vm.vm.make_snapshot(); - vm.vm.push_transaction(normal_l1_transaction.clone()); - vm.vm.push_transaction(protocol_upgrade_transaction.clone()); - - vm.vm.execute(VmExecutionMode::OneTx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert_eq!( - result.result, - ExecutionResult::Halt { - reason: expected_error - } - ); - - vm.vm.rollback_to_the_latest_snapshot(); - vm.vm.make_snapshot(); - vm.vm.push_transaction(protocol_upgrade_transaction); - vm.vm.push_transaction(normal_l1_transaction); - - vm.vm.execute(VmExecutionMode::OneTx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed()); +fn protocol_upgrade_is_first() { + test_protocol_upgrade_is_first::>(); } -/// In this test we try to test how force deployments could be done via protocol upgrade transactions. #[test] -fn test_force_deploy_upgrade() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let storage_view = vm.storage.clone(); - let bytecode_hash = hash_bytecode(&read_test_contract()); - - let known_code_key = get_known_code_key(&bytecode_hash); - // It is generally expected that all the keys will be set as known prior to the protocol upgrade. - storage_view - .borrow_mut() - .set_value(known_code_key, u256_to_h256(1.into())); - drop(storage_view); - - let address_to_deploy = H160::random(); - // Here we just use some random transaction of protocol upgrade type: - let transaction = get_forced_deploy_tx(&[ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash, - // The address on which to deploy the bytecode hash to - address: address_to_deploy, - // Whether to run the constructor on the force deployment - call_constructor: false, - // The value with which to initialize a contract - value: U256::zero(), - // The constructor calldata - input: vec![], - }]); - - vm.vm.push_transaction(transaction); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "The force upgrade was not successful" - ); - - let expected_slots = [(bytecode_hash, get_code_key(&address_to_deploy))]; - - // Verify that the bytecode has been set correctly - verify_required_storage( - &expected_slots, - &mut *vm.storage.borrow_mut(), - vm.vm.inner.world_diff().get_storage_state(), - ); +fn force_deploy_upgrade() { + test_force_deploy_upgrade::>(); } -/// Here we show how the work with the complex upgrader could be done #[test] -fn test_complex_upgrader() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let bytecode_hash = hash_bytecode(&read_complex_upgrade()); - let msg_sender_test_hash = hash_bytecode(&read_msg_sender_test()); - - // Let's assume that the bytecode for the implementation of the complex upgrade - // is already deployed in some address in user space - let upgrade_impl = H160::random(); - let account_code_key = get_code_key(&upgrade_impl); - - { - let mut storage = vm.storage.borrow_mut(); - storage.set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); - storage.set_value( - get_known_code_key(&msg_sender_test_hash), - u256_to_h256(1.into()), - ); - storage.set_value(account_code_key, bytecode_hash); - storage.store_factory_dep(bytecode_hash, read_complex_upgrade()); - storage.store_factory_dep(msg_sender_test_hash, read_msg_sender_test()); - } - - let address_to_deploy1 = H160::random(); - let address_to_deploy2 = H160::random(); - - let transaction = get_complex_upgrade_tx( - upgrade_impl, - address_to_deploy1, - address_to_deploy2, - bytecode_hash, - ); - - vm.vm.push_transaction(transaction); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "The force upgrade was not successful" - ); - - let expected_slots = [ - (bytecode_hash, get_code_key(&address_to_deploy1)), - (bytecode_hash, get_code_key(&address_to_deploy2)), - ]; - - // Verify that the bytecode has been set correctly - verify_required_storage( - &expected_slots, - &mut *vm.storage.borrow_mut(), - vm.vm.inner.world_diff().get_storage_state(), - ); -} - -#[derive(Debug, Clone)] -struct ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash: H256, - // The address on which to deploy the bytecode hash to - address: Address, - // Whether to run the constructor on the force deployment - call_constructor: bool, - // The value with which to initialize a contract - value: U256, - // The constructor calldata - input: Vec, -} - -fn get_forced_deploy_tx(deployment: &[ForceDeployment]) -> Transaction { - let deployer = deployer_contract(); - let contract_function = deployer.function("forceDeployOnAddresses").unwrap(); - - let encoded_deployments: Vec<_> = deployment - .iter() - .map(|deployment| { - Token::Tuple(vec![ - Token::FixedBytes(deployment.bytecode_hash.as_bytes().to_vec()), - Token::Address(deployment.address), - Token::Bool(deployment.call_constructor), - Token::Uint(deployment.value), - Token::Bytes(deployment.input.clone()), - ]) - }) - .collect(); - - let params = [Token::Array(encoded_deployments)]; - - let calldata = contract_function - .encode_input(¶ms) - .expect("failed to encode parameters"); - - let execute = Execute { - contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), - calldata, - factory_deps: vec![], - value: U256::zero(), - }; - - Transaction { - common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { - sender: CONTRACT_FORCE_DEPLOYER_ADDRESS, - gas_limit: U256::from(200_000_000u32), - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - ..Default::default() - }), - execute, - received_timestamp_ms: 0, - raw_bytes: None, - } -} - -// Returns the transaction that performs a complex protocol upgrade. -// The first param is the address of the implementation of the complex upgrade -// in user-space, while the next 3 params are params of the implementation itself -// For the explanation for the parameters, please refer to: -// etc/contracts-test-data/complex-upgrade/complex-upgrade.sol -fn get_complex_upgrade_tx( - implementation_address: Address, - address1: Address, - address2: Address, - bytecode_hash: H256, -) -> Transaction { - let impl_contract = get_complex_upgrade_abi(); - let impl_function = impl_contract.function("someComplexUpgrade").unwrap(); - let impl_calldata = impl_function - .encode_input(&[ - Token::Address(address1), - Token::Address(address2), - Token::FixedBytes(bytecode_hash.as_bytes().to_vec()), - ]) - .unwrap(); - - let complex_upgrader = get_complex_upgrader_abi(); - let upgrade_function = complex_upgrader.function("upgrade").unwrap(); - let complex_upgrader_calldata = upgrade_function - .encode_input(&[ - Token::Address(implementation_address), - Token::Bytes(impl_calldata), - ]) - .unwrap(); - - let execute = Execute { - contract_address: Some(COMPLEX_UPGRADER_ADDRESS), - calldata: complex_upgrader_calldata, - factory_deps: vec![], - value: U256::zero(), - }; - - Transaction { - common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { - sender: CONTRACT_FORCE_DEPLOYER_ADDRESS, - gas_limit: U256::from(200_000_000u32), - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - ..Default::default() - }), - execute, - received_timestamp_ms: 0, - raw_bytes: None, - } -} - -fn read_msg_sender_test() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/msg-sender.sol/MsgSenderTest.json") -} - -fn get_complex_upgrader_abi() -> Contract { - load_sys_contract("ComplexUpgrader") +fn complex_upgrader() { + test_complex_upgrader::>(); } 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 d85a504de40..9889e26e4d2 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/upgrade.rs @@ -1,354 +1,21 @@ -use zk_evm_1_5_0::aux_structures::Timestamp; -use zksync_contracts::{deployer_contract, load_sys_contract, read_bytecode}; -use zksync_test_account::TxType; -use zksync_types::{ - ethabi::{Contract, Token}, - get_code_key, get_known_code_key, - protocol_upgrade::ProtocolUpgradeTxCommonData, - Address, Execute, ExecuteTransactionCommon, Transaction, COMPLEX_UPGRADER_ADDRESS, - CONTRACT_DEPLOYER_ADDRESS, CONTRACT_FORCE_DEPLOYER_ADDRESS, H160, H256, - REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; - -use super::utils::{get_complex_upgrade_abi, read_test_contract}; use crate::{ - interface::{ - storage::WriteStorage, ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, - VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, - }, - vm_latest::{ - tests::{ - tester::VmTesterBuilder, - utils::{read_complex_upgrade, verify_required_storage}, - }, - HistoryEnabled, + versions::testonly::upgrade::{ + test_complex_upgrader, test_force_deploy_upgrade, test_protocol_upgrade_is_first, }, + vm_latest::{HistoryEnabled, Vm}, }; -/// In this test we ensure that the requirements for protocol upgrade transactions are enforced by the bootloader: -/// - This transaction must be the only one in block -/// - If present, this transaction must be the first one in block #[test] -fn test_protocol_upgrade_is_first() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let bytecode_hash = hash_bytecode(&read_test_contract()); - vm.vm - .storage - .borrow_mut() - .set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); - - // Here we just use some random transaction of protocol upgrade type: - let protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash, - // The address on which to deploy the bytecode hash to - address: H160::random(), - // Whether to run the constructor on the force deployment - call_constructor: false, - // The value with which to initialize a contract - value: U256::zero(), - // The constructor calldata - input: vec![], - }]); - - // Another random upgrade transaction - let another_protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash, - // The address on which to deploy the bytecode hash to - address: H160::random(), - // Whether to run the constructor on the force deployment - call_constructor: false, - // The value with which to initialize a contract - value: U256::zero(), - // The constructor calldata - input: vec![], - }]); - - let normal_l1_transaction = vm.rich_accounts[0] - .get_deploy_tx(&read_test_contract(), None, TxType::L1 { serial_id: 0 }) - .tx; - - let expected_error = - Halt::UnexpectedVMBehavior("Assertion error: Protocol upgrade tx not first".to_string()); - - vm.vm.make_snapshot(); - // Test 1: there must be only one system transaction in block - vm.vm.push_transaction(protocol_upgrade_transaction.clone()); - vm.vm.push_transaction(normal_l1_transaction.clone()); - vm.vm.push_transaction(another_protocol_upgrade_transaction); - - vm.vm.execute(VmExecutionMode::OneTx); - vm.vm.execute(VmExecutionMode::OneTx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert_eq!( - result.result, - ExecutionResult::Halt { - reason: expected_error.clone() - } - ); - - // Test 2: the protocol upgrade tx must be the first one in block - vm.vm.rollback_to_the_latest_snapshot(); - vm.vm.make_snapshot(); - vm.vm.push_transaction(normal_l1_transaction.clone()); - vm.vm.push_transaction(protocol_upgrade_transaction.clone()); - - vm.vm.execute(VmExecutionMode::OneTx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert_eq!( - result.result, - ExecutionResult::Halt { - reason: expected_error - } - ); - - vm.vm.rollback_to_the_latest_snapshot(); - vm.vm.make_snapshot(); - vm.vm.push_transaction(protocol_upgrade_transaction); - vm.vm.push_transaction(normal_l1_transaction); - - vm.vm.execute(VmExecutionMode::OneTx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!(!result.result.is_failed()); +fn protocol_upgrade_is_first() { + test_protocol_upgrade_is_first::>(); } -/// In this test we try to test how force deployments could be done via protocol upgrade transactions. #[test] -fn test_force_deploy_upgrade() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let storage_view = vm.storage.clone(); - let bytecode_hash = hash_bytecode(&read_test_contract()); - - let known_code_key = get_known_code_key(&bytecode_hash); - // It is generally expected that all the keys will be set as known prior to the protocol upgrade. - storage_view - .borrow_mut() - .set_value(known_code_key, u256_to_h256(1.into())); - drop(storage_view); - - let address_to_deploy = H160::random(); - // Here we just use some random transaction of protocol upgrade type: - let transaction = get_forced_deploy_tx(&[ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash, - // The address on which to deploy the bytecode hash to - address: address_to_deploy, - // Whether to run the constructor on the force deployment - call_constructor: false, - // The value with which to initialize a contract - value: U256::zero(), - // The constructor calldata - input: vec![], - }]); - - vm.vm.push_transaction(transaction); - - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "The force upgrade was not successful" - ); - - let expected_slots = vec![(bytecode_hash, get_code_key(&address_to_deploy))]; - - // Verify that the bytecode has been set correctly - verify_required_storage(&vm.vm.state, expected_slots); +fn force_deploy_upgrade() { + test_force_deploy_upgrade::>(); } -/// Here we show how the work with the complex upgrader could be done #[test] -fn test_complex_upgrader() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let storage_view = vm.storage.clone(); - - let bytecode_hash = hash_bytecode(&read_complex_upgrade()); - let msg_sender_test_hash = hash_bytecode(&read_msg_sender_test()); - - // Let's assume that the bytecode for the implementation of the complex upgrade - // is already deployed in some address in user space - let upgrade_impl = H160::random(); - let account_code_key = get_code_key(&upgrade_impl); - - storage_view - .borrow_mut() - .set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); - storage_view.borrow_mut().set_value( - get_known_code_key(&msg_sender_test_hash), - u256_to_h256(1.into()), - ); - storage_view - .borrow_mut() - .set_value(account_code_key, bytecode_hash); - drop(storage_view); - - vm.vm.state.decommittment_processor.populate( - vec![ - ( - h256_to_u256(bytecode_hash), - bytes_to_be_words(read_complex_upgrade()), - ), - ( - h256_to_u256(msg_sender_test_hash), - bytes_to_be_words(read_msg_sender_test()), - ), - ], - Timestamp(0), - ); - - let address_to_deploy1 = H160::random(); - let address_to_deploy2 = H160::random(); - - let transaction = get_complex_upgrade_tx( - upgrade_impl, - address_to_deploy1, - address_to_deploy2, - bytecode_hash, - ); - - vm.vm.push_transaction(transaction); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "The force upgrade was not successful" - ); - - let expected_slots = vec![ - (bytecode_hash, get_code_key(&address_to_deploy1)), - (bytecode_hash, get_code_key(&address_to_deploy2)), - ]; - - // Verify that the bytecode has been set correctly - verify_required_storage(&vm.vm.state, expected_slots); -} - -#[derive(Debug, Clone)] -struct ForceDeployment { - // The bytecode hash to put on an address - bytecode_hash: H256, - // The address on which to deploy the bytecode hash to - address: Address, - // Whether to run the constructor on the force deployment - call_constructor: bool, - // The value with which to initialize a contract - value: U256, - // The constructor calldata - input: Vec, -} - -fn get_forced_deploy_tx(deployment: &[ForceDeployment]) -> Transaction { - let deployer = deployer_contract(); - let contract_function = deployer.function("forceDeployOnAddresses").unwrap(); - - let encoded_deployments: Vec<_> = deployment - .iter() - .map(|deployment| { - Token::Tuple(vec![ - Token::FixedBytes(deployment.bytecode_hash.as_bytes().to_vec()), - Token::Address(deployment.address), - Token::Bool(deployment.call_constructor), - Token::Uint(deployment.value), - Token::Bytes(deployment.input.clone()), - ]) - }) - .collect(); - - let params = [Token::Array(encoded_deployments)]; - - let calldata = contract_function - .encode_input(¶ms) - .expect("failed to encode parameters"); - - let execute = Execute { - contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), - calldata, - factory_deps: vec![], - value: U256::zero(), - }; - - Transaction { - common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { - sender: CONTRACT_FORCE_DEPLOYER_ADDRESS, - gas_limit: U256::from(200_000_000u32), - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - ..Default::default() - }), - execute, - received_timestamp_ms: 0, - raw_bytes: None, - } -} - -// Returns the transaction that performs a complex protocol upgrade. -// The first param is the address of the implementation of the complex upgrade -// in user-space, while the next 3 params are params of the implementation itself -// For the explanation for the parameters, please refer to: -// etc/contracts-test-data/complex-upgrade/complex-upgrade.sol -fn get_complex_upgrade_tx( - implementation_address: Address, - address1: Address, - address2: Address, - bytecode_hash: H256, -) -> Transaction { - let impl_contract = get_complex_upgrade_abi(); - let impl_function = impl_contract.function("someComplexUpgrade").unwrap(); - let impl_calldata = impl_function - .encode_input(&[ - Token::Address(address1), - Token::Address(address2), - Token::FixedBytes(bytecode_hash.as_bytes().to_vec()), - ]) - .unwrap(); - - let complex_upgrader = get_complex_upgrader_abi(); - let upgrade_function = complex_upgrader.function("upgrade").unwrap(); - let complex_upgrader_calldata = upgrade_function - .encode_input(&[ - Token::Address(implementation_address), - Token::Bytes(impl_calldata), - ]) - .unwrap(); - - let execute = Execute { - contract_address: Some(COMPLEX_UPGRADER_ADDRESS), - calldata: complex_upgrader_calldata, - factory_deps: vec![], - value: U256::zero(), - }; - - Transaction { - common_data: ExecuteTransactionCommon::ProtocolUpgrade(ProtocolUpgradeTxCommonData { - sender: CONTRACT_FORCE_DEPLOYER_ADDRESS, - gas_limit: U256::from(200_000_000u32), - gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), - ..Default::default() - }), - execute, - received_timestamp_ms: 0, - raw_bytes: None, - } -} - -fn read_msg_sender_test() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/msg-sender.sol/MsgSenderTest.json") -} - -fn get_complex_upgrader_abi() -> Contract { - load_sys_contract("ComplexUpgrader") +fn complex_upgrader() { + test_complex_upgrader::>(); } From ae0042ce1b9e6f7251f0f664ab23d920944a62a0 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 17:52:17 +0300 Subject: [PATCH 07/18] Unify rollback tests --- core/lib/multivm/src/versions/testonly/mod.rs | 1 + .../src/versions/testonly/rollbacks.rs | 183 ++++++++++++++++++ .../src/versions/testonly/tester/mod.rs | 13 +- .../multivm/src/versions/vm_fast/tests/mod.rs | 2 +- .../src/versions/vm_fast/tests/rollbacks.rs | 171 +--------------- .../src/versions/vm_latest/tests/rollbacks.rs | 178 ++--------------- .../versions/vm_latest/tests/tester/mod.rs | 1 - 7 files changed, 217 insertions(+), 332 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/rollbacks.rs diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index dd361a906d0..0dca6328d4f 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -32,6 +32,7 @@ pub(super) mod nonce_holder; pub(super) mod precompiles; pub(super) mod refunds; pub(super) mod require_eip712; +pub(super) mod rollbacks; pub(super) mod secp256r1; mod shadow; pub(super) mod simple_execution; diff --git a/core/lib/multivm/src/versions/testonly/rollbacks.rs b/core/lib/multivm/src/versions/testonly/rollbacks.rs new file mode 100644 index 00000000000..380f91dc0e8 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/rollbacks.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; + +use assert_matches::assert_matches; +use ethabi::Token; +use zksync_contracts::{get_loadnext_contract, test_contracts::LoadnextContractExecutionParams}; +use zksync_test_account::{DeployContractsTx, TxType}; +use zksync_types::{Address, Execute, U256}; + +use super::{ + read_test_contract, + tester::{TransactionTestInfo, TxModifier, VmTesterBuilder}, + ContractToDeploy, TestedVm, +}; +use crate::interface::{storage::ReadStorage, ExecutionResult, TxExecutionMode, VmInterfaceExt}; + +pub(crate) fn test_vm_rollbacks() { + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + let mut account = vm.rich_accounts[0].clone(); + let counter = read_test_contract(); + let tx_0 = account.get_deploy_tx(&counter, None, TxType::L2).tx; + let tx_1 = account.get_deploy_tx(&counter, None, TxType::L2).tx; + let tx_2 = account.get_deploy_tx(&counter, None, TxType::L2).tx; + + let result_without_rollbacks = vm.execute_and_verify_txs(&vec![ + TransactionTestInfo::new_processed(tx_0.clone(), false), + TransactionTestInfo::new_processed(tx_1.clone(), false), + TransactionTestInfo::new_processed(tx_2.clone(), false), + ]); + + // reset vm + vm.reset_with_empty_storage(); + + let result_with_rollbacks = vm.execute_and_verify_txs(&vec![ + TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongSignatureLength.into()), + TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongMagicValue.into()), + TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongSignature.into()), + // The correct nonce is 0, this tx will fail + TransactionTestInfo::new_rejected(tx_2.clone(), TxModifier::WrongNonce.into()), + // This tx will succeed + TransactionTestInfo::new_processed(tx_0.clone(), false), + // The correct nonce is 1, this tx will fail + TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::NonceReused.into()), + // The correct nonce is 1, this tx will fail + TransactionTestInfo::new_rejected(tx_2.clone(), TxModifier::WrongNonce.into()), + // This tx will succeed + TransactionTestInfo::new_processed(tx_1, false), + // The correct nonce is 2, this tx will fail + TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::NonceReused.into()), + // This tx will succeed + TransactionTestInfo::new_processed(tx_2.clone(), false), + // This tx will fail + TransactionTestInfo::new_rejected(tx_2, TxModifier::NonceReused.into()), + TransactionTestInfo::new_rejected(tx_0, TxModifier::NonceReused.into()), + ]); + + pretty_assertions::assert_eq!(result_without_rollbacks, result_with_rollbacks); +} + +pub(crate) fn test_vm_loadnext_rollbacks() { + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + let mut account = vm.rich_accounts[0].clone(); + + let loadnext_contract = get_loadnext_contract(); + let loadnext_constructor_data = &[Token::Uint(U256::from(100))]; + let DeployContractsTx { + tx: loadnext_deploy_tx, + address, + .. + } = account.get_deploy_tx_with_factory_deps( + &loadnext_contract.bytecode, + Some(loadnext_constructor_data), + loadnext_contract.factory_deps.clone(), + TxType::L2, + ); + + let loadnext_tx_1 = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(address), + calldata: LoadnextContractExecutionParams { + reads: 100, + writes: 100, + events: 100, + hashes: 500, + recursive_calls: 10, + deploys: 60, + } + .to_bytes(), + value: Default::default(), + factory_deps: vec![], + }, + None, + ); + + let loadnext_tx_2 = account.get_l2_tx_for_execute( + Execute { + contract_address: Some(address), + calldata: LoadnextContractExecutionParams { + reads: 100, + writes: 100, + events: 100, + hashes: 500, + recursive_calls: 10, + deploys: 60, + } + .to_bytes(), + value: Default::default(), + factory_deps: vec![], + }, + None, + ); + + let result_without_rollbacks = vm.execute_and_verify_txs(&vec![ + TransactionTestInfo::new_processed(loadnext_deploy_tx.clone(), false), + TransactionTestInfo::new_processed(loadnext_tx_1.clone(), false), + TransactionTestInfo::new_processed(loadnext_tx_2.clone(), false), + ]); + + // reset vm + vm.reset_with_empty_storage(); + + let result_with_rollbacks = vm.execute_and_verify_txs(&vec![ + TransactionTestInfo::new_processed(loadnext_deploy_tx.clone(), false), + TransactionTestInfo::new_processed(loadnext_tx_1.clone(), true), + TransactionTestInfo::new_rejected( + loadnext_deploy_tx.clone(), + TxModifier::NonceReused.into(), + ), + TransactionTestInfo::new_processed(loadnext_tx_1, false), + TransactionTestInfo::new_processed(loadnext_tx_2.clone(), true), + TransactionTestInfo::new_processed(loadnext_tx_2.clone(), true), + TransactionTestInfo::new_rejected(loadnext_deploy_tx, TxModifier::NonceReused.into()), + TransactionTestInfo::new_processed(loadnext_tx_2, false), + ]); + + assert_eq!(result_without_rollbacks, result_with_rollbacks); +} + +pub(crate) fn test_rollback_in_call_mode() { + let counter_bytecode = read_test_contract(); + let counter_address = Address::repeat_byte(1); + + let mut vm = VmTesterBuilder::new() + .with_empty_in_memory_storage() + .with_execution_mode(TxExecutionMode::EthCall) + .with_custom_contracts(vec![ContractToDeploy::new( + counter_bytecode, + counter_address, + )]) + .with_random_rich_accounts(1) + .build::(); + let account = &mut vm.rich_accounts[0]; + let tx = account.get_test_contract_transaction(counter_address, true, None, false, TxType::L2); + + let (compression_result, vm_result) = vm + .vm + .execute_transaction_with_bytecode_compression(tx, true); + compression_result.unwrap(); + assert_matches!( + vm_result.result, + ExecutionResult::Revert { output } + if output.to_string().contains("This method always reverts") + ); + + let storage_logs = &vm_result.logs.storage_logs; + let deduplicated_logs = storage_logs + .iter() + .filter_map(|log| log.log.is_write().then_some((log.log.key, log.log.value))); + let deduplicated_logs: HashMap<_, _> = deduplicated_logs.collect(); + // Check that all storage changes are reverted + let mut storage = vm.storage.borrow_mut(); + for (key, value) in deduplicated_logs { + assert_eq!(storage.inner_mut().read_value(&key), value); + } +} diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index 80b452e45c3..a05f5fc7252 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -11,7 +11,7 @@ use zksync_vm_interface::{ CurrentExecutionState, VmExecutionResultAndLogs, VmInterfaceHistoryEnabled, }; -pub(crate) use self::transaction_test_info::{ExpectedError, TransactionTestInfo}; +pub(crate) use self::transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier}; use super::{get_empty_storage, read_test_contract}; use crate::{ interface::{ @@ -59,6 +59,17 @@ impl VmTester { pub(crate) fn get_eth_balance(&mut self, address: Address) -> U256 { self.vm.read_storage(storage_key_for_eth_balance(&address)) } + + pub(crate) fn reset_with_empty_storage(&mut self) { + let mut storage = get_empty_storage(); + for account in self.rich_accounts.iter().chain(self.deployer.as_ref()) { + make_account_rich(&mut storage, account); + } + + let storage = StorageView::new(storage).to_rc_ptr(); + self.storage = storage.clone(); + self.vm = VM::new(self.l1_batch_env.clone(), self.system_env.clone(), storage); + } } #[derive(Debug)] diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index a1e61e41814..99d82c66d68 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -30,6 +30,7 @@ mod nonce_holder; mod precompiles; mod refunds; mod require_eip712; +mod rollbacks; mod secp256r1; mod simple_execution; mod storage; @@ -39,7 +40,6 @@ mod upgrade; /* // mod call_tracer; FIXME: requires tracers // mod prestate_tracer; FIXME: is pre-state tracer still relevant? -mod rollbacks; mod utils; */ diff --git a/core/lib/multivm/src/versions/vm_fast/tests/rollbacks.rs b/core/lib/multivm/src/versions/vm_fast/tests/rollbacks.rs index cff72d8ec5a..e8af23fa1e9 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/rollbacks.rs @@ -1,176 +1,21 @@ -use assert_matches::assert_matches; -use ethabi::Token; -use zksync_contracts::{get_loadnext_contract, test_contracts::LoadnextContractExecutionParams}; -use zksync_types::{Address, Execute, U256}; -use zksync_vm_interface::VmInterfaceExt; - use crate::{ - interface::{ExecutionResult, TxExecutionMode}, - versions::testonly::ContractToDeploy, - vm_fast::tests::{ - tester::{DeployContractsTx, TransactionTestInfo, TxModifier, TxType, VmTesterBuilder}, - utils::read_test_contract, + versions::testonly::rollbacks::{ + test_rollback_in_call_mode, test_vm_loadnext_rollbacks, test_vm_rollbacks, }, + vm_fast::Vm, }; #[test] -fn test_vm_rollbacks() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let mut account = vm.rich_accounts[0].clone(); - let counter = read_test_contract(); - let tx_0 = account.get_deploy_tx(&counter, None, TxType::L2).tx; - let tx_1 = account.get_deploy_tx(&counter, None, TxType::L2).tx; - let tx_2 = account.get_deploy_tx(&counter, None, TxType::L2).tx; - - let result_without_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_processed(tx_0.clone(), false), - TransactionTestInfo::new_processed(tx_1.clone(), false), - TransactionTestInfo::new_processed(tx_2.clone(), false), - ]); - - // reset vm - vm.reset_with_empty_storage(); - - let result_with_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongSignatureLength.into()), - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongMagicValue.into()), - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongSignature.into()), - // The correct nonce is 0, this tx will fail - TransactionTestInfo::new_rejected(tx_2.clone(), TxModifier::WrongNonce.into()), - // This tx will succeed - TransactionTestInfo::new_processed(tx_0.clone(), false), - // The correct nonce is 1, this tx will fail - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::NonceReused.into()), - // The correct nonce is 1, this tx will fail - TransactionTestInfo::new_rejected(tx_2.clone(), TxModifier::WrongNonce.into()), - // This tx will succeed - TransactionTestInfo::new_processed(tx_1, false), - // The correct nonce is 2, this tx will fail - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::NonceReused.into()), - // This tx will succeed - TransactionTestInfo::new_processed(tx_2.clone(), false), - // This tx will fail - TransactionTestInfo::new_rejected(tx_2, TxModifier::NonceReused.into()), - TransactionTestInfo::new_rejected(tx_0, TxModifier::NonceReused.into()), - ]); - - pretty_assertions::assert_eq!(result_without_rollbacks, result_with_rollbacks); +fn vm_rollbacks() { + test_vm_rollbacks::>(); } #[test] -fn test_vm_loadnext_rollbacks() { - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - let mut account = vm.rich_accounts[0].clone(); - - let loadnext_contract = get_loadnext_contract(); - let loadnext_constructor_data = &[Token::Uint(U256::from(100))]; - let DeployContractsTx { - tx: loadnext_deploy_tx, - address, - .. - } = account.get_deploy_tx_with_factory_deps( - &loadnext_contract.bytecode, - Some(loadnext_constructor_data), - loadnext_contract.factory_deps.clone(), - TxType::L2, - ); - - let loadnext_tx_1 = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: LoadnextContractExecutionParams { - reads: 100, - writes: 100, - events: 100, - hashes: 500, - recursive_calls: 10, - deploys: 60, - } - .to_bytes(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - let loadnext_tx_2 = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: LoadnextContractExecutionParams { - reads: 100, - writes: 100, - events: 100, - hashes: 500, - recursive_calls: 10, - deploys: 60, - } - .to_bytes(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - let result_without_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_processed(loadnext_deploy_tx.clone(), false), - TransactionTestInfo::new_processed(loadnext_tx_1.clone(), false), - TransactionTestInfo::new_processed(loadnext_tx_2.clone(), false), - ]); - - // reset vm - vm.reset_with_empty_storage(); - - let result_with_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_processed(loadnext_deploy_tx.clone(), false), - TransactionTestInfo::new_processed(loadnext_tx_1.clone(), true), - TransactionTestInfo::new_rejected( - loadnext_deploy_tx.clone(), - TxModifier::NonceReused.into(), - ), - TransactionTestInfo::new_processed(loadnext_tx_1, false), - TransactionTestInfo::new_processed(loadnext_tx_2.clone(), true), - TransactionTestInfo::new_processed(loadnext_tx_2.clone(), true), - TransactionTestInfo::new_rejected(loadnext_deploy_tx, TxModifier::NonceReused.into()), - TransactionTestInfo::new_processed(loadnext_tx_2, false), - ]); - - assert_eq!(result_without_rollbacks, result_with_rollbacks); +fn vm_loadnext_rollbacks() { + test_vm_loadnext_rollbacks::>(); } #[test] fn rollback_in_call_mode() { - let counter_bytecode = read_test_contract(); - let counter_address = Address::repeat_byte(1); - - let mut vm = VmTesterBuilder::new() - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::EthCall) - .with_custom_contracts(vec![ContractToDeploy::new( - counter_bytecode, - counter_address, - )]) - .with_random_rich_accounts(1) - .build(); - let account = &mut vm.rich_accounts[0]; - let tx = account.get_test_contract_transaction(counter_address, true, None, false, TxType::L2); - - let (compression_result, vm_result) = vm - .vm - .execute_transaction_with_bytecode_compression(tx, true); - compression_result.unwrap(); - assert_matches!( - vm_result.result, - ExecutionResult::Revert { output } - if output.to_string().contains("This method always reverts") - ); - assert_eq!(vm_result.logs.storage_logs, []); + test_rollback_in_call_mode::>(); } 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 00a5d6494fe..20f05caea08 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -1,157 +1,32 @@ -use assert_matches::assert_matches; use ethabi::Token; use zksync_contracts::{get_loadnext_contract, test_contracts::LoadnextContractExecutionParams}; -use zksync_types::{get_nonce_key, Address, Execute, U256}; +use zksync_types::{get_nonce_key, U256}; use crate::{ interface::{ storage::WriteStorage, tracer::{TracerExecutionStatus, TracerExecutionStopReason}, - ExecutionResult, TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, - VmInterfaceHistoryEnabled, + TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, }, tracers::dynamic::vm_1_5_0::DynTracer, + versions::testonly::rollbacks::{ + test_rollback_in_call_mode, test_vm_loadnext_rollbacks, test_vm_rollbacks, + }, vm_latest::{ - tests::{ - tester::{DeployContractsTx, TransactionTestInfo, TxModifier, TxType, VmTesterBuilder}, - utils::read_test_contract, - }, + tests::tester::{DeployContractsTx, TxType, VmTesterBuilder}, types::internals::ZkSyncVmState, - BootloaderState, HistoryEnabled, HistoryMode, SimpleMemory, ToTracerPointer, VmTracer, + BootloaderState, HistoryEnabled, HistoryMode, SimpleMemory, ToTracerPointer, Vm, VmTracer, }, }; #[test] -fn test_vm_rollbacks() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - let mut account = vm.rich_accounts[0].clone(); - let counter = read_test_contract(); - let tx_0 = account.get_deploy_tx(&counter, None, TxType::L2).tx; - let tx_1 = account.get_deploy_tx(&counter, None, TxType::L2).tx; - let tx_2 = account.get_deploy_tx(&counter, None, TxType::L2).tx; - - let result_without_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_processed(tx_0.clone(), false), - TransactionTestInfo::new_processed(tx_1.clone(), false), - TransactionTestInfo::new_processed(tx_2.clone(), false), - ]); - - // reset vm - vm.reset_with_empty_storage(); - - let result_with_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongSignatureLength.into()), - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongMagicValue.into()), - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::WrongSignature.into()), - // The correct nonce is 0, this tx will fail - TransactionTestInfo::new_rejected(tx_2.clone(), TxModifier::WrongNonce.into()), - // This tx will succeed - TransactionTestInfo::new_processed(tx_0.clone(), false), - // The correct nonce is 1, this tx will fail - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::NonceReused.into()), - // The correct nonce is 1, this tx will fail - TransactionTestInfo::new_rejected(tx_2.clone(), TxModifier::WrongNonce.into()), - // This tx will succeed - TransactionTestInfo::new_processed(tx_1, false), - // The correct nonce is 2, this tx will fail - TransactionTestInfo::new_rejected(tx_0.clone(), TxModifier::NonceReused.into()), - // This tx will succeed - TransactionTestInfo::new_processed(tx_2.clone(), false), - // This tx will fail - TransactionTestInfo::new_rejected(tx_2, TxModifier::NonceReused.into()), - TransactionTestInfo::new_rejected(tx_0, TxModifier::NonceReused.into()), - ]); - - assert_eq!(result_without_rollbacks, result_with_rollbacks); +fn vm_rollbacks() { + test_vm_rollbacks::>(); } #[test] -fn test_vm_loadnext_rollbacks() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - let mut account = vm.rich_accounts[0].clone(); - - let loadnext_contract = get_loadnext_contract(); - let loadnext_constructor_data = &[Token::Uint(U256::from(100))]; - let DeployContractsTx { - tx: loadnext_deploy_tx, - address, - .. - } = account.get_deploy_tx_with_factory_deps( - &loadnext_contract.bytecode, - Some(loadnext_constructor_data), - loadnext_contract.factory_deps.clone(), - TxType::L2, - ); - - let loadnext_tx_1 = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: LoadnextContractExecutionParams { - reads: 100, - writes: 100, - events: 100, - hashes: 500, - recursive_calls: 10, - deploys: 60, - } - .to_bytes(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - let loadnext_tx_2 = account.get_l2_tx_for_execute( - Execute { - contract_address: Some(address), - calldata: LoadnextContractExecutionParams { - reads: 100, - writes: 100, - events: 100, - hashes: 500, - recursive_calls: 10, - deploys: 60, - } - .to_bytes(), - value: Default::default(), - factory_deps: vec![], - }, - None, - ); - - let result_without_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_processed(loadnext_deploy_tx.clone(), false), - TransactionTestInfo::new_processed(loadnext_tx_1.clone(), false), - TransactionTestInfo::new_processed(loadnext_tx_2.clone(), false), - ]); - - // reset vm - vm.reset_with_empty_storage(); - - let result_with_rollbacks = vm.execute_and_verify_txs(&vec![ - TransactionTestInfo::new_processed(loadnext_deploy_tx.clone(), false), - TransactionTestInfo::new_processed(loadnext_tx_1.clone(), true), - TransactionTestInfo::new_rejected( - loadnext_deploy_tx.clone(), - TxModifier::NonceReused.into(), - ), - TransactionTestInfo::new_processed(loadnext_tx_1, false), - TransactionTestInfo::new_processed(loadnext_tx_2.clone(), true), - TransactionTestInfo::new_processed(loadnext_tx_2.clone(), true), - TransactionTestInfo::new_rejected(loadnext_deploy_tx, TxModifier::NonceReused.into()), - TransactionTestInfo::new_processed(loadnext_tx_2, false), - ]); - - assert_eq!(result_without_rollbacks, result_with_rollbacks); +fn vm_loadnext_rollbacks() { + test_vm_loadnext_rollbacks::>(); } // Testing tracer that does not allow the recursion to go deeper than a certain limit @@ -263,34 +138,5 @@ fn test_layered_rollback() { #[test] fn rollback_in_call_mode() { - let counter_bytecode = read_test_contract(); - let counter_address = Address::repeat_byte(1); - - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_execution_mode(TxExecutionMode::EthCall) - .with_custom_contracts(vec![(counter_bytecode, counter_address, false)]) - .with_random_rich_accounts(1) - .build(); - let account = &mut vm.rich_accounts[0]; - let tx = account.get_test_contract_transaction(counter_address, true, None, false, TxType::L2); - - let (compression_result, vm_result) = vm - .vm - .execute_transaction_with_bytecode_compression(tx, true); - compression_result.unwrap(); - assert_matches!( - vm_result.result, - ExecutionResult::Revert { output } - if output.to_string().contains("This method always reverts") - ); - - let storage_logs = vm - .vm - .get_current_execution_state() - .deduplicated_storage_logs; - assert!( - storage_logs.iter().all(|log| !log.is_write()), - "{storage_logs:?}" - ); + test_rollback_in_call_mode::>(); } diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs index 7282a8b897c..f821e00f93f 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs @@ -1,4 +1,3 @@ -pub(crate) use transaction_test_info::{TransactionTestInfo, TxModifier}; pub(crate) use vm_tester::{get_empty_storage, InMemoryStorageView, VmTester, VmTesterBuilder}; pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType}; From 80d86c2d88829f13fbcfc27ffc508e6165553593 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 18:31:25 +0300 Subject: [PATCH 08/18] Fix `test_first_in_batch()` --- core/lib/multivm/src/versions/testonly/l2_blocks.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/lib/multivm/src/versions/testonly/l2_blocks.rs b/core/lib/multivm/src/versions/testonly/l2_blocks.rs index 8df75761c9b..20da6568da8 100644 --- a/core/lib/multivm/src/versions/testonly/l2_blocks.rs +++ b/core/lib/multivm/src/versions/testonly/l2_blocks.rs @@ -14,10 +14,11 @@ use zksync_types::{ }; use zksync_utils::{h256_to_u256, u256_to_h256}; -use super::{default_l1_batch, tester::VmTesterBuilder, TestedVm}; +use super::{default_l1_batch, get_empty_storage, tester::VmTesterBuilder, TestedVm}; use crate::{ interface::{ - ExecutionResult, Halt, L2BlockEnv, TxExecutionMode, VmExecutionMode, VmInterfaceExt, + storage::StorageView, ExecutionResult, Halt, L2BlockEnv, TxExecutionMode, VmExecutionMode, + VmInterfaceExt, }, vm_latest::{ constants::{TX_OPERATOR_L2_BLOCK_INFO_OFFSET, TX_OPERATOR_SLOTS_PER_L2_BLOCK_INFO}, @@ -299,7 +300,6 @@ fn test_first_in_batch( let l1_tx = get_l1_noop(); // Setting the values provided. - let mut storage_ptr = vm.storage.borrow_mut(); let miniblock_info_slot = StorageKey::new( AccountTreeId::new(SYSTEM_CONTEXT_ADDRESS), SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, @@ -314,8 +314,7 @@ fn test_first_in_batch( ); let prev_block_hash_position = get_l2_block_hash_key(miniblock_number - 1); - // FIXME: doesn't work for vm_latest - let storage = storage_ptr.inner_mut(); + let mut storage = get_empty_storage(); storage.set_value( miniblock_info_slot, u256_to_h256(pack_block_info( @@ -332,7 +331,9 @@ fn test_first_in_batch( prev_block_hash_position, L2BlockHasher::legacy_hash(L2BlockNumber(miniblock_number - 1)), ); - drop(storage_ptr); + // Replace the storage entirely. It's not enough to write to the underlying storage (since read values are already cached + // in the storage view). + *vm.storage.borrow_mut() = StorageView::new(storage); // In order to skip checks from the Rust side of the VM, we firstly use some definitely correct L2 block info. // And then override it with the user-provided value From 02290f8c7219cf579ebdf8dac7d58032af2bd11c Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 18:34:08 +0300 Subject: [PATCH 09/18] Remove unused modules in `vm_fast` --- .../src/versions/vm_fast/tests/call_tracer.rs | 92 ------------ .../multivm/src/versions/vm_fast/tests/mod.rs | 5 - .../src/versions/vm_fast/tests/utils.rs | 131 ------------------ 3 files changed, 228 deletions(-) delete mode 100644 core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs delete mode 100644 core/lib/multivm/src/versions/vm_fast/tests/utils.rs diff --git a/core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs deleted file mode 100644 index c97b38b6afc..00000000000 --- a/core/lib/multivm/src/versions/vm_fast/tests/call_tracer.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::sync::Arc; - -use once_cell::sync::OnceCell; -use zksync_types::{Address, Execute}; - -use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface}, - tracers::CallTracer, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - tests::{ - tester::VmTesterBuilder, - utils::{read_max_depth_contract, read_test_contract}, - }, - HistoryEnabled, ToTracerPointer, - }, -}; - -// This test is ultra slow, so it's ignored by default. -#[test] -#[ignore] -fn test_max_depth() { - let contarct = read_max_depth_contract(); - let address = Address::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![(contarct, address, true)]) - .build(); - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: address, - calldata: vec![], - value: Default::default(), - factory_deps: None, - }, - None, - ); - - let result = Arc::new(OnceCell::new()); - let call_tracer = CallTracer::new(result.clone()).into_tracer_pointer(); - vm.vm.push_transaction(tx); - let res = vm.vm.inspect(call_tracer.into(), VmExecutionMode::OneTx); - assert!(result.get().is_some()); - assert!(res.result.is_failed()); -} - -#[test] -fn test_basic_behavior() { - let contarct = read_test_contract(); - let address = Address::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) - .with_empty_in_memory_storage() - .with_random_rich_accounts(1) - .with_deployer() - .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![(contarct, address, true)]) - .build(); - - let increment_by_6_calldata = - "7cf5dab00000000000000000000000000000000000000000000000000000000000000006"; - - let account = &mut vm.rich_accounts[0]; - let tx = account.get_l2_tx_for_execute( - Execute { - contract_address: address, - calldata: hex::decode(increment_by_6_calldata).unwrap(), - value: Default::default(), - factory_deps: None, - }, - None, - ); - - let result = Arc::new(OnceCell::new()); - let call_tracer = CallTracer::new(result.clone()).into_tracer_pointer(); - vm.vm.push_transaction(tx); - let res = vm.vm.inspect(call_tracer.into(), VmExecutionMode::OneTx); - - let call_tracer_result = result.get().unwrap(); - - assert_eq!(call_tracer_result.len(), 1); - // Expect that there are a plenty of subcalls underneath. - let subcall = &call_tracer_result[0].calls; - assert!(subcall.len() > 10); - assert!(!res.result.is_failed()); -} diff --git a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs index 99d82c66d68..f385ca2a438 100644 --- a/core/lib/multivm/src/versions/vm_fast/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_fast/tests/mod.rs @@ -37,11 +37,6 @@ mod storage; mod tracing_execution_error; mod transfer; mod upgrade; -/* -// mod call_tracer; FIXME: requires tracers -// mod prestate_tracer; FIXME: is pre-state tracer still relevant? -mod utils; -*/ trait ObjectSafeEq: fmt::Debug + AsRef { fn eq(&self, other: &dyn ObjectSafeEq) -> bool; diff --git a/core/lib/multivm/src/versions/vm_fast/tests/utils.rs b/core/lib/multivm/src/versions/vm_fast/tests/utils.rs deleted file mode 100644 index b089276406c..00000000000 --- a/core/lib/multivm/src/versions/vm_fast/tests/utils.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::collections::BTreeMap; - -use ethabi::Contract; -use once_cell::sync::Lazy; -use zksync_contracts::{ - load_contract, read_bytecode, read_yul_bytecode, BaseSystemContracts, SystemContractCode, -}; -use zksync_types::{ - utils::storage_key_for_standard_token_balance, AccountTreeId, Address, StorageKey, H160, H256, - U256, -}; -use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; -use zksync_vm2::interface::{HeapId, StateInterface}; - -use crate::interface::storage::ReadStorage; - -pub(crate) static BASE_SYSTEM_CONTRACTS: Lazy = - Lazy::new(BaseSystemContracts::load_from_disk); - -pub(crate) fn verify_required_memory( - state: &impl StateInterface, - required_values: Vec<(U256, HeapId, u32)>, -) { - for (required_value, memory_page, cell) in required_values { - let current_value = state.read_heap_u256(memory_page, cell * 32); - assert_eq!(current_value, required_value); - } -} - -pub(crate) fn verify_required_storage( - required_values: &[(H256, StorageKey)], - main_storage: &mut impl ReadStorage, - storage_changes: &BTreeMap<(H160, U256), U256>, -) { - for &(required_value, key) in required_values { - let current_value = storage_changes - .get(&(*key.account().address(), h256_to_u256(*key.key()))) - .copied() - .unwrap_or_else(|| h256_to_u256(main_storage.read_value(&key))); - - assert_eq!( - u256_to_h256(current_value), - required_value, - "Invalid value at key {key:?}" - ); - } -} -pub(crate) fn get_balance( - token_id: AccountTreeId, - account: &Address, - main_storage: &mut impl ReadStorage, - storage_changes: &BTreeMap<(H160, U256), U256>, -) -> U256 { - let key = storage_key_for_standard_token_balance(token_id, account); - - storage_changes - .get(&(*key.account().address(), h256_to_u256(*key.key()))) - .copied() - .unwrap_or_else(|| h256_to_u256(main_storage.read_value(&key))) -} - -pub(crate) fn read_test_contract() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/counter/counter.sol/Counter.json") -} - -pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { - let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; - let bootloader_code = read_yul_bytecode(artifacts_path, test); - - let bootloader_hash = hash_bytecode(&bootloader_code); - SystemContractCode { - code: bytes_to_be_words(bootloader_code), - hash: bootloader_hash, - } -} - -pub(crate) fn read_error_contract() -> Vec { - read_bytecode( - "etc/contracts-test-data/artifacts-zk/contracts/error/error.sol/SimpleRequire.json", - ) -} - -pub(crate) fn get_execute_error_calldata() -> Vec { - let test_contract = load_contract( - "etc/contracts-test-data/artifacts-zk/contracts/error/error.sol/SimpleRequire.json", - ); - - let function = test_contract.function("require_short").unwrap(); - - function - .encode_input(&[]) - .expect("failed to encode parameters") -} - -pub(crate) fn read_many_owners_custom_account_contract() -> (Vec, Contract) { - let path = "etc/contracts-test-data/artifacts-zk/contracts/custom-account/many-owners-custom-account.sol/ManyOwnersCustomAccount.json"; - (read_bytecode(path), load_contract(path)) -} - -pub(crate) fn read_precompiles_contract() -> Vec { - read_bytecode( - "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", - ) -} - -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_nonce_holder_tester() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/custom-account/nonce-holder-test.sol/NonceHolderTest.json") -} - -pub(crate) fn get_complex_upgrade_abi() -> Contract { - load_contract( - "etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json" - ) -} - -pub(crate) fn read_expensive_contract() -> (Vec, Contract) { - const PATH: &str = - "etc/contracts-test-data/artifacts-zk/contracts/expensive/expensive.sol/Expensive.json"; - (read_bytecode(PATH), load_contract(PATH)) -} - -pub(crate) fn read_proxy_counter_contract() -> (Vec, Contract) { - const PATH: &str = "etc/contracts-test-data/artifacts-zk/contracts/counter/proxy_counter.sol/ProxyCounter.json"; - (read_bytecode(PATH), load_contract(PATH)) -} From f1fd717e66dcee9504cc4fbbc7012e55a319cdfa Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 18:54:12 +0300 Subject: [PATCH 10/18] Remove unused modules in `vm_latest` --- .../src/versions/testonly/migration.rs | 43 ++++++ core/lib/multivm/src/versions/testonly/mod.rs | 20 ++- .../src/versions/testonly/tester/mod.rs | 5 + .../versions/vm_latest/tests/call_tracer.rs | 25 ++- .../versions/vm_latest/tests/evm_emulator.rs | 21 ++- .../src/versions/vm_latest/tests/migration.rs | 50 +----- .../src/versions/vm_latest/tests/mod.rs | 143 ++++++++++++++++-- .../vm_latest/tests/prestate_tracer.rs | 16 +- .../src/versions/vm_latest/tests/rollbacks.rs | 16 +- .../vm_latest/tests/tester/inner_state.rs | 116 -------------- 10 files changed, 237 insertions(+), 218 deletions(-) create mode 100644 core/lib/multivm/src/versions/testonly/migration.rs diff --git a/core/lib/multivm/src/versions/testonly/migration.rs b/core/lib/multivm/src/versions/testonly/migration.rs new file mode 100644 index 00000000000..a7764af2264 --- /dev/null +++ b/core/lib/multivm/src/versions/testonly/migration.rs @@ -0,0 +1,43 @@ +use zksync_test_account::TxType; +use zksync_types::{get_code_key, H256, SYSTEM_CONTEXT_ADDRESS}; + +use super::{get_empty_storage, read_test_contract, tester::VmTesterBuilder, TestedVm}; +use crate::interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}; + +/// This test checks that the new bootloader will work fine even if the previous system context contract is not +/// compatible with it, i.e. the bootloader will upgrade it before starting any transaction. +pub(crate) fn test_migration_for_system_context_aa_interaction() { + let mut storage = get_empty_storage(); + // We will set the system context bytecode to zero. + storage.set_value(get_code_key(&SYSTEM_CONTEXT_ADDRESS), H256::zero()); + + // 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() + .with_storage(storage) + .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_random_rich_accounts(1) + .build::(); + + // Now, we will just proceed with standard transaction execution. + // The bootloader should be able to update system context regardless of whether + // the upgrade transaction is there or not. + let account = &mut vm.rich_accounts[0]; + let counter = read_test_contract(); + let tx = account.get_deploy_tx(&counter, None, TxType::L2).tx; + + vm.vm.push_transaction(tx); + let result = vm.vm.execute(VmExecutionMode::OneTx); + assert!( + !result.result.is_failed(), + "Transaction wasn't successful {:#?}", + result.result + ); + + let batch_result = vm.vm.execute(VmExecutionMode::Batch); + assert!( + !batch_result.result.is_failed(), + "Batch transaction wasn't successful {:#?}", + batch_result.result + ); +} diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 0dca6328d4f..ec1c3559b9f 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -1,7 +1,8 @@ use ethabi::Contract; use once_cell::sync::Lazy; use zksync_contracts::{ - load_contract, read_bytecode, read_yul_bytecode, BaseSystemContracts, SystemContractCode, + load_contract, read_bytecode, read_yul_bytecode, read_zbin_bytecode, BaseSystemContracts, + SystemContractCode, }; use zksync_test_account::Account; use zksync_types::{ @@ -12,7 +13,7 @@ use zksync_types::{ use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, u256_to_h256}; use zksync_vm_interface::{L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode}; -pub(super) use self::tester::TestedVm; +pub(super) use self::tester::{TestedVm, VmTester, VmTesterBuilder}; use crate::{ interface::storage::InMemoryStorage, vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT, }; @@ -28,6 +29,7 @@ pub(super) mod get_used_contracts; pub(super) mod is_write_initial; pub(super) mod l1_tx_execution; pub(super) mod l2_blocks; +pub(super) mod migration; pub(super) mod nonce_holder; pub(super) mod precompiles; pub(super) mod refunds; @@ -49,7 +51,7 @@ fn get_empty_storage() -> InMemoryStorage { InMemoryStorage::with_system_contracts(hash_bytecode) } -fn read_test_contract() -> Vec { +pub(crate) fn read_test_contract() -> Vec { read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/counter/counter.sol/Counter.json") } @@ -101,6 +103,18 @@ fn read_error_contract() -> Vec { ) } +pub(crate) fn read_max_depth_contract() -> Vec { + read_zbin_bytecode( + "core/tests/ts-integration/contracts/zkasm/artifacts/deep_stak.zkasm/deep_stak.zkasm.zbin", + ) +} + +pub(crate) fn read_simple_transfer_contract() -> Vec { + read_bytecode( + "etc/contracts-test-data/artifacts-zk/contracts/simple-transfer/simple-transfer.sol/SimpleTransfer.json", + ) +} + pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; let bootloader_code = read_yul_bytecode(artifacts_path, test); diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index a05f5fc7252..e9f872d2522 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -107,6 +107,11 @@ impl VmTesterBuilder { } } + pub(crate) fn with_system_env(mut self, system_env: SystemEnv) -> Self { + self.system_env = system_env; + self + } + pub(crate) fn with_l1_batch_env(mut self, l1_batch_env: L1BatchEnv) -> Self { self.l1_batch_env = Some(l1_batch_env); self diff --git a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs index e7f26b7faf8..f4d2b1bdd7f 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs @@ -3,17 +3,14 @@ use std::sync::Arc; use once_cell::sync::OnceCell; use zksync_types::{Address, Execute}; +use super::TestedLatestVm; use crate::{ interface::{TxExecutionMode, VmExecutionMode, VmInterface}, tracers::CallTracer, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - tests::{ - tester::VmTesterBuilder, - utils::{read_max_depth_contract, read_test_contract}, - }, - HistoryEnabled, ToTracerPointer, + versions::testonly::{ + read_max_depth_contract, read_test_contract, ContractToDeploy, VmTesterBuilder, }, + vm_latest::{constants::BATCH_COMPUTATIONAL_GAS_LIMIT, ToTracerPointer}, }; // This test is ultra slow, so it's ignored by default. @@ -22,14 +19,14 @@ use crate::{ fn test_max_depth() { let contarct = read_max_depth_contract(); let address = Address::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) + let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![(contarct, address, true)]) - .build(); + .with_custom_contracts(vec![ContractToDeploy::account(contarct, address)]) + .build::(); let account = &mut vm.rich_accounts[0]; let tx = account.get_l2_tx_for_execute( @@ -54,16 +51,16 @@ fn test_max_depth() { #[test] fn test_basic_behavior() { - let contarct = read_test_contract(); + let contract = read_test_contract(); let address = Address::random(); - let mut vm = VmTesterBuilder::new(HistoryEnabled) + let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_custom_contracts(vec![(contarct, address, true)]) - .build(); + .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) + .build::(); let increment_by_6_calldata = "7cf5dab00000000000000000000000000000000000000000000000000000000000000006"; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs b/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs index 34780b73eb0..31c8c1304c4 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs @@ -18,15 +18,12 @@ use zksync_utils::{ bytes_to_be_words, h256_to_u256, }; +use super::TestedLatestVm; use crate::{ interface::{ storage::InMemoryStorage, TxExecutionMode, VmExecutionResultAndLogs, VmInterfaceExt, }, - versions::testonly::default_system_env, - vm_latest::{ - tests::tester::{VmTester, VmTesterBuilder}, - HistoryEnabled, - }, + versions::testonly::{default_system_env, VmTester, VmTesterBuilder}, }; const MOCK_DEPLOYER_PATH: &str = "etc/contracts-test-data/artifacts-zk/contracts/mock-evm/mock-evm.sol/MockContractDeployer.json"; @@ -85,7 +82,7 @@ impl EvmTestBuilder { self } - fn build(self) -> VmTester { + fn build(self) -> VmTester { let mock_emulator = read_bytecode(MOCK_EMULATOR_PATH); let mut storage = self.storage; let mut system_env = default_system_env(); @@ -119,7 +116,7 @@ impl EvmTestBuilder { } } - VmTesterBuilder::new(HistoryEnabled) + VmTesterBuilder::new() .with_system_env(system_env) .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) @@ -137,12 +134,12 @@ fn tracing_evm_contract_deployment() { // The EVM emulator will not be accessed, so we set it to a dummy value. system_env.base_system_smart_contracts.evm_emulator = Some(system_env.base_system_smart_contracts.default_aa.clone()); - let mut vm = VmTesterBuilder::new(HistoryEnabled) + let mut vm = VmTesterBuilder::new() .with_system_env(system_env) .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_random_rich_accounts(1) - .build(); + .build::(); let account = &mut vm.rich_accounts[0]; let args = [Token::Bytes((0..32).collect())]; @@ -222,7 +219,7 @@ fn mock_emulator_with_payment(deploy_emulator: bool) { } fn test_payment( - vm: &mut VmTester, + vm: &mut VmTester, mock_emulator_abi: ðabi::Contract, balance: &mut U256, transferred_value: U256, @@ -407,7 +404,7 @@ fn mock_emulator_with_delegate_call() { } fn test_delegate_call( - vm: &mut VmTester, + vm: &mut VmTester, test_fn: ðabi::Function, from: Address, to: Address, @@ -485,7 +482,7 @@ fn mock_emulator_with_static_call() { } fn test_static_call( - vm: &mut VmTester, + vm: &mut VmTester, test_fn: ðabi::Function, from: Address, to: Address, diff --git a/core/lib/multivm/src/versions/vm_latest/tests/migration.rs b/core/lib/multivm/src/versions/vm_latest/tests/migration.rs index 5b8da255180..f033e2e0163 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/migration.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/migration.rs @@ -1,51 +1,9 @@ -use zksync_types::{get_code_key, H256, SYSTEM_CONTEXT_ADDRESS}; - use crate::{ - interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, - vm_latest::{ - tests::{ - tester::{get_empty_storage, DeployContractsTx, TxType, VmTesterBuilder}, - utils::read_test_contract, - }, - HistoryEnabled, - }, + versions::testonly::migration::test_migration_for_system_context_aa_interaction, + vm_latest::{HistoryEnabled, Vm}, }; -/// This test checks that the new bootloader will work fine even if the previous system context contract is not -/// compatible with it, i.e. the bootloader will upgrade it before starting any transaction. #[test] -fn test_migration_for_system_context_aa_interaction() { - let mut storage = get_empty_storage(); - // We will set the system context bytecode to zero. - storage.set_value(get_code_key(&SYSTEM_CONTEXT_ADDRESS), H256::zero()); - - // 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_storage(storage) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build(); - - // Now, we will just proceed with standard transaction execution. - // The bootloader should be able to update system context regardless of whether - // the upgrade transaction is there or not. - let account = &mut vm.rich_accounts[0]; - let counter = read_test_contract(); - let DeployContractsTx { tx, .. } = account.get_deploy_tx(&counter, None, TxType::L2); - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful {:#?}", - result.result - ); - - let batch_result = vm.vm.execute(VmExecutionMode::Batch); - assert!( - !batch_result.result.is_failed(), - "Batch transaction wasn't successful {:#?}", - batch_result.result - ); +fn migration_for_system_context_aa_interaction() { + test_migration_for_system_context_aa_interaction::>(); } 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 1d72a5f8404..da7d6899444 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/mod.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/mod.rs @@ -1,22 +1,27 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use zk_evm_1_5_0::{ aux_structures::{MemoryPage, Timestamp}, + vm_state::VmLocalState, zkevm_opcode_defs::{ContractCodeSha256Format, VersionedHashLen32}, }; -use zksync_types::{writes::StateDiffRecord, StorageKey, Transaction, H256, U256}; +use zksync_types::{writes::StateDiffRecord, StorageKey, StorageValue, Transaction, H256, U256}; use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256}; -use zksync_vm_interface::{ - CurrentExecutionState, L2BlockEnv, VmExecutionMode, VmExecutionResultAndLogs, -}; use super::{HistoryEnabled, Vm}; use crate::{ - interface::storage::{InMemoryStorage, StorageView}, + interface::{ + storage::{InMemoryStorage, ReadStorage, StorageView, WriteStorage}, + CurrentExecutionState, L2BlockEnv, VmExecutionMode, VmExecutionResultAndLogs, + }, versions::testonly::TestedVm, vm_latest::{ - constants::BOOTLOADER_HEAP_PAGE, tests::tester::VmInstanceInnerState, - tracers::PubdataTracer, types::internals::TransactionData, TracerDispatcher, + constants::BOOTLOADER_HEAP_PAGE, + old_vm::{event_sink::InMemoryEventSink, history_recorder::HistoryRecorder}, + tracers::PubdataTracer, + types::internals::TransactionData, + utils::logs::StorageLogQuery, + AppDataFrameManagerWithHistory, HistoryMode, SimpleMemory, TracerDispatcher, }, }; @@ -46,13 +51,13 @@ mod rollbacks; mod secp256r1; mod simple_execution; mod storage; -mod tester; mod tracing_execution_error; mod transfer; mod upgrade; -mod utils; -impl TestedVm for Vm, HistoryEnabled> { +type TestedLatestVm = Vm, HistoryEnabled>; + +impl TestedVm for TestedLatestVm { type StateDump = VmInstanceInnerState; fn dump_state(&self) -> Self::StateDump { @@ -162,3 +167,119 @@ impl TestedVm for Vm, HistoryEnabled> { self.push_raw_transaction(tx, overhead, refund, true) } } + +#[derive(Clone, Debug)] +pub(crate) struct ModifiedKeysMap(HashMap); + +impl ModifiedKeysMap { + fn new(storage: &mut StorageView) -> Self { + let mut modified_keys = storage.modified_storage_keys().clone(); + let inner = storage.inner_mut(); + // Remove modified keys that were set to the same value (e.g., due to a rollback). + modified_keys.retain(|key, value| inner.read_value(key) != *value); + Self(modified_keys) + } +} + +// We consider hashmaps to be equal even if there is a key +// that is not present in one but has zero value in another. +impl PartialEq for ModifiedKeysMap { + fn eq(&self, other: &Self) -> bool { + for (key, value) in &self.0 { + if *value != other.0.get(key).copied().unwrap_or_default() { + return false; + } + } + for (key, value) in &other.0 { + if *value != self.0.get(key).copied().unwrap_or_default() { + return false; + } + } + true + } +} + +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct DecommitterTestInnerState { + /// There is no way to "truly" compare the storage pointer, + /// so we just compare the modified keys. This is reasonable enough. + pub(crate) modified_storage_keys: ModifiedKeysMap, + pub(crate) known_bytecodes: HistoryRecorder>, H>, + pub(crate) decommitted_code_hashes: HistoryRecorder>, HistoryEnabled>, +} + +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct StorageOracleInnerState { + /// There is no way to "truly" compare the storage pointer, + /// so we just compare the modified keys. This is reasonable enough. + pub(crate) modified_storage_keys: ModifiedKeysMap, + pub(crate) frames_stack: AppDataFrameManagerWithHistory, H>, + pub(crate) paid_changes: HistoryRecorder, H>, + pub(crate) initial_values: HistoryRecorder, H>, + pub(crate) returned_io_refunds: HistoryRecorder, H>, + pub(crate) returned_pubdata_costs: HistoryRecorder, H>, +} + +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct PrecompileProcessorTestInnerState { + pub(crate) timestamp_history: HistoryRecorder, H>, +} + +/// A struct that encapsulates the state of the VM's oracles +/// The state is to be used in tests. +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct VmInstanceInnerState { + event_sink: InMemoryEventSink, + precompile_processor_state: PrecompileProcessorTestInnerState, + memory: SimpleMemory, + decommitter_state: DecommitterTestInnerState, + storage_oracle_state: StorageOracleInnerState, + local_state: VmLocalState, +} + +impl Vm, H> { + // Dump inner state of the VM. + 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(), + }; + let memory = self.state.memory.clone(); + let decommitter_state = DecommitterTestInnerState { + modified_storage_keys: ModifiedKeysMap::new( + &mut self + .state + .decommittment_processor + .get_storage() + .borrow_mut(), + ), + known_bytecodes: self.state.decommittment_processor.known_bytecodes.clone(), + decommitted_code_hashes: self + .state + .decommittment_processor + .get_decommitted_code_hashes_with_history() + .clone(), + }; + + let storage_oracle_state = StorageOracleInnerState { + modified_storage_keys: ModifiedKeysMap::new( + &mut self.state.storage.storage.get_ptr().borrow_mut(), + ), + 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_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(); + + VmInstanceInnerState { + event_sink, + precompile_processor_state, + memory, + decommitter_state, + storage_oracle_state, + local_state, + } + } +} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs index 230b1d0ad87..616abe2d8fe 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs @@ -4,25 +4,23 @@ use once_cell::sync::OnceCell; use zksync_test_account::TxType; use zksync_types::{utils::deployed_address_create, Execute, U256}; +use super::TestedLatestVm; use crate::{ interface::{TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt}, tracers::PrestateTracer, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - tests::{tester::VmTesterBuilder, utils::read_simple_transfer_contract}, - HistoryEnabled, ToTracerPointer, - }, + versions::testonly::{read_simple_transfer_contract, VmTesterBuilder}, + vm_latest::{constants::BATCH_COMPUTATIONAL_GAS_LIMIT, ToTracerPointer}, }; #[test] fn test_prestate_tracer() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) + let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); + .build::(); vm.deploy_test_contract(); let account = &mut vm.rich_accounts[0]; @@ -53,13 +51,13 @@ fn test_prestate_tracer() { #[test] fn test_prestate_tracer_diff_mode() { - let mut vm = VmTesterBuilder::new(HistoryEnabled) + let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) - .build(); + .build::(); let contract = read_simple_transfer_contract(); let tx = vm .deployer 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 20f05caea08..05c95fefb6e 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -1,7 +1,9 @@ use ethabi::Token; use zksync_contracts::{get_loadnext_contract, test_contracts::LoadnextContractExecutionParams}; +use zksync_test_account::{DeployContractsTx, TxType}; use zksync_types::{get_nonce_key, U256}; +use super::TestedLatestVm; use crate::{ interface::{ storage::WriteStorage, @@ -9,13 +11,13 @@ use crate::{ TxExecutionMode, VmExecutionMode, VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, }, tracers::dynamic::vm_1_5_0::DynTracer, - versions::testonly::rollbacks::{ - test_rollback_in_call_mode, test_vm_loadnext_rollbacks, test_vm_rollbacks, + versions::testonly::{ + rollbacks::{test_rollback_in_call_mode, test_vm_loadnext_rollbacks, test_vm_rollbacks}, + VmTesterBuilder, }, vm_latest::{ - tests::tester::{DeployContractsTx, TxType, VmTesterBuilder}, - types::internals::ZkSyncVmState, - BootloaderState, HistoryEnabled, HistoryMode, SimpleMemory, ToTracerPointer, Vm, VmTracer, + types::internals::ZkSyncVmState, BootloaderState, HistoryEnabled, HistoryMode, + SimpleMemory, ToTracerPointer, Vm, VmTracer, }, }; @@ -59,11 +61,11 @@ fn test_layered_rollback() { // This test checks that the layered rollbacks work correctly, i.e. // the rollback by the operator will always revert all the changes - let mut vm = VmTesterBuilder::new(HistoryEnabled) + let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) .with_random_rich_accounts(1) - .build(); + .build::(); let account = &mut vm.rich_accounts[0]; let loadnext_contract = get_loadnext_contract().bytecode; 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 3b17762469c..7f93a265547 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 @@ -16,119 +16,3 @@ use crate::{ }, HistoryMode as CommonHistoryMode, }; - -#[derive(Clone, Debug)] -pub(crate) struct ModifiedKeysMap(HashMap); - -impl ModifiedKeysMap { - fn new(storage: &mut StorageView) -> Self { - let mut modified_keys = storage.modified_storage_keys().clone(); - let inner = storage.inner_mut(); - // Remove modified keys that were set to the same value (e.g., due to a rollback). - modified_keys.retain(|key, value| inner.read_value(key) != *value); - Self(modified_keys) - } -} - -// We consider hashmaps to be equal even if there is a key -// that is not present in one but has zero value in another. -impl PartialEq for ModifiedKeysMap { - fn eq(&self, other: &Self) -> bool { - for (key, value) in &self.0 { - if *value != other.0.get(key).copied().unwrap_or_default() { - return false; - } - } - for (key, value) in &other.0 { - if *value != self.0.get(key).copied().unwrap_or_default() { - return false; - } - } - true - } -} - -#[derive(Clone, PartialEq, Debug)] -pub(crate) struct DecommitterTestInnerState { - /// There is no way to "truly" compare the storage pointer, - /// so we just compare the modified keys. This is reasonable enough. - pub(crate) modified_storage_keys: ModifiedKeysMap, - pub(crate) known_bytecodes: HistoryRecorder>, H>, - pub(crate) decommitted_code_hashes: HistoryRecorder>, HistoryEnabled>, -} - -#[derive(Clone, PartialEq, Debug)] -pub(crate) struct StorageOracleInnerState { - /// There is no way to "truly" compare the storage pointer, - /// so we just compare the modified keys. This is reasonable enough. - pub(crate) modified_storage_keys: ModifiedKeysMap, - pub(crate) frames_stack: AppDataFrameManagerWithHistory, H>, - pub(crate) paid_changes: HistoryRecorder, H>, - pub(crate) initial_values: HistoryRecorder, H>, - pub(crate) returned_io_refunds: HistoryRecorder, H>, - pub(crate) returned_pubdata_costs: HistoryRecorder, H>, -} - -#[derive(Clone, PartialEq, Debug)] -pub(crate) struct PrecompileProcessorTestInnerState { - pub(crate) timestamp_history: HistoryRecorder, H>, -} - -/// A struct that encapsulates the state of the VM's oracles -/// The state is to be used in tests. -#[derive(Clone, PartialEq, Debug)] -pub(crate) struct VmInstanceInnerState { - event_sink: InMemoryEventSink, - precompile_processor_state: PrecompileProcessorTestInnerState, - memory: SimpleMemory, - decommitter_state: DecommitterTestInnerState, - storage_oracle_state: StorageOracleInnerState, - local_state: VmLocalState, -} - -impl Vm, H> { - // Dump inner state of the VM. - 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(), - }; - let memory = self.state.memory.clone(); - let decommitter_state = DecommitterTestInnerState { - modified_storage_keys: ModifiedKeysMap::new( - &mut self - .state - .decommittment_processor - .get_storage() - .borrow_mut(), - ), - known_bytecodes: self.state.decommittment_processor.known_bytecodes.clone(), - decommitted_code_hashes: self - .state - .decommittment_processor - .get_decommitted_code_hashes_with_history() - .clone(), - }; - - let storage_oracle_state = StorageOracleInnerState { - modified_storage_keys: ModifiedKeysMap::new( - &mut self.state.storage.storage.get_ptr().borrow_mut(), - ), - 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_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(); - - VmInstanceInnerState { - event_sink, - precompile_processor_state, - memory, - decommitter_state, - storage_oracle_state, - local_state, - } - } -} From e7964effe0f2a94276f61ef0d9d317339b39b6fc Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 18:57:57 +0300 Subject: [PATCH 11/18] Remove more unused files --- .../vm_latest/tests/tester/inner_state.rs | 18 -- .../versions/vm_latest/tests/tester/mod.rs | 8 - .../tests/tester/transaction_test_info.rs | 218 ------------- .../vm_latest/tests/tester/vm_tester.rs | 299 ------------------ .../src/versions/vm_latest/tests/utils.rs | 143 --------- 5 files changed, 686 deletions(-) delete mode 100644 core/lib/multivm/src/versions/vm_latest/tests/tester/inner_state.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/tests/tester/transaction_test_info.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/tests/tester/vm_tester.rs delete mode 100644 core/lib/multivm/src/versions/vm_latest/tests/utils.rs 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 deleted file mode 100644 index 7f93a265547..00000000000 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/inner_state.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::collections::HashMap; - -use zk_evm_1_5_0::{aux_structures::Timestamp, vm_state::VmLocalState}; -use zksync_types::{StorageKey, StorageValue, U256}; -use zksync_vm_interface::storage::StorageView; - -use crate::{ - interface::storage::{ReadStorage, WriteStorage}, - vm_latest::{ - old_vm::{ - event_sink::InMemoryEventSink, - history_recorder::{AppDataFrameManagerWithHistory, HistoryRecorder}, - }, - utils::logs::StorageLogQuery, - HistoryEnabled, HistoryMode, SimpleMemory, Vm, - }, - HistoryMode as CommonHistoryMode, -}; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs deleted file mode 100644 index f821e00f93f..00000000000 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub(crate) use vm_tester::{get_empty_storage, InMemoryStorageView, VmTester, VmTesterBuilder}; -pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType}; - -pub(crate) use self::inner_state::VmInstanceInnerState; // FIXME - -mod inner_state; -mod transaction_test_info; -mod vm_tester; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/transaction_test_info.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/transaction_test_info.rs deleted file mode 100644 index 08667ccc625..00000000000 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/transaction_test_info.rs +++ /dev/null @@ -1,218 +0,0 @@ -use zksync_types::{ExecuteTransactionCommon, Transaction}; - -use crate::{ - interface::{ - CurrentExecutionState, ExecutionResult, Halt, TxRevertReason, VmExecutionMode, - VmExecutionResultAndLogs, VmInterface, VmInterfaceExt, VmInterfaceHistoryEnabled, - VmRevertReason, - }, - vm_latest::{tests::tester::vm_tester::VmTester, HistoryEnabled}, -}; - -#[derive(Debug, Clone)] -pub(crate) enum TxModifier { - WrongSignatureLength, - WrongSignature, - WrongMagicValue, - WrongNonce, - NonceReused, -} - -#[derive(Debug, Clone)] -pub(crate) enum TxExpectedResult { - Rejected { error: ExpectedError }, - Processed { rollback: bool }, -} - -#[derive(Debug, Clone)] -pub(crate) struct TransactionTestInfo { - tx: Transaction, - result: TxExpectedResult, -} - -#[derive(Debug, Clone)] -pub(crate) struct ExpectedError { - pub(crate) revert_reason: TxRevertReason, - pub(crate) modifier: Option, -} - -impl From for ExpectedError { - fn from(value: TxModifier) -> Self { - let revert_reason = match value { - TxModifier::WrongSignatureLength => { - Halt::ValidationFailed(VmRevertReason::General { - msg: "Signature length is incorrect".to_string(), - data: vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 83, 105, 103, 110, 97, 116, 117, 114, 101, 32, - 108, 101, 110, 103, 116, 104, 32, 105, 115, 32, 105, 110, 99, 111, 114, 114, 101, 99, - 116, 0, 0, 0, - ], - }) - } - TxModifier::WrongSignature => { - Halt::ValidationFailed(VmRevertReason::General { - msg: "Account validation returned invalid magic value. Most often this means that the signature is incorrect".to_string(), - data: vec![], - }) - } - TxModifier::WrongMagicValue => { - Halt::ValidationFailed(VmRevertReason::General { - msg: "v is neither 27 nor 28".to_string(), - data: vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 118, 32, 105, 115, 32, 110, 101, 105, 116, 104, - 101, 114, 32, 50, 55, 32, 110, 111, 114, 32, 50, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - }) - - } - TxModifier::WrongNonce => { - Halt::ValidationFailed(VmRevertReason::General { - msg: "Incorrect nonce".to_string(), - data: vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 73, 110, 99, 111, 114, 114, 101, 99, 116, 32, 110, - 111, 110, 99, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - }) - } - TxModifier::NonceReused => { - Halt::ValidationFailed(VmRevertReason::General { - msg: "Reusing the same nonce twice".to_string(), - data: vec![ - 8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 82, 101, 117, 115, 105, 110, 103, 32, 116, 104, - 101, 32, 115, 97, 109, 101, 32, 110, 111, 110, 99, 101, 32, 116, 119, 105, 99, 101, 0, - 0, 0, 0, - ], - }) - } - }; - - ExpectedError { - revert_reason: TxRevertReason::Halt(revert_reason), - modifier: Some(value), - } - } -} - -impl TransactionTestInfo { - pub(crate) fn new_rejected( - mut transaction: Transaction, - expected_error: ExpectedError, - ) -> Self { - transaction.common_data = match transaction.common_data { - ExecuteTransactionCommon::L2(mut data) => { - if let Some(modifier) = &expected_error.modifier { - match modifier { - TxModifier::WrongSignatureLength => { - data.signature = data.signature[..data.signature.len() - 20].to_vec() - } - TxModifier::WrongSignature => data.signature = vec![27u8; 65], - TxModifier::WrongMagicValue => data.signature = vec![1u8; 65], - TxModifier::WrongNonce => { - // Do not need to modify signature for nonce error - } - TxModifier::NonceReused => { - // Do not need to modify signature for nonce error - } - } - } - ExecuteTransactionCommon::L2(data) - } - _ => panic!("L1 transactions are not supported"), - }; - - Self { - tx: transaction, - result: TxExpectedResult::Rejected { - error: expected_error, - }, - } - } - - pub(crate) fn new_processed(transaction: Transaction, should_be_rollbacked: bool) -> Self { - Self { - tx: transaction, - result: TxExpectedResult::Processed { - rollback: should_be_rollbacked, - }, - } - } - - fn verify_result(&self, result: &VmExecutionResultAndLogs) { - match &self.result { - TxExpectedResult::Rejected { error } => match &result.result { - ExecutionResult::Success { .. } => { - panic!("Transaction should be reverted {:?}", self.tx.nonce()) - } - ExecutionResult::Revert { output } => match &error.revert_reason { - TxRevertReason::TxReverted(expected) => { - assert_eq!(output, expected) - } - _ => { - panic!("Error types mismatch"); - } - }, - ExecutionResult::Halt { reason } => match &error.revert_reason { - TxRevertReason::Halt(expected) => { - assert_eq!(reason, expected) - } - _ => { - panic!("Error types mismatch"); - } - }, - }, - TxExpectedResult::Processed { .. } => { - assert!(!result.result.is_failed()); - } - } - } - - fn should_rollback(&self) -> bool { - match &self.result { - TxExpectedResult::Rejected { .. } => true, - TxExpectedResult::Processed { rollback } => *rollback, - } - } -} - -impl VmTester { - pub(crate) fn execute_and_verify_txs( - &mut self, - txs: &[TransactionTestInfo], - ) -> CurrentExecutionState { - for tx_test_info in txs { - self.execute_tx_and_verify(tx_test_info.clone()); - } - self.vm.execute(VmExecutionMode::Batch); - let mut state = self.vm.get_current_execution_state(); - state.used_contract_hashes.sort(); - state - } - - pub(crate) fn execute_tx_and_verify( - &mut self, - tx_test_info: TransactionTestInfo, - ) -> VmExecutionResultAndLogs { - let inner_state_before = self.vm.dump_inner_state(); - self.vm.make_snapshot(); - self.vm.push_transaction(tx_test_info.tx.clone()); - let result = self.vm.execute(VmExecutionMode::OneTx); - tx_test_info.verify_result(&result); - if tx_test_info.should_rollback() { - self.vm.rollback_to_the_latest_snapshot(); - let inner_state_after = self.vm.dump_inner_state(); - assert_eq!( - inner_state_before, inner_state_after, - "Inner state before and after rollback should be equal" - ); - } - result - } -} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/tester/vm_tester.rs b/core/lib/multivm/src/versions/vm_latest/tests/tester/vm_tester.rs deleted file mode 100644 index 1fe4232c778..00000000000 --- a/core/lib/multivm/src/versions/vm_latest/tests/tester/vm_tester.rs +++ /dev/null @@ -1,299 +0,0 @@ -use std::marker::PhantomData; - -use zksync_contracts::BaseSystemContracts; -use zksync_types::{ - block::L2BlockHasher, - fee_model::BatchFeeInput, - get_code_key, get_is_account_key, - helpers::unix_timestamp_ms, - utils::{deployed_address_create, storage_key_for_eth_balance}, - Address, L1BatchNumber, L2BlockNumber, L2ChainId, Nonce, ProtocolVersionId, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, u256_to_h256}; - -use crate::{ - interface::{ - storage::{InMemoryStorage, StoragePtr, StorageView, WriteStorage}, - L1BatchEnv, L2Block, L2BlockEnv, SystemEnv, TxExecutionMode, VmExecutionMode, VmFactory, - VmInterface, VmInterfaceExt, - }, - vm_latest::{ - constants::BATCH_COMPUTATIONAL_GAS_LIMIT, - tests::{ - tester::{Account, TxType}, - utils::read_test_contract, - }, - utils::l2_blocks::load_last_l2_block, - Vm, - }, - HistoryMode, -}; - -pub(crate) type InMemoryStorageView = StorageView; - -pub(crate) struct VmTester { - pub(crate) vm: Vm, - pub(crate) storage: StoragePtr, - pub(crate) fee_account: Address, - pub(crate) deployer: Option, - pub(crate) test_contract: Option
, - pub(crate) rich_accounts: Vec, - pub(crate) custom_contracts: Vec, - _phantom: std::marker::PhantomData, -} - -impl VmTester { - pub(crate) fn deploy_test_contract(&mut self) { - let contract = read_test_contract(); - let tx = self - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; - let nonce = tx.nonce().unwrap().0.into(); - self.vm.push_transaction(tx); - self.vm.execute(VmExecutionMode::OneTx); - let deployed_address = - deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); - self.test_contract = Some(deployed_address); - } - - pub(crate) fn reset_with_empty_storage(&mut self) { - self.storage = StorageView::new(get_empty_storage()).to_rc_ptr(); - self.reset_state(false); - } - - /// Reset the state of the VM to the initial state. - /// If `use_latest_l2_block` is true, then the VM will use the latest L2 block from storage, - /// otherwise it will use the first L2 block of l1 batch env - pub(crate) fn reset_state(&mut self, use_latest_l2_block: bool) { - for account in self.rich_accounts.iter_mut() { - account.nonce = Nonce(0); - make_account_rich(self.storage.clone(), account); - } - if let Some(deployer) = &self.deployer { - make_account_rich(self.storage.clone(), deployer); - } - - if !self.custom_contracts.is_empty() { - println!("Inserting custom contracts is not yet supported") - // `insert_contracts(&mut self.storage, &self.custom_contracts);` - } - - let mut l1_batch = self.vm.batch_env.clone(); - if use_latest_l2_block { - let last_l2_block = load_last_l2_block(&self.storage).unwrap_or(L2Block { - number: 0, - timestamp: 0, - hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - }); - l1_batch.first_l2_block = L2BlockEnv { - number: last_l2_block.number + 1, - timestamp: std::cmp::max(last_l2_block.timestamp + 1, l1_batch.timestamp), - prev_block_hash: last_l2_block.hash, - max_virtual_blocks_to_create: 1, - }; - } - - let vm = Vm::new(l1_batch, self.vm.system_env.clone(), self.storage.clone()); - - if self.test_contract.is_some() { - self.deploy_test_contract(); - } - - self.vm = vm; - } -} - -pub(crate) type ContractsToDeploy = (Vec, Address, bool); - -pub(crate) struct VmTesterBuilder { - storage: Option, - l1_batch_env: Option, - system_env: SystemEnv, - deployer: Option, - rich_accounts: Vec, - custom_contracts: Vec, - _phantom: PhantomData, -} - -impl Clone for VmTesterBuilder { - fn clone(&self) -> Self { - Self { - storage: None, - l1_batch_env: self.l1_batch_env.clone(), - system_env: self.system_env.clone(), - deployer: self.deployer.clone(), - rich_accounts: self.rich_accounts.clone(), - custom_contracts: self.custom_contracts.clone(), - _phantom: PhantomData, - } - } -} - -#[allow(dead_code)] -impl VmTesterBuilder { - pub(crate) fn new(_: H) -> Self { - Self { - storage: None, - l1_batch_env: None, - system_env: SystemEnv { - zk_porter_available: false, - version: ProtocolVersionId::latest(), - base_system_smart_contracts: BaseSystemContracts::playground(), - bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - execution_mode: TxExecutionMode::VerifyExecute, - default_validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT, - chain_id: L2ChainId::from(270), - }, - deployer: None, - rich_accounts: vec![], - custom_contracts: vec![], - _phantom: PhantomData, - } - } - - pub(crate) fn with_l1_batch_env(mut self, l1_batch_env: L1BatchEnv) -> Self { - self.l1_batch_env = Some(l1_batch_env); - self - } - - pub(crate) fn with_system_env(mut self, system_env: SystemEnv) -> Self { - self.system_env = system_env; - self - } - - pub(crate) fn with_storage(mut self, storage: InMemoryStorage) -> Self { - self.storage = Some(storage); - self - } - - pub(crate) fn with_base_system_smart_contracts( - mut self, - base_system_smart_contracts: BaseSystemContracts, - ) -> Self { - self.system_env.base_system_smart_contracts = base_system_smart_contracts; - self - } - - pub(crate) fn with_bootloader_gas_limit(mut self, gas_limit: u32) -> Self { - self.system_env.bootloader_gas_limit = gas_limit; - self - } - - pub(crate) fn with_execution_mode(mut self, execution_mode: TxExecutionMode) -> Self { - self.system_env.execution_mode = execution_mode; - self - } - - pub(crate) fn with_empty_in_memory_storage(mut self) -> Self { - self.storage = Some(get_empty_storage()); - self - } - - pub(crate) fn with_random_rich_accounts(mut self, number: u32) -> Self { - for _ in 0..number { - let account = Account::random(); - self.rich_accounts.push(account); - } - self - } - - pub(crate) fn with_rich_accounts(mut self, accounts: Vec) -> Self { - self.rich_accounts.extend(accounts); - self - } - - pub(crate) fn with_deployer(mut self) -> Self { - let deployer = Account::random(); - self.deployer = Some(deployer); - self - } - - pub(crate) fn with_custom_contracts(mut self, contracts: Vec) -> Self { - self.custom_contracts = contracts; - self - } - - pub(crate) fn build(self) -> VmTester { - let l1_batch_env = self - .l1_batch_env - .unwrap_or_else(|| default_l1_batch(L1BatchNumber(1))); - - let mut raw_storage = self.storage.unwrap_or_else(get_empty_storage); - insert_contracts(&mut raw_storage, &self.custom_contracts); - let storage_ptr = StorageView::new(raw_storage).to_rc_ptr(); - for account in self.rich_accounts.iter() { - make_account_rich(storage_ptr.clone(), account); - } - if let Some(deployer) = &self.deployer { - make_account_rich(storage_ptr.clone(), deployer); - } - let fee_account = l1_batch_env.fee_account; - - let vm = Vm::new(l1_batch_env, self.system_env, storage_ptr.clone()); - - VmTester { - vm, - storage: storage_ptr, - fee_account, - deployer: self.deployer, - test_contract: None, - rich_accounts: self.rich_accounts.clone(), - custom_contracts: self.custom_contracts.clone(), - _phantom: PhantomData, - } - } -} - -pub(crate) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { - let timestamp = unix_timestamp_ms(); - L1BatchEnv { - previous_batch_hash: None, - number, - timestamp, - fee_input: BatchFeeInput::l1_pegged( - 50_000_000_000, // 50 gwei - 250_000_000, // 0.25 gwei - ), - fee_account: Address::random(), - enforced_base_fee: None, - first_l2_block: L2BlockEnv { - number: 1, - timestamp, - prev_block_hash: L2BlockHasher::legacy_hash(L2BlockNumber(0)), - max_virtual_blocks_to_create: 100, - }, - } -} - -pub(crate) fn make_account_rich(storage: StoragePtr, account: &Account) { - let key = storage_key_for_eth_balance(&account.address); - storage - .as_ref() - .borrow_mut() - .set_value(key, u256_to_h256(U256::from(10u64.pow(19)))); -} - -pub(crate) fn get_empty_storage() -> InMemoryStorage { - InMemoryStorage::with_system_contracts(hash_bytecode) -} - -// Inserts the contracts into the test environment, bypassing the -// deployer system contract. Besides the reference to storage -// it accepts a `contracts` tuple of information about the contract -// and whether or not it is an account. -fn insert_contracts(raw_storage: &mut InMemoryStorage, contracts: &[ContractsToDeploy]) { - for (contract, address, is_account) in contracts { - let deployer_code_key = get_code_key(address); - raw_storage.set_value(deployer_code_key, hash_bytecode(contract)); - - if *is_account { - let is_account_key = get_is_account_key(address); - raw_storage.set_value(is_account_key, u256_to_h256(1_u32.into())); - } - - raw_storage.store_factory_dep(hash_bytecode(contract), contract.clone()); - } -} diff --git a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs b/core/lib/multivm/src/versions/vm_latest/tests/utils.rs deleted file mode 100644 index 4d728962feb..00000000000 --- a/core/lib/multivm/src/versions/vm_latest/tests/utils.rs +++ /dev/null @@ -1,143 +0,0 @@ -use ethabi::Contract; -use once_cell::sync::Lazy; -use zksync_contracts::{ - load_contract, read_bytecode, read_yul_bytecode, read_zbin_bytecode, BaseSystemContracts, - SystemContractCode, -}; -use zksync_types::{ - utils::storage_key_for_standard_token_balance, AccountTreeId, Address, StorageKey, H256, U256, -}; -use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, h256_to_u256, u256_to_h256}; - -use crate::{ - interface::storage::{StoragePtr, WriteStorage}, - vm_latest::{tests::tester::InMemoryStorageView, types::internals::ZkSyncVmState, HistoryMode}, -}; - -pub(crate) static BASE_SYSTEM_CONTRACTS: Lazy = - Lazy::new(BaseSystemContracts::load_from_disk); - -// Probably make it a part of vm tester -pub(crate) fn verify_required_storage( - state: &ZkSyncVmState, - required_values: Vec<(H256, StorageKey)>, -) { - for (required_value, key) in required_values { - let current_value = state.storage.storage.read_from_storage(&key); - - assert_eq!( - u256_to_h256(current_value), - required_value, - "Invalid value at key {key:?}" - ); - } -} - -pub(crate) fn verify_required_memory( - state: &ZkSyncVmState, - required_values: Vec<(U256, u32, u32)>, -) { - for (required_value, memory_page, cell) in required_values { - let current_value = state - .memory - .read_slot(memory_page as usize, cell as usize) - .value; - assert_eq!(current_value, required_value); - } -} - -pub(crate) fn get_balance( - token_id: AccountTreeId, - account: &Address, - main_storage: StoragePtr, -) -> U256 { - let key = storage_key_for_standard_token_balance(token_id, account); - h256_to_u256(main_storage.borrow_mut().read_value(&key)) -} - -pub(crate) fn read_test_contract() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/counter/counter.sol/Counter.json") -} - -pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { - let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; - let bootloader_code = read_yul_bytecode(artifacts_path, test); - - let bootloader_hash = hash_bytecode(&bootloader_code); - SystemContractCode { - code: bytes_to_be_words(bootloader_code), - hash: bootloader_hash, - } -} - -pub(crate) fn read_nonce_holder_tester() -> Vec { - read_bytecode("etc/contracts-test-data/artifacts-zk/contracts/custom-account/nonce-holder-test.sol/NonceHolderTest.json") -} - -pub(crate) fn read_error_contract() -> Vec { - read_bytecode( - "etc/contracts-test-data/artifacts-zk/contracts/error/error.sol/SimpleRequire.json", - ) -} - -pub(crate) fn read_simple_transfer_contract() -> Vec { - read_bytecode( - "etc/contracts-test-data/artifacts-zk/contracts/simple-transfer/simple-transfer.sol/SimpleTransfer.json", - ) -} - -pub(crate) fn get_execute_error_calldata() -> Vec { - let test_contract = load_contract( - "etc/contracts-test-data/artifacts-zk/contracts/error/error.sol/SimpleRequire.json", - ); - - let function = test_contract.function("require_short").unwrap(); - - function - .encode_input(&[]) - .expect("failed to encode parameters") -} - -pub(crate) fn read_many_owners_custom_account_contract() -> (Vec, Contract) { - let path = "etc/contracts-test-data/artifacts-zk/contracts/custom-account/many-owners-custom-account.sol/ManyOwnersCustomAccount.json"; - (read_bytecode(path), load_contract(path)) -} - -pub(crate) fn read_max_depth_contract() -> Vec { - read_zbin_bytecode( - "core/tests/ts-integration/contracts/zkasm/artifacts/deep_stak.zkasm/deep_stak.zkasm.zbin", - ) -} - -pub(crate) fn read_precompiles_contract() -> Vec { - read_bytecode( - "etc/contracts-test-data/artifacts-zk/contracts/precompiles/precompiles.sol/Precompiles.json", - ) -} - -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") -} - -pub(crate) fn get_complex_upgrade_abi() -> Contract { - load_contract( - "etc/contracts-test-data/artifacts-zk/contracts/complex-upgrade/complex-upgrade.sol/ComplexUpgrade.json" - ) -} - -pub(crate) fn read_expensive_contract() -> (Vec, Contract) { - const PATH: &str = - "etc/contracts-test-data/artifacts-zk/contracts/expensive/expensive.sol/Expensive.json"; - (read_bytecode(PATH), load_contract(PATH)) -} - -pub(crate) fn read_proxy_counter_contract() -> (Vec, Contract) { - const PATH: &str = "etc/contracts-test-data/artifacts-zk/contracts/counter/proxy_counter.sol/ProxyCounter.json"; - (read_bytecode(PATH), load_contract(PATH)) -} From 2ebac02c60588630765ab2299fae58998efccad0 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 19:04:24 +0300 Subject: [PATCH 12/18] Simplify `VmTester` --- .../multivm/src/versions/testonly/circuits.rs | 1 - .../src/versions/testonly/nonce_holder.rs | 1 - .../src/versions/testonly/precompiles.rs | 3 -- .../src/versions/testonly/simple_execution.rs | 2 - .../multivm/src/versions/testonly/storage.rs | 1 - .../src/versions/testonly/tester/mod.rs | 44 +++---------------- .../testonly/tracing_execution_error.rs | 1 - .../multivm/src/versions/testonly/transfer.rs | 2 - .../versions/vm_latest/tests/call_tracer.rs | 2 - .../vm_latest/tests/prestate_tracer.rs | 21 +++------ 10 files changed, 10 insertions(+), 68 deletions(-) diff --git a/core/lib/multivm/src/versions/testonly/circuits.rs b/core/lib/multivm/src/versions/testonly/circuits.rs index 26259247013..fbd01e15c0f 100644 --- a/core/lib/multivm/src/versions/testonly/circuits.rs +++ b/core/lib/multivm/src/versions/testonly/circuits.rs @@ -13,7 +13,6 @@ pub(crate) fn test_circuits() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/nonce_holder.rs b/core/lib/multivm/src/versions/testonly/nonce_holder.rs index 2d46c293237..8c7c01ddbdc 100644 --- a/core/lib/multivm/src/versions/testonly/nonce_holder.rs +++ b/core/lib/multivm/src/versions/testonly/nonce_holder.rs @@ -77,7 +77,6 @@ pub(crate) fn test_nonce_holder() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() .with_custom_contracts(vec![ContractToDeploy::account( read_nonce_holder_tester(), account.address, diff --git a/core/lib/multivm/src/versions/testonly/precompiles.rs b/core/lib/multivm/src/versions/testonly/precompiles.rs index 4ab7d49ee88..bb734a6e591 100644 --- a/core/lib/multivm/src/versions/testonly/precompiles.rs +++ b/core/lib/multivm/src/versions/testonly/precompiles.rs @@ -15,7 +15,6 @@ pub(crate) fn test_keccak() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) @@ -52,7 +51,6 @@ pub(crate) fn test_sha256() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) @@ -87,7 +85,6 @@ pub(crate) fn test_ecrecover() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/simple_execution.rs b/core/lib/multivm/src/versions/testonly/simple_execution.rs index 933022380be..59aa6255675 100644 --- a/core/lib/multivm/src/versions/testonly/simple_execution.rs +++ b/core/lib/multivm/src/versions/testonly/simple_execution.rs @@ -7,7 +7,6 @@ use crate::interface::{ExecutionResult, VmExecutionMode, VmInterfaceExt}; pub(crate) fn test_estimate_fee() { let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_deployer() .with_random_rich_accounts(1) .build::(); @@ -31,7 +30,6 @@ pub(crate) fn test_estimate_fee() { pub(crate) fn test_simple_execute() { let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_deployer() .with_random_rich_accounts(1) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/storage.rs b/core/lib/multivm/src/versions/testonly/storage.rs index 86218675b5e..a64b589ed50 100644 --- a/core/lib/multivm/src/versions/testonly/storage.rs +++ b/core/lib/multivm/src/versions/testonly/storage.rs @@ -17,7 +17,6 @@ fn test_storage(first_tx_calldata: Vec, second_tx_calldata: Ve let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() .with_random_rich_accounts(1) .with_custom_contracts(vec![ContractToDeploy::new(bytecode, test_contract_address)]) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index e9f872d2522..d02bdaada9b 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -26,33 +26,25 @@ use crate::{ mod transaction_test_info; -// FIXME: revise fields #[derive(Debug)] pub(crate) struct VmTester { pub(crate) vm: VM, pub(crate) system_env: SystemEnv, pub(crate) l1_batch_env: L1BatchEnv, pub(crate) storage: StoragePtr>, - pub(crate) deployer: Option, pub(crate) test_contract: Option
, pub(crate) rich_accounts: Vec, - pub(crate) custom_contracts: Vec, } impl VmTester { pub(crate) fn deploy_test_contract(&mut self) { let contract = read_test_contract(); - let tx = self - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; + let account = &mut self.rich_accounts[0]; + let tx = account.get_deploy_tx(&contract, None, TxType::L2).tx; let nonce = tx.nonce().unwrap().0.into(); self.vm.push_transaction(tx); self.vm.execute(VmExecutionMode::OneTx); - let deployed_address = - deployed_address_create(self.deployer.as_ref().unwrap().address, nonce); + let deployed_address = deployed_address_create(account.address, nonce); self.test_contract = Some(deployed_address); } @@ -62,7 +54,7 @@ impl VmTester { pub(crate) fn reset_with_empty_storage(&mut self) { let mut storage = get_empty_storage(); - for account in self.rich_accounts.iter().chain(self.deployer.as_ref()) { + for account in &self.rich_accounts { make_account_rich(&mut storage, account); } @@ -77,31 +69,16 @@ pub(crate) struct VmTesterBuilder { storage: Option, l1_batch_env: Option, system_env: SystemEnv, - deployer: Option, rich_accounts: Vec, custom_contracts: Vec, } -impl Clone for VmTesterBuilder { - fn clone(&self) -> Self { - Self { - storage: None, - l1_batch_env: self.l1_batch_env.clone(), - system_env: self.system_env.clone(), - deployer: self.deployer.clone(), - rich_accounts: self.rich_accounts.clone(), - custom_contracts: self.custom_contracts.clone(), - } - } -} - impl VmTesterBuilder { pub(crate) fn new() -> Self { Self { storage: None, l1_batch_env: None, system_env: default_system_env(), - deployer: None, rich_accounts: vec![], custom_contracts: vec![], } @@ -158,12 +135,6 @@ impl VmTesterBuilder { self } - pub(crate) fn with_deployer(mut self) -> Self { - let deployer = Account::random(); - self.deployer = Some(deployer); - self - } - pub(crate) fn with_custom_contracts(mut self, contracts: Vec) -> Self { self.custom_contracts = contracts; self @@ -180,12 +151,9 @@ impl VmTesterBuilder { let mut raw_storage = self.storage.unwrap_or_else(get_empty_storage); ContractToDeploy::insert_all(&self.custom_contracts, &mut raw_storage); let storage = StorageView::new(raw_storage).to_rc_ptr(); - for account in self.rich_accounts.iter() { + for account in &self.rich_accounts { make_account_rich(storage.borrow_mut().inner_mut(), account); } - if let Some(deployer) = &self.deployer { - make_account_rich(storage.borrow_mut().inner_mut(), deployer); - } let vm = VM::new( l1_batch_env.clone(), @@ -197,10 +165,8 @@ impl VmTesterBuilder { system_env: self.system_env, l1_batch_env, storage, - deployer: self.deployer, test_contract: None, rich_accounts: self.rich_accounts.clone(), - custom_contracts: self.custom_contracts.clone(), } } } diff --git a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs index 1072ae17333..2f7f6d212ce 100644 --- a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs +++ b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs @@ -29,7 +29,6 @@ pub(crate) fn test_tracing_of_execution_errors() { contract_address, )]) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() .with_random_rich_accounts(1) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/transfer.rs b/core/lib/multivm/src/versions/testonly/transfer.rs index 6e054ca977e..7a7948686e8 100644 --- a/core/lib/multivm/src/versions/testonly/transfer.rs +++ b/core/lib/multivm/src/versions/testonly/transfer.rs @@ -53,7 +53,6 @@ fn test_send_or_transfer(test_option: TestOptions) { let mut vm = VmTesterBuilder::new() .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() .with_random_rich_accounts(1) .with_custom_contracts(vec![ ContractToDeploy::new(test_bytecode, test_contract_address), @@ -138,7 +137,6 @@ fn test_reentrancy_protection_send_or_transfer(test_option: TestOp let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_deployer() .with_random_rich_accounts(1) .with_custom_contracts(vec![ ContractToDeploy::new(test_bytecode, test_contract_address), diff --git a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs index f4d2b1bdd7f..b5a1eb4066c 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs @@ -22,7 +22,6 @@ fn test_max_depth() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contarct, address)]) @@ -56,7 +55,6 @@ fn test_basic_behavior() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) diff --git a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs index 616abe2d8fe..5f64a8c0c2e 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs @@ -17,7 +17,6 @@ fn test_prestate_tracer() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); @@ -54,34 +53,24 @@ fn test_prestate_tracer_diff_mode() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_random_rich_accounts(1) - .with_deployer() .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); let contract = read_simple_transfer_contract(); - let tx = vm - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; + let account = &mut vm.rich_accounts[0]; + let tx = account.get_deploy_tx(&contract, None, TxType::L2).tx; let nonce = tx.nonce().unwrap().0.into(); vm.vm.push_transaction(tx); vm.vm.execute(VmExecutionMode::OneTx); - let deployed_address = deployed_address_create(vm.deployer.as_ref().unwrap().address, nonce); + let deployed_address = deployed_address_create(account.address, nonce); vm.test_contract = Some(deployed_address); // Deploy a second copy of the contract to see its appearance in the pre-state - let tx2 = vm - .deployer - .as_mut() - .expect("You have to initialize builder with deployer") - .get_deploy_tx(&contract, None, TxType::L2) - .tx; + let tx2 = account.get_deploy_tx(&contract, None, TxType::L2).tx; let nonce2 = tx2.nonce().unwrap().0.into(); vm.vm.push_transaction(tx2); vm.vm.execute(VmExecutionMode::OneTx); - let deployed_address2 = deployed_address_create(vm.deployer.as_ref().unwrap().address, nonce2); + let deployed_address2 = deployed_address_create(account.address, nonce2); let account = &mut vm.rich_accounts[0]; From b67372dce9518a98b3e62806e081904c3ccbfdcd Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Tue, 15 Oct 2024 19:32:44 +0300 Subject: [PATCH 13/18] Document tester --- core/lib/multivm/src/versions/testonly/mod.rs | 9 +++++++++ core/lib/multivm/src/versions/testonly/tester/mod.rs | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index ec1c3559b9f..50744d97753 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -1,3 +1,12 @@ +//! Reusable tests and tooling for low-level VM testing. +//! +//! # How it works +//! +//! - [`TestedVm`] defines test-specific VM extensions. It's currently implemented for the latest legacy VM +//! (`vm_latest`) and the fast VM (`vm_fast`). +//! - Submodules of this module define test functions generic by `TestedVm`. Specific VM versions implement `TestedVm` +//! and can create tests based on these test functions with minimum amount of boilerplate code. + use ethabi::Contract; use once_cell::sync::Lazy; use zksync_contracts::{ diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index d02bdaada9b..69ee2d99b33 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -26,6 +26,7 @@ use crate::{ mod transaction_test_info; +/// VM tester that provides prefunded accounts, storage handle etc. #[derive(Debug)] pub(crate) struct VmTester { pub(crate) vm: VM, @@ -64,6 +65,7 @@ impl VmTester { } } +/// Builder for [`VmTester`]. #[derive(Debug)] pub(crate) struct VmTesterBuilder { storage: Option, @@ -200,7 +202,6 @@ pub(crate) trait TestedVm: /// Returns `true` iff the decommit is fresh. fn manually_decommit(&mut self, code_hash: H256) -> bool; - // FIXME: u32 -> usize fn verify_required_bootloader_heap(&self, cells: &[(u32, U256)]); fn write_to_bootloader_heap(&mut self, cells: &[(usize, U256)]); From 3612a4609f72b7a138c4c36abc97fdf0607a2ae1 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Wed, 16 Oct 2024 15:10:00 +0300 Subject: [PATCH 14/18] Fix / update tests --- .../src/versions/testonly/l1_tx_execution.rs | 7 ++- .../src/versions/testonly/migration.rs | 43 ------------------- core/lib/multivm/src/versions/testonly/mod.rs | 7 +-- .../src/versions/testonly/nonce_holder.rs | 11 ++--- 4 files changed, 11 insertions(+), 57 deletions(-) delete mode 100644 core/lib/multivm/src/versions/testonly/migration.rs diff --git a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs index e614eb03468..3536210a710 100644 --- a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs +++ b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs @@ -102,9 +102,8 @@ pub(crate) 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. 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); + // We changed one slot inside contract. + assert_eq!(res.initial_storage_writes - basic_initial_writes, 1); // No repeated writes let repeated_writes = res.repeated_storage_writes; @@ -131,7 +130,7 @@ pub(crate) fn test_l1_tx_execution() { assert!(result.result.is_failed(), "The transaction should fail"); let res = StorageWritesDeduplicator::apply_on_empty_state(&result.logs.storage_logs); - assert_eq!(res.initial_storage_writes, basic_initial_writes); + assert_eq!(res.initial_storage_writes, basic_initial_writes + 1); assert_eq!(res.repeated_storage_writes, 1); } diff --git a/core/lib/multivm/src/versions/testonly/migration.rs b/core/lib/multivm/src/versions/testonly/migration.rs deleted file mode 100644 index a7764af2264..00000000000 --- a/core/lib/multivm/src/versions/testonly/migration.rs +++ /dev/null @@ -1,43 +0,0 @@ -use zksync_test_account::TxType; -use zksync_types::{get_code_key, H256, SYSTEM_CONTEXT_ADDRESS}; - -use super::{get_empty_storage, read_test_contract, tester::VmTesterBuilder, TestedVm}; -use crate::interface::{TxExecutionMode, VmExecutionMode, VmInterfaceExt}; - -/// This test checks that the new bootloader will work fine even if the previous system context contract is not -/// compatible with it, i.e. the bootloader will upgrade it before starting any transaction. -pub(crate) fn test_migration_for_system_context_aa_interaction() { - let mut storage = get_empty_storage(); - // We will set the system context bytecode to zero. - storage.set_value(get_code_key(&SYSTEM_CONTEXT_ADDRESS), H256::zero()); - - // 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() - .with_storage(storage) - .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) - .build::(); - - // Now, we will just proceed with standard transaction execution. - // The bootloader should be able to update system context regardless of whether - // the upgrade transaction is there or not. - let account = &mut vm.rich_accounts[0]; - let counter = read_test_contract(); - let tx = account.get_deploy_tx(&counter, None, TxType::L2).tx; - - vm.vm.push_transaction(tx); - let result = vm.vm.execute(VmExecutionMode::OneTx); - assert!( - !result.result.is_failed(), - "Transaction wasn't successful {:#?}", - result.result - ); - - let batch_result = vm.vm.execute(VmExecutionMode::Batch); - assert!( - !batch_result.result.is_failed(), - "Batch transaction wasn't successful {:#?}", - batch_result.result - ); -} diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 50744d97753..50c48ff0de8 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -10,7 +10,7 @@ use ethabi::Contract; use once_cell::sync::Lazy; use zksync_contracts::{ - load_contract, read_bytecode, read_yul_bytecode, read_zbin_bytecode, BaseSystemContracts, + load_contract, read_bootloader_code, read_bytecode, read_zbin_bytecode, BaseSystemContracts, SystemContractCode, }; use zksync_test_account::Account; @@ -38,7 +38,6 @@ pub(super) mod get_used_contracts; pub(super) mod is_write_initial; pub(super) mod l1_tx_execution; pub(super) mod l2_blocks; -pub(super) mod migration; pub(super) mod nonce_holder; pub(super) mod precompiles; pub(super) mod refunds; @@ -125,9 +124,7 @@ pub(crate) fn read_simple_transfer_contract() -> Vec { } pub(crate) fn get_bootloader(test: &str) -> SystemContractCode { - let artifacts_path = "contracts/system-contracts/bootloader/tests/artifacts/"; - let bootloader_code = read_yul_bytecode(artifacts_path, test); - + let bootloader_code = read_bootloader_code(test); let bootloader_hash = hash_bytecode(&bootloader_code); SystemContractCode { code: bytes_to_be_words(bootloader_code), diff --git a/core/lib/multivm/src/versions/testonly/nonce_holder.rs b/core/lib/multivm/src/versions/testonly/nonce_holder.rs index 8c7c01ddbdc..5e20e3eef3c 100644 --- a/core/lib/multivm/src/versions/testonly/nonce_holder.rs +++ b/core/lib/multivm/src/versions/testonly/nonce_holder.rs @@ -74,6 +74,7 @@ fn run_nonce_test( pub(crate) fn test_nonce_holder() { let mut account = Account::random(); + let hex_addr = hex::encode(account.address.to_fixed_bytes()); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) @@ -90,7 +91,7 @@ pub(crate) fn test_nonce_holder() { &mut account, 1u32, NonceHolderTestMode::SetValueUnderNonce, - Some("Previous nonce has not been used".to_string()), + Some("Error function_selector = 0x13595475, data = 0x13595475".to_string()), "Allowed to set value under non sequential value", ); @@ -141,7 +142,7 @@ pub(crate) fn test_nonce_holder() { &mut account, 10u32, NonceHolderTestMode::IncreaseMinNonceBy5, - Some("Reusing the same nonce twice".to_string()), + Some(format!("Error function_selector = 0xe90aded4, data = 0xe90aded4000000000000000000000000{hex_addr}000000000000000000000000000000000000000000000000000000000000000a")), "Allowed to reuse nonce below the minimal one", ); @@ -161,7 +162,7 @@ pub(crate) fn test_nonce_holder() { &mut account, 13u32, NonceHolderTestMode::IncreaseMinNonceBy5, - Some("Reusing the same nonce twice".to_string()), + Some(format!("Error function_selector = 0xe90aded4, data = 0xe90aded4000000000000000000000000{hex_addr}000000000000000000000000000000000000000000000000000000000000000d")), "Allowed to reuse the same nonce twice", ); @@ -181,7 +182,7 @@ pub(crate) fn test_nonce_holder() { &mut account, 16u32, NonceHolderTestMode::IncreaseMinNonceTooMuch, - Some("The value for incrementing the nonce is too high".to_string()), + Some("Error function_selector = 0x45ac24a6, data = 0x45ac24a600000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000040000000000000000000000".to_string()), "Allowed for incrementing min nonce too much", ); @@ -191,7 +192,7 @@ pub(crate) fn test_nonce_holder() { &mut account, 16u32, NonceHolderTestMode::LeaveNonceUnused, - Some("The nonce was not set as used".to_string()), + Some(format!("Error function_selector = 0x1f2f8478, data = 0x1f2f8478000000000000000000000000{hex_addr}0000000000000000000000000000000000000000000000000000000000000010")), "Allowed to leave nonce as unused", ); } From 2fd46ee2cb6d209affbc651ea2003996a3048ab7 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 17 Oct 2024 13:10:40 +0300 Subject: [PATCH 15/18] Eliminate randomness in `VmTester` --- .../src/versions/testonly/block_tip.rs | 2 +- .../versions/testonly/bytecode_publishing.rs | 2 +- .../multivm/src/versions/testonly/circuits.rs | 2 +- .../src/versions/testonly/code_oracle.rs | 6 +-- .../src/versions/testonly/default_aa.rs | 2 +- .../src/versions/testonly/gas_limit.rs | 2 +- .../versions/testonly/get_used_contracts.rs | 2 +- .../src/versions/testonly/is_write_initial.rs | 7 ++-- .../src/versions/testonly/l1_tx_execution.rs | 4 +- .../src/versions/testonly/l2_blocks.rs | 10 ++--- core/lib/multivm/src/versions/testonly/mod.rs | 26 +++++++++---- .../src/versions/testonly/nonce_holder.rs | 34 +++++++++-------- .../src/versions/testonly/precompiles.rs | 6 +-- .../multivm/src/versions/testonly/refunds.rs | 10 +++-- .../src/versions/testonly/require_eip712.rs | 38 +++++++++---------- .../src/versions/testonly/rollbacks.rs | 6 +-- .../src/versions/testonly/secp256r1.rs | 2 +- .../multivm/src/versions/testonly/shadow.rs | 6 +-- .../src/versions/testonly/simple_execution.rs | 4 +- .../multivm/src/versions/testonly/storage.rs | 2 +- .../src/versions/testonly/tester/mod.rs | 22 ++++++----- .../testonly/tracing_execution_error.rs | 2 +- .../multivm/src/versions/testonly/transfer.rs | 4 +- .../multivm/src/versions/testonly/upgrade.rs | 6 +-- .../versions/vm_latest/tests/call_tracer.rs | 4 +- .../versions/vm_latest/tests/evm_emulator.rs | 4 +- .../vm_latest/tests/prestate_tracer.rs | 4 +- .../src/versions/vm_latest/tests/rollbacks.rs | 2 +- 28 files changed, 117 insertions(+), 104 deletions(-) diff --git a/core/lib/multivm/src/versions/testonly/block_tip.rs b/core/lib/multivm/src/versions/testonly/block_tip.rs index dee563dc582..d65be589cc6 100644 --- a/core/lib/multivm/src/versions/testonly/block_tip.rs +++ b/core/lib/multivm/src/versions/testonly/block_tip.rs @@ -133,7 +133,7 @@ fn execute_test(test_data: L1MessengerTestData) -> TestStatistics let mut vm = VmTesterBuilder::new() .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_l1_batch_env(batch_env) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs b/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs index 97cf0db35d1..33af7be8cc6 100644 --- a/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs +++ b/core/lib/multivm/src/versions/testonly/bytecode_publishing.rs @@ -12,7 +12,7 @@ pub(crate) fn test_bytecode_publishing() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let counter = read_test_contract(); diff --git a/core/lib/multivm/src/versions/testonly/circuits.rs b/core/lib/multivm/src/versions/testonly/circuits.rs index fbd01e15c0f..cf70fe1ba2c 100644 --- a/core/lib/multivm/src/versions/testonly/circuits.rs +++ b/core/lib/multivm/src/versions/testonly/circuits.rs @@ -12,7 +12,7 @@ use crate::{ pub(crate) fn test_circuits() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/code_oracle.rs b/core/lib/multivm/src/versions/testonly/code_oracle.rs index 0a709be3c88..a8391feda25 100644 --- a/core/lib/multivm/src/versions/testonly/code_oracle.rs +++ b/core/lib/multivm/src/versions/testonly/code_oracle.rs @@ -37,7 +37,7 @@ pub(crate) fn test_code_oracle() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ContractToDeploy::new( precompile_contract_bytecode, precompiles_contract_address, @@ -128,7 +128,7 @@ pub(crate) fn test_code_oracle_big_bytecode() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ContractToDeploy::new( precompile_contract_bytecode, precompiles_contract_address, @@ -190,7 +190,7 @@ pub(crate) fn test_refunds_in_code_oracle() { for decommit in [false, true] { let mut vm = VmTesterBuilder::new() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ContractToDeploy::new( precompile_contract_bytecode.clone(), precompiles_contract_address, diff --git a/core/lib/multivm/src/versions/testonly/default_aa.rs b/core/lib/multivm/src/versions/testonly/default_aa.rs index 422855a5dbe..3f121dcf7e6 100644 --- a/core/lib/multivm/src/versions/testonly/default_aa.rs +++ b/core/lib/multivm/src/versions/testonly/default_aa.rs @@ -19,7 +19,7 @@ pub(crate) fn test_default_aa_interaction() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let counter = read_test_contract(); diff --git a/core/lib/multivm/src/versions/testonly/gas_limit.rs b/core/lib/multivm/src/versions/testonly/gas_limit.rs index b9957f0d8c0..5e31eb2b159 100644 --- a/core/lib/multivm/src/versions/testonly/gas_limit.rs +++ b/core/lib/multivm/src/versions/testonly/gas_limit.rs @@ -12,7 +12,7 @@ pub(crate) fn test_tx_gas_limit_offset() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let gas_limit = 9999.into(); diff --git a/core/lib/multivm/src/versions/testonly/get_used_contracts.rs b/core/lib/multivm/src/versions/testonly/get_used_contracts.rs index be74d89c400..faaf507fcf0 100644 --- a/core/lib/multivm/src/versions/testonly/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/testonly/get_used_contracts.rs @@ -127,7 +127,7 @@ fn execute_proxy_counter( counter_address, )]) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let (proxy_counter_bytecode, proxy_counter_abi) = read_proxy_counter_contract(); diff --git a/core/lib/multivm/src/versions/testonly/is_write_initial.rs b/core/lib/multivm/src/versions/testonly/is_write_initial.rs index 7ba29b2810b..ef1fe2088c1 100644 --- a/core/lib/multivm/src/versions/testonly/is_write_initial.rs +++ b/core/lib/multivm/src/versions/testonly/is_write_initial.rs @@ -1,4 +1,4 @@ -use zksync_test_account::{Account, TxType}; +use zksync_test_account::TxType; use zksync_types::get_nonce_key; use super::{read_test_contract, tester::VmTesterBuilder, TestedVm}; @@ -8,13 +8,12 @@ pub(crate) fn test_is_write_initial_behaviour() { // In this test, we check result of `is_write_initial` at different stages. // The main idea is to check that `is_write_initial` storage uses the correct cache for initial_writes and doesn't // messed up it with the repeated writes during the one batch execution. - - let mut account = Account::random(); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) + .with_rich_accounts(1) .build::(); + let account = &mut vm.rich_accounts[0]; let nonce_key = get_nonce_key(&account.address); // Check that the next write to the nonce key will be initial. diff --git a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs index 3536210a710..212b1f16f20 100644 --- a/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs +++ b/core/lib/multivm/src/versions/testonly/l1_tx_execution.rs @@ -38,7 +38,7 @@ pub(crate) fn test_l1_tx_execution() { .with_empty_in_memory_storage() .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let contract_code = read_test_contract(); @@ -143,7 +143,7 @@ pub(crate) fn test_l1_tx_execution_high_gas_limit() { .with_empty_in_memory_storage() .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let account = &mut vm.rich_accounts[0]; diff --git a/core/lib/multivm/src/versions/testonly/l2_blocks.rs b/core/lib/multivm/src/versions/testonly/l2_blocks.rs index 20da6568da8..8ea8cc958dc 100644 --- a/core/lib/multivm/src/versions/testonly/l2_blocks.rs +++ b/core/lib/multivm/src/versions/testonly/l2_blocks.rs @@ -53,7 +53,7 @@ pub(crate) fn test_l2_block_initialization_timestamp() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); // Override the timestamp of the current L2 block to be 0. @@ -91,7 +91,7 @@ pub(crate) fn test_l2_block_initialization_number_non_zero() { .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) .with_l1_batch_env(l1_batch) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let l1_tx = get_l1_noop(); @@ -123,7 +123,7 @@ fn test_same_l2_block( .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) .with_l1_batch_env(l1_batch) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let l1_tx = get_l1_noop(); @@ -196,7 +196,7 @@ fn test_new_l2_block( .with_empty_in_memory_storage() .with_l1_batch_env(l1_batch) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let l1_tx = get_l1_noop(); @@ -295,7 +295,7 @@ fn test_first_in_batch( .with_empty_in_memory_storage() .with_l1_batch_env(l1_batch) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let l1_tx = get_l1_noop(); diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 50c48ff0de8..5cffdb09332 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -13,11 +13,10 @@ use zksync_contracts::{ load_contract, read_bootloader_code, read_bytecode, read_zbin_bytecode, BaseSystemContracts, SystemContractCode, }; -use zksync_test_account::Account; use zksync_types::{ block::L2BlockHasher, fee_model::BatchFeeInput, get_code_key, get_is_account_key, - helpers::unix_timestamp_ms, utils::storage_key_for_eth_balance, Address, L1BatchNumber, - L2BlockNumber, L2ChainId, ProtocolVersionId, U256, + utils::storage_key_for_eth_balance, Address, L1BatchNumber, L2BlockNumber, L2ChainId, + ProtocolVersionId, U256, }; use zksync_utils::{bytecode::hash_bytecode, bytes_to_be_words, u256_to_h256}; use zksync_vm_interface::{L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode}; @@ -145,7 +144,7 @@ pub(super) fn default_system_env() -> SystemEnv { } pub(super) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { - let timestamp = unix_timestamp_ms(); + let timestamp = number.0.into(); L1BatchEnv { previous_batch_hash: None, number, @@ -154,7 +153,7 @@ pub(super) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { 50_000_000_000, // 50 gwei 250_000_000, // 0.25 gwei ), - fee_account: Address::random(), + fee_account: Address::repeat_byte(1), enforced_base_fee: None, first_l2_block: L2BlockEnv { number: 1, @@ -165,8 +164,8 @@ pub(super) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { } } -pub(super) fn make_account_rich(storage: &mut InMemoryStorage, account: &Account) { - let key = storage_key_for_eth_balance(&account.address); +pub(super) fn make_address_rich(storage: &mut InMemoryStorage, address: Address) { + let key = storage_key_for_eth_balance(&address); storage.set_value(key, u256_to_h256(U256::from(10_u64.pow(19)))); } @@ -175,6 +174,7 @@ pub(super) struct ContractToDeploy { bytecode: Vec, address: Address, is_account: bool, + is_funded: bool, } impl ContractToDeploy { @@ -183,6 +183,7 @@ impl ContractToDeploy { bytecode, address, is_account: false, + is_funded: false, } } @@ -191,9 +192,16 @@ impl ContractToDeploy { bytecode, address, is_account: true, + is_funded: false, } } + #[must_use] + pub fn funded(mut self) -> Self { + self.is_funded = true; + self + } + pub fn insert(&self, storage: &mut InMemoryStorage) { let deployer_code_key = get_code_key(&self.address); storage.set_value(deployer_code_key, hash_bytecode(&self.bytecode)); @@ -202,6 +210,10 @@ impl ContractToDeploy { storage.set_value(is_account_key, u256_to_h256(1_u32.into())); } storage.store_factory_dep(hash_bytecode(&self.bytecode), self.bytecode.clone()); + + if self.is_funded { + make_address_rich(storage, self.address); + } } /// Inserts the contracts into the test environment, bypassing the deployer system contract. diff --git a/core/lib/multivm/src/versions/testonly/nonce_holder.rs b/core/lib/multivm/src/versions/testonly/nonce_holder.rs index 5e20e3eef3c..8ef120c693c 100644 --- a/core/lib/multivm/src/versions/testonly/nonce_holder.rs +++ b/core/lib/multivm/src/versions/testonly/nonce_holder.rs @@ -73,22 +73,24 @@ fn run_nonce_test( } pub(crate) fn test_nonce_holder() { - let mut account = Account::random(); - let hex_addr = hex::encode(account.address.to_fixed_bytes()); - let mut vm = VmTesterBuilder::new() + let builder = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(1); + let account_address = builder.rich_account(0).address; + let mut vm = builder .with_custom_contracts(vec![ContractToDeploy::account( read_nonce_holder_tester(), - account.address, + account_address, )]) - .with_rich_accounts(vec![account.clone()]) .build::(); + let account = &mut vm.rich_accounts[0]; + let hex_addr = hex::encode(account.address.to_fixed_bytes()); // Test 1: trying to set value under non sequential nonce value. run_nonce_test( &mut vm.vm, - &mut account, + account, 1u32, NonceHolderTestMode::SetValueUnderNonce, Some("Error function_selector = 0x13595475, data = 0x13595475".to_string()), @@ -98,7 +100,7 @@ pub(crate) fn test_nonce_holder() { // Test 2: increase min nonce by 1 with sequential nonce ordering: run_nonce_test( &mut vm.vm, - &mut account, + account, 0u32, NonceHolderTestMode::IncreaseMinNonceBy1, None, @@ -108,7 +110,7 @@ pub(crate) fn test_nonce_holder() { // Test 3: correctly set value under nonce with sequential nonce ordering: run_nonce_test( &mut vm.vm, - &mut account, + account, 1u32, NonceHolderTestMode::SetValueUnderNonce, None, @@ -118,7 +120,7 @@ pub(crate) fn test_nonce_holder() { // Test 5: migrate to the arbitrary nonce ordering: run_nonce_test( &mut vm.vm, - &mut account, + account, 2u32, NonceHolderTestMode::SwitchToArbitraryOrdering, None, @@ -128,7 +130,7 @@ pub(crate) fn test_nonce_holder() { // Test 6: increase min nonce by 5 run_nonce_test( &mut vm.vm, - &mut account, + account, 6u32, NonceHolderTestMode::IncreaseMinNonceBy5, None, @@ -139,7 +141,7 @@ pub(crate) fn test_nonce_holder() { // tx with nonce 10 should not be allowed run_nonce_test( &mut vm.vm, - &mut account, + account, 10u32, NonceHolderTestMode::IncreaseMinNonceBy5, Some(format!("Error function_selector = 0xe90aded4, data = 0xe90aded4000000000000000000000000{hex_addr}000000000000000000000000000000000000000000000000000000000000000a")), @@ -149,7 +151,7 @@ pub(crate) fn test_nonce_holder() { // Test 8: we should be able to use nonce 13 run_nonce_test( &mut vm.vm, - &mut account, + account, 13u32, NonceHolderTestMode::SetValueUnderNonce, None, @@ -159,7 +161,7 @@ pub(crate) fn test_nonce_holder() { // Test 9: we should not be able to reuse nonce 13 run_nonce_test( &mut vm.vm, - &mut account, + account, 13u32, NonceHolderTestMode::IncreaseMinNonceBy5, Some(format!("Error function_selector = 0xe90aded4, data = 0xe90aded4000000000000000000000000{hex_addr}000000000000000000000000000000000000000000000000000000000000000d")), @@ -169,7 +171,7 @@ pub(crate) fn test_nonce_holder() { // Test 10: we should be able to simply use nonce 14, while bumping the minimal nonce by 5 run_nonce_test( &mut vm.vm, - &mut account, + account, 14u32, NonceHolderTestMode::IncreaseMinNonceBy5, None, @@ -179,7 +181,7 @@ pub(crate) fn test_nonce_holder() { // Test 11: Do not allow bumping nonce by too much run_nonce_test( &mut vm.vm, - &mut account, + account, 16u32, NonceHolderTestMode::IncreaseMinNonceTooMuch, Some("Error function_selector = 0x45ac24a6, data = 0x45ac24a600000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000040000000000000000000000".to_string()), @@ -189,7 +191,7 @@ pub(crate) fn test_nonce_holder() { // Test 12: Do not allow not setting a nonce as used run_nonce_test( &mut vm.vm, - &mut account, + account, 16u32, NonceHolderTestMode::LeaveNonceUnused, Some(format!("Error function_selector = 0x1f2f8478, data = 0x1f2f8478000000000000000000000000{hex_addr}0000000000000000000000000000000000000000000000000000000000000010")), diff --git a/core/lib/multivm/src/versions/testonly/precompiles.rs b/core/lib/multivm/src/versions/testonly/precompiles.rs index bb734a6e591..3abb7cb3902 100644 --- a/core/lib/multivm/src/versions/testonly/precompiles.rs +++ b/core/lib/multivm/src/versions/testonly/precompiles.rs @@ -14,7 +14,7 @@ pub(crate) fn test_keccak() { let address = Address::random(); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) @@ -50,7 +50,7 @@ pub(crate) fn test_sha256() { let address = Address::random(); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) @@ -84,7 +84,7 @@ pub(crate) fn test_ecrecover() { // Execute simple transfer and check that exactly 1 `ecrecover` call was made (it's done during tx validation). let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/refunds.rs b/core/lib/multivm/src/versions/testonly/refunds.rs index 7b1a473fa5d..d0934249de8 100644 --- a/core/lib/multivm/src/versions/testonly/refunds.rs +++ b/core/lib/multivm/src/versions/testonly/refunds.rs @@ -15,7 +15,7 @@ pub(crate) fn test_predetermined_refunded_gas() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let l1_batch = vm.l1_batch_env.clone(); @@ -49,8 +49,9 @@ pub(crate) fn test_predetermined_refunded_gas() { .with_empty_in_memory_storage() .with_l1_batch_env(l1_batch.clone()) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) + .with_rich_accounts(1) .build::(); + assert_eq!(account.address(), vm.rich_accounts[0].address()); vm.vm .push_transaction_with_refund(tx.clone(), result.refunds.gas_refunded); @@ -99,8 +100,9 @@ pub(crate) fn test_predetermined_refunded_gas() { .with_empty_in_memory_storage() .with_l1_batch_env(l1_batch) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account.clone()]) + .with_rich_accounts(1) .build::(); + assert_eq!(account.address(), vm.rich_accounts[0].address()); let changed_operator_suggested_refund = result.refunds.gas_refunded + 1000; vm.vm @@ -164,7 +166,7 @@ pub(crate) fn test_negative_pubdata_for_transaction() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ContractToDeploy::new( expensive_contract_bytecode, expensive_contract_address, diff --git a/core/lib/multivm/src/versions/testonly/require_eip712.rs b/core/lib/multivm/src/versions/testonly/require_eip712.rs index a9a4fdd0604..1ea3964d7cd 100644 --- a/core/lib/multivm/src/versions/testonly/require_eip712.rs +++ b/core/lib/multivm/src/versions/testonly/require_eip712.rs @@ -1,9 +1,8 @@ use ethabi::Token; use zksync_eth_signer::TransactionParameters; -use zksync_test_account::Account; use zksync_types::{ - fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, Eip712Domain, Execute, L2ChainId, - Nonce, Transaction, U256, + fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, Address, Eip712Domain, Execute, + L2ChainId, Nonce, Transaction, U256, }; use super::{ @@ -19,24 +18,21 @@ pub(crate) fn test_require_eip712() { // - `private_address` - EOA account, where we have the key // - `account_address` - AA account, where the contract is deployed // - beneficiary - an EOA account, where we'll try to transfer the tokens. - let account_abstraction = Account::random(); - let mut private_account = Account::random(); - let beneficiary = Account::random(); + let aa_address = Address::repeat_byte(0x10); + let beneficiary_address = Address::repeat_byte(0x20); let (bytecode, contract) = read_many_owners_custom_account_contract(); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_custom_contracts(vec![ContractToDeploy::account( - bytecode, - account_abstraction.address, - )]) + .with_custom_contracts(vec![ + ContractToDeploy::account(bytecode, aa_address).funded() + ]) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_rich_accounts(vec![account_abstraction.clone(), private_account.clone()]) + .with_rich_accounts(1) .build::(); - - assert_eq!(vm.get_eth_balance(beneficiary.address), U256::from(0)); - + assert_eq!(vm.get_eth_balance(beneficiary_address), U256::from(0)); let chain_id: u32 = 270; + let mut private_account = vm.rich_accounts[0].clone(); // First, let's set the owners of the AA account to the `private_address`. // (so that messages signed by `private_address`, are authorized to act on behalf of the AA account). @@ -47,7 +43,7 @@ pub(crate) fn test_require_eip712() { let tx = private_account.get_l2_tx_for_execute( Execute { - contract_address: Some(account_abstraction.address), + contract_address: Some(aa_address), calldata: encoded_input, value: Default::default(), factory_deps: vec![], @@ -65,7 +61,7 @@ pub(crate) fn test_require_eip712() { // Normally this would not work - unless the operator is malicious. let aa_raw_tx = TransactionParameters { nonce: U256::from(0), - to: Some(beneficiary.address), + to: Some(beneficiary_address), gas: U256::from(100000000), gas_price: Some(U256::from(10000000)), value: U256::from(888000088), @@ -85,7 +81,7 @@ pub(crate) fn test_require_eip712() { let mut l2_tx: L2Tx = L2Tx::from_request(tx_request, 10000, false).unwrap(); l2_tx.set_input(aa_tx, hash); // Pretend that operator is malicious and sets the initiator to the AA account. - l2_tx.common_data.initiator_address = account_abstraction.address; + l2_tx.common_data.initiator_address = aa_address; let transaction: Transaction = l2_tx.into(); vm.vm.push_transaction(transaction); @@ -93,7 +89,7 @@ pub(crate) fn test_require_eip712() { assert!(!result.result.is_failed()); assert_eq!( - vm.get_eth_balance(beneficiary.address), + vm.get_eth_balance(beneficiary_address), U256::from(888000088) ); // Make sure that the tokens were transferred from the AA account. @@ -104,7 +100,7 @@ pub(crate) fn test_require_eip712() { // // Now send the 'classic' EIP712 transaction let tx_712 = L2Tx::new( - Some(beneficiary.address), + Some(beneficiary_address), vec![], Nonce(1), Fee { @@ -113,7 +109,7 @@ pub(crate) fn test_require_eip712() { max_priority_fee_per_gas: U256::from(1000000000), gas_per_pubdata_limit: U256::from(1000000000), }, - account_abstraction.address, + aa_address, U256::from(28374938), vec![], Default::default(), @@ -140,7 +136,7 @@ pub(crate) fn test_require_eip712() { vm.vm.execute(VmExecutionMode::OneTx); assert_eq!( - vm.get_eth_balance(beneficiary.address), + vm.get_eth_balance(beneficiary_address), U256::from(916375026) ); assert_eq!( diff --git a/core/lib/multivm/src/versions/testonly/rollbacks.rs b/core/lib/multivm/src/versions/testonly/rollbacks.rs index 5574afa847c..cab3427899e 100644 --- a/core/lib/multivm/src/versions/testonly/rollbacks.rs +++ b/core/lib/multivm/src/versions/testonly/rollbacks.rs @@ -17,7 +17,7 @@ pub(crate) fn test_vm_rollbacks() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let mut account = vm.rich_accounts[0].clone(); @@ -83,7 +83,7 @@ pub(crate) fn test_vm_loadnext_rollbacks() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let mut account = vm.rich_accounts[0].clone(); @@ -184,7 +184,7 @@ pub(crate) fn test_rollback_in_call_mode() { counter_bytecode, counter_address, )]) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let account = &mut vm.rich_accounts[0]; let tx = account.get_test_contract_transaction(counter_address, true, None, false, TxType::L2); diff --git a/core/lib/multivm/src/versions/testonly/secp256r1.rs b/core/lib/multivm/src/versions/testonly/secp256r1.rs index 2401b5b9dd9..60197913601 100644 --- a/core/lib/multivm/src/versions/testonly/secp256r1.rs +++ b/core/lib/multivm/src/versions/testonly/secp256r1.rs @@ -13,7 +13,7 @@ pub(crate) fn test_secp256r1() { .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) .with_execution_mode(TxExecutionMode::EthCall) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let account = &mut vm.rich_accounts[0]; diff --git a/core/lib/multivm/src/versions/testonly/shadow.rs b/core/lib/multivm/src/versions/testonly/shadow.rs index de21b93c522..d962e4411b0 100644 --- a/core/lib/multivm/src/versions/testonly/shadow.rs +++ b/core/lib/multivm/src/versions/testonly/shadow.rs @@ -22,7 +22,7 @@ use crate::{ }, utils::get_max_gas_per_pubdata_byte, versions::testonly::{ - default_l1_batch, default_system_env, make_account_rich, ContractToDeploy, + default_l1_batch, default_system_env, make_address_rich, ContractToDeploy, }, vm_fast, vm_latest, vm_latest::HistoryEnabled, @@ -82,8 +82,8 @@ impl Harness { } fn setup_storage(&self, storage: &mut InMemoryStorage) { - make_account_rich(storage, &self.alice); - make_account_rich(storage, &self.bob); + make_address_rich(storage, self.alice.address); + make_address_rich(storage, self.bob.address); self.storage_contract.insert(storage); let storage_contract_key = StorageKey::new( diff --git a/core/lib/multivm/src/versions/testonly/simple_execution.rs b/core/lib/multivm/src/versions/testonly/simple_execution.rs index 59aa6255675..fcd7a144ab1 100644 --- a/core/lib/multivm/src/versions/testonly/simple_execution.rs +++ b/core/lib/multivm/src/versions/testonly/simple_execution.rs @@ -7,7 +7,7 @@ use crate::interface::{ExecutionResult, VmExecutionMode, VmInterfaceExt}; pub(crate) fn test_estimate_fee() { let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); vm_tester.deploy_test_contract(); @@ -30,7 +30,7 @@ pub(crate) fn test_estimate_fee() { pub(crate) fn test_simple_execute() { let mut vm_tester = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); vm_tester.deploy_test_contract(); diff --git a/core/lib/multivm/src/versions/testonly/storage.rs b/core/lib/multivm/src/versions/testonly/storage.rs index a64b589ed50..c4e3b9bf739 100644 --- a/core/lib/multivm/src/versions/testonly/storage.rs +++ b/core/lib/multivm/src/versions/testonly/storage.rs @@ -17,7 +17,7 @@ fn test_storage(first_tx_calldata: Vec, second_tx_calldata: Ve let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ContractToDeploy::new(bytecode, test_contract_address)]) .build::(); diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index 69ee2d99b33..2efccd9b149 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -5,7 +5,7 @@ use zksync_test_account::{Account, TxType}; use zksync_types::{ utils::{deployed_address_create, storage_key_for_eth_balance}, writes::StateDiffRecord, - Address, L1BatchNumber, StorageKey, Transaction, H256, U256, + Address, K256PrivateKey, L1BatchNumber, StorageKey, Transaction, H256, U256, }; use zksync_vm_interface::{ CurrentExecutionState, VmExecutionResultAndLogs, VmInterfaceHistoryEnabled, @@ -20,7 +20,7 @@ use crate::{ VmInterfaceExt, }, versions::testonly::{ - default_l1_batch, default_system_env, make_account_rich, ContractToDeploy, + default_l1_batch, default_system_env, make_address_rich, ContractToDeploy, }, }; @@ -56,7 +56,7 @@ impl VmTester { pub(crate) fn reset_with_empty_storage(&mut self) { let mut storage = get_empty_storage(); for account in &self.rich_accounts { - make_account_rich(&mut storage, account); + make_address_rich(&mut storage, account.address); } let storage = StorageView::new(storage).to_rc_ptr(); @@ -124,17 +124,19 @@ impl VmTesterBuilder { self } - pub(crate) fn with_random_rich_accounts(mut self, number: u32) -> Self { - for _ in 0..number { - let account = Account::random(); + /// Creates the specified number of pre-funded accounts. + pub(crate) fn with_rich_accounts(mut self, number: u32) -> Self { + for i in 0..number { + let account_private_key_bytes = H256::from_low_u64_be(u64::from(i) + 1); + let account = + Account::new(K256PrivateKey::from_bytes(account_private_key_bytes).unwrap()); self.rich_accounts.push(account); } self } - pub(crate) fn with_rich_accounts(mut self, accounts: Vec) -> Self { - self.rich_accounts.extend(accounts); - self + pub(crate) fn rich_account(&self, index: usize) -> &Account { + &self.rich_accounts[index] } pub(crate) fn with_custom_contracts(mut self, contracts: Vec) -> Self { @@ -154,7 +156,7 @@ impl VmTesterBuilder { ContractToDeploy::insert_all(&self.custom_contracts, &mut raw_storage); let storage = StorageView::new(raw_storage).to_rc_ptr(); for account in &self.rich_accounts { - make_account_rich(storage.borrow_mut().inner_mut(), account); + make_address_rich(storage.borrow_mut().inner_mut(), account.address); } let vm = VM::new( diff --git a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs index 2f7f6d212ce..36b458b849d 100644 --- a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs +++ b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs @@ -29,7 +29,7 @@ pub(crate) fn test_tracing_of_execution_errors() { contract_address, )]) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let account = &mut vm.rich_accounts[0]; diff --git a/core/lib/multivm/src/versions/testonly/transfer.rs b/core/lib/multivm/src/versions/testonly/transfer.rs index 7a7948686e8..647570f2343 100644 --- a/core/lib/multivm/src/versions/testonly/transfer.rs +++ b/core/lib/multivm/src/versions/testonly/transfer.rs @@ -53,7 +53,7 @@ fn test_send_or_transfer(test_option: TestOptions) { let mut vm = VmTesterBuilder::new() .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ ContractToDeploy::new(test_bytecode, test_contract_address), ContractToDeploy::new(recipient_bytecode, recipient_address), @@ -137,7 +137,7 @@ fn test_reentrancy_protection_send_or_transfer(test_option: TestOp let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_custom_contracts(vec![ ContractToDeploy::new(test_bytecode, test_contract_address), ContractToDeploy::new(reentrant_recipient_bytecode, reentrant_recipient_address), diff --git a/core/lib/multivm/src/versions/testonly/upgrade.rs b/core/lib/multivm/src/versions/testonly/upgrade.rs index 7fb6e6f4d01..0639dba0dc0 100644 --- a/core/lib/multivm/src/versions/testonly/upgrade.rs +++ b/core/lib/multivm/src/versions/testonly/upgrade.rs @@ -27,7 +27,7 @@ pub(crate) fn test_protocol_upgrade_is_first() { let mut vm = VmTesterBuilder::new() .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); // Here we just use some random transaction of protocol upgrade type: @@ -117,7 +117,7 @@ pub(crate) fn test_force_deploy_upgrade() { let mut vm = VmTesterBuilder::new() .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let address_to_deploy = H160::random(); @@ -172,7 +172,7 @@ pub(crate) fn test_complex_upgrader() { let mut vm = VmTesterBuilder::new() .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let address_to_deploy1 = H160::random(); diff --git a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs index b5a1eb4066c..e1dfdc7e68c 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/call_tracer.rs @@ -21,7 +21,7 @@ fn test_max_depth() { let address = Address::random(); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contarct, address)]) @@ -54,7 +54,7 @@ fn test_basic_behavior() { let address = Address::random(); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .with_custom_contracts(vec![ContractToDeploy::account(contract, address)]) diff --git a/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs b/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs index 31c8c1304c4..4d6e77aed51 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/evm_emulator.rs @@ -120,7 +120,7 @@ impl EvmTestBuilder { .with_system_env(system_env) .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build() } } @@ -138,7 +138,7 @@ fn tracing_evm_contract_deployment() { .with_system_env(system_env) .with_storage(storage) .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let account = &mut vm.rich_accounts[0]; diff --git a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs index 5f64a8c0c2e..838c4e342dc 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/prestate_tracer.rs @@ -16,7 +16,7 @@ use crate::{ fn test_prestate_tracer() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); @@ -52,7 +52,7 @@ fn test_prestate_tracer() { fn test_prestate_tracer_diff_mode() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() - .with_random_rich_accounts(1) + .with_rich_accounts(1) .with_bootloader_gas_limit(BATCH_COMPUTATIONAL_GAS_LIMIT) .with_execution_mode(TxExecutionMode::VerifyExecute) .build::(); 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 05c95fefb6e..c948315266a 100644 --- a/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs +++ b/core/lib/multivm/src/versions/vm_latest/tests/rollbacks.rs @@ -64,7 +64,7 @@ fn test_layered_rollback() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) - .with_random_rich_accounts(1) + .with_rich_accounts(1) .build::(); let account = &mut vm.rich_accounts[0]; From 9e21be7dd10c6a86bd51795c7aa1d4521cd3ba14 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Thu, 17 Oct 2024 13:50:46 +0300 Subject: [PATCH 16/18] Eliminate remaining randomness --- .../lib/multivm/src/versions/testonly/block_tip.rs | 6 +++--- core/lib/multivm/src/versions/testonly/circuits.rs | 2 +- .../multivm/src/versions/testonly/code_oracle.rs | 6 +++--- .../src/versions/testonly/get_used_contracts.rs | 6 ++++-- .../lib/multivm/src/versions/testonly/l2_blocks.rs | 10 +++++----- .../multivm/src/versions/testonly/precompiles.rs | 4 ++-- core/lib/multivm/src/versions/testonly/refunds.rs | 2 +- core/lib/multivm/src/versions/testonly/shadow.rs | 4 ++-- core/lib/multivm/src/versions/testonly/storage.rs | 2 +- .../multivm/src/versions/testonly/tester/mod.rs | 7 ++----- .../versions/testonly/tracing_execution_error.rs | 4 ++-- core/lib/multivm/src/versions/testonly/transfer.rs | 8 ++++---- core/lib/multivm/src/versions/testonly/upgrade.rs | 14 +++++++------- core/tests/test_account/src/lib.rs | 6 ++++++ 14 files changed, 43 insertions(+), 38 deletions(-) diff --git a/core/lib/multivm/src/versions/testonly/block_tip.rs b/core/lib/multivm/src/versions/testonly/block_tip.rs index d65be589cc6..7700f347ca6 100644 --- a/core/lib/multivm/src/versions/testonly/block_tip.rs +++ b/core/lib/multivm/src/versions/testonly/block_tip.rs @@ -44,7 +44,7 @@ fn populate_mimic_calls(data: L1MessengerTestData) -> Vec> { let complex_upgrade = get_complex_upgrade_abi(); let l1_messenger = load_sys_contract("L1Messenger"); - let logs_mimic_calls = (0..data.l2_to_l1_logs).map(|_| MimicCallInfo { + let logs_mimic_calls = (0..data.l2_to_l1_logs).map(|i| MimicCallInfo { to: L1_MESSENGER_ADDRESS, who_to_mimic: KNOWN_CODES_STORAGE_ADDRESS, data: l1_messenger @@ -52,8 +52,8 @@ fn populate_mimic_calls(data: L1MessengerTestData) -> Vec> { .unwrap() .encode_input(&[ Token::Bool(false), - Token::FixedBytes(H256::random().0.to_vec()), - Token::FixedBytes(H256::random().0.to_vec()), + Token::FixedBytes(H256::from_low_u64_be(2 * i as u64).0.to_vec()), + Token::FixedBytes(H256::from_low_u64_be(2 * i as u64 + 1).0.to_vec()), ]) .unwrap(), }); diff --git a/core/lib/multivm/src/versions/testonly/circuits.rs b/core/lib/multivm/src/versions/testonly/circuits.rs index cf70fe1ba2c..9503efe9208 100644 --- a/core/lib/multivm/src/versions/testonly/circuits.rs +++ b/core/lib/multivm/src/versions/testonly/circuits.rs @@ -20,7 +20,7 @@ pub(crate) fn test_circuits() { let account = &mut vm.rich_accounts[0]; let tx = account.get_l2_tx_for_execute( Execute { - contract_address: Some(Address::random()), + contract_address: Some(Address::repeat_byte(1)), calldata: Vec::new(), value: U256::from(1u8), factory_deps: vec![], diff --git a/core/lib/multivm/src/versions/testonly/code_oracle.rs b/core/lib/multivm/src/versions/testonly/code_oracle.rs index a8391feda25..b786539329b 100644 --- a/core/lib/multivm/src/versions/testonly/code_oracle.rs +++ b/core/lib/multivm/src/versions/testonly/code_oracle.rs @@ -19,7 +19,7 @@ fn generate_large_bytecode() -> Vec { } pub(crate) fn test_code_oracle() { - let precompiles_contract_address = Address::random(); + let precompiles_contract_address = Address::repeat_byte(1); let precompile_contract_bytecode = read_precompiles_contract(); // Filling the zkevm bytecode @@ -110,7 +110,7 @@ fn find_code_oracle_cost_log( } pub(crate) fn test_code_oracle_big_bytecode() { - let precompiles_contract_address = Address::random(); + let precompiles_contract_address = Address::repeat_byte(1); let precompile_contract_bytecode = read_precompiles_contract(); let big_zkevm_bytecode = generate_large_bytecode(); @@ -168,7 +168,7 @@ pub(crate) fn test_code_oracle_big_bytecode() { } pub(crate) fn test_refunds_in_code_oracle() { - let precompiles_contract_address = Address::random(); + let precompiles_contract_address = Address::repeat_byte(1); let precompile_contract_bytecode = read_precompiles_contract(); let normal_zkevm_bytecode = read_test_contract(); diff --git a/core/lib/multivm/src/versions/testonly/get_used_contracts.rs b/core/lib/multivm/src/versions/testonly/get_used_contracts.rs index faaf507fcf0..fbad94a0eee 100644 --- a/core/lib/multivm/src/versions/testonly/get_used_contracts.rs +++ b/core/lib/multivm/src/versions/testonly/get_used_contracts.rs @@ -24,6 +24,7 @@ pub(crate) fn test_get_used_contracts() { let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_execution_mode(TxExecutionMode::VerifyExecute) + .with_rich_accounts(1) .build::(); assert!(known_bytecodes_without_base_system_contracts(&vm.vm).is_empty()); @@ -31,7 +32,7 @@ pub(crate) fn test_get_used_contracts() { // create and push and execute some not-empty factory deps transaction with success status // to check that `get_decommitted_hashes()` updates let contract_code = read_test_contract(); - let mut account = Account::random(); + let account = &mut vm.rich_accounts[0]; let tx = account.get_deploy_tx(&contract_code, None, TxType::L1 { serial_id: 0 }); vm.vm.push_transaction(tx.tx.clone()); let result = vm.vm.execute(VmExecutionMode::OneTx); @@ -58,7 +59,8 @@ pub(crate) fn test_get_used_contracts() { .take(calldata.len() * 1024) .cloned() .collect(); - let account2 = Account::random(); + let account2 = Account::from_seed(u32::MAX); + assert_ne!(account2.address, account.address); let tx2 = account2.get_l1_tx( Execute { contract_address: Some(CONTRACT_DEPLOYER_ADDRESS), diff --git a/core/lib/multivm/src/versions/testonly/l2_blocks.rs b/core/lib/multivm/src/versions/testonly/l2_blocks.rs index 8ea8cc958dc..634a9b34bf6 100644 --- a/core/lib/multivm/src/versions/testonly/l2_blocks.rs +++ b/core/lib/multivm/src/versions/testonly/l2_blocks.rs @@ -7,8 +7,8 @@ use assert_matches::assert_matches; use zksync_system_constants::REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE; use zksync_types::{ block::{pack_block_info, L2BlockHasher}, - AccountTreeId, Execute, ExecuteTransactionCommon, L1BatchNumber, L1TxCommonData, L2BlockNumber, - ProtocolVersionId, StorageKey, Transaction, H160, H256, SYSTEM_CONTEXT_ADDRESS, + AccountTreeId, Address, Execute, ExecuteTransactionCommon, L1BatchNumber, L1TxCommonData, + L2BlockNumber, ProtocolVersionId, StorageKey, Transaction, H256, SYSTEM_CONTEXT_ADDRESS, SYSTEM_CONTEXT_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_CURRENT_L2_BLOCK_INFO_POSITION, SYSTEM_CONTEXT_CURRENT_TX_ROLLING_HASH_POSITION, U256, }; @@ -29,13 +29,13 @@ use crate::{ fn get_l1_noop() -> Transaction { Transaction { common_data: ExecuteTransactionCommon::L1(L1TxCommonData { - sender: H160::random(), + sender: Address::repeat_byte(1), gas_limit: U256::from(2000000u32), gas_per_pubdata_limit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE.into(), ..Default::default() }), execute: Execute { - contract_address: Some(H160::zero()), + contract_address: Some(Address::repeat_byte(0xc0)), calldata: vec![], value: U256::zero(), factory_deps: vec![], @@ -47,7 +47,7 @@ fn get_l1_noop() -> Transaction { pub(crate) fn test_l2_block_initialization_timestamp() { // This test checks that the L2 block initialization works correctly. - // Here we check that that the first block must have timestamp that is greater or equal to the timestamp + // Here we check that the first block must have timestamp that is greater or equal to the timestamp // of the current batch. let mut vm = VmTesterBuilder::new() diff --git a/core/lib/multivm/src/versions/testonly/precompiles.rs b/core/lib/multivm/src/versions/testonly/precompiles.rs index 3abb7cb3902..270afab0731 100644 --- a/core/lib/multivm/src/versions/testonly/precompiles.rs +++ b/core/lib/multivm/src/versions/testonly/precompiles.rs @@ -11,7 +11,7 @@ use crate::{ pub(crate) fn test_keccak() { // Execute special transaction and check that at least 1000 keccak calls were made. let contract = read_precompiles_contract(); - let address = Address::random(); + let address = Address::repeat_byte(1); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_rich_accounts(1) @@ -47,7 +47,7 @@ pub(crate) fn test_keccak() { pub(crate) fn test_sha256() { // Execute special transaction and check that at least 1000 `sha256` calls were made. let contract = read_precompiles_contract(); - let address = Address::random(); + let address = Address::repeat_byte(1); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_rich_accounts(1) diff --git a/core/lib/multivm/src/versions/testonly/refunds.rs b/core/lib/multivm/src/versions/testonly/refunds.rs index d0934249de8..565607dff10 100644 --- a/core/lib/multivm/src/versions/testonly/refunds.rs +++ b/core/lib/multivm/src/versions/testonly/refunds.rs @@ -158,7 +158,7 @@ pub(crate) fn test_predetermined_refunded_gas() { } pub(crate) fn test_negative_pubdata_for_transaction() { - let expensive_contract_address = Address::random(); + let expensive_contract_address = Address::repeat_byte(1); let (expensive_contract_bytecode, expensive_contract) = read_expensive_contract(); let expensive_function = expensive_contract.function("expensive").unwrap(); let cleanup_function = expensive_contract.function("cleanUp").unwrap(); diff --git a/core/lib/multivm/src/versions/testonly/shadow.rs b/core/lib/multivm/src/versions/testonly/shadow.rs index d962e4411b0..6a7d42b06fc 100644 --- a/core/lib/multivm/src/versions/testonly/shadow.rs +++ b/core/lib/multivm/src/versions/testonly/shadow.rs @@ -70,8 +70,8 @@ impl Harness { fn new(l1_batch_env: &L1BatchEnv) -> Self { Self { - alice: Account::random(), - bob: Account::random(), + alice: Account::from_seed(0), + bob: Account::from_seed(1), storage_contract: ContractToDeploy::new( read_bytecode(Self::STORAGE_CONTRACT_PATH), Self::STORAGE_CONTRACT_ADDRESS, diff --git a/core/lib/multivm/src/versions/testonly/storage.rs b/core/lib/multivm/src/versions/testonly/storage.rs index c4e3b9bf739..4951272a60c 100644 --- a/core/lib/multivm/src/versions/testonly/storage.rs +++ b/core/lib/multivm/src/versions/testonly/storage.rs @@ -10,7 +10,7 @@ fn test_storage(first_tx_calldata: Vec, second_tx_calldata: Ve "etc/contracts-test-data/artifacts-zk/contracts/storage/storage.sol/StorageTester.json", ); - let test_contract_address = Address::random(); + let test_contract_address = Address::repeat_byte(1); // 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. diff --git a/core/lib/multivm/src/versions/testonly/tester/mod.rs b/core/lib/multivm/src/versions/testonly/tester/mod.rs index 2efccd9b149..4bab9bca610 100644 --- a/core/lib/multivm/src/versions/testonly/tester/mod.rs +++ b/core/lib/multivm/src/versions/testonly/tester/mod.rs @@ -5,7 +5,7 @@ use zksync_test_account::{Account, TxType}; use zksync_types::{ utils::{deployed_address_create, storage_key_for_eth_balance}, writes::StateDiffRecord, - Address, K256PrivateKey, L1BatchNumber, StorageKey, Transaction, H256, U256, + Address, L1BatchNumber, StorageKey, Transaction, H256, U256, }; use zksync_vm_interface::{ CurrentExecutionState, VmExecutionResultAndLogs, VmInterfaceHistoryEnabled, @@ -127,10 +127,7 @@ impl VmTesterBuilder { /// Creates the specified number of pre-funded accounts. pub(crate) fn with_rich_accounts(mut self, number: u32) -> Self { for i in 0..number { - let account_private_key_bytes = H256::from_low_u64_be(u64::from(i) + 1); - let account = - Account::new(K256PrivateKey::from_bytes(account_private_key_bytes).unwrap()); - self.rich_accounts.push(account); + self.rich_accounts.push(Account::from_seed(i)); } self } diff --git a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs index 36b458b849d..e87e6eb7c06 100644 --- a/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs +++ b/core/lib/multivm/src/versions/testonly/tracing_execution_error.rs @@ -1,5 +1,5 @@ use zksync_contracts::load_contract; -use zksync_types::{Execute, H160}; +use zksync_types::{Address, Execute}; use super::{ read_error_contract, tester::VmTesterBuilder, ContractToDeploy, TestedVm, BASE_SYSTEM_CONTRACTS, @@ -20,7 +20,7 @@ fn get_execute_error_calldata() -> Vec { } pub(crate) fn test_tracing_of_execution_errors() { - let contract_address = H160::random(); + let contract_address = Address::repeat_byte(1); let mut vm = VmTesterBuilder::new() .with_empty_in_memory_storage() .with_base_system_smart_contracts(BASE_SYSTEM_CONTRACTS.clone()) diff --git a/core/lib/multivm/src/versions/testonly/transfer.rs b/core/lib/multivm/src/versions/testonly/transfer.rs index 647570f2343..051826a64f2 100644 --- a/core/lib/multivm/src/versions/testonly/transfer.rs +++ b/core/lib/multivm/src/versions/testonly/transfer.rs @@ -22,8 +22,8 @@ fn test_send_or_transfer(test_option: TestOptions) { "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/TransferTest.json", ); - let test_contract_address = Address::random(); - let recipient_address = Address::random(); + let test_contract_address = Address::repeat_byte(1); + let recipient_address = Address::repeat_byte(2); let (value, calldata) = match test_option { TestOptions::Send(value) => ( @@ -106,8 +106,8 @@ fn test_reentrancy_protection_send_or_transfer(test_option: TestOp "etc/contracts-test-data/artifacts-zk/contracts/transfer/transfer.sol/ReentrantRecipient.json", ); - let test_contract_address = Address::random(); - let reentrant_recipient_address = Address::random(); + let test_contract_address = Address::repeat_byte(1); + let reentrant_recipient_address = Address::repeat_byte(2); let (value, calldata) = match test_option { TestOptions::Send(value) => ( diff --git a/core/lib/multivm/src/versions/testonly/upgrade.rs b/core/lib/multivm/src/versions/testonly/upgrade.rs index 0639dba0dc0..9401cbb4ba8 100644 --- a/core/lib/multivm/src/versions/testonly/upgrade.rs +++ b/core/lib/multivm/src/versions/testonly/upgrade.rs @@ -5,7 +5,7 @@ use zksync_types::{ get_code_key, get_known_code_key, protocol_upgrade::ProtocolUpgradeTxCommonData, Address, Execute, ExecuteTransactionCommon, Transaction, COMPLEX_UPGRADER_ADDRESS, - CONTRACT_DEPLOYER_ADDRESS, CONTRACT_FORCE_DEPLOYER_ADDRESS, H160, H256, + CONTRACT_DEPLOYER_ADDRESS, CONTRACT_FORCE_DEPLOYER_ADDRESS, H256, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, }; use zksync_utils::{bytecode::hash_bytecode, h256_to_u256, u256_to_h256}; @@ -35,7 +35,7 @@ pub(crate) fn test_protocol_upgrade_is_first() { // The bytecode hash to put on an address bytecode_hash, // The address on which to deploy the bytecode hash to - address: H160::random(), + address: Address::repeat_byte(1), // Whether to run the constructor on the force deployment call_constructor: false, // The value with which to initialize a contract @@ -49,7 +49,7 @@ pub(crate) fn test_protocol_upgrade_is_first() { // The bytecode hash to put on an address bytecode_hash, // The address on which to deploy the bytecode hash to - address: H160::random(), + address: Address::repeat_byte(2), // Whether to run the constructor on the force deployment call_constructor: false, // The value with which to initialize a contract @@ -120,7 +120,7 @@ pub(crate) fn test_force_deploy_upgrade() { .with_rich_accounts(1) .build::(); - let address_to_deploy = H160::random(); + let address_to_deploy = Address::repeat_byte(1); // Here we just use some random transaction of protocol upgrade type: let transaction = get_forced_deploy_tx(&[ForceDeployment { // The bytecode hash to put on an address @@ -158,7 +158,7 @@ pub(crate) fn test_complex_upgrader() { let msg_sender_test_hash = hash_bytecode(&read_msg_sender_test()); // Let's assume that the bytecode for the implementation of the complex upgrade // is already deployed in some address in user space - let upgrade_impl = H160::random(); + let upgrade_impl = Address::repeat_byte(1); let account_code_key = get_code_key(&upgrade_impl); storage.set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into())); storage.set_value( @@ -175,8 +175,8 @@ pub(crate) fn test_complex_upgrader() { .with_rich_accounts(1) .build::(); - let address_to_deploy1 = H160::random(); - let address_to_deploy2 = H160::random(); + let address_to_deploy1 = Address::repeat_byte(0xfe); + let address_to_deploy2 = Address::repeat_byte(0xff); let transaction = get_complex_upgrade_tx( upgrade_impl, diff --git a/core/tests/test_account/src/lib.rs b/core/tests/test_account/src/lib.rs index 39a36694526..cfb539c0e0f 100644 --- a/core/tests/test_account/src/lib.rs +++ b/core/tests/test_account/src/lib.rs @@ -54,6 +54,12 @@ impl Account { Self::new(K256PrivateKey::random_using(rng)) } + /// Creates an account deterministically from the provided seed. + pub fn from_seed(seed: u32) -> Self { + let private_key_bytes = H256::from_low_u64_be(u64::from(seed) + 1); + Self::new(K256PrivateKey::from_bytes(private_key_bytes).unwrap()) + } + pub fn get_l2_tx_for_execute(&mut self, execute: Execute, fee: Option) -> Transaction { let tx = self.get_l2_tx_for_execute_with_nonce(execute, fee, self.nonce); self.nonce += 1; From 9c108124a364e6c16d68d09b91effad613774709 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 18 Oct 2024 09:22:55 +0300 Subject: [PATCH 17/18] Make timestamp more realistic --- core/lib/multivm/src/versions/testonly/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 5cffdb09332..7e60f7a6d61 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -144,7 +144,8 @@ pub(super) fn default_system_env() -> SystemEnv { } pub(super) fn default_l1_batch(number: L1BatchNumber) -> L1BatchEnv { - let timestamp = number.0.into(); + // Add a bias to the timestamp to make it more realistic / "random". + let timestamp = 1_700_000_000 + u64::from(number.0); L1BatchEnv { previous_batch_hash: None, number, From 3ba9c99dfc4e3a2f181de039d039fd36a9aa85d6 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 18 Oct 2024 09:23:20 +0300 Subject: [PATCH 18/18] Document VM testing --- core/lib/multivm/README.md | 12 ++++++++++++ core/lib/multivm/src/versions/README.md | 17 ----------------- core/lib/multivm/src/versions/testonly/mod.rs | 2 ++ 3 files changed, 14 insertions(+), 17 deletions(-) delete mode 100644 core/lib/multivm/src/versions/README.md diff --git a/core/lib/multivm/README.md b/core/lib/multivm/README.md index 5e2af426ae5..f5e8a552242 100644 --- a/core/lib/multivm/README.md +++ b/core/lib/multivm/README.md @@ -4,3 +4,15 @@ This crate represents a wrapper over several versions of VM that have been used glue code that allows switching the VM version based on the externally provided marker while preserving the public interface. This crate exists to enable the external node to process breaking upgrades and re-execute all the transactions from the genesis block. + +## Developer guidelines + +### Adding tests + +If you want to add unit tests for the VM wrapper, consider the following: + +- Whenever possible, make tests reusable; declare test logic in the [`testonly`](src/versions/testonly/mod.rs) module, + and then instantiate tests using this logic for the supported VM versions. If necessary, extend the tested VM trait so + that test logic can be defined in a generic way. See the `testonly` module docs for more detailed guidelines. +- Do not use an RNG where it can be avoided (e.g., for test contract addresses). +- Avoid using zero / default values in cases they can be treated specially by the tested code. diff --git a/core/lib/multivm/src/versions/README.md b/core/lib/multivm/src/versions/README.md deleted file mode 100644 index 01c57509197..00000000000 --- a/core/lib/multivm/src/versions/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# MultiVM dependencies - -This folder contains the old versions of the VM we have used in the past. The `multivm` crate uses them to dynamically -switch the version we use to be able to sync from the genesis. This is a temporary measure until a "native" solution is -implemented (i.e., the `vm` crate would itself know the changes between versions, and thus we will have only the -functional diff between versions, not several fully-fledged VMs). - -## Versions - -| Name | Protocol versions | Description | -| ---------------------- | ----------------- | --------------------------------------------------------------------- | -| vm_m5 | 0 - 3 | Release for the testnet launch | -| vm_m6 | 4 - 6 | Release for the mainnet launch | -| vm_1_3_2 | 7 - 12 | Release 1.3.2 of the crypto circuits | -| vm_virtual_blocks | 13 - 15 | Adding virtual blocks to help with block number / timestamp migration | -| vm_refunds_enhancement | 16 - 17 | Fixing issue related to refunds in VM | -| vm_boojum_integration | 18 - | New Proving system (boojum), vm version 1.4.0 | diff --git a/core/lib/multivm/src/versions/testonly/mod.rs b/core/lib/multivm/src/versions/testonly/mod.rs index 7e60f7a6d61..838ba98a9aa 100644 --- a/core/lib/multivm/src/versions/testonly/mod.rs +++ b/core/lib/multivm/src/versions/testonly/mod.rs @@ -6,6 +6,8 @@ //! (`vm_latest`) and the fast VM (`vm_fast`). //! - Submodules of this module define test functions generic by `TestedVm`. Specific VM versions implement `TestedVm` //! and can create tests based on these test functions with minimum amount of boilerplate code. +//! - Tests use [`VmTester`] built using [`VmTesterBuilder`] to create a VM instance. This allows to set up storage for the VM, +//! custom [`SystemEnv`] / [`L1BatchEnv`], deployed contracts, pre-funded accounts etc. use ethabi::Contract; use once_cell::sync::Lazy;