anvil_zksync_core/node/inner/
vm_runner.rs

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    /// Whether VM should generate system logs.
60    generate_system_logs: bool,
61    /// Optional field for reporting progress while replaying transactions.
62    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    // Prints the gas details of the transaction for debugging purposes.
107    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    /// Validates L2 transaction
130    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            // We ignore `inner` because bytecode
183            // compression error currently does not hold
184            // any precise information
185            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        // Print gas details if enabled
234        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        // Print storage logs if enabled
245        if config.show_storage_logs != ShowStorageLogs::None {
246            print_storage_logs_details(config.show_storage_logs, &tx_result);
247        }
248        // Print VM details if enabled
249        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    /// Runs transaction and commits it to a new block.
262    #[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            // If the transaction can not be validated, we return immediately
280            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            // Halt means that something went really bad with the transaction execution
298            // (in most cases invalid signature, but it could also be bootloader panic etc).
299            // In such cases, we should not persist the VM data and should pretend that
300            // the transaction never existed.
301            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            // During impersonation, we skip account validation (which is responsible for updating nonce)
309            // so we do it manually for each transaction that didn't result in a halt.
310            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            // In boojum, we store account properties outside of state (so state has only hash).
320            // For now, we simply put the original preimages into the factory deps.
321            // The result type here is the 'era' crate - that is not modified to fit boojum os yet.
322            // once it is - we will not need this hack anymore.
323            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 to unwrap here as Halt is handled above
393
394        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        // Prepare a new block context and a new batch env
417        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        // Advance clock as we are consuming next timestamp for this block
421
422        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                // For boojum, we have to pass the iterator handle to the storage
439                // as boojum has different storage layout, so it has to scan over whole storage.
440                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        // Compute block hash. Note that the computed block hash here will be different than that in production.
453        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        // Execute transactions and bootloader
463        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            // Update progress bar
493            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                        // Validation errors are reported and the execution proceeds
504                        AnvilNodeError::TransactionValidationFailed { .. } => {
505                            let error_report = ExecutionErrorReport::new(&e, &tx);
506                            sh_eprintln!("{error_report}");
507                            executor.rollback_last_tx().await?;
508                        }
509                        // Halts are reported and the execution proceeds
510                        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                        // Other errors are not recoverable so we pass them up
516                        // the execution stack immediately
517                        _ => return Err(e),
518                    }
519                }
520            }
521        }
522        // TODO: This is the correct hash as reported by VM, but we can't compute it correct above
523        //       because we don't know which txs are going to be halted
524        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            // Create an empty virtual block at the end of the batch (only if the last block was
537            // not empty, i.e. virtual).
538            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            // If system log generation is enabled we run realistic (and time-consuming) bootloader flow
557            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            // Otherwise we mock the execution with a single bootloader iteration
564            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    /// Set or unset the progress report.
592    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    // Get transaction factory deps
604    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    // Ensure that *dynamic* factory deps (ones that may be created when executing EVM contracts)
615    // are added into the lookup map as well.
616    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    /// Decodes a `bytes` tx result to its concrete parameter type.
807    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        // Perform a transaction to get storage to an intermediate state
880        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        // Execute next transaction using a fresh in-memory node and mocked fork client
886        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(), // keccak selector for "transact_retrieve1()"
946            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}