From 2bf331cbd155b653756a1d088bcbbc7e27828f2f Mon Sep 17 00:00:00 2001 From: AntonD3 Date: Wed, 20 May 2026 15:20:58 +0200 Subject: [PATCH 1/6] refactor: bootloader zk tx flow --- basic_bootloader/Cargo.toml | 2 - .../src/bootloader/block_flow/zk/mod.rs | 11 +- basic_bootloader/src/bootloader/constants.rs | 5 - basic_bootloader/src/bootloader/errors.rs | 2 - basic_bootloader/src/bootloader/mod.rs | 1 - .../src/bootloader/transaction/access_list.rs | 2 +- .../transaction/rlp_encoded/transaction.rs | 4 +- .../transaction_flow/gas_helpers.rs | 342 ++++++------------ .../transaction_flow/refund_calculation.rs | 31 +- .../src/bootloader/transaction_flow/zk/mod.rs | 17 +- .../zk/process_l1_transaction.rs | 57 ++- .../transaction_flow/zk/validation_impl.rs | 88 ++--- forward_system/Cargo.toml | 13 +- forward_system/src/run/convert.rs | 1 - proof_running_system/Cargo.toml | 13 +- tests/evm_divergence_validator/Cargo.toml | 2 +- .../basic_bootloader_forward/Cargo.toml | 2 - tests/revm_runner/src/revm_runner.rs | 3 +- tests/rig/Cargo.toml | 1 - tests/rig/src/chain.rs | 3 +- 20 files changed, 254 insertions(+), 346 deletions(-) diff --git a/basic_bootloader/Cargo.toml b/basic_bootloader/Cargo.toml index dee256dfd..d51e8fd82 100644 --- a/basic_bootloader/Cargo.toml +++ b/basic_bootloader/Cargo.toml @@ -42,8 +42,6 @@ eip-4844 = ["system_hooks/point_eval_precompile"] pectra = ["eip-2935", "eip-7702", "eip-7623", "eip-4844", "zk_ee/pectra-blobs", "system_hooks/bls12_381", "system_hooks/blake2f"] burn_base_fee = [] cycle_marker = ["system_hooks/cycle_marker", "cycle_marker/log_to_file"] -resources_for_tester = [] -unlimited_native = [] disable_system_contracts = [] error_origins = ["zk_ee/error_origins"] delegation = ["zk_ee/delegation", "evm_interpreter/delegation", "system_hooks/delegation", "basic_system/delegation"] diff --git a/basic_bootloader/src/bootloader/block_flow/zk/mod.rs b/basic_bootloader/src/bootloader/block_flow/zk/mod.rs index 253fde32e..910ce7609 100644 --- a/basic_bootloader/src/bootloader/block_flow/zk/mod.rs +++ b/basic_bootloader/src/bootloader/block_flow/zk/mod.rs @@ -65,24 +65,19 @@ where "Block blob gas limit reached, invalidating transaction\n" ); Err(InvalidTransaction::BlockBlobGasLimitReached) - } else if !cfg!(feature = "resources_for_tester") - && computational_native_used > MAX_NATIVE_COMPUTATIONAL - { - // ZKsync OS-specific resources are not checked for evm tester + } else if computational_native_used > MAX_NATIVE_COMPUTATIONAL { system_log!( system, "Block native limit reached, invalidating transaction\n" ); Err(InvalidTransaction::BlockNativeLimitReached) - } else if !cfg!(feature = "resources_for_tester") && pubdata_used > system.get_pubdata_limit() { - // ZKsync OS-specific resources are not checked for evm tester + } else if pubdata_used > system.get_pubdata_limit() { system_log!( system, "Block pubdata limit reached, invalidating transaction\n" ); Err(InvalidTransaction::BlockPubdataLimitReached) - } else if !cfg!(feature = "resources_for_tester") && logs_used > MAX_NUMBER_OF_LOGS { - // ZKsync OS-specific resources are not checked for evm tester + } else if logs_used > MAX_NUMBER_OF_LOGS { system_log!( system, "Block logs limit reached, invalidating transaction\n" diff --git a/basic_bootloader/src/bootloader/constants.rs b/basic_bootloader/src/bootloader/constants.rs index 5f0236bb0..8a05f1979 100644 --- a/basic_bootloader/src/bootloader/constants.rs +++ b/basic_bootloader/src/bootloader/constants.rs @@ -63,11 +63,6 @@ pub const CALLDATA_TOKEN_GAS_COST: u64 = 4; /// EIP-7623 minimal "token" cost pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; -/// EVM tester requires a high native_per_gas, but it hard-codes -/// low gas prices. We need to bypass the usual way to compute this -/// value. The value is so high because of modexp tests. -pub const TESTER_NATIVE_PER_GAS: u64 = 25_000; - // Default native price for L1->L2 transactions. // TODO (EVM-1157): find a reasonable value for it. pub const L1_TX_NATIVE_PRICE: U256 = U256::from_limbs([10, 0, 0, 0]); diff --git a/basic_bootloader/src/bootloader/errors.rs b/basic_bootloader/src/bootloader/errors.rs index 181d7d91b..9bedbd704 100644 --- a/basic_bootloader/src/bootloader/errors.rs +++ b/basic_bootloader/src/bootloader/errors.rs @@ -71,8 +71,6 @@ pub enum InvalidTransaction { AccessListNotSupported, /// Unacceptable pubdata price. PubdataPriceTooHigh, - /// Block gas limit is too high. - BlockGasLimitTooHigh, /// Protocol upgrade tx should be first in the block. UpgradeTxNotFirst, /// Bootloader received insufficient fees diff --git a/basic_bootloader/src/bootloader/mod.rs b/basic_bootloader/src/bootloader/mod.rs index f8e8e766f..2e1e2b343 100644 --- a/basic_bootloader/src/bootloader/mod.rs +++ b/basic_bootloader/src/bootloader/mod.rs @@ -86,7 +86,6 @@ where S::Allocator::default(), )?; - // we will model initial calldata buffer as just another "heap" let mut system: System = System::init_from_metadata_and_oracle(metadata, oracle)?; let mut system_functions = HooksStorage::new_in(system.get_allocator()); diff --git a/basic_bootloader/src/bootloader/transaction/access_list.rs b/basic_bootloader/src/bootloader/transaction/access_list.rs index a28b5560e..658f90544 100644 --- a/basic_bootloader/src/bootloader/transaction/access_list.rs +++ b/basic_bootloader/src/bootloader/transaction/access_list.rs @@ -1,4 +1,4 @@ -use super::Transaction; + use super::Transaction; use crate::bootloader::errors::TxError; use evm_interpreter::ERGS_PER_GAS; use zk_ee::system::{Ergs, Resource, Resources}; diff --git a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs index 3e439d764..aae6a110a 100644 --- a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs +++ b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs @@ -26,7 +26,9 @@ pub struct RlpEncodedTransaction { // time. tx_hash: Option, // Note: this field is not the recovered signer, but rather an address - // passed by oracle. Needs to be checked to be equal to recovered address. + // passed by oracle. Needs to be checked to be equal to recovered address + // (only in untrusted environments, e.g. proving mode; trusted forward/ + // sequencer paths may skip ecrecover and rely on the oracle hint). from: B160, } diff --git a/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs b/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs index d0074e881..005fea6d0 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs @@ -20,157 +20,6 @@ use zk_ee::system::{Computational, Ergs, Resources}; use zk_ee::system::{Resource, MAX_NATIVE_COMPUTATIONAL}; use zk_ee::system_log; -/// Policy trait for handling arithmetic validation errors during resource creation. -/// -/// This trait allows L1 and L2 transactions to handle errors differently: -/// - L1: Only returns internal errors (validation errors are logged and saturated) -/// - L2: Returns both validation and internal errors -pub trait ResourcesCreationErrorPolicy { - /// The return error type for create_resources_for_tx. - /// For L1: BootloaderSubsystemError (no validation errors possible) - /// For L2: TxError (both validation and internal errors) - type Error; - - /// The error type that describes arithmetic validation failures. - /// For L1: a descriptive enum for logging - /// For L2: InvalidTransaction - type ArithmeticError; - - /// Create an error for native limit underflow - fn native_underflow_error(operation: &'static str) -> Self::ArithmeticError; - - /// Create an error for intrinsic gas exceeding gas limit - fn intrinsic_gas_overflow_error( - intrinsic_overhead: u64, - gas_limit: u64, - ) -> Self::ArithmeticError; - - /// Handle an arithmetic validation error. - /// For L1: logs the error and returns Ok(saturated_value) - /// For L2: returns Err(Self::Error) - fn handle_arithmetic_error( - system: &mut System, - error: Self::ArithmeticError, - ) -> Result; - - /// Convert an internal error to the policy's error type - #[allow(dead_code)] // Reserved for future use if internal errors are added - fn from_internal_error(error: BootloaderSubsystemError) -> Self::Error; - - /// Convert a validation error to the policy's error type. - /// For L1: should never be called (deployment checks don't apply) - /// For L2: wraps in TxError::Validation - fn from_validation_error(error: InvalidTransaction) -> Self::Error; -} - -/// Arithmetic error descriptor for L1 transactions -#[derive(Debug)] -pub enum L1ArithmeticError { - /// Native limit underflow during an operation - NativeUnderflow { operation: &'static str }, - /// Gas limit is less than intrinsic gas overhead - IntrinsicGasOverflow { - intrinsic_overhead: u64, - gas_limit: u64, - }, -} - -/// Resource creation policy for L1 transactions: log and saturate on errors -pub struct L1ResourcesPolicy; - -impl ResourcesCreationErrorPolicy for L1ResourcesPolicy { - type Error = BootloaderSubsystemError; - type ArithmeticError = L1ArithmeticError; - - fn native_underflow_error(operation: &'static str) -> Self::ArithmeticError { - L1ArithmeticError::NativeUnderflow { operation } - } - - fn intrinsic_gas_overflow_error( - intrinsic_overhead: u64, - gas_limit: u64, - ) -> Self::ArithmeticError { - L1ArithmeticError::IntrinsicGasOverflow { - intrinsic_overhead, - gas_limit, - } - } - - fn handle_arithmetic_error( - system: &mut System, - error: Self::ArithmeticError, - ) -> Result { - match error { - L1ArithmeticError::NativeUnderflow { operation } => { - system_log!( - system, - "Native underflow during {}, saturating to 0 for L1 tx", - operation - ); - Ok(0) - } - L1ArithmeticError::IntrinsicGasOverflow { - intrinsic_overhead, - gas_limit, - } => { - system_log!( - system, - "Gas limit {} < intrinsic gas {} for L1 tx, saturating to 0", - gas_limit, - intrinsic_overhead - ); - Ok(0) - } - } - } - - fn from_internal_error(error: BootloaderSubsystemError) -> Self::Error { - error - } - - fn from_validation_error(error: InvalidTransaction) -> Self::Error { - // L1 transactions never have deployment validation, so this should never be called - unreachable!( - "L1ResourcesPolicy should never encounter validation error: {:?}", - error - ) - } -} - -/// Resource creation policy for L2 transactions: fail on arithmetic errors -pub struct L2ResourcesPolicy; - -impl ResourcesCreationErrorPolicy for L2ResourcesPolicy { - type Error = TxError; - type ArithmeticError = InvalidTransaction; - - fn native_underflow_error(_operation: &'static str) -> Self::ArithmeticError { - InvalidTransaction::OutOfNativeResourcesDuringValidation - } - - fn intrinsic_gas_overflow_error( - _intrinsic_overhead: u64, - _gas_limit: u64, - ) -> Self::ArithmeticError { - InvalidTransaction::OutOfGasDuringValidation - } - - fn handle_arithmetic_error( - _system: &mut System, - error: Self::ArithmeticError, - ) -> Result { - Err(TxError::Validation(error)) - } - - fn from_internal_error(error: BootloaderSubsystemError) -> Self::Error { - TxError::Internal(error) - } - - fn from_validation_error(error: InvalidTransaction) -> Self::Error { - TxError::Validation(error) - } -} - pub struct ResourcesForTx { // Resources to run the transaction. // These will be capped to MAX_NATIVE_COMPUTATIONAL, to prevent @@ -179,8 +28,6 @@ pub struct ResourcesForTx { /// Resources in excess of MAX_NATIVE_COMPUTATIONAL. /// These resources can only be used for paying for pubdata. pub withheld: S::Resources, - /// Computational native charged for as intrinsic - pub intrinsic_computational_native_charged: u64, } impl core::fmt::Debug for ResourcesForTx { @@ -189,10 +36,6 @@ impl core::fmt::Debug for ResourcesForTx { .field("gas", &(self.main_resources.ergs().0 / ERGS_PER_GAS)) .field("main_resources", &self.main_resources) .field("withheld", &self.withheld) - .field( - "intrinsic_computational_native_charged", - &self.intrinsic_computational_native_charged, - ) .finish() } } @@ -254,12 +97,7 @@ pub fn calculate_l1_tx_intrinsic_computational_native_resources(calldata_byte_le /// This function used both for L1 and L2 transactions. /// /// Computes the analogue of revm's `intrinsic_cost`: the gas that must be -/// pre-charged before the transaction body runs. Per EIP-2930/EIP-7702 the -/// per-address, per-storage-key and per-authorization costs are part of this -/// intrinsic gas. Moving them into this helper means the inner access-list / -/// authorization-list processors only need to account for native resources — -/// gas is already deducted from `main_resources` when the tx's resources are -/// materialized. +/// pre-charged before the transaction body runs. pub fn calculate_tx_intrinsic_gas( calldata_len: u64, calldata_tokens: u64, @@ -314,102 +152,137 @@ pub fn calculate_l2_tx_intrinsic_pubdata(authorization_list_num: u64, is_service } /// -/// Create initial resources for a transaction. +/// Create initial resources for a transaction. Pure constructor: splits the +/// native budget into `main_resources` (capped at `MAX_NATIVE_COMPUTATIONAL`) +/// and `withheld` (the excess, only spendable on pubdata at refund time), and +/// loads `gas_limit · ERGS_PER_GAS` ergs into `main_resources`. /// -/// The `P` parameter controls how arithmetic validation errors are handled: -/// - Use `L1ResourcesPolicy` for L1 transactions: logs and saturates (never fails validation) -/// Returns `Result<..., BootloaderSubsystemError>` - validation errors are impossible -/// - Use `L2ResourcesPolicy` for L2 transactions: returns validation errors -/// Returns `Result<..., TxError>` - can fail with validation or internal errors -pub fn create_resources_for_tx>( - system: &mut System, +/// Intrinsic gas / native / pubdata are NOT subtracted here. Callers must +/// charge them via [`charge_intrinsic_pubdata`], +/// [`charge_intrinsic_computational_native`] and [`charge_intrinsic_gas`] +/// with whatever error semantics they need (L2 surfaces validation errors, +/// L1 logs and saturates). +/// +/// Note: for zero gas price, we use "unlimited native". +pub fn create_resources_for_tx( gas_limit: u64, free_native: bool, native_prepaid_from_gas: u64, - native_per_pubdata_byte: u64, - intrinsic_gas: u64, - intrinsic_computational_native: u64, - intrinsic_pubdata: u64, -) -> Result, P::Error> +) -> ResourcesForTx where S::Metadata: ZkSpecificPricingMetadata, { - // This is the real limit, which we later use to compute native_used. - // From it, we discount intrinsic pubdata and then take the min - // with the MAX_NATIVE_COMPUTATIONAL. - // We do those operations in that order because the pubdata charge - // isn't computational. - // We can consider in the future to keep two limits, so that pubdata - // is not charged from computational resource. - // Note: for zero gas price, we use "unlimited native" - let native_limit = if cfg!(feature = "unlimited_native") || free_native { - u64::MAX - 1 // So any saturation below can not be subtracted from it + let native_total = if free_native { + u64::MAX - 1 // So any saturating subtraction below cannot underflow it } else { native_prepaid_from_gas }; - // Charge intrinsic pubdata - let intrinsic_pubdata_overhead = native_per_pubdata_byte.saturating_mul(intrinsic_pubdata); - let native_limit = match native_limit.checked_sub(intrinsic_pubdata_overhead) { - Some(val) => val, - None => P::handle_arithmetic_error( - system, - P::native_underflow_error("subtracting pubdata overhead"), - )?, - }; - - // EVM tester requires high native limits, so for it we never hold off resources. - // But for the real world, we bound the available resources. - - #[cfg(feature = "resources_for_tester")] - let withheld = S::Resources::from_ergs(Ergs::empty()); - - #[cfg(not(feature = "resources_for_tester"))] - let (native_limit, withheld) = if native_limit <= MAX_NATIVE_COMPUTATIONAL { - (native_limit, S::Resources::from_ergs(Ergs::empty())) + // Always cap the computational budget at `MAX_NATIVE_COMPUTATIONAL`. + // Anything above the cap can only be spent on pubdata at refund time. + let (main_native_u64, withheld) = if native_total <= MAX_NATIVE_COMPUTATIONAL { + (native_total, S::Resources::from_ergs(Ergs::empty())) } else { - let withheld = + let withheld_native = <::Resources as Resources>::Native::from_computational( - native_limit - MAX_NATIVE_COMPUTATIONAL, + native_total - MAX_NATIVE_COMPUTATIONAL, ); ( MAX_NATIVE_COMPUTATIONAL, - S::Resources::from_native(withheld), + S::Resources::from_native(withheld_native), ) }; - // Charge intrinsic computational native - let native_limit = match native_limit.checked_sub(intrinsic_computational_native) { - Some(val) => val, - None => P::handle_arithmetic_error( - system, - P::native_underflow_error("subtracting intrinsic computational native"), - )?, - }; - - let native_limit = + let main_native = <::Resources as Resources>::Native::from_computational( - native_limit, + main_native_u64, ); + let ergs = gas_limit.saturating_mul(ERGS_PER_GAS); + let main_resources = S::Resources::from_ergs_and_native(Ergs(ergs), main_native); - // Check if intrinsic gas exceeds gas limit - let gas_limit_for_tx = match gas_limit.checked_sub(intrinsic_gas) { - Some(val) => val, - None => P::handle_arithmetic_error( - system, - P::intrinsic_gas_overflow_error(intrinsic_gas, gas_limit), - )?, - }; - - let ergs = gas_limit_for_tx.saturating_mul(ERGS_PER_GAS); // we checked at the very start that gas_limit * ERGS_PER_GAS doesn't overflow - let main_resources = S::Resources::from_ergs_and_native(Ergs(ergs), native_limit); - - Ok(ResourcesForTx { + ResourcesForTx { main_resources, withheld, - intrinsic_computational_native_charged: intrinsic_computational_native, - }) + } +} + +/// Charge intrinsic pubdata cost (native-only). Drains `withheld` first, +/// then spills into `main_resources`. Underlying `charge` already saturates +/// each resource to zero on insufficient funds; this helper returns `Err(())` +/// if the total budget couldn't cover the cost, so the caller can decide +/// whether to surface a validation error (L2) or just log (L1). +/// +/// Equivalent in steady state to the original behavior of +/// `create_resources_for_tx`, which subtracted pubdata cost from the total +/// native budget before splitting into `main`/`withheld`. +pub fn charge_intrinsic_pubdata( + resources: &mut ResourcesForTx, + intrinsic_pubdata: u64, + native_per_pubdata: u64, +) -> Result<(), ()> { + let total_cost = native_per_pubdata.saturating_mul(intrinsic_pubdata); + if total_cost == 0 { + return Ok(()); + } + + let withheld_avail = resources.withheld.native().as_u64(); + let from_withheld = total_cost.min(withheld_avail); + let from_main = total_cost - from_withheld; + + if from_withheld > 0 { + let cost = S::Resources::from_native( + <::Native as Computational>::from_computational( + from_withheld, + ), + ); + // Saturates withheld to 0 if insufficient — for our `min` choice it + // should be exact, but be defensive. + let _ = resources.withheld.charge(&cost); + } + + if from_main > 0 { + let cost = S::Resources::from_native( + <::Native as Computational>::from_computational(from_main), + ); + if resources.main_resources.charge(&cost).is_err() { + return Err(()); + } + } + + Ok(()) +} + +/// Charge intrinsic computational native against `main_resources`. +/// `charge` saturates the resource to zero if insufficient; we surface that +/// as `Err(())` so the caller can map it to its preferred error variant. +pub fn charge_intrinsic_computational_native( + main: &mut S::Resources, + intrinsic_computational_native: u64, +) -> Result<(), ()> { + if intrinsic_computational_native == 0 { + return Ok(()); + } + let cost = S::Resources::from_native( + <::Native as Computational>::from_computational( + intrinsic_computational_native, + ), + ); + main.charge(&cost).map_err(|_| ()) +} + +/// Charge intrinsic gas (in EVM gas units) against `main_resources` ergs. +/// `charge` saturates the resource to zero on underflow; we surface that as +/// `Err(())`. +pub fn charge_intrinsic_gas( + main: &mut S::Resources, + intrinsic_gas: u64, +) -> Result<(), ()> { + if intrinsic_gas == 0 { + return Ok(()); + } + let cost = S::Resources::from_ergs(Ergs(intrinsic_gas.saturating_mul(ERGS_PER_GAS))); + main.charge(&cost).map_err(|_| ()) } /// @@ -465,6 +338,7 @@ pub(crate) fn get_gas_price Result { let base_fee = system.get_eip1559_basefee(); // If base fee is zero, then we ignore priority fee + // TODO: not ignore? if base_fee.is_zero() { Ok(U256::ZERO) } else { diff --git a/basic_bootloader/src/bootloader/transaction_flow/refund_calculation.rs b/basic_bootloader/src/bootloader/transaction_flow/refund_calculation.rs index fa9dd7879..13974c56e 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/refund_calculation.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/refund_calculation.rs @@ -55,29 +55,30 @@ pub(crate) fn compute_gas_refund( #[allow(unused_mut)] let mut gas_used = core::cmp::max(gas_used, minimal_gas_used); - // Note: for zero gas price, we use "unlimited native" - let full_native_limit = if cfg!(feature = "unlimited_native") || native_per_gas == 0 { + // When the chain doesn't price native (`native_per_gas == 0`), the user's + // budget is treated as unlimited and the gas-from-native correction below + // is a no-op (`checked_div` returns None). + let full_native_limit = if native_per_gas == 0 { u64::MAX - 1 } else { gas_limit.saturating_mul(native_per_gas) }; let native_used = full_native_limit.saturating_sub(resources.native().remaining().as_u64()); - #[cfg(not(feature = "unlimited_native"))] - { - // Adjust gas_used with difference with used native - let delta_gas = native_used - .checked_div(native_per_gas) - .map(|q| q as i64 - gas_used as i64) - .unwrap_or(0); + // Adjust gas_used with difference with used native. With `native_per_gas == 0` + // the division returns None and `delta_gas` falls back to 0 — i.e. no + // correction when native is unpriced. + let delta_gas = native_used + .checked_div(native_per_gas) + .map(|q| q as i64 - gas_used as i64) + .unwrap_or(0); - if delta_gas > 0 { - // In this case, the native resource consumption is more than the - // gas consumption accounted for. Consume extra gas. - gas_used += delta_gas as u64; - } - // TODO: return delta_gas to gas_used? + if delta_gas > 0 { + // In this case, the native resource consumption is more than the + // gas consumption accounted for. Consume extra gas. + gas_used += delta_gas as u64; } + // TODO: return delta_gas to gas_used? let total_gas_refund = gas_limit - gas_used; system_log!(system, "Refund after accounting for unused gas, refund counters and native cost: {total_gas_refund}\n"); diff --git a/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs b/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs index 4d66c5008..6c12a92fd 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs @@ -129,6 +129,12 @@ pub struct TxContextForPreAndPostProcessing { /// recovered by subtracting the residual from `FORMAL_INFINITE` and /// compared against the formula as an upper bound. pub intrinsic_resources: S::Resources, + /// Intrinsic computational native that was precharged against + /// `resources.main_resources` during validation. Recorded here so + /// `after_execution` can add it back when computing the total + /// `computational_native_used`, and so `verify_intrinsic_native` can + /// compare actual consumption against the formula. + pub intrinsic_computational_native: u64, /// Number of EIP-7702 authorization list entries in the transaction. /// Used by `verify_intrinsic_native` to skip the overcharging check when /// authorizations are present (failed auths consume much less native than @@ -154,6 +160,10 @@ impl core::fmt::Debug for TxContextForPreAndPostProcessing .field("total_pubdata", &self.total_pubdata) .field("native_used", &self.native_used) .field("intrinsic_resources", &self.intrinsic_resources) + .field( + "intrinsic_computational_native", + &self.intrinsic_computational_native, + ) .field("authorization_list_num", &self.authorization_list_num) .finish() } @@ -551,15 +561,14 @@ where _transaction_data_keeper: &mut impl BlockTransactionsDataKeeper, _tracer: &mut impl Tracer, ) -> Self::ExecutionResult<'a> { - // Add back the intrinsic native charged in get_resources_for_tx, - // as initial_resources doesn't include them. + // Add back as initial_resources doesn't include the intrinsic. let computational_native_used = context .resources_before_refund .clone() .diff(context.initial_resources.clone()) .native() .as_u64() - .saturating_add(context.resources.intrinsic_computational_native_charged); + .saturating_add(context.intrinsic_computational_native); #[cfg(not(target_arch = "riscv32"))] cycle_marker::log_marker( @@ -923,7 +932,7 @@ where let initial = S::Resources::FORMAL_INFINITE.native().as_u64(); let remaining = context.intrinsic_resources.native().as_u64(); let actual_used = initial.saturating_sub(remaining); - let formula = context.resources.intrinsic_computational_native_charged; + let formula = context.intrinsic_computational_native; system_log!( system, "intrinsic native verification: formula={}, actually_used={}\n", diff --git a/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs b/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs index b7c88baa4..024dd5c8b 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs @@ -9,8 +9,9 @@ use crate::bootloader::runner::RunnerMemoryBuffers; use crate::bootloader::transaction::abi_encoded::AbiEncodedTransaction; use crate::bootloader::transaction_flow::gas_helpers::{ calculate_l1_tx_intrinsic_computational_native_resources, calculate_tx_intrinsic_gas, + charge_intrinsic_computational_native, charge_intrinsic_gas, charge_intrinsic_pubdata, check_enough_resources_for_pubdata, create_resources_for_tx, - get_resources_to_charge_for_pubdata, L1ResourcesPolicy, ResourcesForTx, + get_resources_to_charge_for_pubdata, ResourcesForTx, }; use crate::bootloader::transaction_flow::refund_calculation::{compute_gas_refund, RefundInfo}; use crate::bootloader::transaction_flow::{ExecutionOutput, ExecutionResult}; @@ -107,11 +108,11 @@ where ResourcesForTx { main_resources: mut resources, withheld: withheld_resources, - intrinsic_computational_native_charged, }, native_per_gas, native_per_pubdata, minimal_gas_used, + intrinsic_computational_native, } = prepare_and_check_resources::( system, transaction, @@ -370,13 +371,13 @@ where )?; } - // Add back the intrinsic native charged in get_resources_for_tx, - // as initial_resources doesn't include them. + // Add back the intrinsic native charged in `prepare_and_check_resources`, + // as `initial_resources` is snapshotted after that precharge. let computational_native_used = resources_before_refund .diff(initial_resources) .native() .as_u64() - + intrinsic_computational_native_charged; + + intrinsic_computational_native; // Restore the saved returndata into the return buffer so that the // ExecutionResult can borrow it with the correct lifetime. @@ -417,6 +418,11 @@ struct ResourceAndFeeInfo { native_per_pubdata: u64, native_per_gas: u64, minimal_gas_used: u64, + /// Intrinsic computational native that was precharged during resource + /// preparation. Hoisted out so callers can add it back to the total + /// `computational_native_used` reported at end-of-tx (since + /// `initial_resources` is captured after the precharge). + intrinsic_computational_native: u64, } /// @@ -512,18 +518,42 @@ where let intrinsic_computational_native = calculate_l1_tx_intrinsic_computational_native_resources( transaction.calldata().len() as u64, ); - // With L1ResourcesPolicy, this returns Result, BootloaderSubsystemError> - // Validation errors are type-safe impossible - they're logged and saturated instead - let resources = create_resources_for_tx::( - system, + + // Materialize the gross resource budget for the tx, then charge the + // intrinsic overheads. L1 transactions cannot be invalidated (the + // priority queue requires the tx to be processed), so underflow on any + // of the charges is logged and absorbed — the user ends up with a + // zero-saturated resource and `gas_used == gas_limit` downstream. + let mut resources = create_resources_for_tx::( gas_limit, native_per_gas == 0, native_prepaid_from_gas, - native_per_pubdata, - intrinsic_gas, + ); + if charge_intrinsic_pubdata(&mut resources, intrinsic_pubdata, native_per_pubdata).is_err() { + system_log!( + system, + "L1 tx: native budget below intrinsic pubdata cost, saturating to 0\n" + ); + } + if charge_intrinsic_computational_native::( + &mut resources.main_resources, intrinsic_computational_native, - intrinsic_pubdata, - )?; + ) + .is_err() + { + system_log!( + system, + "L1 tx: native budget below intrinsic computational native cost, saturating to 0\n" + ); + } + if charge_intrinsic_gas::(&mut resources.main_resources, intrinsic_gas).is_err() { + system_log!( + system, + "L1 tx: gas limit {} below intrinsic gas {}, saturating to 0\n", + gas_limit, + intrinsic_gas + ); + } // L1 transactions might have a gas limit < minimal_gas_used. This should be // prevented by L1 validation, but we log and saturate if it happens. @@ -541,6 +571,7 @@ where native_per_pubdata, native_per_gas, minimal_gas_used, + intrinsic_computational_native, }) } diff --git a/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs b/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs index 3f0053508..bb0b5de57 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs @@ -7,7 +7,8 @@ use crate::bootloader::transaction::rlp_encoded::AccessListForAddress; use crate::bootloader::transaction::{charge_keccak, Transaction}; use crate::bootloader::transaction_flow::gas_helpers::{ calculate_l2_tx_intrinsic_computational_native_resources, calculate_l2_tx_intrinsic_pubdata, - calculate_tx_intrinsic_gas, create_resources_for_tx, get_gas_price, L2ResourcesPolicy, + calculate_tx_intrinsic_gas, charge_intrinsic_computational_native, charge_intrinsic_gas, + charge_intrinsic_pubdata, create_resources_for_tx, get_gas_price, }; use crate::bootloader::BasicBootloaderExecutionConfig; use crate::require; @@ -36,11 +37,10 @@ use zk_ee::{internal_error, out_of_native_resources}; use zk_ee::{utils::*, wrap_error}; /// -/// Will perform basic validation, namely - checking signature, minimal resource requirements for transaction validity, -/// and will pre-charge sender to cover worst case cost. It may perform IO if needed to e.g. warm up some storage slots, +/// Will perform basic validation, namely - checking signature, minimal resource requirements for transaction validity. +/// It may perform IO if needed to e.g. warm up some storage slots, /// or mark delegation /// -/// NOTE: This function will open and close IO frame pub(crate) fn validate_and_compute_fee_for_transaction< S: EthereumLikeTypes, Config: BasicBootloaderExecutionConfig, @@ -72,24 +72,14 @@ where let calldata = transaction.calldata(); - // Validate block-level invariants (for non-service transactions) + // Validate that the transaction's gas limit is not larger than the block's gas limit if !transaction.is_service() { - { - // Validate that the transaction's gas limit is not larger than - // the block's gas limit. - let block_gas_limit = system.get_gas_limit(); - // First, check block gas limit can be represented as ergs. - require!( - block_gas_limit <= MAX_BLOCK_GAS_LIMIT, - InvalidTransaction::BlockGasLimitTooHigh, - system - )?; - require!( - tx_gas_limit <= block_gas_limit, - InvalidTransaction::CallerGasLimitMoreThanBlock, - system - )?; - } + let block_gas_limit = system.get_gas_limit(); + require!( + tx_gas_limit <= block_gas_limit, + InvalidTransaction::CallerGasLimitMoreThanBlock, + system + )?; } // EIP-7623 @@ -116,16 +106,18 @@ where )? }; - let native_per_gas = { - if native_price.is_zero() { - return Err(internal_error!("Native price cannot be 0").into()); - } - - if cfg!(feature = "resources_for_tester") { - crate::bootloader::constants::TESTER_NATIVE_PER_GAS - } else if Config::SIMULATION && gas_price.is_zero() { + // `native_price == 0` means the chain doesn't price native. Downstream + // treats `native_per_gas == 0` as "unlimited native budget" (see + // `create_resources_for_tx` and `compute_gas_refund`), so we propagate the + // zero directly without doing any division. This replaces the historical + // `resources_for_tester` / `unlimited_native` compile-time features. + let (native_per_gas, native_per_pubdata) = if native_price.is_zero() { + (0u64, 0u64) + } else { + let native_per_gas = if Config::SIMULATION && gas_price.is_zero() { // For simulation, if gas price isn't set, we use base fee // for native calculation + // TODO: wrong error u256_try_to_u64(&system.get_eip1559_basefee().div_ceil(native_price)).ok_or( TxError::Validation(InvalidTransaction::NativeResourcesAreTooExpensive), )? @@ -133,12 +125,11 @@ where u256_try_to_u64(&gas_price.div_ceil(native_price)).ok_or(TxError::Validation( InvalidTransaction::NativeResourcesAreTooExpensive, ))? - } + }; + let native_per_pubdata = u256_try_to_u64(&pubdata_price.wrapping_div(native_price)) + .ok_or(TxError::Validation(InvalidTransaction::PubdataPriceTooHigh))?; + (native_per_gas, native_per_pubdata) }; - - // We checked native_price != 0 above - let native_per_pubdata = u256_try_to_u64(&pubdata_price.wrapping_div(native_price)) - .ok_or(TxError::Validation(InvalidTransaction::PubdataPriceTooHigh))?; let native_prepaid_from_gas = native_per_gas.saturating_mul(tx_gas_limit); let mut access_list_accounts = 0; @@ -187,17 +178,27 @@ where let intrinsic_pubdata = calculate_l2_tx_intrinsic_pubdata(authorization_list_num, transaction.is_service()); - // Now we will materialize resources, from which we will try to charge intrinsic cost on top. - let tx_resources = create_resources_for_tx::( - system, + // Materialize the gross resource budget for the tx, then charge the + // intrinsic overheads. Underflow on any of the charges surfaces as a + // validation error so the whole tx is dropped without state changes. + let mut tx_resources = create_resources_for_tx::( tx_gas_limit, native_per_gas == 0, native_prepaid_from_gas, - native_per_pubdata, - intrinsic_gas, + ); + charge_intrinsic_pubdata(&mut tx_resources, intrinsic_pubdata, native_per_pubdata) + .map_err(|()| { + TxError::Validation(InvalidTransaction::OutOfNativeResourcesDuringValidation) + })?; + charge_intrinsic_computational_native::( + &mut tx_resources.main_resources, intrinsic_computational_native, - intrinsic_pubdata, - )?; + ) + .map_err(|()| { + TxError::Validation(InvalidTransaction::OutOfNativeResourcesDuringValidation) + })?; + charge_intrinsic_gas::(&mut tx_resources.main_resources, intrinsic_gas) + .map_err(|()| TxError::Validation(InvalidTransaction::OutOfGasDuringValidation))?; system_log!( system, @@ -496,6 +497,7 @@ where initial_resources: S::Resources::empty(), resources_before_refund: S::Resources::empty(), intrinsic_resources, + intrinsic_computational_native, authorization_list_num, }) } @@ -518,9 +520,9 @@ pub(crate) fn compute_calldata_tokens( #[cfg(feature = "eip-7623")] { let floor_tokens_gas_cost = num_tokens.saturating_mul(TOTAL_COST_FLOOR_PER_TOKEN); - let intrinsic_gas = TX_INTRINSIC_GAS.saturating_add(floor_tokens_gas_cost); + let floor_gas = TX_INTRINSIC_GAS.saturating_add(floor_tokens_gas_cost); - (num_tokens, intrinsic_gas) + (num_tokens, floor_gas) } #[cfg(not(feature = "eip-7623"))] diff --git a/forward_system/Cargo.toml b/forward_system/Cargo.toml index 60f97490f..dcc56566c 100644 --- a/forward_system/Cargo.toml +++ b/forward_system/Cargo.toml @@ -38,7 +38,6 @@ testing = ["zk_ee/testing", "evm_interpreter/testing", "basic_system/testing", " default = ["testing"] error_origins = ["zk_ee/error_origins"] no_print = [] -unlimited_native = ["basic_bootloader/unlimited_native"] # A feature for every specific use case @@ -48,8 +47,12 @@ production = ["basic_bootloader/eip-7702", "system_hooks/p256_precompile"] # Features used for tests. for_tests = ["production", "basic_bootloader/eip-4844"] -# Features used for eth_runner -eth_runner = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "unlimited_native","basic_bootloader/burn_base_fee", "basic_bootloader/eip-4844"] +# Features used for eth_runner. +# Test profiles that previously enabled `unlimited_native` / `resources_for_tester` +# should now configure `native_price = 0` in the block metadata, which makes the +# native budget effectively unlimited (subject to the `MAX_NATIVE_COMPUTATIONAL` +# per-tx cap and the block-level limits). +eth_runner = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/burn_base_fee", "basic_bootloader/eip-4844"] # Additional features used for eth_stf in eth_runner eth_stf = ["basic_bootloader/eip-7702","basic_bootloader/eip-7623", "system_hooks/bls12_381", "system_hooks/blake2f", "evm_interpreter/bls12_381", "evm_interpreter/blake2f"] @@ -58,14 +61,14 @@ eth_stf = ["basic_bootloader/eip-7702","basic_bootloader/eip-7623", "system_hook pectra = ["basic_bootloader/pectra", "evm_interpreter/bls12_381", "evm_interpreter/blake2f", "system_hooks/p256_precompile"] # Features used for evm_tester -evm_tester = ["basic_bootloader/resources_for_tester", "basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/eip-7702", "system_hooks/p256_precompile", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles", "unlimited_native", "basic_bootloader/eip-4844"] +evm_tester = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/eip-7702", "system_hooks/p256_precompile", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles", "basic_bootloader/eip-4844"] # Features used for evm_tester with Pectra hardfork evm_tester_pectra = ["evm_tester", "pectra"] # Features used for legacy evm_tester. Only defined in forward system, # as the legacy evm_tester does not perform a proof run. -evm_tester_legacy = ["basic_bootloader/resources_for_tester", "basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/eip-7702", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles", "unlimited_native"] +evm_tester_legacy = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/eip-7702", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles"] [dev-dependencies] tempfile = "3" diff --git a/forward_system/src/run/convert.rs b/forward_system/src/run/convert.rs index dbab66756..0af9cf15d 100644 --- a/forward_system/src/run/convert.rs +++ b/forward_system/src/run/convert.rs @@ -121,7 +121,6 @@ impl IntoInterface basic_bootloader::bootloader::errors::InvalidTransaction::InvalidChainId => { InvalidTransaction::InvalidChainId } basic_bootloader::bootloader::errors::InvalidTransaction::AccessListNotSupported => { InvalidTransaction::AccessListNotSupported } basic_bootloader::bootloader::errors::InvalidTransaction::PubdataPriceTooHigh => { InvalidTransaction::PubdataPriceTooHigh } - basic_bootloader::bootloader::errors::InvalidTransaction::BlockGasLimitTooHigh => { InvalidTransaction::BlockGasLimitTooHigh } basic_bootloader::bootloader::errors::InvalidTransaction::UpgradeTxNotFirst => { InvalidTransaction::UpgradeTxNotFirst } basic_bootloader::bootloader::errors::InvalidTransaction::ReceivedInsufficientFees { received, required } => { InvalidTransaction::ReceivedInsufficientFees { received, required } } basic_bootloader::bootloader::errors::InvalidTransaction::InvalidMagic => { InvalidTransaction::InvalidMagic } diff --git a/proof_running_system/Cargo.toml b/proof_running_system/Cargo.toml index 53d57bbe0..7195d76d9 100644 --- a/proof_running_system/Cargo.toml +++ b/proof_running_system/Cargo.toml @@ -35,8 +35,7 @@ proving = ["crypto/proving", "basic_system/proving", "delegation"] delegation = ["u256/delegation", "zk_ee/delegation", "evm_interpreter/delegation", "basic_system/delegation", "system_hooks/delegation", "basic_bootloader/delegation"] scalloc = [] cycle_marker = ["zk_ee/cycle_marker", "evm_interpreter/cycle_marker", "basic_system/cycle_marker", "basic_bootloader/cycle_marker"] -unlimited_native = ["basic_bootloader/unlimited_native"] -benchmarking = ["cycle_marker", "unlimited_native"] +benchmarking = ["cycle_marker"] global-alloc = ["zk_ee/global-alloc"] error_origins = ["zk_ee/error_origins"] multiblock-batch = [] @@ -52,8 +51,12 @@ production = ["basic_bootloader/eip-7702", "system_hooks/p256_precompile"] # cannot be proved (the transpiler replayer rejects them by design). for_tests = ["production", "state-diffs-pi", "basic_bootloader/eip-4844"] -# Features used for eth_runner -eth_runner = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "state-diffs-pi", "unlimited_native", "basic_bootloader/burn_base_fee", "basic_bootloader/eip-4844"] +# Features used for eth_runner. +# Test profiles that previously enabled `unlimited_native` / `resources_for_tester` +# should now configure `native_price = 0` in the block metadata, which makes the +# native budget effectively unlimited (subject to the `MAX_NATIVE_COMPUTATIONAL` +# per-tx cap and the block-level limits). +eth_runner = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "state-diffs-pi", "basic_bootloader/burn_base_fee", "basic_bootloader/eip-4844"] # Additional features used for eth_stf in eth_runner eth_stf = ["basic_bootloader/eip-7702","basic_bootloader/eip-7623", "system_hooks/bls12_381", "system_hooks/blake2f", "evm_interpreter/bls12_381", "evm_interpreter/blake2f"] @@ -62,7 +65,7 @@ eth_stf = ["basic_bootloader/eip-7702","basic_bootloader/eip-7623", "system_hook pectra = ["basic_bootloader/pectra", "evm_interpreter/bls12_381", "evm_interpreter/blake2f", "system_hooks/p256_precompile"] # Features used for evm_tester -evm_tester = ["basic_bootloader/resources_for_tester", "basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "state-diffs-pi", "basic_bootloader/eip-7702", "system_hooks/p256_precompile", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles", "unlimited_native", "basic_bootloader/eip-4844"] +evm_tester = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "state-diffs-pi", "basic_bootloader/eip-7702", "system_hooks/p256_precompile", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles", "basic_bootloader/eip-4844"] # Features used for evm_tester with Pectra hardfork evm_tester_pectra = ["evm_tester", "pectra"] diff --git a/tests/evm_divergence_validator/Cargo.toml b/tests/evm_divergence_validator/Cargo.toml index 6db97f90a..753fa9408 100644 --- a/tests/evm_divergence_validator/Cargo.toml +++ b/tests/evm_divergence_validator/Cargo.toml @@ -32,7 +32,7 @@ log = { workspace = true } hex = { workspace = true } tempfile = "3" -rig = { path = "../rig", features = ["no_print", "unlimited_native"] } +rig = { path = "../rig", features = ["no_print"] } zksync_os_tests_common = { path = "../common" } zksync_os_revm_runner = { path = "../revm_runner" } zk_ee = { path = "../../zk_ee" } diff --git a/tests/fuzzer/fuzz/wrappers/basic_bootloader_forward/Cargo.toml b/tests/fuzzer/fuzz/wrappers/basic_bootloader_forward/Cargo.toml index d98ff8bf0..6fce360ae 100644 --- a/tests/fuzzer/fuzz/wrappers/basic_bootloader_forward/Cargo.toml +++ b/tests/fuzzer/fuzz/wrappers/basic_bootloader_forward/Cargo.toml @@ -31,7 +31,5 @@ eip-7623 = [] eip-4844 = ["system_hooks/point_eval_precompile"] burn_base_fee = [] cycle_marker = ["system_hooks/cycle_marker", "cycle_marker/log_to_file"] -resources_for_tester = [] -unlimited_native = [] disable_system_contracts = [] error_origins = ["zk_ee/error_origins"] diff --git a/tests/revm_runner/src/revm_runner.rs b/tests/revm_runner/src/revm_runner.rs index 15c87a938..a98b63136 100644 --- a/tests/revm_runner/src/revm_runner.rs +++ b/tests/revm_runner/src/revm_runner.rs @@ -59,7 +59,8 @@ where spec: ZkSpecId, /// When true, REVM computes gas independently instead of using /// ZKsync OS's `gas_used` as an override. Best combined with - /// `unlimited_native` so that gas models are equivalent. + /// `native_price = 0` in the block metadata so that the ZKsync OS + /// native-correction is disabled and gas models are equivalent. independent_gas: bool, } diff --git a/tests/rig/Cargo.toml b/tests/rig/Cargo.toml index dd1903767..4d1a63eea 100644 --- a/tests/rig/Cargo.toml +++ b/tests/rig/Cargo.toml @@ -43,7 +43,6 @@ testing = [] e2e_proving = ["airbender-host"] no_print = ["forward_system/no_print"] cycle_marker = ["dep:cycle_marker", "zksync_os_runner/cycle_marker", "zk_ee/cycle_marker", "evm_interpreter/cycle_marker", "basic_system/cycle_marker", "basic_bootloader/cycle_marker"] -unlimited_native = ["forward_system/unlimited_native"] production = ["forward_system/production"] # Features for test instances for_tests = ["forward_system/for_tests", "callable_oracles/testing"] diff --git a/tests/rig/src/chain.rs b/tests/rig/src/chain.rs index 0d898c06a..f7a1ed525 100644 --- a/tests/rig/src/chain.rs +++ b/tests/rig/src/chain.rs @@ -206,7 +206,8 @@ pub struct RunConfig { // Can be enabled via ZKSYNC_REVM_CONSISTENCY_CHECK env var. pub check_revm_consistency: bool, /// When true, REVM computes gas independently instead of using - /// ZKsync OS's `gas_used` override. Best combined with `unlimited_native`. + /// ZKsync OS's `gas_used` override. Best combined with `native_price = 0` + /// in the block metadata. pub revm_independent_gas: bool, pub update_state_after_block_execution: bool, } From d913bc96200b4029971bc23e9858aaf535719e1d Mon Sep 17 00:00:00 2001 From: AntonD3 Date: Wed, 20 May 2026 18:44:58 +0200 Subject: [PATCH 2/6] Use 0 native price in evm tester --- forward_system/Cargo.toml | 4 ---- tests/evm_tester/src/vm/zk_ee/mod.rs | 2 +- tests/rig/Cargo.toml | 3 --- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/forward_system/Cargo.toml b/forward_system/Cargo.toml index dcc56566c..7b9e8d93d 100644 --- a/forward_system/Cargo.toml +++ b/forward_system/Cargo.toml @@ -66,9 +66,5 @@ evm_tester = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", " # Features used for evm_tester with Pectra hardfork evm_tester_pectra = ["evm_tester", "pectra"] -# Features used for legacy evm_tester. Only defined in forward system, -# as the legacy evm_tester does not perform a proof run. -evm_tester_legacy = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/eip-7702", "basic_bootloader/burn_base_fee", "system_hooks/mock-unsupported-precompiles"] - [dev-dependencies] tempfile = "3" diff --git a/tests/evm_tester/src/vm/zk_ee/mod.rs b/tests/evm_tester/src/vm/zk_ee/mod.rs index 4058231ab..af401731a 100644 --- a/tests/evm_tester/src/vm/zk_ee/mod.rs +++ b/tests/evm_tester/src/vm/zk_ee/mod.rs @@ -91,7 +91,7 @@ impl ZKsyncOS { let context = BlockContext { eip1559_basefee: ruint::Uint::from_str(&system_context.base_fee.to_string()) .expect("Invalid basefee"), - native_price: ruint::aliases::U256::from(1), + native_price: ruint::aliases::U256::from(0), pubdata_price: Default::default(), timestamp: system_context.block_timestamp as u64, gas_limit, diff --git a/tests/rig/Cargo.toml b/tests/rig/Cargo.toml index 4d1a63eea..278158922 100644 --- a/tests/rig/Cargo.toml +++ b/tests/rig/Cargo.toml @@ -53,6 +53,3 @@ eth_stf = ["forward_system/eth_stf"] evm_tester = ["forward_system/evm_tester"] pectra = ["forward_system/pectra"] evm_tester_pectra = ["forward_system/evm_tester_pectra"] -# Features used for legacy evm_tester. Only defined in forward system, -# as the legacy evm_tester does not perform a proof run. -evm_tester_legacy = ["forward_system/evm_tester_legacy"] From 39f6d27d3ddd407679aba7aea19f105cefdcd7fd Mon Sep 17 00:00:00 2001 From: AntonD3 Date: Mon, 25 May 2026 14:16:50 +0200 Subject: [PATCH 3/6] Few improvements --- .../src/bootloader/transaction/access_list.rs | 2 +- .../transaction/rlp_encoded/transaction.rs | 3 +- .../transaction_flow/gas_helpers.rs | 159 ++++++------------ .../zk/process_l1_transaction.rs | 38 ++--- .../transaction_flow/zk/validation_impl.rs | 68 +++----- 5 files changed, 96 insertions(+), 174 deletions(-) diff --git a/basic_bootloader/src/bootloader/transaction/access_list.rs b/basic_bootloader/src/bootloader/transaction/access_list.rs index 658f90544..a28b5560e 100644 --- a/basic_bootloader/src/bootloader/transaction/access_list.rs +++ b/basic_bootloader/src/bootloader/transaction/access_list.rs @@ -1,4 +1,4 @@ - use super::Transaction; +use super::Transaction; use crate::bootloader::errors::TxError; use evm_interpreter::ERGS_PER_GAS; use zk_ee::system::{Ergs, Resource, Resources}; diff --git a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs index aae6a110a..1c0161357 100644 --- a/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs +++ b/basic_bootloader/src/bootloader/transaction/rlp_encoded/transaction.rs @@ -27,8 +27,7 @@ pub struct RlpEncodedTransaction { tx_hash: Option, // Note: this field is not the recovered signer, but rather an address // passed by oracle. Needs to be checked to be equal to recovered address - // (only in untrusted environments, e.g. proving mode; trusted forward/ - // sequencer paths may skip ecrecover and rely on the oracle hint). + // in untrusted environments, e.g. proving mode from: B160, } diff --git a/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs b/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs index 005fea6d0..1a84de26a 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs @@ -152,137 +152,90 @@ pub fn calculate_l2_tx_intrinsic_pubdata(authorization_list_num: u64, is_service } /// -/// Create initial resources for a transaction. Pure constructor: splits the -/// native budget into `main_resources` (capped at `MAX_NATIVE_COMPUTATIONAL`) -/// and `withheld` (the excess, only spendable on pubdata at refund time), and -/// loads `gas_limit · ERGS_PER_GAS` ergs into `main_resources`. +/// Create initial resources for a transaction, applying all three intrinsic +/// charges (pubdata, computational native, gas) in u64 arithmetic. /// -/// Intrinsic gas / native / pubdata are NOT subtracted here. Callers must -/// charge them via [`charge_intrinsic_pubdata`], -/// [`charge_intrinsic_computational_native`] and [`charge_intrinsic_gas`] -/// with whatever error semantics they need (L2 surfaces validation errors, -/// L1 logs and saturates). +/// Charges are saturating: on underflow the running counter goes to 0, the +/// first observed failure is recorded, and remaining charges still apply. /// -/// Note: for zero gas price, we use "unlimited native". pub fn create_resources_for_tx( gas_limit: u64, free_native: bool, native_prepaid_from_gas: u64, -) -> ResourcesForTx + native_per_pubdata_byte: u64, + intrinsic_gas: u64, + intrinsic_computational_native: u64, + intrinsic_pubdata: u64, +) -> (ResourcesForTx, Option) where S::Metadata: ZkSpecificPricingMetadata, { - let native_total = if free_native { + let mut first_error: Option = None; + + // Gross native budget. + let native_limit = if free_native { u64::MAX - 1 // So any saturating subtraction below cannot underflow it } else { native_prepaid_from_gas }; - // Always cap the computational budget at `MAX_NATIVE_COMPUTATIONAL`. - // Anything above the cap can only be spent on pubdata at refund time. - let (main_native_u64, withheld) = if native_total <= MAX_NATIVE_COMPUTATIONAL { - (native_total, S::Resources::from_ergs(Ergs::empty())) + // Charge intrinsic pubdata. Subtracted from the total native budget + // before splitting into main / withheld, matching pre-refactor semantics. + let intrinsic_pubdata_overhead = native_per_pubdata_byte.saturating_mul(intrinsic_pubdata); + let native_limit = match native_limit.checked_sub(intrinsic_pubdata_overhead) { + Some(val) => val, + None => { + first_error.get_or_insert(InvalidTransaction::OutOfNativeResourcesDuringValidation); + 0 + } + }; + + // Split: anything above MAX_NATIVE_COMPUTATIONAL goes into `withheld` + // (only spendable on pubdata at refund time). + let (native_limit, withheld) = if native_limit <= MAX_NATIVE_COMPUTATIONAL { + (native_limit, S::Resources::from_ergs(Ergs::empty())) } else { let withheld_native = <::Resources as Resources>::Native::from_computational( - native_total - MAX_NATIVE_COMPUTATIONAL, + native_limit - MAX_NATIVE_COMPUTATIONAL, ); - ( MAX_NATIVE_COMPUTATIONAL, S::Resources::from_native(withheld_native), ) }; - let main_native = + // Charge intrinsic computational native against the post-split main budget. + let native_limit = match native_limit.checked_sub(intrinsic_computational_native) { + Some(val) => val, + None => { + first_error.get_or_insert(InvalidTransaction::OutOfNativeResourcesDuringValidation); + 0 + } + }; + let native_limit = <::Resources as Resources>::Native::from_computational( - main_native_u64, + native_limit, ); - let ergs = gas_limit.saturating_mul(ERGS_PER_GAS); - let main_resources = S::Resources::from_ergs_and_native(Ergs(ergs), main_native); - - ResourcesForTx { - main_resources, - withheld, - } -} - -/// Charge intrinsic pubdata cost (native-only). Drains `withheld` first, -/// then spills into `main_resources`. Underlying `charge` already saturates -/// each resource to zero on insufficient funds; this helper returns `Err(())` -/// if the total budget couldn't cover the cost, so the caller can decide -/// whether to surface a validation error (L2) or just log (L1). -/// -/// Equivalent in steady state to the original behavior of -/// `create_resources_for_tx`, which subtracted pubdata cost from the total -/// native budget before splitting into `main`/`withheld`. -pub fn charge_intrinsic_pubdata( - resources: &mut ResourcesForTx, - intrinsic_pubdata: u64, - native_per_pubdata: u64, -) -> Result<(), ()> { - let total_cost = native_per_pubdata.saturating_mul(intrinsic_pubdata); - if total_cost == 0 { - return Ok(()); - } - let withheld_avail = resources.withheld.native().as_u64(); - let from_withheld = total_cost.min(withheld_avail); - let from_main = total_cost - from_withheld; - - if from_withheld > 0 { - let cost = S::Resources::from_native( - <::Native as Computational>::from_computational( - from_withheld, - ), - ); - // Saturates withheld to 0 if insufficient — for our `min` choice it - // should be exact, but be defensive. - let _ = resources.withheld.charge(&cost); - } - - if from_main > 0 { - let cost = S::Resources::from_native( - <::Native as Computational>::from_computational(from_main), - ); - if resources.main_resources.charge(&cost).is_err() { - return Err(()); + // Charge intrinsic gas against gas_limit. + let gas_limit_for_tx = match gas_limit.checked_sub(intrinsic_gas) { + Some(val) => val, + None => { + first_error.get_or_insert(InvalidTransaction::OutOfGasDuringValidation); + 0 } - } - - Ok(()) -} - -/// Charge intrinsic computational native against `main_resources`. -/// `charge` saturates the resource to zero if insufficient; we surface that -/// as `Err(())` so the caller can map it to its preferred error variant. -pub fn charge_intrinsic_computational_native( - main: &mut S::Resources, - intrinsic_computational_native: u64, -) -> Result<(), ()> { - if intrinsic_computational_native == 0 { - return Ok(()); - } - let cost = S::Resources::from_native( - <::Native as Computational>::from_computational( - intrinsic_computational_native, - ), - ); - main.charge(&cost).map_err(|_| ()) -} - -/// Charge intrinsic gas (in EVM gas units) against `main_resources` ergs. -/// `charge` saturates the resource to zero on underflow; we surface that as -/// `Err(())`. -pub fn charge_intrinsic_gas( - main: &mut S::Resources, - intrinsic_gas: u64, -) -> Result<(), ()> { - if intrinsic_gas == 0 { - return Ok(()); - } - let cost = S::Resources::from_ergs(Ergs(intrinsic_gas.saturating_mul(ERGS_PER_GAS))); - main.charge(&cost).map_err(|_| ()) + }; + let ergs = gas_limit_for_tx.saturating_mul(ERGS_PER_GAS); + + let main_resources = S::Resources::from_ergs_and_native(Ergs(ergs), native_limit); + ( + ResourcesForTx { + main_resources, + withheld, + }, + first_error, + ) } /// diff --git a/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs b/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs index 024dd5c8b..4c20aee2f 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs @@ -9,7 +9,6 @@ use crate::bootloader::runner::RunnerMemoryBuffers; use crate::bootloader::transaction::abi_encoded::AbiEncodedTransaction; use crate::bootloader::transaction_flow::gas_helpers::{ calculate_l1_tx_intrinsic_computational_native_resources, calculate_tx_intrinsic_gas, - charge_intrinsic_computational_native, charge_intrinsic_gas, charge_intrinsic_pubdata, check_enough_resources_for_pubdata, create_resources_for_tx, get_resources_to_charge_for_pubdata, ResourcesForTx, }; @@ -519,39 +518,22 @@ where transaction.calldata().len() as u64, ); - // Materialize the gross resource budget for the tx, then charge the - // intrinsic overheads. L1 transactions cannot be invalidated (the - // priority queue requires the tx to be processed), so underflow on any - // of the charges is logged and absorbed — the user ends up with a - // zero-saturated resource and `gas_used == gas_limit` downstream. - let mut resources = create_resources_for_tx::( + let (resources, charge_err) = create_resources_for_tx::( gas_limit, native_per_gas == 0, native_prepaid_from_gas, - ); - if charge_intrinsic_pubdata(&mut resources, intrinsic_pubdata, native_per_pubdata).is_err() { - system_log!( - system, - "L1 tx: native budget below intrinsic pubdata cost, saturating to 0\n" - ); - } - if charge_intrinsic_computational_native::( - &mut resources.main_resources, + native_per_pubdata, + intrinsic_gas, intrinsic_computational_native, - ) - .is_err() - { - system_log!( - system, - "L1 tx: native budget below intrinsic computational native cost, saturating to 0\n" - ); - } - if charge_intrinsic_gas::(&mut resources.main_resources, intrinsic_gas).is_err() { + intrinsic_pubdata, + ); + // We are not invalidating L1 txs in case of there is not enough resources to cover intrinsic costs. + // It shouldn't be reachable in practice, as we checking it on l1, but we want to be extra safe. + if let Some(e) = charge_err { system_log!( system, - "L1 tx: gas limit {} below intrinsic gas {}, saturating to 0\n", - gas_limit, - intrinsic_gas + "L1 tx: intrinsic charge underflow ({:?}), saturating\n", + e ); } diff --git a/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs b/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs index bb0b5de57..09e6cd8c8 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/zk/validation_impl.rs @@ -7,8 +7,7 @@ use crate::bootloader::transaction::rlp_encoded::AccessListForAddress; use crate::bootloader::transaction::{charge_keccak, Transaction}; use crate::bootloader::transaction_flow::gas_helpers::{ calculate_l2_tx_intrinsic_computational_native_resources, calculate_l2_tx_intrinsic_pubdata, - calculate_tx_intrinsic_gas, charge_intrinsic_computational_native, charge_intrinsic_gas, - charge_intrinsic_pubdata, create_resources_for_tx, get_gas_price, + calculate_tx_intrinsic_gas, create_resources_for_tx, get_gas_price, }; use crate::bootloader::BasicBootloaderExecutionConfig; use crate::require; @@ -107,29 +106,25 @@ where }; // `native_price == 0` means the chain doesn't price native. Downstream - // treats `native_per_gas == 0` as "unlimited native budget" (see - // `create_resources_for_tx` and `compute_gas_refund`), so we propagate the - // zero directly without doing any division. This replaces the historical - // `resources_for_tester` / `unlimited_native` compile-time features. - let (native_per_gas, native_per_pubdata) = if native_price.is_zero() { - (0u64, 0u64) + // treats `native_per_gas == 0` as "unlimited native budget" + let native_per_gas = if native_price.is_zero() { + 0u64 + } else if Config::SIMULATION && gas_price.is_zero() { + // For simulation, if gas price isn't set, we use base fee + // for native calculation + u256_try_to_u64(&system.get_eip1559_basefee().div_ceil(native_price)).ok_or( + TxError::Validation(InvalidTransaction::NativeResourcesAreTooExpensive), + )? } else { - let native_per_gas = if Config::SIMULATION && gas_price.is_zero() { - // For simulation, if gas price isn't set, we use base fee - // for native calculation - // TODO: wrong error - u256_try_to_u64(&system.get_eip1559_basefee().div_ceil(native_price)).ok_or( - TxError::Validation(InvalidTransaction::NativeResourcesAreTooExpensive), - )? - } else { - u256_try_to_u64(&gas_price.div_ceil(native_price)).ok_or(TxError::Validation( - InvalidTransaction::NativeResourcesAreTooExpensive, - ))? - }; - let native_per_pubdata = u256_try_to_u64(&pubdata_price.wrapping_div(native_price)) - .ok_or(TxError::Validation(InvalidTransaction::PubdataPriceTooHigh))?; - (native_per_gas, native_per_pubdata) + u256_try_to_u64(&gas_price.div_ceil(native_price)).ok_or(TxError::Validation( + InvalidTransaction::NativeResourcesAreTooExpensive, + ))? }; + // If native resources are free (native_price == 0), pubdata is free too: + // `checked_div` returns `None` and we fall back to 0. + let native_per_pubdata = + u256_try_to_u64(&pubdata_price.checked_div(native_price).unwrap_or_default()) + .ok_or(TxError::Validation(InvalidTransaction::PubdataPriceTooHigh))?; let native_prepaid_from_gas = native_per_gas.saturating_mul(tx_gas_limit); let mut access_list_accounts = 0; @@ -178,27 +173,20 @@ where let intrinsic_pubdata = calculate_l2_tx_intrinsic_pubdata(authorization_list_num, transaction.is_service()); - // Materialize the gross resource budget for the tx, then charge the - // intrinsic overheads. Underflow on any of the charges surfaces as a - // validation error so the whole tx is dropped without state changes. - let mut tx_resources = create_resources_for_tx::( + // Materialize the tx's resource budget and charge the intrinsic overheads. + // Underflow on any of the charges surfaces as a validation error + let (tx_resources, charge_err) = create_resources_for_tx::( tx_gas_limit, native_per_gas == 0, native_prepaid_from_gas, - ); - charge_intrinsic_pubdata(&mut tx_resources, intrinsic_pubdata, native_per_pubdata) - .map_err(|()| { - TxError::Validation(InvalidTransaction::OutOfNativeResourcesDuringValidation) - })?; - charge_intrinsic_computational_native::( - &mut tx_resources.main_resources, + native_per_pubdata, + intrinsic_gas, intrinsic_computational_native, - ) - .map_err(|()| { - TxError::Validation(InvalidTransaction::OutOfNativeResourcesDuringValidation) - })?; - charge_intrinsic_gas::(&mut tx_resources.main_resources, intrinsic_gas) - .map_err(|()| TxError::Validation(InvalidTransaction::OutOfGasDuringValidation))?; + intrinsic_pubdata, + ); + if let Some(e) = charge_err { + return Err(TxError::Validation(e)); + } system_log!( system, From 29dd140d93365203229eaa3be07a65017ec477e6 Mon Sep 17 00:00:00 2001 From: AntonD3 Date: Mon, 25 May 2026 14:25:48 +0200 Subject: [PATCH 4/6] Fix comments --- .../src/bootloader/transaction_flow/gas_helpers.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs b/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs index 1a84de26a..2e3d138a0 100644 --- a/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs +++ b/basic_bootloader/src/bootloader/transaction_flow/gas_helpers.rs @@ -180,7 +180,7 @@ where }; // Charge intrinsic pubdata. Subtracted from the total native budget - // before splitting into main / withheld, matching pre-refactor semantics. + // before splitting into main / withheld. let intrinsic_pubdata_overhead = native_per_pubdata_byte.saturating_mul(intrinsic_pubdata); let native_limit = match native_limit.checked_sub(intrinsic_pubdata_overhead) { Some(val) => val, @@ -291,7 +291,6 @@ pub(crate) fn get_gas_price Result { let base_fee = system.get_eip1559_basefee(); // If base fee is zero, then we ignore priority fee - // TODO: not ignore? if base_fee.is_zero() { Ok(U256::ZERO) } else { From 17cc19cec788e00eedb5f532e4d2b59d44f2a27c Mon Sep 17 00:00:00 2001 From: AntonD3 Date: Tue, 26 May 2026 18:17:18 +0200 Subject: [PATCH 5/6] Avoid mulh in the binary --- basic_system/src/system_functions/keccak256.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/basic_system/src/system_functions/keccak256.rs b/basic_system/src/system_functions/keccak256.rs index ed5aab609..63591a535 100644 --- a/basic_system/src/system_functions/keccak256.rs +++ b/basic_system/src/system_functions/keccak256.rs @@ -33,6 +33,8 @@ pub fn keccak256_native_cost_u64(len: usize) -> u64 { keccak256_native_cost_for_rounds_u64(rounds) } +// Note: `#[inline(never)]` is required, otherwise compiler produces signed multiplication instruction. +#[inline(never)] pub const fn keccak256_native_cost_for_rounds_u64(rounds: usize) -> u64 { (rounds as u64) * KECCAK256_ROUND_NATIVE_COST + KECCAK256_BASE_NATIVE_COST } From 25585ed641fa401b6abd9b0d569e02aef1865c19 Mon Sep 17 00:00:00 2001 From: AntonD3 Date: Wed, 27 May 2026 12:24:34 +0200 Subject: [PATCH 6/6] Remove unlimited_native uses --- bench_scripts/bench.sh | 4 ++-- bench_scripts/run_bench_side.sh | 6 +++--- forward_system/Cargo.toml | 4 ---- proof_running_system/Cargo.toml | 4 ---- tests/evm_divergence_validator/README.md | 2 +- tests/evm_divergence_validator/src/runner.rs | 2 +- tests/evm_tester/src/vm/zk_ee/mod.rs | 2 +- tests/instances/eth_runner/README.md | 4 ++-- .../eth_runner/scripts/run_parallel_processes.sh | 2 +- tests/instances/eth_runner/src/block.rs | 2 +- tests/instances/precompiles/src/lib.rs | 8 ++++---- 11 files changed, 16 insertions(+), 24 deletions(-) diff --git a/bench_scripts/bench.sh b/bench_scripts/bench.sh index 4bee4e55a..8183551d6 100755 --- a/bench_scripts/bench.sh +++ b/bench_scripts/bench.sh @@ -9,8 +9,8 @@ BLOCKS_DIR="$REPO_ROOT/tests/instances/eth_runner/blocks" # Use the first available block for quick mode QUICK_BLOCK="$(ls "$BLOCKS_DIR" | head -1)" -FEATURES="rig/no_print,rig/cycle_marker,rig/unlimited_native" -PRECOMPILE_FEATURES="rig/no_print,precompiles/cycle_marker,rig/unlimited_native" +FEATURES="rig/no_print,rig/cycle_marker" +PRECOMPILE_FEATURES="rig/no_print,precompiles/cycle_marker" ETH_RUNNER_MANIFEST="$REPO_ROOT/tests/instances/eth_runner/Cargo.toml" PRECOMPILE_MANIFEST="$REPO_ROOT/tests/instances/precompiles/Cargo.toml" diff --git a/bench_scripts/run_bench_side.sh b/bench_scripts/run_bench_side.sh index 988646f34..5bc6224d2 100755 --- a/bench_scripts/run_bench_side.sh +++ b/bench_scripts/run_bench_side.sh @@ -44,14 +44,14 @@ else PROFILE="--release" fi if grep -q "for-tests-benchmarking-pectra" zksync_os/dump_bin.sh; then - PRECOMPILES_FEATURES="rig/no_print,precompiles/cycle_marker,precompiles/pectra,rig/unlimited_native" + PRECOMPILES_FEATURES="rig/no_print,precompiles/cycle_marker,precompiles/pectra" PRECOMPILES_TESTS="test_precompiles test_pectra_precompiles test_kzg_regression" else - PRECOMPILES_FEATURES="rig/no_print,precompiles/cycle_marker,rig/unlimited_native" + PRECOMPILES_FEATURES="rig/no_print,precompiles/cycle_marker" PRECOMPILES_TESTS="test_precompiles" fi -EVM_FEATURES="rig/no_print,rig/cycle_marker,rig/unlimited_native" +EVM_FEATURES="rig/no_print,rig/cycle_marker" for dir in tests/instances/eth_runner/blocks/*; do blk=$(basename "$dir") diff --git a/forward_system/Cargo.toml b/forward_system/Cargo.toml index 7b9e8d93d..fbf2ba51e 100644 --- a/forward_system/Cargo.toml +++ b/forward_system/Cargo.toml @@ -48,10 +48,6 @@ production = ["basic_bootloader/eip-7702", "system_hooks/p256_precompile"] for_tests = ["production", "basic_bootloader/eip-4844"] # Features used for eth_runner. -# Test profiles that previously enabled `unlimited_native` / `resources_for_tester` -# should now configure `native_price = 0` in the block metadata, which makes the -# native budget effectively unlimited (subject to the `MAX_NATIVE_COMPUTATIONAL` -# per-tx cap and the block-level limits). eth_runner = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "basic_bootloader/burn_base_fee", "basic_bootloader/eip-4844"] # Additional features used for eth_stf in eth_runner diff --git a/proof_running_system/Cargo.toml b/proof_running_system/Cargo.toml index 7195d76d9..36e1c036c 100644 --- a/proof_running_system/Cargo.toml +++ b/proof_running_system/Cargo.toml @@ -52,10 +52,6 @@ production = ["basic_bootloader/eip-7702", "system_hooks/p256_precompile"] for_tests = ["production", "state-diffs-pi", "basic_bootloader/eip-4844"] # Features used for eth_runner. -# Test profiles that previously enabled `unlimited_native` / `resources_for_tester` -# should now configure `native_price = 0` in the block metadata, which makes the -# native budget effectively unlimited (subject to the `MAX_NATIVE_COMPUTATIONAL` -# per-tx cap and the block-level limits). eth_runner = ["basic_bootloader/disable_system_contracts", "zk_ee/prevrandao", "state-diffs-pi", "basic_bootloader/burn_base_fee", "basic_bootloader/eip-4844"] # Additional features used for eth_stf in eth_runner diff --git a/tests/evm_divergence_validator/README.md b/tests/evm_divergence_validator/README.md index 7b349abdc..6a4d87965 100644 --- a/tests/evm_divergence_validator/README.md +++ b/tests/evm_divergence_validator/README.md @@ -193,5 +193,5 @@ The tool uses the existing REVM consistency checker from `tests/revm_runner/`, w ## Design notes - All steps run in a single block. Multi-block scenarios are not yet supported. -- The tool enables `unlimited_native` and `independent_gas`, so ZKsync OS gas accounting follows standard EVM rules and is not overridden from ZKsync OS to REVM. The validator reports `gas_used` per step, but does not treat per-transaction gas differences as a separate divergence check — gas differences surface through balance diffs in the state comparison. +- The tool enables unlimited native(`native_price` == 0) and `independent_gas`, so ZKsync OS gas accounting follows standard EVM rules and is not overridden from ZKsync OS to REVM. The validator reports `gas_used` per step, but does not treat per-transaction gas differences as a separate divergence check — gas differences surface through balance diffs in the state comparison. - The REVM side uses `zksync-os-revm` (adapted REVM), which accounts for ZKsync-specific behaviors (precompile differences, fee distribution, etc.). This is intentional — divergences caught here are real bugs, not known differences. diff --git a/tests/evm_divergence_validator/src/runner.rs b/tests/evm_divergence_validator/src/runner.rs index 61cc14ce9..4b486b0e7 100644 --- a/tests/evm_divergence_validator/src/runner.rs +++ b/tests/evm_divergence_validator/src/runner.rs @@ -485,7 +485,7 @@ fn parse_alloy_u256(s: &str) -> anyhow::Result { fn make_block_context(block: &BlockDef) -> rig::BlockContext { rig::BlockContext { eip1559_basefee: ruint::aliases::U256::from(block.basefee.unwrap_or(DEFAULT_MAX_FEE)), - native_price: ruint::aliases::U256::from(10u64), + native_price: ruint::aliases::U256::from(0u64), // native resources will be unlimited pubdata_price: Default::default(), timestamp: block.timestamp.unwrap_or(1_700_000_000), gas_limit: block.gas_limit.unwrap_or(30_000_000), diff --git a/tests/evm_tester/src/vm/zk_ee/mod.rs b/tests/evm_tester/src/vm/zk_ee/mod.rs index af401731a..5f05d775a 100644 --- a/tests/evm_tester/src/vm/zk_ee/mod.rs +++ b/tests/evm_tester/src/vm/zk_ee/mod.rs @@ -91,7 +91,7 @@ impl ZKsyncOS { let context = BlockContext { eip1559_basefee: ruint::Uint::from_str(&system_context.base_fee.to_string()) .expect("Invalid basefee"), - native_price: ruint::aliases::U256::from(0), + native_price: ruint::aliases::U256::from(0), // native resources will be unlimited pubdata_price: Default::default(), timestamp: system_context.block_timestamp as u64, gas_limit, diff --git a/tests/instances/eth_runner/README.md b/tests/instances/eth_runner/README.md index 8c3147bf0..dd938d70e 100644 --- a/tests/instances/eth_runner/README.md +++ b/tests/instances/eth_runner/README.md @@ -24,7 +24,7 @@ The tool has two modes: `single-run` and `live-run`. The former takes as argumen From the root of the project, run: ```raw -RUST_LOG=eth_runner=info cargo run -p eth_runner --release --features rig/no_print,rig/unlimited_native -- single-run --block-dir tests/instances/eth_runner/blocks/22244135 --randomized +RUST_LOG=eth_runner=info cargo run -p eth_runner --release --features rig/no_print -- single-run --block-dir tests/instances/eth_runner/blocks/22244135 --randomized ``` This will run the example block committed to the repo (22244135). Some more example blocks can be found in https://github.com/antoniolocascio/ethereum-block-examples. @@ -34,7 +34,7 @@ This will run the example block committed to the repo (22244135). Some more exam From the root of the projects, run: ```raw -RUST_LOG=eth_runner=info cargo run -p eth_runner --release --features rig/no_print,rig/unlimited_native -- live-run --start-block 19299000 --end-block 19299005 --endpoint ENDPOINT --db ../db +RUST_LOG=eth_runner=info cargo run -p eth_runner --release --features rig/no_print -- live-run --start-block 19299000 --end-block 19299005 --endpoint ENDPOINT --db ../db ``` This command will fetch blocks in the range [19299000, 19299005] from the Ethereum archive node `ENDPOINT`. It creates a local database to cache some RPC information. diff --git a/tests/instances/eth_runner/scripts/run_parallel_processes.sh b/tests/instances/eth_runner/scripts/run_parallel_processes.sh index 299fc1572..316f074ce 100755 --- a/tests/instances/eth_runner/scripts/run_parallel_processes.sh +++ b/tests/instances/eth_runner/scripts/run_parallel_processes.sh @@ -88,7 +88,7 @@ for i in $(seq 1 $NUM_PROCESSES); do CMD="$CMD REFETCH_TRACES=$REFETCH_TRACES" fi - CMD="$CMD cargo run --manifest-path tests/instances/eth_runner/Cargo.toml --release --features rig/no_print,rig/unlimited_native -- \ + CMD="$CMD cargo run --manifest-path tests/instances/eth_runner/Cargo.toml --release --features rig/no_print -- \ live-run \ --start-block $CURRENT_START \ --end-block $CURRENT_END \ diff --git a/tests/instances/eth_runner/src/block.rs b/tests/instances/eth_runner/src/block.rs index 3a209d4b6..70814bcfc 100644 --- a/tests/instances/eth_runner/src/block.rs +++ b/tests/instances/eth_runner/src/block.rs @@ -26,7 +26,7 @@ impl Block { timestamp: self.result.header.timestamp, eip1559_basefee: base_fee, pubdata_price: U256::ZERO, - native_price: (base_fee / U256::from(100)).max(U256::ONE), + native_price: U256::ZERO, coinbase: B160::from_alloy(self.result.header.beneficiary), gas_limit: self.result.header.gas_limit, pubdata_limit: u64::MAX, diff --git a/tests/instances/precompiles/src/lib.rs b/tests/instances/precompiles/src/lib.rs index 0c943d87c..087573ed3 100644 --- a/tests/instances/precompiles/src/lib.rs +++ b/tests/instances/precompiles/src/lib.rs @@ -104,9 +104,9 @@ fn run_precompile_inner( wallet.clone(), ); - // We use a very high native per gas ratio + // Unlimited native (native price == 0) let block_context = BlockContext { - native_price: U256::ONE, + native_price: U256::ZERO, eip1559_basefee: U256::from(25_000), ..Default::default() }; @@ -6617,9 +6617,9 @@ fn test_regression_p256_is_warm() { wallet.clone(), ); - // We use a very high native per gas ratio + // Unlimited native (native price == 0) let block_context = BlockContext { - native_price: U256::ONE, + native_price: U256::ZERO, eip1559_basefee: U256::from(25_000), ..Default::default() };