1use crate::bootloader_debug::BootloaderDebug;
2use crate::formatter;
3use crate::formatter::errors::view::ExecutionErrorReport;
4use crate::formatter::log::{compute_gas_details, Formatter};
5use crate::formatter::transaction::summary::TransactionSummary;
6use crate::node::batch::{MainBatchExecutorFactory, TraceCalls};
7use crate::node::diagnostics::transaction::known_addresses_after_transaction;
8use crate::node::diagnostics::vm::balance_diff::extract_balance_diffs;
9use crate::node::diagnostics::vm::traces::extract_addresses;
10use crate::node::error::ToHaltError;
11use crate::node::inner::fork_storage::ForkStorage;
12use crate::node::inner::in_memory_inner::BlockContext;
13use crate::node::storage_logs::print_storage_logs_details;
14use crate::node::time::Time;
15use crate::node::traces::decoder::CallTraceDecoderBuilder;
16use crate::node::{
17 compute_hash, InMemoryNodeInner, StorageKeyLayout, TestNodeFeeInputProvider, TransactionResult,
18 TxBatch, TxExecutionInfo,
19};
20use crate::system_contracts::SystemContracts;
21use crate::utils::create_debug_output;
22use anvil_zksync_common::shell::get_shell;
23use anvil_zksync_common::{sh_eprintln, sh_err, sh_println};
24use anvil_zksync_config::TestNodeConfig;
25use anvil_zksync_traces::{
26 build_call_trace_arena, decode_trace_arena, filter_call_trace_arena,
27 identifier::SignaturesIdentifier, render_trace_arena_inner,
28};
29use anvil_zksync_types::{ShowGasDetails, ShowStorageLogs, ShowVMDetails};
30use indicatif::ProgressBar;
31use std::collections::HashMap;
32use std::sync::{Arc, RwLock};
33use zksync_contracts::BaseSystemContractsHashes;
34use zksync_error::anvil_zksync;
35use zksync_error::anvil_zksync::node::{AnvilNodeError, AnvilNodeResult};
36use zksync_multivm::interface::executor::BatchExecutor;
37use zksync_multivm::interface::storage::{ReadStorage, WriteStorage};
38use zksync_multivm::interface::{
39 BatchTransactionExecutionResult, ExecutionResult, FinishedL1Batch, L1BatchEnv, L2BlockEnv,
40 TxExecutionMode, VmEvent, VmExecutionResultAndLogs,
41};
42use zksync_multivm::zk_evm_latest::ethereum_types::{Address, H160, U256, U64};
43use zksync_types::block::L2BlockHasher;
44use zksync_types::bytecode::BytecodeHash;
45use zksync_types::commitment::{PubdataParams, PubdataType};
46use zksync_types::web3::Bytes;
47use zksync_types::{
48 api, h256_to_address, h256_to_u256, u256_to_h256, ExecuteTransactionCommon, L2BlockNumber,
49 L2TxCommonData, StorageKey, StorageValue, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS,
50};
51
52pub struct VmRunner {
53 executor_factory: MainBatchExecutorFactory<TraceCalls>,
54 bootloader_debug_result: Arc<RwLock<eyre::Result<BootloaderDebug, String>>>,
55
56 time: Time,
57 fork_storage: ForkStorage,
58 system_contracts: SystemContracts,
59 generate_system_logs: bool,
61 progress_report: Option<ProgressBar>,
63 storage_layout: StorageKeyLayout,
64}
65
66pub(super) struct TxBatchExecutionResult {
67 pub(super) tx_results: Vec<TransactionResult>,
68 pub(super) base_system_contracts_hashes: BaseSystemContractsHashes,
69 pub(super) batch_env: L1BatchEnv,
70 pub(super) block_ctxs: Vec<BlockContext>,
71 pub(super) finished_l1_batch: FinishedL1Batch,
72 pub(super) modified_storage_keys: HashMap<StorageKey, StorageValue>,
73}
74
75impl VmRunner {
76 pub(super) fn new(
77 time: Time,
78 fork_storage: ForkStorage,
79 system_contracts: SystemContracts,
80 generate_system_logs: bool,
81 enforced_bytecode_compression: bool,
82 storage_layout: StorageKeyLayout,
83 ) -> Self {
84 let bootloader_debug_result = Arc::new(std::sync::RwLock::new(Err(
85 "Tracer has not been run yet".to_string(),
86 )));
87 Self {
88 executor_factory: MainBatchExecutorFactory::<TraceCalls>::new(
89 enforced_bytecode_compression,
90 bootloader_debug_result.clone(),
91 system_contracts.boojum.clone(),
92 ),
93 bootloader_debug_result,
94
95 time,
96 fork_storage,
97 system_contracts,
98 generate_system_logs,
99 progress_report: None,
100 storage_layout,
101 }
102 }
103}
104
105impl VmRunner {
106 fn display_detailed_gas_info(
108 &self,
109 bootloader_debug_result: Option<&eyre::Result<BootloaderDebug, String>>,
110 spent_on_pubdata: u64,
111 fee_input_provider: &TestNodeFeeInputProvider,
112 ) -> eyre::Result<(), String> {
113 if let Some(bootloader_result) = bootloader_debug_result {
114 let bootloader_debug = bootloader_result.clone()?;
115
116 let gas_details = compute_gas_details(&bootloader_debug, spent_on_pubdata);
117 let mut formatter = Formatter::new();
118
119 let fee_model_config = fee_input_provider.get_fee_model_config();
120
121 formatter.print_gas_details(&gas_details, &fee_model_config);
122
123 Ok(())
124 } else {
125 Err("Bootloader tracer didn't finish.".to_owned())
126 }
127 }
128
129 fn validate_tx(
131 &self,
132 batch_env: &L1BatchEnv,
133 tx_data: &L2TxCommonData,
134 ) -> Result<(), anvil_zksync::tx_invalid::TransactionValidationError> {
135 let max_gas = U256::from(u64::MAX);
136 if tx_data.fee.gas_limit > max_gas {
137 return Err(anvil_zksync::tx_invalid::InvalidGasLimit {
138 tx_gas_limit: Box::new(tx_data.fee.gas_limit),
139 max_gas: Box::new(max_gas),
140 });
141 }
142
143 if tx_data.fee.gas_per_pubdata_limit > max_gas {
144 return Err(anvil_zksync::tx_invalid::GasPerPubdataLimit {
145 tx_gas_per_pubdata_limit: Box::new(tx_data.fee.gas_per_pubdata_limit),
146 max_gas: Box::new(max_gas),
147 });
148 }
149
150 let l2_gas_price = batch_env.fee_input.fair_l2_gas_price();
151 if tx_data.fee.max_fee_per_gas < l2_gas_price.into() {
152 return Err(anvil_zksync::tx_invalid::MaxFeePerGasTooLow {
153 max_fee_per_gas: Box::new(tx_data.fee.max_fee_per_gas),
154 l2_gas_price: Box::new(l2_gas_price.into()),
155 });
156 }
157
158 if tx_data.fee.max_fee_per_gas < tx_data.fee.max_priority_fee_per_gas {
159 return Err(anvil_zksync::tx_invalid::MaxPriorityFeeGreaterThanMaxFee {
160 max_fee_per_gas: Box::new(tx_data.fee.max_fee_per_gas),
161 max_priority_fee_per_gas: Box::new(tx_data.fee.max_priority_fee_per_gas),
162 });
163 }
164 Ok(())
165 }
166
167 async fn run_tx_pretty(
168 &mut self,
169 tx: &Transaction,
170 executor: &mut dyn BatchExecutor<ForkStorage>,
171 config: &TestNodeConfig,
172 fee_input_provider: &TestNodeFeeInputProvider,
173 ) -> AnvilNodeResult<BatchTransactionExecutionResult> {
174 let verbosity = get_shell().verbosity;
175
176 let BatchTransactionExecutionResult {
177 tx_result,
178 compression_result,
179 call_traces,
180 } = executor.execute_tx(tx.clone()).await?;
181 compression_result.map_err(|_inner| {
182 anvil_zksync::node::TransactionHalt {
186 inner: Box::new(anvil_zksync::halt::FailedToPublishCompressedBytecodes),
187 transaction_hash: Box::new(tx.hash()),
188 }
189 })?;
190
191 let spent_on_pubdata =
192 tx_result.statistics.gas_used - tx_result.statistics.computational_gas_used as u64;
193
194 let mut known_addresses = known_addresses_after_transaction(tx);
195 let mut trace_output = None;
196
197 if !call_traces.is_empty() {
198 let mut builder = CallTraceDecoderBuilder::default();
199
200 builder = builder.with_signature_identifier(SignaturesIdentifier::global());
201
202 let decoder = builder.build();
203 let mut arena = build_call_trace_arena(&call_traces, &tx_result);
204 extract_addresses(&arena, &mut known_addresses);
205
206 if verbosity >= 2 {
207 decode_trace_arena(&mut arena, &decoder).await;
208 let filtered_arena = filter_call_trace_arena(&arena, verbosity);
209 trace_output = Some(render_trace_arena_inner(&filtered_arena, false));
210 }
211 }
212
213 let balance_diffs: Vec<formatter::transaction::balance_diff::BalanceDiff> =
214 extract_balance_diffs(&known_addresses, &tx_result.logs.storage_logs)
215 .into_iter()
216 .map(Into::into)
217 .collect();
218
219 sh_println!(
220 "{}",
221 TransactionSummary::new(
222 config.get_l2_gas_price(),
223 tx,
224 &tx_result,
225 (verbosity >= 1).then_some(balance_diffs),
226 )
227 );
228
229 if let Some(trace_output) = trace_output {
230 sh_println!("\nTraces:\n{}", trace_output);
231 }
232
233 if config.show_gas_details != ShowGasDetails::None {
235 self.display_detailed_gas_info(
236 Some(&self.bootloader_debug_result.read().unwrap()),
237 spent_on_pubdata,
238 fee_input_provider,
239 )
240 .unwrap_or_else(|err| {
241 sh_err!("{}", format!("Cannot display gas details: {err}"));
242 });
243 }
244 if config.show_storage_logs != ShowStorageLogs::None {
246 print_storage_logs_details(config.show_storage_logs, &tx_result);
247 }
248 if config.show_vm_details != ShowVMDetails::None {
250 let mut formatter = Formatter::new();
251 formatter.print_vm_details(&tx_result);
252 }
253
254 Ok(BatchTransactionExecutionResult {
255 tx_result,
256 compression_result: Ok(()),
257 call_traces,
258 })
259 }
260
261 #[allow(clippy::too_many_arguments)]
263 async fn run_tx(
264 &mut self,
265 tx: &Transaction,
266 tx_index: u64,
267 next_log_index: &mut usize,
268 block_ctx: &BlockContext,
269 batch_env: &L1BatchEnv,
270 executor: &mut dyn BatchExecutor<ForkStorage>,
271 config: &TestNodeConfig,
272 fee_input_provider: &TestNodeFeeInputProvider,
273 impersonating: bool,
274 ) -> AnvilNodeResult<TransactionResult> {
275 let tx_hash = tx.hash();
276 let transaction_type = tx.tx_format();
277
278 if let ExecuteTransactionCommon::L2(l2_tx_data) = &tx.common_data {
279 self.validate_tx(batch_env, l2_tx_data).map_err(|e| {
281 anvil_zksync::node::TransactionValidationFailed {
282 inner: Box::new(e),
283 transaction_hash: Box::new(tx_hash),
284 }
285 })?;
286 }
287
288 let BatchTransactionExecutionResult {
289 tx_result: result,
290 compression_result: _,
291 call_traces,
292 } = self
293 .run_tx_pretty(tx, executor, config, fee_input_provider)
294 .await?;
295
296 if let ExecutionResult::Halt { reason } = result.result {
297 return Err(anvil_zksync::node::TransactionHalt {
302 inner: Box::new(reason.to_halt_error().await),
303 transaction_hash: Box::new(tx_hash),
304 });
305 }
306
307 if impersonating {
308 let nonce_key = self.storage_layout.get_nonce_key(&tx.initiator_account());
311 let nonce = h256_to_u256(self.fork_storage.read_value(&nonce_key));
312 let nonce = u256_to_h256(nonce + 1);
313 self.fork_storage.set_value(nonce_key, nonce);
314 }
315
316 let mut new_bytecodes = new_bytecodes(tx, &result);
317
318 if self.system_contracts.boojum.use_boojum {
319 new_bytecodes.extend(result.dynamic_factory_deps.clone());
324 }
325
326 let logs = result
327 .logs
328 .events
329 .iter()
330 .enumerate()
331 .map(|(log_idx, log)| api::Log {
332 address: log.address,
333 topics: log.indexed_topics.clone(),
334 data: Bytes(log.value.clone()),
335 block_hash: Some(block_ctx.hash),
336 block_number: Some(block_ctx.miniblock.into()),
337 l1_batch_number: Some(U64::from(batch_env.number.0)),
338 transaction_hash: Some(tx_hash),
339 transaction_index: Some(U64::from(tx_index)),
340 log_index: Some(U256::from(log_idx)),
341 transaction_log_index: Some(U256::from(log_idx)),
342 log_type: None,
343 removed: Some(false),
344 block_timestamp: Some(block_ctx.timestamp.into()),
345 })
346 .collect();
347
348 let tx_receipt = api::TransactionReceipt {
349 transaction_hash: tx_hash,
350 transaction_index: U64::from(tx_index),
351 block_hash: block_ctx.hash,
352 block_number: block_ctx.miniblock.into(),
353 l1_batch_tx_index: Some(U64::from(tx_index)),
354 l1_batch_number: Some(U64::from(batch_env.number.0)),
355 from: tx.initiator_account(),
356 to: tx.recipient_account(),
357 cumulative_gas_used: Default::default(),
358 gas_used: Some(tx.gas_limit() - result.refunds.gas_refunded),
359 contract_address: contract_address_from_tx_result(&result),
360 logs,
361 l2_to_l1_logs: result
362 .logs
363 .user_l2_to_l1_logs
364 .iter()
365 .enumerate()
366 .map(|(log_index, log)| api::L2ToL1Log {
367 block_hash: Some(block_ctx.hash),
368 block_number: block_ctx.miniblock.into(),
369 l1_batch_number: Some(U64::from(batch_env.number.0)),
370 log_index: U256::from(*next_log_index + log_index),
371 transaction_index: U64::from(tx_index),
372 transaction_hash: tx_hash,
373 transaction_log_index: U256::from(log_index),
374 tx_index_in_l1_batch: Some(U64::from(tx_index)),
375 shard_id: log.0.shard_id.into(),
376 is_service: log.0.is_service,
377 sender: log.0.sender,
378 key: log.0.key,
379 value: log.0.value,
380 })
381 .collect(),
382 status: if result.result.is_failed() {
383 U64::from(0)
384 } else {
385 U64::from(1)
386 },
387 effective_gas_price: Some(fee_input_provider.gas_price().into()),
388 transaction_type: Some((transaction_type as u32).into()),
389 logs_bloom: Default::default(),
390 };
391 *next_log_index += result.logs.user_l2_to_l1_logs.len();
392 let debug = create_debug_output(tx, &result, call_traces).expect("create debug output"); Ok(TransactionResult {
395 info: TxExecutionInfo {
396 tx: tx.clone(),
397 batch_number: batch_env.number.0,
398 miniblock_number: block_ctx.miniblock,
399 },
400 new_bytecodes,
401 receipt: tx_receipt,
402 debug,
403 })
404 }
405
406 pub(super) async fn run_tx_batch(
407 &mut self,
408 TxBatch { txs, impersonating }: TxBatch,
409 node_inner: &mut InMemoryNodeInner,
410 ) -> AnvilNodeResult<TxBatchExecutionResult> {
411 let system_contracts = self
412 .system_contracts
413 .contracts(TxExecutionMode::VerifyExecute, impersonating)
414 .clone();
415 let base_system_contracts_hashes = system_contracts.hashes();
416 let system_env =
418 node_inner.create_system_env(system_contracts, TxExecutionMode::VerifyExecute);
419 let (batch_env, mut block_ctx) = node_inner.create_l1_batch_env().await;
420 if self.time.advance_timestamp() != block_ctx.timestamp {
423 return Err(anvil_zksync::node::generic_error!(
424 "Advancing clock produced different timestamp than expected. This should never happen -- please report this as a bug."
425 ));
426 };
427
428 let pubdata_params = PubdataParams {
429 l2_da_validator_address: Address::zero(),
430 pubdata_type: PubdataType::Rollup,
431 };
432 let mut executor = if self.system_contracts.boojum.use_boojum {
433 self.executor_factory.init_main_batch(
434 self.fork_storage.clone(),
435 batch_env.clone(),
436 system_env.clone(),
437 pubdata_params,
438 Some(self.fork_storage.inner.read().unwrap().raw_storage.clone()),
441 )
442 } else {
443 self.executor_factory.init_main_batch(
444 self.fork_storage.clone(),
445 batch_env.clone(),
446 system_env.clone(),
447 pubdata_params,
448 None,
449 )
450 };
451
452 let tx_hashes = txs.iter().map(|t| t.hash()).collect::<Vec<_>>();
454 block_ctx.hash = compute_hash(
455 system_env.version,
456 (block_ctx.miniblock as u32).into(),
457 block_ctx.timestamp,
458 block_ctx.prev_block_hash,
459 &tx_hashes,
460 );
461
462 let mut tx_results = Vec::with_capacity(tx_hashes.len());
464 let mut tx_index = 0;
465 let mut next_log_index = 0;
466 let total = txs.len();
467
468 for tx in txs {
469 if let Some(ref pb) = self.progress_report {
470 pb.set_message(format!(
471 "Replaying transaction {}/{} from 0x{:x}...",
472 tx_index + 1,
473 total,
474 tx.hash()
475 ));
476 }
477
478 let result = self
479 .run_tx(
480 &tx,
481 tx_index,
482 &mut next_log_index,
483 &block_ctx,
484 &batch_env,
485 &mut executor,
486 &node_inner.config,
487 &node_inner.fee_input_provider,
488 impersonating,
489 )
490 .await;
491
492 if let Some(ref pb) = self.progress_report {
494 pb.inc(1);
495 }
496 match result {
497 Ok(tx_result) => {
498 tx_results.push(tx_result);
499 tx_index += 1;
500 }
501 Err(e) => {
502 match &e {
503 AnvilNodeError::TransactionValidationFailed { .. } => {
505 let error_report = ExecutionErrorReport::new(&e, &tx);
506 sh_eprintln!("{error_report}");
507 executor.rollback_last_tx().await?;
508 }
509 AnvilNodeError::TransactionHalt { inner, .. } => {
511 let error_report = ExecutionErrorReport::new(inner.as_ref(), &tx);
512 sh_eprintln!("{error_report}");
513 executor.rollback_last_tx().await?;
514 }
515 _ => return Err(e),
518 }
519 }
520 }
521 }
522 block_ctx.hash = compute_hash(
525 system_env.version,
526 (block_ctx.miniblock as u32).into(),
527 block_ctx.timestamp,
528 block_ctx.prev_block_hash,
529 tx_results
530 .iter()
531 .map(|tx_result| &tx_result.receipt.transaction_hash),
532 );
533
534 let mut block_ctxs = vec![block_ctx.clone()];
535 if !tx_results.is_empty() {
536 let mut virtual_block_ctx = block_ctx.new_block(&mut self.time);
539 virtual_block_ctx.hash = L2BlockHasher::new(
540 L2BlockNumber(virtual_block_ctx.miniblock as u32),
541 virtual_block_ctx.timestamp,
542 block_ctx.hash,
543 )
544 .finalize(system_env.version);
545 let l2_block_env = L2BlockEnv {
546 number: (block_ctx.miniblock + 1) as u32,
547 timestamp: block_ctx.timestamp + 1,
548 prev_block_hash: block_ctx.hash,
549 max_virtual_blocks_to_create: 1,
550 };
551 executor.start_next_l2_block(l2_block_env).await?;
552 block_ctxs.push(virtual_block_ctx);
553 }
554
555 let (finished_l1_batch, modified_storage_keys) = if self.generate_system_logs {
556 let (finished_l1_batch, storage_view) = Box::new(executor).finish_batch().await?;
558 (
559 finished_l1_batch,
560 storage_view.modified_storage_keys().clone(),
561 )
562 } else {
563 let mut finished_l1_batch = FinishedL1Batch::mock();
565 let (bootloader_execution_result, storage_view) = executor.bootloader().await?;
566 finished_l1_batch.block_tip_execution_result = bootloader_execution_result;
567 (
568 finished_l1_batch,
569 storage_view.modified_storage_keys().clone(),
570 )
571 };
572 assert!(
573 !finished_l1_batch
574 .block_tip_execution_result
575 .result
576 .is_failed(),
577 "VM must not fail when finalizing block: {:#?}",
578 finished_l1_batch.block_tip_execution_result.result
579 );
580
581 Ok(TxBatchExecutionResult {
582 tx_results,
583 base_system_contracts_hashes,
584 batch_env,
585 block_ctxs,
586 finished_l1_batch,
587 modified_storage_keys,
588 })
589 }
590
591 pub fn set_progress_report(&mut self, bar: Option<ProgressBar>) {
593 self.progress_report = bar;
594 }
595}
596
597fn new_bytecodes(
598 tx: &Transaction,
599 result: &VmExecutionResultAndLogs,
600) -> Vec<(zksync_types::H256, Vec<u8>)> {
601 let saved_factory_deps = VmEvent::extract_bytecodes_marked_as_known(&result.logs.events);
602
603 let factory_deps = &tx.execute.factory_deps;
605 let mut tx_factory_deps: HashMap<_, _> = factory_deps
606 .iter()
607 .map(|bytecode| {
608 (
609 BytecodeHash::for_bytecode(bytecode).value(),
610 bytecode.clone(),
611 )
612 })
613 .collect();
614 tx_factory_deps.extend(result.dynamic_factory_deps.clone());
617 saved_factory_deps
618 .map(|bytecode_hash| {
619 let bytecode = tx_factory_deps.get(&bytecode_hash).unwrap_or_else(|| {
620 panic!(
621 "Failed to get factory deps on tx: bytecode hash: {:?}, tx hash: {}",
622 bytecode_hash,
623 tx.hash()
624 )
625 });
626 (bytecode_hash, bytecode.clone())
627 })
628 .collect::<Vec<_>>()
629}
630
631fn contract_address_from_tx_result(execution_result: &VmExecutionResultAndLogs) -> Option<H160> {
632 for query in execution_result.logs.storage_logs.iter().rev() {
633 if query.log.is_write() && query.log.key.address() == &ACCOUNT_CODE_STORAGE_ADDRESS {
634 return Some(h256_to_address(query.log.key.key()));
635 }
636 }
637 None
638}
639
640#[cfg(test)]
641mod test {
642 use super::*;
643 use crate::node::fork::{Fork, ForkClient, ForkDetails};
644 use crate::testing::{TransactionBuilder, STORAGE_CONTRACT_BYTECODE};
645 use alloy::dyn_abi::{DynSolType, DynSolValue};
646 use alloy::primitives::U256 as AlloyU256;
647 use anvil_zksync::node::AnvilNodeResult;
648 use anvil_zksync_common::cache::CacheConfig;
649 use anvil_zksync_config::constants::{
650 DEFAULT_ACCOUNT_BALANCE, DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
651 DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, DEFAULT_FAIR_PUBDATA_PRICE, DEFAULT_L1_GAS_PRICE,
652 DEFAULT_L2_GAS_PRICE, TEST_NODE_NETWORK_ID,
653 };
654 use anvil_zksync_config::types::SystemContractsOptions;
655 use std::str::FromStr;
656 use zksync_multivm::interface::executor::BatchExecutorFactory;
657 use zksync_multivm::interface::storage::StorageView;
658 use zksync_multivm::interface::{L2Block, SystemEnv};
659 use zksync_multivm::vm_latest::constants::BATCH_COMPUTATIONAL_GAS_LIMIT;
660 use zksync_multivm::vm_latest::utils::l2_blocks::load_last_l2_block;
661 use zksync_types::fee::Fee;
662 use zksync_types::fee_model::BatchFeeInput;
663 use zksync_types::l2::{L2Tx, TransactionType};
664 use zksync_types::utils::deployed_address_create;
665 use zksync_types::{
666 u256_to_h256, K256PrivateKey, L1BatchNumber, L2ChainId, Nonce, ProtocolVersionId, H256,
667 };
668
669 struct VmRunnerTester {
670 vm_runner: VmRunner,
671 config: TestNodeConfig,
672 system_contracts: SystemContracts,
673 }
674
675 impl VmRunnerTester {
676 fn new_custom(fork_client: Option<ForkClient>, config: TestNodeConfig) -> Self {
677 let storage_layout = if config.boojum.use_boojum {
678 StorageKeyLayout::BoojumOs
679 } else {
680 StorageKeyLayout::ZkEra
681 };
682
683 let time = Time::new(0);
684 let fork_storage = ForkStorage::new(
685 Fork::new(fork_client, CacheConfig::None),
686 SystemContractsOptions::BuiltIn,
687 ProtocolVersionId::latest(),
688 None,
689 None,
690 );
691 let system_contracts = SystemContracts::from_options(
692 config.system_contracts_options,
693 config.system_contracts_path.clone(),
694 ProtocolVersionId::latest(),
695 config.use_evm_interpreter,
696 config.boojum.clone(),
697 );
698 let vm_runner = VmRunner::new(
699 time,
700 fork_storage,
701 system_contracts.clone(),
702 false,
703 config.is_bytecode_compression_enforced(),
704 storage_layout,
705 );
706 VmRunnerTester {
707 vm_runner,
708 config,
709 system_contracts,
710 }
711 }
712
713 fn new() -> Self {
714 Self::new_custom(None, TestNodeConfig::default())
715 }
716
717 fn make_rich(&self, account: &Address) {
718 let key = zksync_types::utils::storage_key_for_eth_balance(account);
719 self.vm_runner
720 .fork_storage
721 .set_value(key, u256_to_h256(U256::from(DEFAULT_ACCOUNT_BALANCE)));
722 }
723
724 async fn test_tx(&mut self, tx: Transaction) -> AnvilNodeResult<TransactionResult> {
725 Ok(self.test_txs(vec![tx]).await?.into_iter().next().unwrap())
726 }
727
728 async fn test_txs(
729 &mut self,
730 txs: Vec<Transaction>,
731 ) -> AnvilNodeResult<Vec<TransactionResult>> {
732 let system_env = SystemEnv {
733 zk_porter_available: false,
734 version: ProtocolVersionId::latest(),
735 base_system_smart_contracts: self
736 .system_contracts
737 .contracts(TxExecutionMode::VerifyExecute, false)
738 .clone(),
739 bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT,
740 execution_mode: TxExecutionMode::VerifyExecute,
741 default_validation_computational_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT,
742 chain_id: L2ChainId::from(TEST_NODE_NETWORK_ID),
743 };
744 let last_l2_block = load_last_l2_block(
745 &StorageView::new(self.vm_runner.fork_storage.clone()).to_rc_ptr(),
746 )
747 .unwrap_or_else(|| L2Block {
748 number: 0,
749 hash: H256::from_str(
750 "0xe8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c",
751 )
752 .unwrap(),
753 timestamp: 0,
754 });
755 let block_ctx = BlockContext {
756 hash: Default::default(),
757 batch: last_l2_block.number + 1,
758 miniblock: (last_l2_block.number + 1) as u64,
759 timestamp: last_l2_block.timestamp + 1,
760 prev_block_hash: last_l2_block.hash,
761 };
762 let batch_env = L1BatchEnv {
763 previous_batch_hash: None,
764 number: L1BatchNumber::from(block_ctx.batch),
765 timestamp: block_ctx.timestamp,
766 fee_input: BatchFeeInput::l1_pegged(DEFAULT_L1_GAS_PRICE, DEFAULT_L2_GAS_PRICE),
767 fee_account: H160::zero(),
768 enforced_base_fee: None,
769 first_l2_block: L2BlockEnv {
770 number: block_ctx.miniblock as u32,
771 timestamp: block_ctx.timestamp,
772 prev_block_hash: block_ctx.prev_block_hash,
773 max_virtual_blocks_to_create: 1,
774 },
775 };
776 let mut executor = self.vm_runner.executor_factory.init_batch(
777 self.vm_runner.fork_storage.clone(),
778 batch_env.clone(),
779 system_env,
780 PubdataParams::default(),
781 );
782
783 let mut log_index = 0;
784 let mut results = vec![];
785 for (i, tx) in txs.into_iter().enumerate() {
786 results.push(
787 self.vm_runner
788 .run_tx(
789 &tx,
790 i as u64,
791 &mut log_index,
792 &block_ctx,
793 &batch_env,
794 executor.as_mut(),
795 &self.config,
796 &TestNodeFeeInputProvider::default(),
797 false,
798 )
799 .await?,
800 );
801 }
802 Ok(results)
803 }
804 }
805
806 fn decode_tx_result(output: &[u8], param_type: DynSolType) -> DynSolValue {
808 let result = DynSolType::Bytes
809 .abi_decode(output)
810 .expect("failed decoding output");
811 let result_bytes = match result {
812 DynSolValue::Bytes(bytes) => bytes,
813 _ => panic!("expected bytes but got a different type"),
814 };
815
816 param_type
817 .abi_decode(&result_bytes)
818 .expect("failed decoding output")
819 }
820
821 #[tokio::test]
822 async fn test_run_l2_tx_validates_tx_gas_limit_too_high() {
823 let mut tester = VmRunnerTester::new();
824 let tx = TransactionBuilder::new()
825 .set_gas_limit(U256::from(u64::MAX) + 1)
826 .build();
827 let max_gas = U256::from(u64::MAX);
828 let expected = AnvilNodeError::TransactionValidationFailed {
829 transaction_hash: Box::new(tx.hash()),
830 inner: Box::new(anvil_zksync::tx_invalid::InvalidGasLimit {
831 tx_gas_limit: Box::new(tx.common_data.fee.gas_limit),
832 max_gas: Box::new(max_gas),
833 }),
834 };
835 let err = tester.test_tx(tx.into()).await.unwrap_err();
836 assert_eq!(err, expected);
837 }
838
839 #[tokio::test]
840 async fn test_run_l2_tx_validates_tx_max_fee_per_gas_too_low() {
841 let mut tester = VmRunnerTester::new();
842 let tx = TransactionBuilder::new()
843 .set_max_fee_per_gas(U256::from(DEFAULT_L2_GAS_PRICE - 1))
844 .build();
845 let expected = AnvilNodeError::TransactionValidationFailed {
846 transaction_hash: Box::new(tx.hash()),
847 inner: Box::new(anvil_zksync::tx_invalid::MaxFeePerGasTooLow {
848 max_fee_per_gas: Box::new(tx.common_data.fee.max_fee_per_gas),
849 l2_gas_price: Box::new(DEFAULT_L2_GAS_PRICE.into()),
850 }),
851 };
852 let err = tester.test_tx(tx.into()).await.unwrap_err();
853 assert_eq!(err, expected);
854 }
855
856 #[tokio::test]
857 async fn test_run_l2_tx_validates_tx_max_priority_fee_per_gas_higher_than_max_fee_per_gas() {
858 let mut tester = VmRunnerTester::new();
859 let max_priority_fee_per_gas = U256::from(250_000_000 + 1);
860 let tx = TransactionBuilder::new()
861 .set_max_priority_fee_per_gas(max_priority_fee_per_gas)
862 .build();
863
864 let expected = AnvilNodeError::TransactionValidationFailed {
865 transaction_hash: Box::new(tx.hash()),
866 inner: Box::new(anvil_zksync::tx_invalid::MaxPriorityFeeGreaterThanMaxFee {
867 max_fee_per_gas: Box::new(tx.common_data.fee.max_fee_per_gas),
868 max_priority_fee_per_gas: Box::new(tx.common_data.fee.max_priority_fee_per_gas),
869 }),
870 };
871 let err = tester.test_tx(tx.into()).await.unwrap_err();
872 assert_eq!(err, expected);
873 }
874
875 #[tokio::test]
876 async fn test_run_tx_raw_does_not_panic_on_mock_fork_client_call() {
877 let mut tester = VmRunnerTester::new();
878
879 let tx = TransactionBuilder::new().build();
881 tester.make_rich(&tx.initiator_account());
882 let res = tester.test_tx(tx.into()).await.unwrap();
883 assert_eq!(res.receipt.status, U64::from(1));
884
885 let fork_details = ForkDetails {
887 chain_id: TEST_NODE_NETWORK_ID.into(),
888 batch_number: L1BatchNumber(1),
889 block_number: L2BlockNumber(2),
890 block_hash: Default::default(),
891 block_timestamp: 1002,
892 api_block: api::Block::default(),
893 l1_gas_price: 1000,
894 l2_fair_gas_price: DEFAULT_L2_GAS_PRICE,
895 fair_pubdata_price: DEFAULT_FAIR_PUBDATA_PRICE,
896 estimate_gas_price_scale_factor: DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
897 estimate_gas_scale_factor: DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
898 ..Default::default()
899 };
900 let mock_fork_client = ForkClient::mock(
901 fork_details,
902 tester
903 .vm_runner
904 .fork_storage
905 .inner
906 .read()
907 .unwrap()
908 .raw_storage
909 .clone(),
910 );
911 let mut tester =
912 VmRunnerTester::new_custom(Some(mock_fork_client), TestNodeConfig::default());
913 let tx = TransactionBuilder::new().build();
914 tester.make_rich(&tx.initiator_account());
915 tester
916 .test_tx(tx.into())
917 .await
918 .expect("transaction must pass with mock fork client");
919 }
920
921 #[tokio::test]
922 async fn test_transact_returns_data_in_built_in_without_security_mode() {
923 let mut tester = VmRunnerTester::new_custom(
924 None,
925 TestNodeConfig {
926 system_contracts_options: SystemContractsOptions::BuiltInWithoutSecurity,
927 ..Default::default()
928 },
929 );
930
931 let private_key = K256PrivateKey::from_bytes(H256::repeat_byte(0xef)).unwrap();
932 let from_account = private_key.address();
933 tester.make_rich(&from_account);
934
935 let deployed_address = deployed_address_create(from_account, U256::zero());
936 let deploy_tx = TransactionBuilder::deploy_contract(
937 &private_key,
938 hex::decode(STORAGE_CONTRACT_BYTECODE).unwrap(),
939 None,
940 Nonce(0),
941 );
942
943 let mut tx = L2Tx::new_signed(
944 Some(deployed_address),
945 hex::decode("bbf55335").unwrap(), Nonce(1),
947 Fee {
948 gas_limit: U256::from(4_000_000),
949 max_fee_per_gas: U256::from(250_000_000),
950 max_priority_fee_per_gas: U256::from(250_000_000),
951 gas_per_pubdata_limit: U256::from(50000),
952 },
953 U256::from(0),
954 zksync_types::L2ChainId::from(260),
955 &private_key,
956 vec![],
957 Default::default(),
958 )
959 .expect("failed signing tx");
960 tx.common_data.transaction_type = TransactionType::LegacyTransaction;
961 tx.set_input(vec![], H256::repeat_byte(0x2));
962
963 let result = tester
964 .test_txs(vec![deploy_tx.into(), tx.into()])
965 .await
966 .expect("failed tx");
967 assert_eq!(
968 result[1].receipt.status,
969 U64::from(1),
970 "invalid status {:?}",
971 result[1].receipt.status
972 );
973
974 let actual = decode_tx_result(&result[1].debug.output.0, DynSolType::Uint(256));
975 let expected = DynSolValue::Uint(AlloyU256::from(1024), 256);
976 assert_eq!(expected, actual, "invalid result");
977 }
978}