Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multivm tests for era_vm #234

Merged
merged 24 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
32a6e62
Add basic tester module for era_vm
MarcosNicolau Aug 16, 2024
c58540b
Add tests utils
MarcosNicolau Aug 16, 2024
0424f0e
Add basic bootloader tests
MarcosNicolau Aug 16, 2024
6768958
Add bootloader initial gas to vm
MarcosNicolau Aug 16, 2024
e306a2e
Fix tester module
MarcosNicolau Aug 20, 2024
31f0252
Add missing hooks Hook parsing
MarcosNicolau Aug 20, 2024
40e0d60
Add into system_log function
MarcosNicolau Aug 20, 2024
6a44603
Remove old tests
MarcosNicolau Aug 20, 2024
37f06af
Add get_current_execution_state
MarcosNicolau Aug 20, 2024
0e7c3e1
Add execution logs and statistics
MarcosNicolau Aug 20, 2024
c4cb6d6
Implement PostResult hook
MarcosNicolau Aug 20, 2024
fabe14b
Implement PubDataRequested hook
MarcosNicolau Aug 20, 2024
2b7c27c
Implement refunds
MarcosNicolau Aug 20, 2024
b53c810
Add push transaction inner to pass custom predefined refund
MarcosNicolau Aug 20, 2024
7ab61ac
Implement snapshots and rollbacks
MarcosNicolau Aug 20, 2024
2e2f63f
Make Vm needed fields and methods public
MarcosNicolau Aug 20, 2024
83c5ab4
Test util function to sort execution state
MarcosNicolau Aug 20, 2024
d4f4fdb
Add all remaining tests to era_vm
MarcosNicolau Aug 20, 2024
a525e56
Run zk fmt
MarcosNicolau Aug 20, 2024
ac13bf4
Fix PubDataRequested get_storage_diff
MarcosNicolau Aug 20, 2024
fcc3e70
Address warnings
MarcosNicolau Aug 20, 2024
96c6c9b
Merge branch 'era_vm_integration_v2' into tests/era_vm
MarcosNicolau Aug 20, 2024
32e98e7
Update get_storage_changes_from_snapshot return type
MarcosNicolau Aug 20, 2024
93ba138
Add whole state to VmDump
MarcosNicolau Aug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/lib/multivm/src/versions/era_vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ mod event;
mod hook;
mod initial_bootloader_memory;
mod snapshot;
#[cfg(test)]
mod tests;
mod transaction_data;
pub mod vm;
52 changes: 52 additions & 0 deletions core/lib/multivm/src/versions/era_vm/tests/bootloader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use zksync_types::U256;

use crate::{
interface::{ExecutionResult, Halt, TxExecutionMode, VmExecutionMode, VmInterface},
versions::era_vm::tests::{
tester::VmTesterBuilder,
utils::{get_bootloader, verify_required_memory, BASE_SYSTEM_CONTRACTS},
},
};

#[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.execution,
vec![(correct_first_cell, vm2::FIRST_HEAP, 0)],
);
}

#[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
}
));
}
3 changes: 3 additions & 0 deletions core/lib/multivm/src/versions/era_vm/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod bootloader;
mod tester;
mod utils;
6 changes: 6 additions & 0 deletions core/lib/multivm/src/versions/era_vm/tests/tester/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub(crate) use transaction_test_info::{ExpectedError, TransactionTestInfo, TxModifier};
pub(crate) use vm_tester::{default_l1_batch, get_empty_storage, VmTester, VmTesterBuilder};
pub(crate) use zksync_test_account::{Account, DeployContractsTx, TxType};

mod transaction_test_info;
mod vm_tester;
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use era_vm::state::Event;
use zksync_state::{InMemoryStorage, WriteStorage};
use zksync_types::{ExecuteTransactionCommon, Transaction, H160, U256};

use super::VmTester;
use crate::{
era_vm::vm::Vm,
interface::{
CurrentExecutionState, ExecutionResult, Halt, TxRevertReason, VmExecutionMode,
VmExecutionResultAndLogs, VmInterface, VmInterfaceHistoryEnabled, VmRevertReason,
},
};

#[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<TxModifier>,
}

impl From<TxModifier> 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,
}
}
}

// TODO this doesn't include all the state of ModifiedWorld
#[derive(Debug, PartialEq)]
struct VmStateDump {
state: era_vm::Execution,
storage_writes: Vec<((H160, U256), U256)>,
events: Vec<Event>,
}

impl Vm<InMemoryStorage> {
fn dump_state(&self) -> VmStateDump {
VmStateDump {
state: self.inner.execution.clone(),
storage_writes: self
.inner
.state
.storage_changes
.clone()
.into_iter()
.map(|(k, v)| ((k.address, k.key), v))
.collect(),
events: self.inner.state.events.clone().entries,
}
}
}

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 {
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
}
}
Loading
Loading