diff --git a/src/handler.rs b/src/handler.rs index 11ee85d..1d37c44 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -60,7 +60,7 @@ where #[inline] fn pre_execution(&self, evm: &mut Self::Evm) -> Result { // only load the L1BlockInfo for txs that are not l1 messages. - if !evm.ctx().tx().is_l1_msg() { + if !evm.ctx().tx().is_l1_msg() && !evm.ctx().tx().is_system_tx() { let spec = evm.ctx().cfg().spec(); let l1_block_info = L1BlockInfo::try_fetch(&mut evm.ctx().db(), spec)?; *evm.ctx().chain() = l1_block_info; @@ -84,16 +84,21 @@ where let ctx = evm.ctx(); let caller = ctx.tx().caller(); let is_l1_msg = ctx.tx().is_l1_msg(); + let is_system_tx = ctx.tx().is_system_tx(); let kind = ctx.tx().kind(); let spec = ctx.cfg().spec(); let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); + // execute normal checks and transaction processing logic for non-l1-msgs if !is_l1_msg { // We deduct caller max balance after minting and before deducing the // l1 cost, max values is already checked in pre_validate but l1 cost wasn't. pre_execution::validate_against_state_and_deduct_caller::<_, ERROR>(ctx)?; + } + // process rollup fee + if !is_l1_msg && !is_system_tx { let l1_block_info = ctx.chain().clone(); let Some(rlp_bytes) = ctx.tx().rlp_bytes() else { return Err(ERROR::from_string( @@ -112,7 +117,10 @@ where } caller_account.data.info.balance = caller_account.data.info.balance.saturating_sub(tx_l1_cost); - } else { + } + + // execute l1 msg checks + if is_l1_msg { // Load caller's account. let (tx, journal) = ctx.tx_journal(); let mut caller_account = journal.load_account(caller)?; diff --git a/src/tests/fees.rs b/src/tests/fees.rs index 64acd78..f7aa83b 100644 --- a/src/tests/fees.rs +++ b/src/tests/fees.rs @@ -2,6 +2,7 @@ use crate::{ builder::ScrollBuilder, handler::ScrollHandler, test_utils::{context_with_funds, CALLER}, + transaction::SYSTEM_ADDRESS, ScrollSpecId, }; use std::boxed::Box; @@ -45,3 +46,25 @@ fn test_should_deduct_correct_fees_curie() -> Result<(), Box Result<(), Box> { + let ctx = context_with_funds(U256::from(70_000)) + .modify_cfg_chained(|cfg| cfg.spec = ScrollSpecId::CURIE) + .modify_tx_chained(|tx| { + tx.base.caller = SYSTEM_ADDRESS; + tx.base.gas_price = 0 + }); + + let mut evm = ctx.clone().build_scroll(); + let handler = ScrollHandler::<_, EVMError<_>, EthFrame<_, _, _>>::new(); + + handler.pre_execution(&mut evm).unwrap(); + + let caller_account = evm.ctx().journal().load_account(CALLER)?; + + // gas price is 0, no data fee => balance is unchanged. + assert_eq!(caller_account.data.info.balance, U256::from(70_000)); + + Ok(()) +} diff --git a/src/transaction.rs b/src/transaction.rs index b112fa6..49fd8ad 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,14 +1,20 @@ use revm::context::{Transaction, TxEnv}; -use revm_primitives::{Address, Bytes, TxKind, B256, U256}; +use revm_primitives::{address, Address, Bytes, TxKind, B256, U256}; /// The type for a l1 message transaction. pub const L1_MESSAGE_TYPE: u8 = 0x7E; +/// The caller address of EIP-2935 system transactions. +pub const SYSTEM_ADDRESS: Address = address!("0xfffffffffffffffffffffffffffffffffffffffe"); + #[auto_impl::auto_impl(&, Arc, Box)] pub trait ScrollTxTr: Transaction { /// Whether the transaction is an L1 message. fn is_l1_msg(&self) -> bool; + /// Whether the transaction is a system transaction (e.g. EIP-2935). + fn is_system_tx(&self) -> bool; + /// The RLP encoded transaction bytes which are used to calculate the cost associated with /// posting the transaction on L1. fn rlp_bytes(&self) -> Option<&Bytes>; @@ -111,6 +117,10 @@ impl ScrollTxTr for ScrollTransaction { self.tx_type() == L1_MESSAGE_TYPE } + fn is_system_tx(&self) -> bool { + self.caller() == SYSTEM_ADDRESS + } + fn rlp_bytes(&self) -> Option<&Bytes> { self.rlp_bytes.as_ref() }