Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Comment thread
greged93 marked this conversation as resolved.
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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
76 changes: 68 additions & 8 deletions src/tests/l1_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn core::error::Error>> {
fn test_l1_message_validate_lacking_funds() -> Result<(), Box<dyn core::error::Error>> {
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();
Expand All @@ -32,7 +34,7 @@ fn test_validate_lacking_funds_l1_message() -> Result<(), Box<dyn core::error::E
}

#[test]
fn test_load_account_l1_message() -> Result<(), Box<dyn core::error::Error>> {
fn test_l1_message_load_accounts() -> Result<(), Box<dyn core::error::Error>> {
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();
Expand All @@ -46,7 +48,7 @@ fn test_load_account_l1_message() -> Result<(), Box<dyn core::error::Error>> {
}

#[test]
fn test_deduct_caller_l1_message() -> Result<(), Box<dyn core::error::Error>> {
fn test_l1_message_should_not_deduct_caller() -> Result<(), Box<dyn core::error::Error>> {
let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE);

let mut evm = ctx.build_scroll();
Expand All @@ -64,7 +66,7 @@ fn test_deduct_caller_l1_message() -> Result<(), Box<dyn core::error::Error>> {
}

#[test]
fn test_last_frame_result_l1_message() -> Result<(), Box<dyn core::error::Error>> {
fn test_l1_message_last_frame_result() -> Result<(), Box<dyn core::error::Error>> {
let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE);

let mut evm = ctx.build_scroll();
Expand All @@ -86,7 +88,7 @@ fn test_last_frame_result_l1_message() -> Result<(), Box<dyn core::error::Error>
}

#[test]
fn test_refund_l1_message() -> Result<(), Box<dyn core::error::Error>> {
fn test_l1_message_should_not_refund() -> Result<(), Box<dyn core::error::Error>> {
let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE);

let mut evm = ctx.build_scroll();
Expand All @@ -107,7 +109,7 @@ fn test_refund_l1_message() -> Result<(), Box<dyn core::error::Error>> {
}

#[test]
fn test_reward_beneficiary_l1_message() -> Result<(), Box<dyn core::error::Error>> {
fn test_l1_message_should_not_reward_beneficiary() -> Result<(), Box<dyn core::error::Error>> {
let ctx = context().modify_tx_chained(|tx| tx.base.tx_type = L1_MESSAGE_TYPE);

let mut evm = ctx.build_scroll();
Expand All @@ -129,7 +131,7 @@ fn test_reward_beneficiary_l1_message() -> Result<(), Box<dyn core::error::Error
}

#[test]
fn test_should_revert_with_out_of_funds_l1_message() -> Result<(), Box<dyn core::error::Error>> {
fn test_l1_message_should_revert_with_out_of_funds() -> Result<(), Box<dyn core::error::Error>> {
let ctx = context().modify_tx_chained(|tx| {
tx.base.tx_type = L1_MESSAGE_TYPE;
tx.base.value = U256::ONE;
Expand All @@ -150,3 +152,61 @@ fn test_should_revert_with_out_of_funds_l1_message() -> Result<(), Box<dyn core:

Ok(())
}

#[test]
fn test_l1_message_should_pass_validation() -> Result<(), Box<dyn core::error::Error>> {
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<dyn core::error::Error>> {
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<dyn core::error::Error>> {
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(())
}
Loading