Skip to content

Commit af149a0

Browse files
joonazanslowli
andauthored
feat: vm2 account validation (#2863)
Implements an account validation gas limit and the validation tracer for vm2, along with better tests for validation. Instead of a second gas limit like in vm_latest, the normal gas limit is used. Unfortunately this means that the VM is not safe to use in the sequencer until we forbid the use of gasleft. I didn't do it here because it requires something like taint analysis and could break existing contracts that didn't know that gasleft is forbidden. --------- Co-authored-by: Alex Ostrovski <[email protected]> Co-authored-by: Alex Ostrovski <[email protected]>
1 parent 18e4307 commit af149a0

File tree

33 files changed

+1220
-383
lines changed

33 files changed

+1220
-383
lines changed

core/lib/contracts/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub fn l1_messenger_contract() -> Contract {
180180

181181
/// Reads bytecode from the path RELATIVE to the Cargo workspace location.
182182
pub fn read_bytecode(relative_path: impl AsRef<Path> + std::fmt::Debug) -> Vec<u8> {
183-
read_bytecode_from_path(relative_path).expect("Exists")
183+
read_bytecode_from_path(relative_path).expect("Failed to open file")
184184
}
185185

186186
pub fn eth_contract() -> Contract {

core/lib/multivm/src/tracers/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
pub use self::{
2-
call_tracer::CallTracer, multivm_dispatcher::TracerDispatcher, prestate_tracer::PrestateTracer,
3-
storage_invocation::StorageInvocations, validator::ValidationTracer,
2+
call_tracer::CallTracer,
3+
multivm_dispatcher::TracerDispatcher,
4+
prestate_tracer::PrestateTracer,
5+
storage_invocation::StorageInvocations,
6+
validator::{ValidationTracer, TIMESTAMP_ASSERTER_FUNCTION_SELECTOR},
47
};
58

69
mod call_tracer;

core/lib/multivm/src/tracers/validator/mod.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{
55
};
66

77
use once_cell::sync::OnceCell;
8+
pub use vm_latest::TIMESTAMP_ASSERTER_FUNCTION_SELECTOR;
89
use zksync_system_constants::{
910
ACCOUNT_CODE_STORAGE_ADDRESS, BOOTLOADER_ADDRESS, CONTRACT_DEPLOYER_ADDRESS,
1011
L2_BASE_TOKEN_ADDRESS, MSG_VALUE_SIMULATOR_ADDRESS, SYSTEM_CONTEXT_ADDRESS,
@@ -13,10 +14,7 @@ use zksync_types::{
1314
address_to_u256, u256_to_h256, vm::VmVersion, web3::keccak256, AccountTreeId, Address,
1415
StorageKey, H256, U256,
1516
};
16-
use zksync_vm_interface::{
17-
tracer::{TimestampAsserterParams, ValidationTraces},
18-
L1BatchEnv,
19-
};
17+
use zksync_vm_interface::tracer::{TimestampAsserterParams, ValidationTraces};
2018

2119
use self::types::{NewTrustedValidationItems, ValidationTracerMode};
2220
use crate::{
@@ -54,7 +52,7 @@ pub struct ValidationTracer<H> {
5452
computational_gas_limit: u32,
5553
timestamp_asserter_params: Option<TimestampAsserterParams>,
5654
vm_version: VmVersion,
57-
l1_batch_env: L1BatchEnv,
55+
l1_batch_timestamp: u64,
5856
pub result: Arc<OnceCell<ViolatedValidationRule>>,
5957
pub traces: Arc<Mutex<ValidationTraces>>,
6058
_marker: PhantomData<fn(H) -> H>,
@@ -65,7 +63,7 @@ type ValidationRoundResult = Result<NewTrustedValidationItems, ViolatedValidatio
6563
impl<H> ValidationTracer<H> {
6664
const MAX_ALLOWED_SLOT_OFFSET: u32 = 127;
6765

68-
pub fn new(params: ValidationParams, vm_version: VmVersion, l1_batch_env: L1BatchEnv) -> Self {
66+
pub fn new(params: ValidationParams, vm_version: VmVersion, l1_batch_timestamp: u64) -> Self {
6967
Self {
7068
validation_mode: ValidationTracerMode::NoValidation,
7169
auxilary_allowed_slots: Default::default(),
@@ -83,7 +81,7 @@ impl<H> ValidationTracer<H> {
8381
result: Arc::new(OnceCell::new()),
8482
traces: Arc::new(Mutex::new(ValidationTraces::default())),
8583
_marker: Default::default(),
86-
l1_batch_env,
84+
l1_batch_timestamp,
8785
}
8886
}
8987

core/lib/multivm/src/tracers/validator/vm_latest/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use zk_evm_1_5_0::{
22
tracing::{BeforeExecutionData, VmLocalStateData},
3-
zkevm_opcode_defs::{ContextOpcode, FarCallABI, LogOpcode, Opcode},
3+
zkevm_opcode_defs::{ContextOpcode, FarCallABI, LogOpcode, Opcode, RetOpcode},
44
};
55
use zksync_system_constants::KECCAK256_PRECOMPILE_ADDRESS;
66
use zksync_types::{
@@ -116,8 +116,7 @@ impl<H: HistoryMode> ValidationTracer<H> {
116116
// using self.l1_batch_env.timestamp is ok here because the tracer is always
117117
// used in a oneshot execution mode
118118
if end
119-
< self.l1_batch_env.timestamp
120-
+ params.min_time_till_end.as_secs()
119+
< self.l1_batch_timestamp + params.min_time_till_end.as_secs()
121120
{
122121
return Err(
123122
ViolatedValidationRule::TimestampAssertionCloseToRangeEnd,
@@ -168,6 +167,13 @@ impl<H: HistoryMode> ValidationTracer<H> {
168167
});
169168
}
170169
}
170+
171+
Opcode::Ret(RetOpcode::Panic)
172+
if state.vm_local_state.callstack.current.ergs_remaining == 0 =>
173+
{
174+
// Actual gas limit was reached, not the validation gas limit.
175+
return Err(ViolatedValidationRule::TookTooManyComputationalGas(0));
176+
}
171177
_ => {}
172178
}
173179

core/lib/multivm/src/versions/shadow/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{
2525
mod tests;
2626

2727
type ReferenceVm<S = InMemoryStorage> = vm_latest::Vm<StorageView<S>, HistoryEnabled>;
28-
type ShadowedFastVm<S = InMemoryStorage> = crate::vm_instance::ShadowedFastVm<S>;
28+
type ShadowedFastVm<S = InMemoryStorage> = crate::vm_instance::ShadowedFastVm<S, (), ()>;
2929

3030
fn hash_block(block_env: L2BlockEnv, tx_hashes: &[H256]) -> H256 {
3131
let mut hasher = L2BlockHasher::new(
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use assert_matches::assert_matches;
2+
use zksync_test_contracts::TestContract;
3+
use zksync_types::{u256_to_h256, AccountTreeId, Address, StorageKey};
4+
use zksync_vm_interface::tracer::ViolatedValidationRule;
5+
6+
use super::{
7+
get_empty_storage, require_eip712::make_aa_transaction, tester::VmTesterBuilder,
8+
ContractToDeploy, TestedVm, TestedVmForValidation,
9+
};
10+
use crate::interface::TxExecutionMode;
11+
12+
/// Checks that every limitation imposed on account validation results in an appropriate error.
13+
/// The actual misbehavior cases are found in "validation-rule-breaker.sol".
14+
pub(crate) fn test_account_validation_rules<VM: TestedVm + TestedVmForValidation>() {
15+
assert_matches!(test_rule::<VM>(0), None);
16+
assert_matches!(
17+
test_rule::<VM>(1),
18+
Some(ViolatedValidationRule::TouchedDisallowedStorageSlots(_, _))
19+
);
20+
assert_matches!(
21+
test_rule::<VM>(2),
22+
Some(ViolatedValidationRule::CalledContractWithNoCode(_))
23+
);
24+
assert_matches!(test_rule::<VM>(3), None);
25+
assert_matches!(
26+
test_rule::<VM>(4),
27+
Some(ViolatedValidationRule::TookTooManyComputationalGas(_))
28+
)
29+
}
30+
31+
fn test_rule<VM: TestedVm + TestedVmForValidation>(rule: u32) -> Option<ViolatedValidationRule> {
32+
let aa_address = Address::repeat_byte(0x10);
33+
let beneficiary_address = Address::repeat_byte(0x20);
34+
35+
// Set the type of misbehaviour of the AA contract
36+
let mut storage_with_rule_break_set = get_empty_storage();
37+
storage_with_rule_break_set.set_value(
38+
StorageKey::new(AccountTreeId::new(aa_address), u256_to_h256(0.into())),
39+
u256_to_h256(rule.into()),
40+
);
41+
42+
let bytecode = TestContract::validation_test().bytecode.to_vec();
43+
let mut vm = VmTesterBuilder::new()
44+
.with_empty_in_memory_storage()
45+
.with_custom_contracts(vec![
46+
ContractToDeploy::account(bytecode, aa_address).funded()
47+
])
48+
.with_storage(storage_with_rule_break_set)
49+
.with_execution_mode(TxExecutionMode::VerifyExecute)
50+
.with_rich_accounts(1)
51+
.build::<VM>();
52+
53+
let private_account = vm.rich_accounts[0].clone();
54+
55+
vm.vm.run_validation(
56+
make_aa_transaction(aa_address, beneficiary_address, &private_account),
57+
55,
58+
)
59+
}

core/lib/multivm/src/versions/testonly/l1_messenger.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@ pub(crate) fn test_rollup_da_output_hash_match<VM: TestedVm>() {
9999

100100
// Firstly, deploy tx. It should publish the bytecode of the "test contract"
101101
let counter_bytecode = TestContract::counter().bytecode;
102-
let tx = account
103-
.get_deploy_tx(&counter_bytecode, None, TxType::L2)
104-
.tx;
102+
let tx = account.get_deploy_tx(counter_bytecode, None, TxType::L2).tx;
105103
// We do not use compression here, to have the bytecode published in full.
106104
let (_, result) = vm
107105
.vm

core/lib/multivm/src/versions/testonly/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ use zksync_vm_interface::{
2424
pubdata::PubdataBuilder, L1BatchEnv, L2BlockEnv, SystemEnv, TxExecutionMode,
2525
};
2626

27-
pub(super) use self::tester::{TestedVm, VmTester, VmTesterBuilder};
27+
pub(super) use self::tester::{
28+
validation_params, TestedVm, TestedVmForValidation, VmTester, VmTesterBuilder,
29+
};
2830
use crate::{
2931
interface::storage::InMemoryStorage, pubdata_builders::RollupPubdataBuilder,
3032
vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT,
3133
};
3234

35+
pub(super) mod account_validation_rules;
3336
pub(super) mod block_tip;
3437
pub(super) mod bootloader;
3538
pub(super) mod bytecode_publishing;

core/lib/multivm/src/versions/testonly/require_eip712.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ethabi::Token;
22
use zksync_eth_signer::TransactionParameters;
3-
use zksync_test_contracts::TestContract;
3+
use zksync_test_contracts::{Account, TestContract};
44
use zksync_types::{
55
fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, Address, Eip712Domain, Execute,
66
L2ChainId, Nonce, Transaction, U256,
@@ -30,7 +30,6 @@ pub(crate) fn test_require_eip712<VM: TestedVm>() {
3030
.with_rich_accounts(1)
3131
.build::<VM>();
3232
assert_eq!(vm.get_eth_balance(beneficiary_address), U256::from(0));
33-
let chain_id: u32 = 270;
3433
let mut private_account = vm.rich_accounts[0].clone();
3534

3635
// First, let's set the owners of the AA account to the `private_address`.
@@ -97,7 +96,30 @@ pub(crate) fn test_require_eip712<VM: TestedVm>() {
9796
vm.get_eth_balance(private_account.address)
9897
);
9998

100-
// // Now send the 'classic' EIP712 transaction
99+
// Now send the 'classic' EIP712 transaction
100+
101+
let transaction: Transaction =
102+
make_aa_transaction(aa_address, beneficiary_address, &private_account).into();
103+
vm.vm.push_transaction(transaction);
104+
vm.vm.execute(InspectExecutionMode::OneTx);
105+
106+
assert_eq!(
107+
vm.get_eth_balance(beneficiary_address),
108+
U256::from(916375026)
109+
);
110+
assert_eq!(
111+
private_account_balance,
112+
vm.get_eth_balance(private_account.address)
113+
);
114+
}
115+
116+
pub(crate) fn make_aa_transaction(
117+
aa_address: Address,
118+
beneficiary_address: Address,
119+
private_account: &Account,
120+
) -> L2Tx {
121+
let chain_id: u32 = 270;
122+
101123
let tx_712 = L2Tx::new(
102124
Some(beneficiary_address),
103125
vec![],
@@ -130,16 +152,5 @@ pub(crate) fn test_require_eip712<VM: TestedVm>() {
130152
let mut l2_tx = L2Tx::from_request(aa_txn_request, 100000, false).unwrap();
131153
l2_tx.set_input(encoded_tx, aa_hash);
132154

133-
let transaction: Transaction = l2_tx.into();
134-
vm.vm.push_transaction(transaction);
135-
vm.vm.execute(InspectExecutionMode::OneTx);
136-
137-
assert_eq!(
138-
vm.get_eth_balance(beneficiary_address),
139-
U256::from(916375026)
140-
);
141-
assert_eq!(
142-
private_account_balance,
143-
vm.get_eth_balance(private_account.address)
144-
);
155+
l2_tx
145156
}

core/lib/multivm/src/versions/testonly/tester/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{collections::HashSet, fmt, rc::Rc};
33
use zksync_contracts::BaseSystemContracts;
44
use zksync_test_contracts::{Account, TestContract, TxType};
55
use zksync_types::{
6+
l2::L2Tx,
67
utils::{deployed_address_create, storage_key_for_eth_balance},
78
writes::StateDiffRecord,
89
Address, L1BatchNumber, StorageKey, Transaction, H256, U256,
@@ -14,6 +15,7 @@ use crate::{
1415
interface::{
1516
pubdata::{PubdataBuilder, PubdataInput},
1617
storage::{InMemoryStorage, StoragePtr, StorageView},
18+
tracer::{ValidationParams, ViolatedValidationRule},
1719
CurrentExecutionState, InspectExecutionMode, L1BatchEnv, L2BlockEnv, SystemEnv,
1820
TxExecutionMode, VmExecutionResultAndLogs, VmFactory, VmInterfaceExt,
1921
VmInterfaceHistoryEnabled,
@@ -231,3 +233,22 @@ pub(crate) trait TestedVm:
231233
/// Returns pubdata input.
232234
fn pubdata_input(&self) -> PubdataInput;
233235
}
236+
237+
pub(crate) trait TestedVmForValidation {
238+
fn run_validation(&mut self, tx: L2Tx, timestamp: u64) -> Option<ViolatedValidationRule>;
239+
}
240+
241+
pub(crate) fn validation_params(tx: &L2Tx, system: &SystemEnv) -> ValidationParams {
242+
let user_address = tx.common_data.initiator_address;
243+
let paymaster_address = tx.common_data.paymaster_params.paymaster;
244+
ValidationParams {
245+
user_address,
246+
paymaster_address,
247+
trusted_slots: Default::default(),
248+
trusted_addresses: Default::default(),
249+
// field `trustedAddress` of ValidationRuleBreaker
250+
trusted_address_slots: [(Address::repeat_byte(0x10), 2.into())].into(),
251+
computational_gas_limit: system.default_validation_computational_gas_limit,
252+
timestamp_asserter_params: None,
253+
}
254+
}

0 commit comments

Comments
 (0)