Skip to content

Commit

Permalink
Add basic test setup for era_vm
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcosNicolau committed Aug 13, 2024
1 parent bea26df commit 62d4e8b
Show file tree
Hide file tree
Showing 8 changed files with 895 additions and 147 deletions.
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;
53 changes: 53 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,53 @@
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.state,
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,247 @@
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::state::VMState,
storage_writes: Vec<((H160, U256), U256)>,
events: Vec<Event>,
}

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

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

0 comments on commit 62d4e8b

Please sign in to comment.