Skip to content

Commit

Permalink
test(api): Add tests for EVM emulator – API (#3054)
Browse files Browse the repository at this point in the history
## What ❔

- Adds unit tests in the API server crate testing a mock EVM emulator.
- Allows `to == None` for `eth_call` and `debug_traceCall` RPC methods
if EVM emulation is enabled, to align with Ethereum node behavior.
- Fixes an integer overflow when estimating gas for L1 / upgrade
transactions.

## Why ❔

Ensures that EVM emulation will work as expected.

## 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 `zk_supervisor fmt` and `zk_supervisor
lint`.

fix(api): Allow `to == None` for `eth_call` and `debug_traceCall`
fix(api): Avoid integer overflow when estimating gas for L1 / upgrade
transactions
  • Loading branch information
slowli authored Oct 16, 2024
1 parent 6747529 commit 6918180
Show file tree
Hide file tree
Showing 17 changed files with 695 additions and 265 deletions.
34 changes: 17 additions & 17 deletions core/lib/vm_executor/src/oneshot/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use zksync_types::{
};
use zksync_utils::{h256_to_u256, time::seconds_since_epoch};

use super::env::OneshotEnvParameters;
use super::{env::OneshotEnvParameters, ContractsKind};

/// Block information necessary to execute a transaction / call. Unlike [`ResolvedBlockInfo`], this information is *partially* resolved,
/// which is beneficial for some data workflows.
Expand Down Expand Up @@ -178,7 +178,7 @@ impl ResolvedBlockInfo {
}
}

impl<T> OneshotEnvParameters<T> {
impl<C: ContractsKind> OneshotEnvParameters<C> {
pub(super) async fn to_env_inner(
&self,
connection: &mut Connection<'_, Core>,
Expand All @@ -194,28 +194,30 @@ impl<T> OneshotEnvParameters<T> {
)
.await?;

let (system, l1_batch) = self.prepare_env(
execution_mode,
resolved_block_info,
next_block,
fee_input,
enforced_base_fee,
);
let (system, l1_batch) = self
.prepare_env(
execution_mode,
resolved_block_info,
next_block,
fee_input,
enforced_base_fee,
)
.await?;
Ok(OneshotEnv {
system,
l1_batch,
current_block,
})
}

fn prepare_env(
async fn prepare_env(
&self,
execution_mode: TxExecutionMode,
resolved_block_info: &ResolvedBlockInfo,
next_block: L2BlockEnv,
fee_input: BatchFeeInput,
enforced_base_fee: Option<u64>,
) -> (SystemEnv, L1BatchEnv) {
) -> anyhow::Result<(SystemEnv, L1BatchEnv)> {
let &Self {
operator_account,
validation_computational_gas_limit,
Expand All @@ -228,11 +230,9 @@ impl<T> OneshotEnvParameters<T> {
version: resolved_block_info.protocol_version,
base_system_smart_contracts: self
.base_system_contracts
.get_by_protocol_version(
resolved_block_info.protocol_version,
resolved_block_info.use_evm_emulator,
)
.clone(),
.base_system_contracts(resolved_block_info)
.await
.context("failed getting base system contracts")?,
bootloader_gas_limit: BATCH_COMPUTATIONAL_GAS_LIMIT,
execution_mode,
default_validation_computational_gas_limit: validation_computational_gas_limit,
Expand All @@ -247,7 +247,7 @@ impl<T> OneshotEnvParameters<T> {
enforced_base_fee,
first_l2_block: next_block,
};
(system_env, l1_batch_env)
Ok((system_env, l1_batch_env))
}
}

Expand Down
77 changes: 70 additions & 7 deletions core/lib/vm_executor/src/oneshot/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
use std::{fmt, marker::PhantomData};

use async_trait::async_trait;
use zksync_contracts::BaseSystemContracts;
use zksync_types::ProtocolVersionId;

use super::ResolvedBlockInfo;
use crate::shared::Sealed;

/// Kind of base system contracts used as a marker in the [`BaseSystemContractsProvider`] trait.
pub trait ContractsKind: fmt::Debug + Sealed {}

/// Marker for [`BaseSystemContracts`] used for gas estimation.
#[derive(Debug)]
pub struct EstimateGas(());

impl Sealed for EstimateGas {}
impl ContractsKind for EstimateGas {}

/// Marker for [`BaseSystemContracts`] used for calls and transaction execution.
#[derive(Debug)]
pub struct CallOrExecute(());

impl Sealed for CallOrExecute {}
impl ContractsKind for CallOrExecute {}

/// Provider of [`BaseSystemContracts`] for oneshot execution.
///
/// The main implementation of this trait is [`MultiVMBaseSystemContracts`], which selects contracts
/// based on [`ProtocolVersionId`].
#[async_trait]
pub trait BaseSystemContractsProvider<C: ContractsKind>: fmt::Debug + Send + Sync {
/// Returns base system contracts for executing a transaction on top of the provided block.
///
/// Implementations are encouraged to cache returned contracts for performance; caching is **not** performed
/// by the caller.
///
/// # Errors
///
/// Returned errors are treated as unrecoverable for a particular execution, but further executions are not affected.
async fn base_system_contracts(
&self,
block_info: &ResolvedBlockInfo,
) -> anyhow::Result<BaseSystemContracts>;
}

/// System contracts (bootloader and default account abstraction) for all supported VM versions.
#[derive(Debug, Clone)]
pub(super) struct MultiVMBaseSystemContracts {
#[derive(Debug)]
pub struct MultiVMBaseSystemContracts<C> {
/// Contracts to be used for pre-virtual-blocks protocol versions.
pre_virtual_blocks: BaseSystemContracts,
/// Contracts to be used for post-virtual-blocks protocol versions.
Expand All @@ -24,11 +67,12 @@ pub(super) struct MultiVMBaseSystemContracts {
vm_1_5_0_increased_memory: BaseSystemContracts,
/// Contracts to be used after the protocol defense upgrade
vm_protocol_defense: BaseSystemContracts,
// We use `fn() -> C` marker so that the `MultiVMBaseSystemContracts` unconditionally implements `Send + Sync`.
_contracts_kind: PhantomData<fn() -> C>,
}

impl MultiVMBaseSystemContracts {
/// Gets contracts for a certain version.
pub fn get_by_protocol_version(
impl<C: ContractsKind> MultiVMBaseSystemContracts<C> {
fn get_by_protocol_version(
&self,
version: ProtocolVersionId,
use_evm_emulator: bool,
Expand Down Expand Up @@ -71,8 +115,11 @@ impl MultiVMBaseSystemContracts {
base
}
}
}

pub(super) fn load_estimate_gas_blocking() -> Self {
impl MultiVMBaseSystemContracts<EstimateGas> {
/// Returned system contracts (mainly the bootloader) are tuned to provide accurate execution metrics.
pub fn load_estimate_gas_blocking() -> Self {
Self {
pre_virtual_blocks: BaseSystemContracts::estimate_gas_pre_virtual_blocks(),
post_virtual_blocks: BaseSystemContracts::estimate_gas_post_virtual_blocks(),
Expand All @@ -86,10 +133,14 @@ impl MultiVMBaseSystemContracts {
vm_1_5_0_increased_memory:
BaseSystemContracts::estimate_gas_post_1_5_0_increased_memory(),
vm_protocol_defense: BaseSystemContracts::estimate_gas_post_protocol_defense(),
_contracts_kind: PhantomData,
}
}
}

pub(super) fn load_eth_call_blocking() -> Self {
impl MultiVMBaseSystemContracts<CallOrExecute> {
/// Returned system contracts (mainly the bootloader) are tuned to provide better UX (e.g. revert messages).
pub fn load_eth_call_blocking() -> Self {
Self {
pre_virtual_blocks: BaseSystemContracts::playground_pre_virtual_blocks(),
post_virtual_blocks: BaseSystemContracts::playground_post_virtual_blocks(),
Expand All @@ -103,6 +154,18 @@ impl MultiVMBaseSystemContracts {
vm_1_5_0_increased_memory: BaseSystemContracts::playground_post_1_5_0_increased_memory(
),
vm_protocol_defense: BaseSystemContracts::playground_post_protocol_defense(),
_contracts_kind: PhantomData,
}
}
}

#[async_trait]
impl<C: ContractsKind> BaseSystemContractsProvider<C> for MultiVMBaseSystemContracts<C> {
async fn base_system_contracts(
&self,
block_info: &ResolvedBlockInfo,
) -> anyhow::Result<BaseSystemContracts> {
Ok(self
.get_by_protocol_version(block_info.protocol_version(), block_info.use_evm_emulator()))
}
}
80 changes: 22 additions & 58 deletions core/lib/vm_executor/src/oneshot/env.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,49 @@
use std::marker::PhantomData;
use std::sync::Arc;

use anyhow::Context;
use zksync_dal::{Connection, Core};
use zksync_multivm::interface::{OneshotEnv, TxExecutionMode};
use zksync_types::{fee_model::BatchFeeInput, l2::L2Tx, AccountTreeId, L2ChainId};

use crate::oneshot::{contracts::MultiVMBaseSystemContracts, ResolvedBlockInfo};

/// Marker for [`OneshotEnvParameters`] used for gas estimation.
#[derive(Debug)]
pub struct EstimateGas(());

/// Marker for [`OneshotEnvParameters`] used for calls and/or transaction execution.
#[derive(Debug)]
pub struct CallOrExecute(());
use super::{
BaseSystemContractsProvider, CallOrExecute, ContractsKind, EstimateGas, ResolvedBlockInfo,
};

/// Oneshot environment parameters that are expected to be constant or rarely change during the program lifetime.
/// These parameters can be used to create [a full environment](OneshotEnv) for transaction / call execution.
///
/// Notably, these parameters include base system contracts (bootloader and default account abstraction) for all supported
/// VM versions.
#[derive(Debug)]
pub struct OneshotEnvParameters<T> {
pub struct OneshotEnvParameters<C: ContractsKind> {
pub(super) chain_id: L2ChainId,
pub(super) base_system_contracts: MultiVMBaseSystemContracts,
pub(super) base_system_contracts: Arc<dyn BaseSystemContractsProvider<C>>,
pub(super) operator_account: AccountTreeId,
pub(super) validation_computational_gas_limit: u32,
_ty: PhantomData<T>,
}

impl<T> OneshotEnvParameters<T> {
impl<C: ContractsKind> OneshotEnvParameters<C> {
/// Creates env parameters.
pub fn new(
base_system_contracts: Arc<dyn BaseSystemContractsProvider<C>>,
chain_id: L2ChainId,
operator_account: AccountTreeId,
validation_computational_gas_limit: u32,
) -> Self {
Self {
chain_id,
base_system_contracts,
operator_account,
validation_computational_gas_limit,
}
}

/// Returns gas limit for account validation of transactions.
pub fn validation_computational_gas_limit(&self) -> u32 {
self.validation_computational_gas_limit
}
}

impl OneshotEnvParameters<EstimateGas> {
/// Creates env parameters for gas estimation.
///
/// System contracts (mainly, bootloader) for these params are tuned to provide accurate
/// execution metrics.
pub async fn for_gas_estimation(
chain_id: L2ChainId,
operator_account: AccountTreeId,
) -> anyhow::Result<Self> {
Ok(Self {
chain_id,
base_system_contracts: tokio::task::spawn_blocking(
MultiVMBaseSystemContracts::load_estimate_gas_blocking,
)
.await
.context("failed loading system contracts for gas estimation")?,
operator_account,
validation_computational_gas_limit: u32::MAX,
_ty: PhantomData,
})
}

/// Prepares environment for gas estimation.
pub async fn to_env(
&self,
Expand All @@ -78,28 +64,6 @@ impl OneshotEnvParameters<EstimateGas> {
}

impl OneshotEnvParameters<CallOrExecute> {
/// Creates env parameters for transaction / call execution.
///
/// System contracts (mainly, bootloader) for these params tuned to provide better UX
/// experience (e.g. revert messages).
pub async fn for_execution(
chain_id: L2ChainId,
operator_account: AccountTreeId,
validation_computational_gas_limit: u32,
) -> anyhow::Result<Self> {
Ok(Self {
chain_id,
base_system_contracts: tokio::task::spawn_blocking(
MultiVMBaseSystemContracts::load_eth_call_blocking,
)
.await
.context("failed loading system contracts for calls")?,
operator_account,
validation_computational_gas_limit,
_ty: PhantomData,
})
}

/// Prepares environment for a call.
pub async fn to_call_env(
&self,
Expand Down
6 changes: 5 additions & 1 deletion core/lib/vm_executor/src/oneshot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ use zksync_utils::{h256_to_u256, u256_to_h256};

pub use self::{
block::{BlockInfo, ResolvedBlockInfo},
env::{CallOrExecute, EstimateGas, OneshotEnvParameters},
contracts::{
BaseSystemContractsProvider, CallOrExecute, ContractsKind, EstimateGas,
MultiVMBaseSystemContracts,
},
env::OneshotEnvParameters,
mock::MockOneshotExecutor,
};

Expand Down
9 changes: 8 additions & 1 deletion core/node/api_server/src/execution_sandbox/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,16 @@ impl SandboxExecutor {
}

pub(crate) async fn mock(executor: MockOneshotExecutor) -> Self {
Self::custom_mock(executor, SandboxExecutorOptions::mock().await)
}

pub(crate) fn custom_mock(
executor: MockOneshotExecutor,
options: SandboxExecutorOptions,
) -> Self {
Self {
engine: SandboxExecutorEngine::Mock(executor),
options: SandboxExecutorOptions::mock().await,
options,
storage_caches: None,
}
}
Expand Down
Loading

0 comments on commit 6918180

Please sign in to comment.