diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 67a7576..3b70589 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -166,6 +166,14 @@ impl From for MorphChainSpec { } } +impl MorphChainSpec { + pub fn set_hardfork(&mut self, hardfork: MorphHardfork, time: u64) { + self.inner + .hardforks + .insert(hardfork, ForkCondition::Timestamp(time)); + } +} + // ============================================================================= // Trait Implementations // ============================================================================= diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 39a62ba..03658c4 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -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. @@ -42,11 +42,17 @@ impl MorphEvm { // 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(), }) diff --git a/crates/revm/src/exec.rs b/crates/revm/src/exec.rs index 6a9579e..c96c08b 100644 --- a/crates/revm/src/exec.rs +++ b/crates/revm/src/exec.rs @@ -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 ExecuteEvm for MorphEvm where diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index 4cb7e7f..955d21e 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -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}, @@ -15,7 +14,7 @@ use revm::{ }; use crate::{ - MorphEvm, MorphInvalidTransaction, MorphTxEnv, + MorphEvm, MorphInvalidTransaction, error::MorphHaltReason, evm::MorphContext, l1block::L1BlockInfo, @@ -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(()); } @@ -117,15 +128,17 @@ where evm: &mut Self::Evm, exec_result: &mut <::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); } @@ -140,31 +153,31 @@ where evm: &mut Self::Evm, exec_result: &mut <::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()) @@ -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)); @@ -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(()); } @@ -462,7 +472,6 @@ where } } else { // Transfer with evm call. - let tx_origin = evm.tx.clone(); transfer_erc20_with_evm( evm, token_fee_info.caller, @@ -470,8 +479,6 @@ where token_fee_info.token_address, token_amount_required, )?; - // restore the original transaction - evm.tx = tx_origin; } let (_, tx, cfg, journal, _, _) = evm.ctx().all_mut(); @@ -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( evm: &mut MorphEvm, @@ -544,39 +548,30 @@ fn transfer_erc20_with_evm( 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. diff --git a/crates/revm/src/token_fee.rs b/crates/revm/src/token_fee.rs index 193e69a..9ccd58c 100644 --- a/crates/revm/src/token_fee.rs +++ b/crates/revm/src/token_fee.rs @@ -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() { diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index cc5d235..5c49fc2 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -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 @@ -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 } }