diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index 95174ae3c4..5fd5613691 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -65,6 +65,21 @@ pub fn load_accounts< Ok(()) } +/// Validates caller account nonce and code according to EIP-3607. +#[inline] +pub fn validate_account_nonce_and_code_with_components( + caller_info: &mut AccountInfo, + tx: impl Transaction, + cfg: impl Cfg, +) -> Result<(), InvalidTransaction> { + validate_account_nonce_and_code( + caller_info, + tx.nonce(), + cfg.is_eip3607_disabled(), + cfg.is_nonce_check_disabled(), + ) +} + /// Validates caller account nonce and code according to EIP-3607. #[inline] pub fn validate_account_nonce_and_code( @@ -153,12 +168,7 @@ pub fn validate_against_state_and_deduct_caller< // Load caller's account. let caller_account = journal.load_account_code(tx.caller())?.data; - validate_account_nonce_and_code( - &mut caller_account.info, - tx.nonce(), - cfg.is_eip3607_disabled(), - cfg.is_nonce_check_disabled(), - )?; + validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?; let new_balance = calculate_caller_fee(caller_account.info.balance, tx, block, cfg)?; diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index e94f3049c9..6aeaaed158 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -16,7 +16,7 @@ use revm::{ evm::FrameTr, handler::EvmTrError, post_execution::{self, reimburse_caller}, - pre_execution::validate_account_nonce_and_code, + pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components}, EthFrame, EvmTr, FrameResult, Handler, MainnetHandler, }, inspector::{Inspector, InspectorEvmTr, InspectorHandler}, @@ -101,19 +101,10 @@ where &self, evm: &mut Self::Evm, ) -> Result<(), Self::Error> { - let ctx = evm.ctx(); - - let basefee = ctx.block().basefee() as u128; - let blob_price = ctx.block().blob_gasprice().unwrap_or_default(); - let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; - let spec = ctx.cfg().spec(); - let block_number = ctx.block().number(); - let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled(); - let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); - let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); + let (block, tx, cfg, journal, chain, _) = evm.ctx().all_mut(); + let spec = cfg.spec(); - if is_deposit { - let (block, tx, cfg, journal, _, _) = evm.ctx().all_mut(); + if tx.tx_type() == DEPOSIT_TRANSACTION_TYPE { let basefee = block.basefee() as u128; let blob_price = block.blob_gasprice().unwrap_or_default(); // deposit skips max fee check and just deducts the effective balance spending. @@ -148,87 +139,35 @@ where return Ok(()); } - let mut additional_cost = U256::ZERO; - // L1 block info is stored in the context for later use. // and it will be reloaded from the database if it is not for the current block. - if ctx.chain().l2_block != Some(block_number) { - *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; + if chain.l2_block != Some(block.number()) { + *chain = L1BlockInfo::try_fetch(journal.db_mut(), block.number(), spec)?; } - if !ctx.cfg().is_fee_charge_disabled() { - // account for additional cost of l1 fee and operator fee - // account for additional cost of l1 fee and operator fee - let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else { - return Err(ERROR::from_string( - "[OPTIMISM] Failed to load enveloped transaction.".into(), - )); - }; - - // compute L1 cost - additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); - - // compute operator fee - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - let gas_limit = U256::from(ctx.tx().gas_limit()); - let operator_fee_charge = - ctx.chain() - .operator_fee_charge(&enveloped_tx, gas_limit, spec); - additional_cost = additional_cost.saturating_add(operator_fee_charge); - } - } - - let (tx, journal) = ctx.tx_journal_mut(); - let caller_account = journal.load_account_code(tx.caller())?.data; // validates account nonce and code - validate_account_nonce_and_code( - &mut caller_account.info, - tx.nonce(), - is_eip3607_disabled, - is_nonce_check_disabled, - )?; - - let mut new_balance = caller_account.info.balance; - - // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. - // Transfer will be done inside `*_inner` functions. - if !is_balance_check_disabled { - // check additional cost and deduct it from the caller's balances - let Some(balance) = new_balance.checked_sub(additional_cost) else { + validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?; + + // check additional cost and deduct it from the caller's balances + let mut balance = caller_account.info.balance; + + if !cfg.is_fee_charge_disabled() { + let additional_cost = chain.tx_cost_with_tx(tx, spec); + let Some(new_balance) = balance.checked_sub(additional_cost) else { return Err(InvalidTransaction::LackOfFundForMaxFee { fee: Box::new(additional_cost), - balance: Box::new(new_balance), + balance: Box::new(balance), } .into()); }; - tx.ensure_enough_balance(balance)?; - } - - // subtracting max balance spending with value that is going to be deducted later in the call. - let gas_balance_spending = tx - .gas_balance_spending(basefee, blob_price) - .expect("effective balance is always smaller than max balance so it can't overflow"); - - // If the transaction is not a deposit transaction, subtract the L1 data fee from the - // caller's balance directly after minting the requested amount of ETH. - // Additionally deduct the operator fee from the caller's account. - // - // In case of deposit additional cost will be zero. - let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); - - new_balance = new_balance.saturating_sub(op_gas_balance_spending); - - if is_balance_check_disabled { - // Make sure the caller's balance is at least the value of the transaction. - // this is not consensus critical, and it is used in testing. - new_balance = new_balance.max(tx.value()); + balance = new_balance } - let old_balance = - caller_account.caller_initial_modification(new_balance, tx.kind().is_call()); + let balance = calculate_caller_fee(balance, tx, block, cfg)?; + let old_balance = caller_account.caller_initial_modification(balance, tx.kind().is_call()); // NOTE: all changes to the caller account should journaled so in case of error // we can revert the changes. journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); diff --git a/crates/op-revm/src/l1block.rs b/crates/op-revm/src/l1block.rs index fd92a32c46..c3a6dc2172 100644 --- a/crates/op-revm/src/l1block.rs +++ b/crates/op-revm/src/l1block.rs @@ -7,7 +7,7 @@ use crate::{ NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, OPERATOR_FEE_JOVIAN_MULTIPLIER, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, }, - transaction::estimate_tx_compressed_size, + transaction::{estimate_tx_compressed_size, OpTxTr}, OpSpecId, }; use revm::{ @@ -256,6 +256,35 @@ impl L1BlockInfo { self.tx_l1_cost = None; } + /// Calculate additional transaction cost with OpTxTr. + /// + /// Internally calls [`L1BlockInfo::tx_cost`]. + #[track_caller] + pub fn tx_cost_with_tx(&mut self, tx: impl OpTxTr, spec: OpSpecId) -> U256 { + // account for additional cost of l1 fee and operator fee + let enveloped_tx = tx + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + let gas_limit = U256::from(tx.gas_limit()); + self.tx_cost(&enveloped_tx, gas_limit, spec) + } + + /// Calculate additional transaction cost. + #[inline] + pub fn tx_cost(&mut self, enveloped_tx: &[u8], gas_limit: U256, spec: OpSpecId) -> U256 { + // compute L1 cost + let mut additional_cost = self.calculate_tx_l1_cost(enveloped_tx, spec); + + // compute operator fee + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + let operator_fee_charge = self.operator_fee_charge(enveloped_tx, gas_limit, spec); + additional_cost = additional_cost.saturating_add(operator_fee_charge); + } + + additional_cost + } + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [OpSpecId] passed. pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: OpSpecId) -> U256 { if let Some(tx_l1_cost) = self.tx_l1_cost { diff --git a/examples/erc20_gas/src/handler.rs b/examples/erc20_gas/src/handler.rs index 230f2d161c..8213164550 100644 --- a/examples/erc20_gas/src/handler.rs +++ b/examples/erc20_gas/src/handler.rs @@ -2,7 +2,7 @@ use revm::{ context::Cfg, context_interface::{result::HaltReason, Block, ContextTr, JournalTr, Transaction}, handler::{ - pre_execution::{calculate_caller_fee, validate_account_nonce_and_code}, + pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components}, EvmTr, EvmTrError, FrameResult, FrameTr, Handler, }, interpreter::interpreter_action::FrameInit, @@ -54,12 +54,7 @@ where // Load caller's account. let caller_account = journal.load_account_code(tx.caller())?.data; - validate_account_nonce_and_code( - &mut caller_account.info, - tx.nonce(), - cfg.is_eip3607_disabled(), - cfg.is_nonce_check_disabled(), - )?; + validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?; // make changes to the account. Account balance stays the same caller_account