diff --git a/crates/evm/src/evm.rs b/crates/evm/src/evm.rs index 0e4b52b..af467a5 100644 --- a/crates/evm/src/evm.rs +++ b/crates/evm/src/evm.rs @@ -1,6 +1,5 @@ use alloy_evm::{ Database, Evm, EvmEnv, EvmFactory, - precompiles::PrecompilesMap, revm::{ Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm, context::result::{EVMError, ResultAndState}, @@ -9,7 +8,9 @@ use alloy_evm::{ }; use alloy_primitives::{Address, Bytes, Log}; use morph_chainspec::hardfork::MorphHardfork; -use morph_revm::{MorphHaltReason, MorphInvalidTransaction, MorphTxEnv, evm::MorphContext}; +use morph_revm::{ + MorphHaltReason, MorphInvalidTransaction, MorphPrecompiles, MorphTxEnv, evm::MorphContext, +}; use reth_revm::MainContext; use std::ops::{Deref, DerefMut}; @@ -28,7 +29,7 @@ impl EvmFactory for MorphEvmFactory { type HaltReason = MorphHaltReason; type Spec = MorphHardfork; type BlockEnv = MorphBlockEnv; - type Precompiles = PrecompilesMap; + type Precompiles = MorphPrecompiles; fn create_evm( &self, @@ -142,7 +143,7 @@ where type HaltReason = MorphHaltReason; type Spec = MorphHardfork; type BlockEnv = MorphBlockEnv; - type Precompiles = PrecompilesMap; + type Precompiles = MorphPrecompiles; type Inspector = I; fn block(&self) -> &Self::BlockEnv { diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 72d0778..39a62ba 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -1,13 +1,12 @@ -use crate::{MorphBlockEnv, MorphTxEnv}; -use alloy_evm::{Database, precompiles::PrecompilesMap}; +use crate::{MorphBlockEnv, MorphTxEnv, precompiles::MorphPrecompiles}; +use alloy_evm::Database; use alloy_primitives::Log; use morph_chainspec::hardfork::MorphHardfork; use revm::{ Context, Inspector, context::{CfgEnv, ContextError, Evm, FrameStack}, handler::{ - EthFrame, EthPrecompiles, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult, - instructions::EthInstructions, + EthFrame, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult, instructions::EthInstructions, }, inspector::InspectorEvmTr, interpreter::interpreter::EthInterpreter, @@ -27,7 +26,7 @@ pub struct MorphEvm { MorphContext, I, EthInstructions>, - PrecompilesMap, + MorphPrecompiles, EthFrame, >, /// Preserved logs from the last transaction @@ -36,8 +35,13 @@ pub struct MorphEvm { impl MorphEvm { /// Create a new Morph EVM. + /// + /// The precompiles are automatically selected based on the hardfork spec + /// configured in the context's cfg. pub fn new(ctx: MorphContext, inspector: I) -> Self { - let precompiles = PrecompilesMap::from_static(EthPrecompiles::default().precompiles); + // Get the current hardfork spec from context and create matching precompiles + let spec = ctx.cfg.spec; + let precompiles = MorphPrecompiles::new_with_spec(spec); Self::new_inner(Evm { ctx, @@ -56,7 +60,7 @@ impl MorphEvm { MorphContext, I, EthInstructions>, - PrecompilesMap, + MorphPrecompiles, EthFrame, >, ) -> Self { @@ -74,7 +78,7 @@ impl MorphEvm { } /// Consumes self and returns a new Evm type with given Precompiles. - pub fn with_precompiles(self, precompiles: PrecompilesMap) -> Self { + pub fn with_precompiles(self, precompiles: MorphPrecompiles) -> Self { Self::new_inner(self.inner.with_precompiles(precompiles)) } @@ -96,7 +100,7 @@ where { type Context = MorphContext; type Instructions = EthInstructions>; - type Precompiles = PrecompilesMap; + type Precompiles = MorphPrecompiles; type Frame = EthFrame; fn all( diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index 5c25a37..4cb7e7f 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -324,8 +324,10 @@ where } // Fetch token fee info from Token Registry - let token_fee_info = TokenFeeInfo::try_fetch(evm.ctx_mut().db_mut(), token_id, caller)? - .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; + let spec = evm.ctx_ref().cfg().spec(); + let token_fee_info = + TokenFeeInfo::try_fetch(evm.ctx_mut().db_mut(), token_id, caller, spec)? + .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; // Check if token is active if !token_fee_info.is_active { @@ -380,19 +382,19 @@ where let caller_addr = tx.caller(); // Get coinbase address let beneficiary = block.beneficiary(); + // Get the current hardfork for L1 fee calculation + let hardfork = cfg.spec(); // Fetch token fee info from Token Registry - let token_fee_info = TokenFeeInfo::try_fetch(journal.db_mut(), token_id, caller_addr)? - .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; + let token_fee_info = + TokenFeeInfo::try_fetch(journal.db_mut(), token_id, caller_addr, hardfork)? + .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; // Check if token is active if !token_fee_info.is_active { return Err(MorphInvalidTransaction::TokenNotActive(token_id).into()); } - // Get the current hardfork for L1 fee calculation - let hardfork = cfg.spec(); - // Fetch L1 block info from the L1 Gas Price Oracle contract let l1_block_info = L1BlockInfo::try_fetch(journal.db_mut(), hardfork)?; diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 1cbd0fc..7a37e50 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -49,6 +49,7 @@ pub mod evm; pub mod exec; pub mod handler; pub mod l1block; +pub mod precompiles; pub mod token_fee; mod tx; @@ -56,5 +57,6 @@ pub use block::MorphBlockEnv; pub use error::{MorphHaltReason, MorphInvalidTransaction}; pub use evm::MorphEvm; pub use l1block::{L1_GAS_PRICE_ORACLE_ADDRESS, L1BlockInfo}; +pub use precompiles::MorphPrecompiles; pub use token_fee::{L2_TOKEN_REGISTRY_ADDRESS, TokenFeeInfo, get_erc20_balance_with_evm}; pub use tx::{MorphTxEnv, MorphTxExt}; diff --git a/crates/revm/src/precompiles.rs b/crates/revm/src/precompiles.rs new file mode 100644 index 0000000..074f07e --- /dev/null +++ b/crates/revm/src/precompiles.rs @@ -0,0 +1,349 @@ +//! Morph-specific precompile provider. +//! +//! This module provides Morph-specific precompile sets that match the Go implementation +//! at . +//! +//! ## Precompile Sets by Hardfork (Incremental Evolution) +//! +//! ```text +//! Berlin (base) +//! └── Bernoulli/Curie = Berlin - ripemd160 - blake2f +//! └── Morph203/Viridian = Bernoulli + blake2f + ripemd160 +//! └── Emerald = Morph203 + Osaka precompiles +//! ``` +//! +//! | Hardfork | Base | Added | Disabled | +//! |------------------|-----------|------------------------------------------------|-------------------| +//! | Bernoulli/Curie | Berlin | - | ripemd160/blake2f | +//! | Morph203/Viridian| Bernoulli | blake2f, ripemd160 | - | +//! | Emerald | Morph203 | Osaka (P256verify, BLS12-381, point eval, etc) | - | + +use alloy_primitives::Address; +use morph_chainspec::hardfork::MorphHardfork; +use revm::{ + context::Cfg, + context_interface::ContextTr, + handler::{EthPrecompiles, PrecompileProvider}, + interpreter::{CallInputs, InterpreterResult}, + precompile::Precompiles, + primitives::{OnceLock, hardfork::SpecId}, +}; +use std::boxed::Box; +use std::string::String; + +/// Standard precompile addresses +pub mod addresses { + use super::Address; + use revm::precompile::u64_to_address; + + /// ecrecover precompile address (1) + pub const ECRECOVER: Address = u64_to_address(1); + /// sha256 precompile address (2) + pub const SHA256: Address = u64_to_address(2); + /// ripemd160 precompile address (3) + pub const RIPEMD160: Address = u64_to_address(3); + /// identity/datacopy precompile address (4) + pub const IDENTITY: Address = u64_to_address(4); + /// modexp precompile address (5) + pub const MODEXP: Address = u64_to_address(5); + /// bn256Add precompile address (6) + pub const BN256_ADD: Address = u64_to_address(6); + /// bn256ScalarMul precompile address (7) + pub const BN256_MUL: Address = u64_to_address(7); + /// bn256Pairing precompile address (8) + pub const BN256_PAIRING: Address = u64_to_address(8); + /// blake2f precompile address (9) + pub const BLAKE2F: Address = u64_to_address(9); + /// point evaluation precompile address (10) - EIP-4844 + pub const POINT_EVALUATION: Address = u64_to_address(10); + /// P256verify precompile address (256) - RIP-7212 + pub const P256_VERIFY: Address = u64_to_address(256); +} + +/// Morph precompile provider. +/// +/// Implements Morph-specific precompile sets that match the Go implementation. +/// Each hardfork has specific precompiles enabled/disabled. +#[derive(Debug, Clone)] +pub struct MorphPrecompiles { + /// Inner Ethereum precompile provider. + inner: EthPrecompiles, + /// Current Morph hardfork. + spec: MorphHardfork, +} + +impl MorphPrecompiles { + /// Create a new precompile provider with the given Morph hardfork. + /// + /// Maps hardforks to their precompile sets based on the Go implementation: + /// + #[inline] + pub fn new_with_spec(spec: MorphHardfork) -> Self { + let precompiles = match spec { + // Bernoulli and Curie share the same precompile set + // Go implementation has no PrecompiledContractsCurie + MorphHardfork::Bernoulli | MorphHardfork::Curie => bernoulli(), + // Morph203 and Viridian share the same precompile set + MorphHardfork::Morph203 | MorphHardfork::Viridian => morph203(), + // Emerald: adds Osaka precompiles (P256verify, BLS12-381, etc) + MorphHardfork::Emerald | _ => emerald(), + }; + + Self { + inner: EthPrecompiles { + precompiles, + spec: SpecId::default(), + }, + spec, + } + } + + /// Returns the underlying precompiles. + #[inline] + pub fn precompiles(&self) -> &'static Precompiles { + self.inner.precompiles + } + + /// Returns whether the address is a precompile. + #[inline] + pub fn contains(&self, address: &Address) -> bool { + self.inner.contains(address) + } +} + +impl Default for MorphPrecompiles { + fn default() -> Self { + Self::new_with_spec(MorphHardfork::default()) + } +} + +/// Returns precompiles for Bernoulli hardfork. +/// +/// Based on Berlin but with ripemd160 and blake2f excluded. +/// Enabled: ecrecover, sha256, identity, modexp, bn256 ops +/// Disabled: ripemd160 (0x03), blake2f (0x09) +/// +/// Matches: +pub fn bernoulli() -> &'static Precompiles { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| { + let berlin = Precompiles::berlin(); + + // Create a set with only ripemd160 and blake2f to exclude + let mut to_exclude = Precompiles::default(); + if let Some(ripemd) = berlin.get(&addresses::RIPEMD160) { + to_exclude.extend([ripemd.clone()]); + } + if let Some(blake2f) = berlin.get(&addresses::BLAKE2F) { + to_exclude.extend([blake2f.clone()]); + } + + // Return berlin precompiles minus the excluded ones + berlin.difference(&to_exclude) + }) +} + +/// Returns precompiles for Morph203 hardfork. +/// +/// Based on Bernoulli with blake2f and ripemd160 re-enabled. +/// Enabled: ecrecover, sha256, ripemd160, identity, modexp, bn256 ops, blake2f +/// +/// Matches: PrecompiledContractsMorph203 in Go +pub fn morph203() -> &'static Precompiles { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| { + // Start from Bernoulli and add blake2f + ripemd160 + let mut precompiles = bernoulli().clone(); + + let berlin = Precompiles::berlin(); + // Add blake2f back (was disabled in Bernoulli) + if let Some(blake2f) = berlin.get(&addresses::BLAKE2F) { + precompiles.extend([blake2f.clone()]); + } + // Add ripemd160 back (was disabled in Bernoulli) + if let Some(ripemd) = berlin.get(&addresses::RIPEMD160) { + precompiles.extend([ripemd.clone()]); + } + + precompiles + }) +} + +/// Returns precompiles for Emerald hardfork. +/// +/// Based on Morph203/Viridian with Osaka precompiles added. +/// - All standard precompiles (ecrecover, sha256, ripemd160, identity, modexp, bn256 ops, blake2f) +/// - Osaka precompiles (P256verify RIP-7212, BLS12-381 EIP-2537, etc.) +/// +/// Matches: PrecompiledContractsEmerald in Go +pub fn emerald() -> &'static Precompiles { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| { + // Start from Morph203/Viridian + let mut precompiles = morph203().clone(); + + // Add Osaka precompiles (includes P256verify, BLS12-381, etc.) + let osaka = Precompiles::osaka(); + for addr in osaka.addresses() { + // Skip precompiles we already have + if !precompiles.contains(addr) + && let Some(precompile) = osaka.get(addr) + { + precompiles.extend([precompile.clone()]); + } + } + + precompiles + }) +} + +impl PrecompileProvider for MorphPrecompiles +where + CTX: ContextTr>, +{ + type Output = InterpreterResult; + + #[inline] + fn set_spec(&mut self, spec: ::Spec) -> bool { + if spec == self.spec { + return false; + } + *self = Self::new_with_spec(spec); + true + } + + #[inline] + fn run( + &mut self, + context: &mut CTX, + inputs: &CallInputs, + ) -> Result, String> { + self.inner.run(context, inputs) + } + + #[inline] + fn warm_addresses(&self) -> Box> { + self.inner.warm_addresses() + } + + #[inline] + fn contains(&self, address: &Address) -> bool { + Self::contains(self, address) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bernoulli_precompiles() { + let precompiles = bernoulli(); + + // Should have ecrecover, sha256, identity, modexp, bn256 ops + assert!(precompiles.contains(&addresses::ECRECOVER)); + assert!(precompiles.contains(&addresses::SHA256)); + assert!(precompiles.contains(&addresses::IDENTITY)); + assert!(precompiles.contains(&addresses::MODEXP)); + assert!(precompiles.contains(&addresses::BN256_ADD)); + + // ripemd160 and blake2f should NOT be present (disabled in Bernoulli) + // Matches Go: PrecompiledContractsBernoulli + assert!(!precompiles.contains(&addresses::RIPEMD160)); + assert!(!precompiles.contains(&addresses::BLAKE2F)); + } + + #[test] + fn test_curie_uses_bernoulli_precompiles() { + // Curie uses the same precompile set as Bernoulli + // Go implementation has no PrecompiledContractsCurie + let bernoulli_p = MorphPrecompiles::new_with_spec(MorphHardfork::Bernoulli); + let curie_p = MorphPrecompiles::new_with_spec(MorphHardfork::Curie); + + // Both should have the same precompiles + assert_eq!(bernoulli_p.precompiles().len(), curie_p.precompiles().len()); + + // Both should have sha256 enabled, ripemd160/blake2f disabled + assert!(curie_p.contains(&addresses::SHA256)); + assert!(!curie_p.contains(&addresses::RIPEMD160)); + assert!(!curie_p.contains(&addresses::BLAKE2F)); + } + + #[test] + fn test_morph203_precompiles() { + let precompiles = morph203(); + + // Should have blake2f and ripemd160 re-enabled + assert!(precompiles.contains(&addresses::BLAKE2F)); + assert!(precompiles.contains(&addresses::RIPEMD160)); + + // All standard precompiles + assert!(precompiles.contains(&addresses::ECRECOVER)); + assert!(precompiles.contains(&addresses::SHA256)); + + // P256verify not yet added in Morph203 + assert!(!precompiles.contains(&addresses::P256_VERIFY)); + } + + #[test] + fn test_emerald_precompiles() { + let precompiles = emerald(); + + // All standard precompiles should be enabled + assert!(precompiles.contains(&addresses::ECRECOVER)); + assert!(precompiles.contains(&addresses::SHA256)); + assert!(precompiles.contains(&addresses::RIPEMD160)); // Now enabled! + assert!(precompiles.contains(&addresses::IDENTITY)); + assert!(precompiles.contains(&addresses::MODEXP)); + assert!(precompiles.contains(&addresses::BN256_ADD)); + assert!(precompiles.contains(&addresses::BLAKE2F)); + + // P256verify should be present + assert!(precompiles.contains(&addresses::P256_VERIFY)); + } + + #[test] + fn test_precompile_counts_increase() { + let bernoulli_count = bernoulli().len(); + let morph203_count = morph203().len(); + let emerald_count = emerald().len(); + + // Morph203/Viridian should have more than Bernoulli (adds blake2f + ripemd160) + assert!(morph203_count > bernoulli_count); + + // Emerald should have more than Morph203 (adds Osaka precompiles) + assert!(emerald_count > morph203_count); + } + + #[test] + fn test_hardfork_specific_precompiles() { + // Verify that each hardfork has the expected precompile configuration + let bernoulli_p = MorphPrecompiles::new_with_spec(MorphHardfork::Bernoulli); + let curie_p = MorphPrecompiles::new_with_spec(MorphHardfork::Curie); + let morph203_p = MorphPrecompiles::new_with_spec(MorphHardfork::Morph203); + let viridian_p = MorphPrecompiles::new_with_spec(MorphHardfork::Viridian); + let emerald_p = MorphPrecompiles::new_with_spec(MorphHardfork::Emerald); + + // Bernoulli and Curie: no ripemd160, no blake2f (same precompile set) + assert!(!bernoulli_p.contains(&addresses::RIPEMD160)); + assert!(!bernoulli_p.contains(&addresses::BLAKE2F)); + assert!(!curie_p.contains(&addresses::RIPEMD160)); + assert!(!curie_p.contains(&addresses::BLAKE2F)); + + // Morph203 and Viridian: blake2f + ripemd160 enabled, no P256verify (same precompile set) + assert!(morph203_p.contains(&addresses::RIPEMD160)); + assert!(morph203_p.contains(&addresses::BLAKE2F)); + assert!(!morph203_p.contains(&addresses::P256_VERIFY)); + assert!(viridian_p.contains(&addresses::RIPEMD160)); + assert!(viridian_p.contains(&addresses::BLAKE2F)); + assert!(!viridian_p.contains(&addresses::P256_VERIFY)); + assert_eq!( + morph203_p.precompiles().len(), + viridian_p.precompiles().len() + ); + + // Emerald: all precompiles enabled including Osaka precompiles (P256verify, BLS12-381, etc) + assert!(emerald_p.contains(&addresses::RIPEMD160)); + assert!(emerald_p.contains(&addresses::P256_VERIFY)); + } +} diff --git a/crates/revm/src/token_fee.rs b/crates/revm/src/token_fee.rs index 6fd71c4..193e69a 100644 --- a/crates/revm/src/token_fee.rs +++ b/crates/revm/src/token_fee.rs @@ -59,6 +59,7 @@ impl TokenFeeInfo { db: &mut DB, token_id: u16, caller: Address, + spec: MorphHardfork, ) -> Result, DB::Error> { // Get the base slot for this token_id in tokenRegistry mapping let mut token_id_bytes = [0u8; 32]; @@ -116,7 +117,7 @@ impl TokenFeeInfo { // Get caller's token balance let caller_token_balance = - get_erc20_balance(db, token_address, caller, token_balance_slot)?; + get_erc20_balance(db, token_address, caller, token_balance_slot, spec)?; let token_fee = Self { token_address, @@ -207,6 +208,7 @@ pub fn get_erc20_balance( token: Address, account: Address, token_balance_slot: Option, + spec: MorphHardfork, ) -> Result { // If balance slot is provided, read directly from storage if let Some(slot) = token_balance_slot { @@ -222,10 +224,7 @@ pub fn get_erc20_balance( // - `NoOpInspector` satisfies the `Inspector` bound without adding side effects. let db: &mut dyn Database = db; - let mut evm = MorphEvm::new( - MorphContext::new(db, MorphHardfork::Curie), - NoOpInspector {}, - ); + let mut evm = MorphEvm::new(MorphContext::new(db, spec), NoOpInspector {}); match get_erc20_balance_with_evm(&mut evm, token, account) { Ok(balance) => Ok(balance),