Skip to content

Commit 170d194

Browse files
authored
fix(vm): Fix VM divergences related to validation (#3567)
## What ❔ Fixes divergences in VM execution during account validation, or reconsiders what is considered a divergence. ## Why ❔ Fixes some of divergences currently observed during oneshot VM execution in the API server in CI (specifically, during the "custom account" integration test). ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. - [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`.
1 parent 9a5147a commit 170d194

File tree

19 files changed

+395
-103
lines changed

19 files changed

+395
-103
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ use crate::{
1818
versions::testonly::{
1919
default_l1_batch, default_system_env, make_address_rich, ContractToDeploy,
2020
},
21-
vm_fast, vm_latest,
21+
vm_fast,
22+
vm_fast::FastValidationTracer,
23+
vm_latest,
2224
vm_latest::HistoryEnabled,
2325
};
2426

2527
mod tests;
2628

2729
type ReferenceVm<S = InMemoryStorage> = vm_latest::Vm<StorageView<S>, HistoryEnabled>;
28-
type ShadowedFastVm<S = InMemoryStorage, Tr = ()> = crate::vm_instance::ShadowedFastVm<S, Tr, ()>;
30+
type ShadowedFastVm<S = InMemoryStorage, Tr = ()> =
31+
crate::vm_instance::ShadowedFastVm<S, Tr, FastValidationTracer>;
2932

3033
fn hash_block(block_env: L2BlockEnv, tx_hashes: &[H256]) -> H256 {
3134
let mut hasher = L2BlockHasher::new(
Lines changed: 172 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,104 @@
11
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;
2+
use zksync_test_contracts::{Account, TestContract};
3+
use zksync_types::{address_to_h256, fee::Fee, AccountTreeId, Address, StorageKey, H256};
54

65
use super::{
7-
get_empty_storage, require_eip712::make_aa_transaction, tester::VmTesterBuilder,
8-
ContractToDeploy, TestedVm, TestedVmForValidation,
6+
default_system_env, get_empty_storage, require_eip712::make_aa_transaction,
7+
tester::VmTesterBuilder, ContractToDeploy, TestedVm, TestedVmForValidation,
98
};
10-
use crate::interface::TxExecutionMode;
9+
use crate::interface::{
10+
tracer::ViolatedValidationRule, ExecutionResult, Halt, InspectExecutionMode, SystemEnv,
11+
TxExecutionMode, VmExecutionResultAndLogs, VmInterfaceExt,
12+
};
13+
14+
/// Corresponds to test cases in the `ValidationRuleBreaker` contract.
15+
#[derive(Debug, Clone, Copy)]
16+
#[repr(u32)]
17+
enum TestCase {
18+
Baseline = 0,
19+
ReadBootloaderBalance = 1,
20+
CallEoa = 2,
21+
ReadFromTrustedAddressSlot = 3,
22+
RecursiveOutOfGas = 4,
23+
PlainOutOfGas = 5,
24+
PlainOutOfGasWithCatch = 6,
25+
}
1126

1227
/// Checks that every limitation imposed on account validation results in an appropriate error.
1328
/// The actual misbehavior cases are found in "validation-rule-breaker.sol".
1429
pub(crate) fn test_account_validation_rules<VM: TestedVm + TestedVmForValidation>() {
15-
assert_matches!(test_rule::<VM>(0), None);
30+
let (result, violated_rule) = test_rule::<VM>(u32::MAX, TestCase::Baseline);
31+
assert!(!result.result.is_failed(), "{result:#?}");
32+
assert_matches!(violated_rule, None);
33+
34+
let (result, violated_rule) = test_rule::<VM>(u32::MAX, TestCase::ReadBootloaderBalance);
35+
assert_matches!(
36+
&result.result,
37+
ExecutionResult::Halt {
38+
reason: Halt::TracerCustom(_)
39+
}
40+
);
1641
assert_matches!(
17-
test_rule::<VM>(1),
42+
violated_rule,
1843
Some(ViolatedValidationRule::TouchedDisallowedStorageSlots(_, _))
1944
);
45+
46+
let (result, violated_rule) = test_rule::<VM>(u32::MAX, TestCase::CallEoa);
2047
assert_matches!(
21-
test_rule::<VM>(2),
22-
Some(ViolatedValidationRule::CalledContractWithNoCode(_))
48+
&result.result,
49+
ExecutionResult::Halt {
50+
reason: Halt::TracerCustom(_)
51+
}
2352
);
24-
assert_matches!(test_rule::<VM>(3), None);
2553
assert_matches!(
26-
test_rule::<VM>(4),
27-
Some(ViolatedValidationRule::TookTooManyComputationalGas(_))
28-
)
54+
violated_rule,
55+
Some(ViolatedValidationRule::CalledContractWithNoCode(_))
56+
);
57+
58+
let (result, violated_rule) = test_rule::<VM>(u32::MAX, TestCase::ReadFromTrustedAddressSlot);
59+
assert!(!result.result.is_failed(), "{result:#?}");
60+
assert_matches!(violated_rule, None);
61+
62+
for test_case in [TestCase::RecursiveOutOfGas, TestCase::PlainOutOfGas] {
63+
let (result, violated_rule) = test_rule::<VM>(u32::MAX, test_case);
64+
assert_matches!(
65+
&result.result,
66+
ExecutionResult::Halt {
67+
reason: Halt::TracerCustom(_)
68+
}
69+
);
70+
assert_matches!(
71+
violated_rule,
72+
Some(ViolatedValidationRule::TookTooManyComputationalGas(_))
73+
);
74+
}
2975
}
3076

31-
fn test_rule<VM: TestedVm + TestedVmForValidation>(rule: u32) -> Option<ViolatedValidationRule> {
77+
fn test_rule<VM: TestedVmForValidation>(
78+
validation_gas_limit: u32,
79+
test_case: TestCase,
80+
) -> (VmExecutionResultAndLogs, Option<ViolatedValidationRule>) {
3281
let aa_address = Address::repeat_byte(0x10);
3382
let beneficiary_address = Address::repeat_byte(0x20);
3483

3584
// Set the type of misbehaviour of the AA contract
3685
let mut storage_with_rule_break_set = get_empty_storage();
3786
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()),
87+
StorageKey::new(AccountTreeId::new(aa_address), H256::zero()),
88+
H256::from_low_u64_be(test_case as u64),
89+
);
90+
// Set the trusted address.
91+
storage_with_rule_break_set.set_value(
92+
StorageKey::new(AccountTreeId::new(aa_address), H256::from_low_u64_be(1)),
93+
address_to_h256(&Address::from_low_u64_be(0x800a)),
4094
);
4195

4296
let bytecode = TestContract::validation_test().bytecode.to_vec();
4397
let mut vm = VmTesterBuilder::new()
44-
.with_empty_in_memory_storage()
98+
.with_system_env(SystemEnv {
99+
default_validation_computational_gas_limit: validation_gas_limit,
100+
..default_system_env()
101+
})
45102
.with_custom_contracts(vec![
46103
ContractToDeploy::account(bytecode, aa_address).funded()
47104
])
@@ -50,10 +107,102 @@ fn test_rule<VM: TestedVm + TestedVmForValidation>(rule: u32) -> Option<Violated
50107
.with_rich_accounts(1)
51108
.build::<VM>();
52109

53-
let private_account = vm.rich_accounts[0].clone();
110+
let private_account = &mut vm.rich_accounts[0];
111+
let tx = make_aa_transaction(aa_address, beneficiary_address, private_account, None);
112+
vm.vm.run_validation(tx, 55)
113+
}
114+
115+
const OUT_OF_GAS_CASES: [TestCase; 3] = [
116+
TestCase::PlainOutOfGas,
117+
TestCase::PlainOutOfGasWithCatch,
118+
TestCase::RecursiveOutOfGas,
119+
];
120+
121+
pub(crate) fn test_validation_out_of_gas_with_full_tracer<VM: TestedVmForValidation>() {
122+
for test_case in OUT_OF_GAS_CASES {
123+
println!("Testing case: {test_case:?}");
124+
let (result, violated_rule) = test_rule::<VM>(300_000, test_case);
125+
assert_matches!(
126+
&result.result,
127+
ExecutionResult::Halt {
128+
reason: Halt::TracerCustom(_)
129+
}
130+
);
131+
assert_matches!(
132+
violated_rule,
133+
Some(ViolatedValidationRule::TookTooManyComputationalGas(_))
134+
);
135+
}
136+
}
137+
138+
pub(crate) fn test_validation_out_of_gas_with_fast_tracer<VM: TestedVm>() {
139+
for test_case in OUT_OF_GAS_CASES {
140+
println!("Testing case: {test_case:?}");
141+
142+
// Large tx gas limit should lead to a validation-specific halt reason.
143+
let tx_gas_limits: &[_] = if matches!(test_case, TestCase::RecursiveOutOfGas) {
144+
&[100_000_000, 200_000_000, 500_000_000, 1_000_000_000]
145+
} else {
146+
&[1_000_000, 10_000_000, 100_000_000, 1_000_000_000]
147+
};
148+
149+
for &tx_gas_limit in tx_gas_limits {
150+
println!("Testing tx with gas limit: {tx_gas_limit}");
151+
let result = run_validation_with_gas_limit::<VM>(test_case, tx_gas_limit);
152+
assert_matches!(
153+
&result.result,
154+
ExecutionResult::Halt {
155+
reason: Halt::ValidationOutOfGas,
156+
}
157+
);
158+
}
159+
160+
// If the tx gas limit is lower than the validation gas limit, the bootloader should exit super-early.
161+
println!("Testing tx with low gas limit");
162+
let result = run_validation_with_gas_limit::<VM>(test_case, 250_000);
163+
assert_matches!(
164+
&result.result,
165+
ExecutionResult::Halt {
166+
reason: Halt::ValidationFailed(_)
167+
}
168+
);
169+
}
170+
}
171+
172+
fn run_validation_with_gas_limit<VM: TestedVm>(
173+
test_case: TestCase,
174+
tx_gas_limit: u32,
175+
) -> VmExecutionResultAndLogs {
176+
let aa_address = Address::repeat_byte(0x10);
177+
let beneficiary_address = Address::repeat_byte(0x20);
178+
let bytecode = TestContract::validation_test().bytecode.to_vec();
179+
180+
// Configure the AA to run out of gas during validation.
181+
let mut storage_with_rule_break_set = get_empty_storage();
182+
storage_with_rule_break_set.set_value(
183+
StorageKey::new(AccountTreeId::new(aa_address), H256::zero()),
184+
H256::from_low_u64_be(test_case as u64),
185+
);
186+
187+
let mut vm = VmTesterBuilder::new()
188+
.with_system_env(SystemEnv {
189+
default_validation_computational_gas_limit: 300_000,
190+
..default_system_env()
191+
})
192+
.with_custom_contracts(vec![
193+
ContractToDeploy::account(bytecode, aa_address).funded()
194+
])
195+
.with_storage(storage_with_rule_break_set)
196+
.with_execution_mode(TxExecutionMode::VerifyExecute)
197+
.with_rich_accounts(1)
198+
.build::<VM>();
54199

55-
vm.vm.run_validation(
56-
make_aa_transaction(aa_address, beneficiary_address, &private_account),
57-
55,
58-
)
200+
let private_account = &mut vm.rich_accounts[0];
201+
let fee = Fee {
202+
gas_limit: tx_gas_limit.into(),
203+
..Account::default_fee()
204+
};
205+
let tx = make_aa_transaction(aa_address, beneficiary_address, private_account, Some(fee));
206+
vm.vm.push_transaction(tx.into());
207+
vm.vm.execute(InspectExecutionMode::OneTx)
59208
}

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use zksync_eth_signer::TransactionParameters;
33
use zksync_test_contracts::{Account, TestContract};
44
use zksync_types::{
55
fee::Fee, l2::L2Tx, transaction_request::TransactionRequest, Address, Eip712Domain, Execute,
6-
L2ChainId, Nonce, Transaction, U256,
6+
L2ChainId, Transaction, U256,
77
};
88

99
use super::{tester::VmTesterBuilder, ContractToDeploy, TestedVm};
@@ -97,9 +97,8 @@ pub(crate) fn test_require_eip712<VM: TestedVm>() {
9797
);
9898

9999
// Now send the 'classic' EIP712 transaction
100-
101100
let transaction: Transaction =
102-
make_aa_transaction(aa_address, beneficiary_address, &private_account).into();
101+
make_aa_transaction(aa_address, beneficiary_address, &mut private_account, None).into();
103102
vm.vm.push_transaction(transaction);
104103
vm.vm.execute(InspectExecutionMode::OneTx);
105104

@@ -116,20 +115,18 @@ pub(crate) fn test_require_eip712<VM: TestedVm>() {
116115
pub(crate) fn make_aa_transaction(
117116
aa_address: Address,
118117
beneficiary_address: Address,
119-
private_account: &Account,
118+
private_account: &mut Account,
119+
fee: Option<Fee>,
120120
) -> L2Tx {
121121
let chain_id: u32 = 270;
122122

123+
let nonce = private_account.nonce;
124+
private_account.nonce += 1;
123125
let tx_712 = L2Tx::new(
124126
Some(beneficiary_address),
125127
vec![],
126-
Nonce(1),
127-
Fee {
128-
gas_limit: U256::from(1000000000),
129-
max_fee_per_gas: U256::from(1000000000),
130-
max_priority_fee_per_gas: U256::from(1000000000),
131-
gas_per_pubdata_limit: U256::from(1000000000),
132-
},
128+
nonce,
129+
fee.unwrap_or_else(Account::default_fee),
133130
aa_address,
134131
U256::from(28374938),
135132
vec![],

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,12 @@ pub(crate) trait TestedVm:
235235
fn pubdata_input(&self) -> PubdataInput;
236236
}
237237

238-
pub(crate) trait TestedVmForValidation {
239-
fn run_validation(&mut self, tx: L2Tx, timestamp: u64) -> Option<ViolatedValidationRule>;
238+
pub(crate) trait TestedVmForValidation: TestedVm {
239+
fn run_validation(
240+
&mut self,
241+
tx: L2Tx,
242+
timestamp: u64,
243+
) -> (VmExecutionResultAndLogs, Option<ViolatedValidationRule>);
240244
}
241245

242246
pub(crate) fn validation_params(tx: &L2Tx, system: &SystemEnv) -> ValidationParams {
@@ -248,7 +252,7 @@ pub(crate) fn validation_params(tx: &L2Tx, system: &SystemEnv) -> ValidationPara
248252
trusted_slots: Default::default(),
249253
trusted_addresses: Default::default(),
250254
// field `trustedAddress` of ValidationRuleBreaker
251-
trusted_address_slots: [(Address::repeat_byte(0x10), 2.into())].into(),
255+
trusted_address_slots: [(Address::repeat_byte(0x10), 1.into())].into(),
252256
computational_gas_limit: system.default_validation_computational_gas_limit,
253257
timestamp_asserter_params: None,
254258
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ pub use zksync_vm2::interface;
22

33
pub(crate) use self::version::FastVmVersion;
44
pub use self::{
5-
tracers::{CallTracer, FullValidationTracer, StorageInvocationsTracer, ValidationTracer},
5+
tracers::{
6+
CallTracer, FastValidationTracer, FullValidationTracer, StorageInvocationsTracer,
7+
ValidationTracer,
8+
},
69
vm::Vm,
710
};
811

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
use super::TestedFastVm;
2-
use crate::versions::testonly::account_validation_rules::test_account_validation_rules;
2+
use crate::{
3+
versions::testonly::account_validation_rules::{
4+
test_account_validation_rules, test_validation_out_of_gas_with_fast_tracer,
5+
test_validation_out_of_gas_with_full_tracer,
6+
},
7+
vm_fast::FastValidationTracer,
8+
};
39

410
#[test]
5-
fn test_account_validation_rules_fast() {
11+
fn account_validation_rules() {
612
test_account_validation_rules::<TestedFastVm<(), _>>();
713
}
14+
15+
#[test]
16+
fn validation_out_of_gas_with_full_tracer() {
17+
test_validation_out_of_gas_with_full_tracer::<TestedFastVm<(), _>>();
18+
}
19+
20+
#[test]
21+
fn validation_out_of_gas_with_fast_tracer() {
22+
test_validation_out_of_gas_with_fast_tracer::<TestedFastVm<(), FastValidationTracer>>();
23+
}

0 commit comments

Comments
 (0)