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
8 changes: 8 additions & 0 deletions crates/chainspec/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ impl From<Genesis> for MorphChainSpec {
}
}

impl MorphChainSpec {
pub fn set_hardfork(&mut self, hardfork: MorphHardfork, time: u64) {
self.inner
.hardforks
.insert(hardfork, ForkCondition::Timestamp(time));
}
}

// =============================================================================
// Trait Implementations
// =============================================================================
Expand Down
12 changes: 9 additions & 3 deletions crates/revm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use revm::{
EthFrame, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult, instructions::EthInstructions,
},
inspector::InspectorEvmTr,
interpreter::interpreter::EthInterpreter,
interpreter::{Instruction, interpreter::EthInterpreter},
};

/// The Morph EVM context type.
Expand Down Expand Up @@ -42,11 +42,17 @@ impl<DB: Database, I> MorphEvm<DB, I> {
// Get the current hardfork spec from context and create matching precompiles
let spec = ctx.cfg.spec;
let precompiles = MorphPrecompiles::new_with_spec(spec);

let mut instructions = EthInstructions::new_mainnet();
// SELFDESTRUCT is disabled in Morph
instructions.insert_instruction(0xff, Instruction::unknown());
// BLOBHASH is disabled in Morph
instructions.insert_instruction(0x49, Instruction::unknown());
// BLOBBASEFEE is disabled in Morph
instructions.insert_instruction(0x4a, Instruction::unknown());
Self::new_inner(Evm {
ctx,
inspector,
instruction: EthInstructions::new_mainnet(),
instruction: instructions,
precompiles,
frame_stack: FrameStack::new(),
})
Expand Down
2 changes: 1 addition & 1 deletion crates/revm/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use revm::{
};

/// Total gas system transactions are allowed to use.
const SYSTEM_CALL_GAS_LIMIT: u64 = 250_000_000;
const SYSTEM_CALL_GAS_LIMIT: u64 = 200_000;

impl<DB, I> ExecuteEvm for MorphEvm<DB, I>
where
Expand Down
107 changes: 51 additions & 56 deletions crates/revm/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Morph EVM Handler implementation.

use alloy_primitives::{Address, Bytes, U256};
use morph_primitives::L1_TX_TYPE_ID;
use revm::{
ExecuteEvm,
SystemCallEvm,
context::{
Cfg, ContextTr, JournalTr, Transaction,
result::{EVMError, ExecutionResult, InvalidTransaction},
Expand All @@ -15,7 +14,7 @@ use revm::{
};

use crate::{
MorphEvm, MorphInvalidTransaction, MorphTxEnv,
MorphEvm, MorphInvalidTransaction,
error::MorphHaltReason,
evm::MorphContext,
l1block::L1BlockInfo,
Expand Down Expand Up @@ -96,8 +95,20 @@ where
&self,
evm: &mut Self::Evm,
) -> Result<(), Self::Error> {
// L1 message transactions skip all validation - everything is handled on L1 side
if evm.ctx_ref().tx().is_l1_msg() {
let (_, tx, cfg, journal, _, _) = evm.ctx().all_mut();
// System transaction - skip all validation
if cfg.disable_fee_charge {
return Ok(());
}
// L1 message - skip fee validation
if tx.is_l1_msg() {
// Load caller's account
let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;

// Bump nonce for calls (CREATE nonce is bumped in make_create_frame)
if tx.kind().is_call() {
caller.bump_nonce();
}
return Ok(());
}

Expand All @@ -117,15 +128,17 @@ where
evm: &mut Self::Evm,
exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
) -> Result<(), Self::Error> {
// For L1 message transactions, no reimbursement is needed
if evm.ctx_ref().tx().is_l1_msg() {
let (_, tx, cfg, _, _, _) = evm.ctx().all_mut();

// For L1 message transactions & system transactions, no reimbursement is needed
if tx.is_l1_msg() || cfg.disable_fee_charge {
return Ok(());
}

// Check if transaction is AltFeeTx (tx_type 0x7F) which uses token fee
if evm.ctx_ref().tx().is_alt_fee_tx() {
if tx.is_alt_fee_tx() {
// Get fee_token_id directly from MorphTxEnv
let token_id = evm.ctx_ref().tx().fee_token_id.unwrap_or_default();
let token_id = tx.fee_token_id.unwrap_or_default();
return self.reimburse_caller_token_fee(evm, exec_result.gas(), token_id);
}

Expand All @@ -140,31 +153,31 @@ where
evm: &mut Self::Evm,
exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
) -> Result<(), Self::Error> {
// L1 message transactions skip all validation - everything is handled on L1 side
if evm.ctx_ref().tx().is_l1_msg() {
let (block, tx, cfg, journal, _, _) = evm.ctx().all_mut();
// System transaction - skip all reward
if cfg.disable_fee_charge {
return Ok(());
}
// L1 message transactions skip all reward.
// AltFeeTx rewards are already applied when gasFee is deducted.
if evm.ctx_ref().tx().is_alt_fee_tx() {
if tx.is_l1_msg() || tx.is_alt_fee_tx() {
return Ok(());
}

let beneficiary = evm.ctx_ref().block().beneficiary();
let beneficiary = block.beneficiary();

let basefee = evm.ctx_ref().block().basefee() as u128;
let effective_gas_price = evm.ctx_ref().tx().effective_gas_price(basefee);
let basefee = block.basefee() as u128;
let effective_gas_price = tx.effective_gas_price(basefee);

// Get the current hardfork for L1 fee calculation
let hardfork = evm.ctx_ref().cfg().spec();
let hardfork = cfg.spec();

// Fetch L1 block info from the L1 Gas Price Oracle contract
let l1_block_info = L1BlockInfo::try_fetch(evm.ctx_mut().db_mut(), hardfork)?;
let l1_block_info = L1BlockInfo::try_fetch(journal.db_mut(), hardfork)?;

// Get RLP-encoded transaction bytes for L1 fee calculation
// This represents the full transaction data posted to L1 for data availability
let rlp_bytes = evm
.ctx_ref()
.tx()
let rlp_bytes = tx
.rlp_bytes
.as_ref()
.map(|b| b.as_ref())
Expand All @@ -173,9 +186,6 @@ where
// Calculate L1 data fee based on full RLP-encoded transaction
let l1_data_fee = l1_block_info.calculate_tx_l1_cost(rlp_bytes, hardfork);

// Get mutable access to journal components
let journal = evm.ctx().journal_mut();

let gas_used = exec_result.gas().used();

let execution_fee = U256::from(effective_gas_price).saturating_mul(U256::from(gas_used));
Expand All @@ -190,8 +200,8 @@ where

#[inline]
fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
// For L1 message transactions, skip certain validations
if evm.ctx_ref().tx().is_l1_msg() {
// For L1 message transactions & System transaction, skip certain validations
if evm.ctx_ref().tx().is_l1_msg() || evm.ctx_ref().cfg().disable_fee_charge {
// L1 messages have zero gas price, so skip gas price validation
return Ok(());
}
Expand Down Expand Up @@ -462,16 +472,13 @@ where
}
} else {
// Transfer with evm call.
let tx_origin = evm.tx.clone();
transfer_erc20_with_evm(
evm,
token_fee_info.caller,
beneficiary,
token_fee_info.token_address,
token_amount_required,
)?;
// restore the original transaction
evm.tx = tx_origin;
}

let (_, tx, cfg, journal, _, _) = evm.ctx().all_mut();
Expand Down Expand Up @@ -530,9 +537,6 @@ where
Ok((from_storage_slot, to_storage_slot))
}

/// Gas limit for ERC20 transfer calls.
const TRANSFER_GAS_LIMIT: u64 = 200000;

/// Transfers ERC20 tokens by executing a `transfer(address,uint256)` call via the EVM.
fn transfer_erc20_with_evm<DB, I>(
evm: &mut MorphEvm<DB, I>,
Expand All @@ -544,39 +548,30 @@ fn transfer_erc20_with_evm<DB, I>(
where
DB: alloy_evm::Database,
{
let calldata = build_transfer_calldata(to, token_amount);
let tx_origin = evm.tx.clone();

let tx_env = revm::context::TxEnv {
caller,
gas_limit: TRANSFER_GAS_LIMIT,
kind: token_address.into(),
data: calldata,
tx_type: L1_TX_TYPE_ID, // Mark as L1 message to skip gas validation
..Default::default()
};

let tx = MorphTxEnv {
inner: tx_env,
rlp_bytes: None,
..Default::default()
};
match evm.transact_one(tx) {
let calldata = build_transfer_calldata(to, token_amount);
let res = match evm.system_call_one_with_caller(caller, token_address, calldata) {
Ok(result) => {
if !result.is_success() {
return Err(MorphInvalidTransaction::TokenTransferFailed {
if result.is_success() {
Ok(())
} else {
Err(MorphInvalidTransaction::TokenTransferFailed {
reason: format!("{result:?}"),
}
.into());
.into())
}
}
Err(e) => {
return Err(MorphInvalidTransaction::TokenTransferFailed {
reason: format!("Error: {e:?}"),
}
.into());
Err(e) => Err(MorphInvalidTransaction::TokenTransferFailed {
reason: format!("Error: {e:?}"),
}
.into()),
};
Ok(())

// restore the original transaction
evm.tx = tx_origin;

res
}

/// Build the calldata for ERC20 transfer(address,amount) call.
Expand Down
1 change: 1 addition & 0 deletions crates/revm/src/token_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ where
let morph_tx = crate::MorphTxEnv::new(tx);

// Execute using transact_one
evm.cfg.disable_fee_charge = true; // Disable fee charge for system call
match evm.transact_one(morph_tx) {
Ok(result) => {
if result.is_success() {
Expand Down
18 changes: 11 additions & 7 deletions crates/revm/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ impl MorphTxEnv {
// Extract fee_token_id for AltFeeTx (type 0x7F)
let fee_token_info = if tx_type == ALT_FEE_TX_TYPE_ID {
(
extract_fee_token_id_from_rlp(&rlp_bytes),
extract_fee_limit_from_rlp(&rlp_bytes),
Some(extract_fee_token_id_from_rlp(&rlp_bytes)),
Some(extract_fee_limit_from_rlp(&rlp_bytes)),
)
} else {
(0, U256::default())
(None, None)
};

// Build TxEnv from the transaction
Expand Down Expand Up @@ -131,10 +131,14 @@ impl MorphTxEnv {
};

// Use builder pattern to set Morph-specific fields
Self::new(inner)
.with_rlp_bytes(rlp_bytes)
.with_fee_token_id(fee_token_info.0)
.with_fee_limit(fee_token_info.1)
let mut env = Self::new(inner).with_rlp_bytes(rlp_bytes);
if let Some(fee_token_id) = fee_token_info.0 {
env = env.with_fee_token_id(fee_token_id);
};
if let Some(fee_limit) = fee_token_info.1 {
env = env.with_fee_limit(fee_limit);
};
env
}
}

Expand Down