diff --git a/src/handler.rs b/src/handler.rs index b3a6225..8d3f338 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -4,13 +4,13 @@ use crate::{exec::ScrollContextTr, l1block::L1BlockInfo, transaction::ScrollTxTr use std::{boxed::Box, string::ToString}; use revm::{ + bytecode::Bytecode, context::{ result::{HaltReason, InvalidTransaction}, Block, Cfg, ContextTr, JournalTr, Transaction, }, handler::{ - post_execution, pre_execution, pre_execution::validate_account_nonce_and_code, EthFrame, - EvmTr, EvmTrError, FrameResult, FrameTr, Handler, MainnetHandler, + post_execution, EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, Handler, MainnetHandler, }, interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, Gas}, primitives::U256, @@ -77,22 +77,22 @@ where evm: &mut Self::Evm, ) -> Result<(), Self::Error> { // load caller's account. - 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 spec = ctx.cfg().spec(); - let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); - let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); + let ctx_ref = evm.ctx_ref(); + let caller = ctx_ref.tx().caller(); + let is_l1_msg = ctx_ref.tx().is_l1_msg(); + let is_system_tx = ctx_ref.tx().is_system_tx(); + let spec = ctx_ref.cfg().spec(); + let is_eip3607_disabled = ctx_ref.cfg().is_eip3607_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)?; + self.mainnet.validate_against_state_and_deduct_caller(evm)?; } // process rollup fee + let ctx = evm.ctx(); if !is_l1_msg && !is_system_tx { let l1_block_info = ctx.chain().clone(); let Some(rlp_bytes) = ctx.tx().rlp_bytes() else { @@ -122,13 +122,6 @@ where let (tx, journal) = ctx.tx_journal_mut(); let mut caller_account = journal.load_account(caller)?; - validate_account_nonce_and_code( - &mut caller_account.info, - tx.nonce(), - is_eip3607_disabled, - is_nonce_check_disabled, - )?; - // Note: we skip the balance check at pre-execution level if the transaction is a // L1 message and Euclid is enabled. This means the L1 message will reach execution // stage in revm and revert with `OutOfFunds` in the first frame, but still be included @@ -145,6 +138,26 @@ where } } + // EIP-3607: Reject transactions from senders with deployed code. + // + // We check the sender of the L1 message is a EOA on the L2. + // If the sender is a (delegated) EOA on the L1, it should be a (delegated) EOA + // on the L2. + // If the sender is a contract on the L1, address aliasing assures with high probability + // that the L2 sender would be an EOA. + if !is_eip3607_disabled { + let caller_info = &caller_account.info; + let bytecode = match caller_info.code.as_ref() { + Some(bytecode) => bytecode, + None => &Bytecode::default(), + }; + // Allow EOAs whose code is a valid delegation designation, + // i.e. 0xef0100 || address, to continue to originate transactions. + if !bytecode.is_empty() && !bytecode.is_eip7702() { + return Err(InvalidTransaction::RejectCallerWithCode.into()); + } + } + // Bump the nonce for calls. Nonce for CREATE will be bumped in `make_create_frame`. if tx.kind().is_call() { // Nonce is already checked diff --git a/src/tests/l1_message.rs b/src/tests/l1_message.rs index f5da24c..35017b5 100644 --- a/src/tests/l1_message.rs +++ b/src/tests/l1_message.rs @@ -9,18 +9,20 @@ use std::boxed::Box; use crate::test_utils::MIN_TRANSACTION_COST; use revm::{ + bytecode::LegacyRawBytecode, context::{ - result::{EVMError, ExecutionResult, HaltReason, ResultAndState}, + result::{EVMError, ExecutionResult, HaltReason, InvalidTransaction, ResultAndState}, ContextTr, JournalTr, }, handler::{EthFrame, EvmTr, FrameResult, Handler}, interpreter::{CallOutcome, Gas, InstructionResult, InterpreterResult}, + state::Bytecode, ExecuteEvm, }; use revm_primitives::U256; #[test] -fn test_validate_lacking_funds_l1_message() -> Result<(), Box> { +fn test_l1_message_validate_lacking_funds() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE); let mut evm = ctx.build_scroll(); let handler = ScrollHandler::<_, EVMError<_>, EthFrame<_>>::new(); @@ -32,7 +34,7 @@ fn test_validate_lacking_funds_l1_message() -> Result<(), Box Result<(), Box> { +fn test_l1_message_load_accounts() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE); let mut evm = ctx.build_scroll(); let handler = ScrollHandler::<_, EVMError<_>, EthFrame<_>>::new(); @@ -46,7 +48,7 @@ fn test_load_account_l1_message() -> Result<(), Box> { } #[test] -fn test_deduct_caller_l1_message() -> Result<(), Box> { +fn test_l1_message_should_not_deduct_caller() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE); let mut evm = ctx.build_scroll(); @@ -64,7 +66,7 @@ fn test_deduct_caller_l1_message() -> Result<(), Box> { } #[test] -fn test_last_frame_result_l1_message() -> Result<(), Box> { +fn test_l1_message_last_frame_result() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE); let mut evm = ctx.build_scroll(); @@ -86,7 +88,7 @@ fn test_last_frame_result_l1_message() -> Result<(), Box } #[test] -fn test_refund_l1_message() -> Result<(), Box> { +fn test_l1_message_should_not_refund() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE); let mut evm = ctx.build_scroll(); @@ -107,7 +109,7 @@ fn test_refund_l1_message() -> Result<(), Box> { } #[test] -fn test_reward_beneficiary_l1_message() -> Result<(), Box> { +fn test_l1_message_should_not_reward_beneficiary() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE); let mut evm = ctx.build_scroll(); @@ -129,7 +131,7 @@ fn test_reward_beneficiary_l1_message() -> Result<(), Box Result<(), Box> { +fn test_l1_message_should_revert_with_out_of_funds() -> Result<(), Box> { let ctx = context().modify_tx_chained(|tx| { tx.base.tx_type = L1_MESSAGE_TYPE; tx.base.value = U256::ONE; @@ -150,3 +152,61 @@ fn test_should_revert_with_out_of_funds_l1_message() -> Result<(), Box Result<(), Box> { + let ctx = context() + .modify_tx_chained(|tx| { + tx.base.tx_type = L1_MESSAGE_TYPE; + tx.base.value = U256::ONE; + tx.base.gas_price = 0; + }) + // set the base fee of the block above the L1 message gas price to check it passes. + .modify_block_chained(|block| block.basefee = 100); + let mut evm = ctx.build_scroll(); + let handler = ScrollHandler::<_, EVMError<_>, EthFrame<_>>::new(); + + handler.validate(&mut evm)?; + + Ok(()) +} + +#[test] +fn test_l1_message_should_pass_pre_execution() -> Result<(), Box> { + let ctx = context() + .modify_tx_chained(|tx| { + tx.base.tx_type = L1_MESSAGE_TYPE; + }) + // set the caller nonce to 1 and check pre execution passes. + .modify_journal_chained(|journal| { + let caller = journal.load_account(CALLER).unwrap(); + caller.data.info.nonce += 1; + }); + let mut evm = ctx.build_scroll(); + let handler = ScrollHandler::<_, EVMError<_>, EthFrame<_>>::new(); + + handler.pre_execution(&mut evm)?; + + Ok(()) +} + +#[test] +fn test_l1_message_eip_3607() -> Result<(), Box> { + let ctx = context() + .modify_tx_chained(|tx| { + tx.base.tx_type = L1_MESSAGE_TYPE; + }) + // set the caller nonce to 1 and check pre execution passes. + .modify_journal_chained(|journal| { + let caller = journal.load_account(CALLER).unwrap(); + caller.data.info.code = + Some(Bytecode::LegacyAnalyzed(LegacyRawBytecode([1u8; 2].into()).into_analyzed())); + }); + let mut evm = ctx.build_scroll(); + let handler = ScrollHandler::<_, EVMError<_>, EthFrame<_>>::new(); + + let err = handler.pre_execution(&mut evm).unwrap_err(); + assert_eq!(err, EVMError::Transaction(InvalidTransaction::RejectCallerWithCode)); + + Ok(()) +}