From 01409d1c3b3895c85c34c0469819ce2e102371a5 Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:54:24 +0800 Subject: [PATCH 01/14] add ERC20FeeType --- bins/revme/src/cmd/statetest/runner.rs | 3 +- crates/precompile/Cargo.toml | 6 + crates/primitives/src/env.rs | 5 + crates/revm/Cargo.toml | 2 +- crates/revm/src/context/evm_context.rs | 4 + crates/revm/src/context/inner_evm_context.rs | 13 ++ crates/revm/src/morph.rs | 2 + crates/revm/src/morph/erc20_fee.rs | 121 +++++++++++++++++++ crates/revm/src/morph/handler_register.rs | 57 +++++++++ 9 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 crates/revm/src/morph/erc20_fee.rs diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index 80ab935d5d..a0e9a6b609 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -274,8 +274,7 @@ pub fn execute_test_suite( let bytecode = to_analysed(Bytecode::new_raw(info.code)); let acc_info = revm::primitives::AccountInfo { balance: info.balance, - #[cfg(feature = "morph")] - code_size, + code_size: 0, code_hash: keccak_code_hash, #[cfg(feature = "morph-poseidon-codehash")] poseidon_code_hash, diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 761e5f6126..d111ac94c3 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -71,6 +71,12 @@ rstest = "0.22.0" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +secp256k1 = { version = ">=0.28, <=0.29", default-features = false, features = [ + "alloc", + "recovery", + "rand", + "global-context", +] } [features] default = ["std", "kzg-rs", "portable"] diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 13be6a974d..ccfb83f5d7 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -635,6 +635,10 @@ pub struct TxEnv { #[cfg(feature = "morph")] /// Morph fields pub morph: MorphFields, + + #[cfg(feature = "morph")] + /// For ERC20FeeType + pub fee_token_id: Option, } pub enum TxType { @@ -680,6 +684,7 @@ impl Default for TxEnv { optimism: OptimismFields::default(), #[cfg(feature = "morph")] morph: MorphFields::default(), + fee_token_id: None, } } } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 2357d3f507..bec3517ba9 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -67,7 +67,7 @@ rstest = "0.22.0" alloy-provider = "0.3" [features] -default = ["std", "kzg-rs", "portable"] +default = ["std", "kzg-rs", "portable", "morph-default-handler"] std = [ "serde?/std", "serde_json?/std", diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 6b651758d1..3acee3ab1c 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -530,6 +530,8 @@ pub(crate) mod test_utils { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + erc20_fee_info: None, }, precompiles: ContextPrecompiles::default(), } @@ -545,6 +547,8 @@ pub(crate) mod test_utils { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + erc20_fee_info: None, }, precompiles: ContextPrecompiles::default(), } diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index ab86a9ee1f..9002595465 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "morph")] +use crate::morph::Erc20FeeInfo; use crate::{ db::Database, interpreter::{ @@ -33,6 +35,9 @@ pub struct InnerEvmContext { /// Used as temporary value holder to store L1 block info. #[cfg(feature = "morph")] pub l1_block_info: Option, + /// Used as temporary value holder to store Erc20 fee info. + #[cfg(feature = "morph")] + pub erc20_fee_info: Option, } impl Clone for InnerEvmContext @@ -47,6 +52,8 @@ where error: self.error.clone(), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: self.l1_block_info.clone(), + #[cfg(feature = "morph")] + erc20_fee_info: self.erc20_fee_info.clone(), } } } @@ -60,6 +67,8 @@ impl InnerEvmContext { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + erc20_fee_info: None, } } @@ -73,6 +82,8 @@ impl InnerEvmContext { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, + #[cfg(feature = "morph")] + erc20_fee_info: None, } } @@ -88,6 +99,8 @@ impl InnerEvmContext { error: Ok(()), #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: self.l1_block_info, + #[cfg(feature = "morph")] + erc20_fee_info: self.erc20_fee_info, } } diff --git a/crates/revm/src/morph.rs b/crates/revm/src/morph.rs index 2097c88845..6285100b3a 100644 --- a/crates/revm/src/morph.rs +++ b/crates/revm/src/morph.rs @@ -1,6 +1,8 @@ +mod erc20_fee; mod handler_register; mod l1block; +pub use crate::morph::erc20_fee::Erc20FeeInfo; pub use crate::morph::handler_register::{ deduct_caller, load_accounts, morph_handle_register, reward_beneficiary, }; diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs new file mode 100644 index 0000000000..e2aee8be71 --- /dev/null +++ b/crates/revm/src/morph/erc20_fee.rs @@ -0,0 +1,121 @@ +use crate::primitives::{Bytes, TxEnv, TxKind}; +use core::ops::Add; + +use crate::morph::L1_GAS_PRICE_ORACLE_ADDRESS; +use crate::primitives::{address, Address, SpecId, U256}; +use crate::{Database, Evm}; + +// TokenAddressMappingSlot is the storage slot for mapping(uint16 => address) +const TOKEN_ADDRESS_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +// TokenPriceMappingSlot is the storage slot for mapping(uint16 => uint256) +const TOKEN_PRICE_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +// TokenBalanceSlotMappingSlot is the storage slot for mapping(uint16 => bytes32) +const TOKEN_BALANCE_SLOT_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +// System address for receiving ERC20 fees +pub const L2_FEE_VAULT: Address = + address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); + +#[derive(Clone, Debug, Default)] +pub struct Erc20FeeInfo { + /// The ERC20 token address + pub token_address: Address, + /// The price of the token + pub price: U256, + /// The caller address + pub caller: Address, + /// The token balance of caller + pub balance: U256, +} + +impl Erc20FeeInfo { + // Get the token information for gas payment from the state db. + pub(super) fn try_fetch( + db: &mut DB, + token_id: u16, + caller: Address, + ) -> Result, DB::Error> { + // get token address of token_id + let storage_value = load_mapping_value( + db, + L1_GAS_PRICE_ORACLE_ADDRESS, + TOKEN_ADDRESS_MAPPING_SLOT, + token_id.to_be_bytes().to_vec(), + )?; + let token_address: Address = Address::from_word(storage_value.into()); + if token_address.is_zero() { + return Ok(None); + } + + // get token price of token_id + let token_price = load_mapping_value( + db, + L1_GAS_PRICE_ORACLE_ADDRESS, + TOKEN_PRICE_MAPPING_SLOT, + token_id.to_be_bytes().to_vec(), + )?; + if token_price.is_zero() { + return Ok(None); + } + + // get token balance of token_id + let token_balance_slot = load_mapping_value( + db, + L1_GAS_PRICE_ORACLE_ADDRESS, + TOKEN_BALANCE_SLOT_MAPPING_SLOT, + token_id.to_be_bytes().to_vec(), + )?; + + let caller_token_balance = + load_mapping_value(db, token_address, token_balance_slot, caller.to_vec())?; + let ecc20_fee = Erc20FeeInfo { + token_address, + price: token_price, + caller, + balance: caller_token_balance, + }; + + Ok(Some(ecc20_fee)) + } +} + +fn load_mapping_value( + db: &mut DB, + account: Address, + slot_index: U256, + mut key: Vec, +) -> Result::Error> { + let mut pre_image = slot_index.to_be_bytes_vec(); + pre_image.append(&mut key); + let storage_key = crate::primitives::keccak256(pre_image); + let storage_value = db.storage(account, U256::from_be_bytes(storage_key.0))?; + Ok(storage_value) +} + +pub(super) fn transfer_erc20( + db: &mut DB, + token: Address, + from: Address, + to: Address, + amoumt: U256, + balance_slot: U256, +) { + + // Prepare calldata of erc20 transfer + let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; // transfer(address,uint256) + + if balance_slot.is_zero() { + let mut evm = Evm::builder().with_db(db).build(); + let tx = TxEnv { + caller: Address::default(), + gas_limit: u64::MAX, + transact_to: token.into(), + value: U256::from(1_000u64), + data: Bytes::new(), + nonce: None, + chain_id: None, + ..Default::default() + }; + evm.context.evm.env.tx = tx; + let _ = evm.transact(); + } +} diff --git a/crates/revm/src/morph/handler_register.rs b/crates/revm/src/morph/handler_register.rs index b4b38c0847..7eee597a2f 100644 --- a/crates/revm/src/morph/handler_register.rs +++ b/crates/revm/src/morph/handler_register.rs @@ -2,6 +2,9 @@ use crate::handler::mainnet; use crate::handler::mainnet::deduct_caller_inner; +use crate::morph::erc20_fee::{transfer_erc20, L2_FEE_VAULT}; +use crate::morph::Erc20FeeInfo; +use crate::primitives::{Account, Env, TxKind}; use crate::{ handler::register::EvmHandler, interpreter::Gas, @@ -35,6 +38,15 @@ pub fn load_accounts( .map_err(EVMError::Database)?; context.evm.inner.l1_block_info = Some(l1_block_info); + let fee_token_id = context.evm.env.tx.fee_token_id.unwrap_or_default(); + if fee_token_id != 0 { + let caller = context.evm.env.tx.caller; + let erc20_fee_info = + crate::morph::Erc20FeeInfo::try_fetch(&mut context.evm.inner.db, fee_token_id, caller) + .map_err(EVMError::Database)?; + context.evm.inner.erc20_fee_info = erc20_fee_info; + } + mainnet::load_accounts::(context) } @@ -55,6 +67,15 @@ pub fn deduct_caller( // l1 cost, max values is already checked in pre_validate but l1 cost wasn't. deduct_caller_inner::(caller_account.data, &context.evm.inner.env); + if let Some(ref erc20_fee_info) = context.evm.inner.erc20_fee_info { + deduct_caller_use_erc20( + &mut context.evm.inner.db, + erc20_fee_info, + caller_account.data, + &context.evm.inner.env, + ); + } + let Some(rlp_bytes) = &context.evm.inner.env.tx.morph.rlp_bytes else { return Err(EVMError::Custom( "[MORPH] Failed to load transaction rlp_bytes.".to_string(), @@ -91,6 +112,42 @@ pub fn deduct_caller( Ok(()) } +/// Helper function that deducts the caller balance. +#[inline] +fn deduct_caller_use_erc20( + db: &mut DB, + erc20_fee_info: &Erc20FeeInfo, + caller_account: &mut Account, + env: &Env, +) { + // Subtract gas costs from the caller's account. + // We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled. + let mut gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price()); + + //calculate fee for erc20. + + transfer_erc20( + db, + erc20_fee_info.token_address, + erc20_fee_info.caller, + L2_FEE_VAULT, + U256::default(), + U256::default(), + ); + + // set new caller account balance. + caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost); + + // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. + if matches!(env.tx.transact_to, TxKind::Call(_)) { + // Nonce is already checked + caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); + } + + // touch account so we know it is changed. + caller_account.mark_touch(); +} + /// Reward beneficiary with gas fee. #[inline] pub fn reward_beneficiary( From 5df33b4d3518a04c44e39d0ce3a374ee754a8c93 Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:41:20 +0800 Subject: [PATCH 02/14] transfer erc20 fee --- crates/revm/src/morph/erc20_fee.rs | 113 +++++++++++++++++----- crates/revm/src/morph/handler_register.rs | 56 +++++------ 2 files changed, 112 insertions(+), 57 deletions(-) diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs index e2aee8be71..8c6fab9050 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/erc20_fee.rs @@ -1,8 +1,7 @@ use crate::primitives::{Bytes, TxEnv, TxKind}; -use core::ops::Add; use crate::morph::L1_GAS_PRICE_ORACLE_ADDRESS; -use crate::primitives::{address, Address, SpecId, U256}; +use crate::primitives::{address, Address, U256}; use crate::{Database, Evm}; // TokenAddressMappingSlot is the storage slot for mapping(uint16 => address) @@ -12,8 +11,7 @@ const TOKEN_PRICE_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); // TokenBalanceSlotMappingSlot is the storage slot for mapping(uint16 => bytes32) const TOKEN_BALANCE_SLOT_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); // System address for receiving ERC20 fees -pub const L2_FEE_VAULT: Address = - address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); +pub(super) const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); #[derive(Clone, Debug, Default)] pub struct Erc20FeeInfo { @@ -25,6 +23,8 @@ pub struct Erc20FeeInfo { pub caller: Address, /// The token balance of caller pub balance: U256, + /// The users' erc20 balance slot + pub balance_slot: U256, } impl Erc20FeeInfo { @@ -65,13 +65,15 @@ impl Erc20FeeInfo { token_id.to_be_bytes().to_vec(), )?; - let caller_token_balance = - load_mapping_value(db, token_address, token_balance_slot, caller.to_vec())?; + // get caller's token balance + let caller_token_balance = get_erc20_balance(db, token_address, caller, token_balance_slot); + let ecc20_fee = Erc20FeeInfo { token_address, price: token_price, caller, balance: caller_token_balance, + balance_slot: token_balance_slot, }; Ok(Some(ecc20_fee)) @@ -91,31 +93,90 @@ fn load_mapping_value( Ok(storage_value) } +pub(super) fn get_erc20_balance( + db: &mut DB, + token: Address, + account: Address, + token_balance_slot: U256, +) -> U256 { + // If balance slot is provided, try to read directly from storage + if !token_balance_slot.is_zero() { + if let Ok(balance) = load_mapping_value(db, token, token_balance_slot, account.to_vec()) { + return balance; + } + } + + // Fallback: call balanceOf(address) method + // Method signature: balanceOf(address) -> 0x70a08231 + let method_id = [0x70u8, 0xa0, 0x82, 0x31]; + + // Encode calldata: method_id + padded address + let mut calldata = Vec::with_capacity(36); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes + calldata.extend_from_slice(account.as_slice()); + + // Create EVM instance and execute the call + let mut evm = Evm::builder().with_db(db).build(); + let tx = TxEnv { + caller: Address::default(), + gas_limit: u64::MAX, + transact_to: TxKind::Call(token), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + evm.context.evm.env.tx = tx; + + // Execute transaction and extract balance from output + match evm.transact() { + Ok(result) => { + if result.result.is_success() { + // Parse the returned balance (32 bytes) + if let Some(output) = result.result.output() { + if output.len() >= 32 { + return U256::from_be_slice(&output[..32]); + } + } + } + U256::ZERO + } + Err(_) => U256::ZERO, + } +} + pub(super) fn transfer_erc20( db: &mut DB, token: Address, from: Address, to: Address, - amoumt: U256, - balance_slot: U256, + amount: U256, ) { + // Call transfer(address,uint256) method via EVM + // Method signature: transfer(address,uint256) -> 0xa9059cbb + let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // Encode calldata: method_id + padded to address + amount + let mut calldata = Vec::with_capacity(68); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes + calldata.extend_from_slice(to.as_slice()); + calldata.extend_from_slice(&amount.to_be_bytes::<32>()); - // Prepare calldata of erc20 transfer - let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; // transfer(address,uint256) - - if balance_slot.is_zero() { - let mut evm = Evm::builder().with_db(db).build(); - let tx = TxEnv { - caller: Address::default(), - gas_limit: u64::MAX, - transact_to: token.into(), - value: U256::from(1_000u64), - data: Bytes::new(), - nonce: None, - chain_id: None, - ..Default::default() - }; - evm.context.evm.env.tx = tx; - let _ = evm.transact(); - } + // Create EVM instance and execute the call + let mut evm = Evm::builder().with_db(db).build(); + let tx = TxEnv { + caller: from, + gas_limit: u64::MAX, + transact_to: TxKind::Call(token), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + evm.context.evm.env.tx = tx; + let _ = evm.transact(); } diff --git a/crates/revm/src/morph/handler_register.rs b/crates/revm/src/morph/handler_register.rs index 7eee597a2f..55c5c82239 100644 --- a/crates/revm/src/morph/handler_register.rs +++ b/crates/revm/src/morph/handler_register.rs @@ -63,19 +63,6 @@ pub fn deduct_caller( .load_account(context.evm.inner.env.tx.caller, &mut context.evm.inner.db)?; if !context.evm.inner.env.tx.morph.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. - deduct_caller_inner::(caller_account.data, &context.evm.inner.env); - - if let Some(ref erc20_fee_info) = context.evm.inner.erc20_fee_info { - deduct_caller_use_erc20( - &mut context.evm.inner.db, - erc20_fee_info, - caller_account.data, - &context.evm.inner.env, - ); - } - let Some(rlp_bytes) = &context.evm.inner.env.tx.morph.rlp_bytes else { return Err(EVMError::Custom( "[MORPH] Failed to load transaction rlp_bytes.".to_string(), @@ -89,16 +76,30 @@ pub fn deduct_caller( .as_ref() .expect("L1BlockInfo should be loaded") .calculate_tx_l1_cost(rlp_bytes, SPEC::SPEC_ID); - if tx_l1_cost.gt(&caller_account.info.balance) { - return Err(EVMError::Transaction( - InvalidTransaction::LackOfFundForMaxFee { - fee: tx_l1_cost.into(), - balance: caller_account.info.balance.into(), - }, - )); + + if let Some(ref erc20_fee_info) = context.evm.inner.erc20_fee_info { + deduct_caller_use_erc20( + &mut context.evm.inner.db, + erc20_fee_info, + caller_account.data, + &context.evm.inner.env, + ); + } else { + // 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. + deduct_caller_inner::(caller_account.data, &context.evm.inner.env); + + if tx_l1_cost.gt(&caller_account.info.balance) { + return Err(EVMError::Transaction( + InvalidTransaction::LackOfFundForMaxFee { + fee: tx_l1_cost.into(), + balance: caller_account.info.balance.into(), + }, + )); + } + caller_account.data.info.balance = + caller_account.data.info.balance.saturating_sub(tx_l1_cost); } - caller_account.data.info.balance = - caller_account.data.info.balance.saturating_sub(tx_l1_cost); } else { // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. if matches!(context.evm.inner.env.tx.transact_to, TransactTo::Call(_)) { @@ -122,22 +123,15 @@ fn deduct_caller_use_erc20( ) { // Subtract gas costs from the caller's account. // We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled. - let mut gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price()); - - //calculate fee for erc20. - + let gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price()); transfer_erc20( db, erc20_fee_info.token_address, erc20_fee_info.caller, L2_FEE_VAULT, - U256::default(), - U256::default(), + gas_cost, ); - // set new caller account balance. - caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost); - // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. if matches!(env.tx.transact_to, TxKind::Call(_)) { // Nonce is already checked From d75a947bc9cb53c498063c0ec563b07df89d100b Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Fri, 31 Oct 2025 23:56:24 +0800 Subject: [PATCH 03/14] deduct_caller_with_erc20 --- bins/revm-test/Cargo.toml | 2 +- bins/revm-test/src/bin/morph.rs | 39 ++++++++ crates/primitives/src/env.rs | 8 +- crates/revm/src/evm.rs | 96 +++++++++++++++++-- crates/revm/src/handler/mainnet/validation.rs | 7 +- crates/revm/src/morph.rs | 2 +- crates/revm/src/morph/erc20_fee.rs | 47 ++------- crates/revm/src/morph/handler_register.rs | 73 +++----------- 8 files changed, 161 insertions(+), 113 deletions(-) create mode 100644 bins/revm-test/src/bin/morph.rs diff --git a/bins/revm-test/Cargo.toml b/bins/revm-test/Cargo.toml index 7540a00d6d..6f90e8099c 100644 --- a/bins/revm-test/Cargo.toml +++ b/bins/revm-test/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] bytes = "1.7" hex = "0.4" -revm = { path = "../../crates/revm", version = "14.0.0", default-features=false } +revm = { path = "../../crates/revm", version = "14.0.0", features=["morph-default-handler"] } microbench = "0.5" alloy-sol-macro = "0.8.0" alloy-sol-types = "0.8.0" diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs new file mode 100644 index 0000000000..3498987d5e --- /dev/null +++ b/bins/revm-test/src/bin/morph.rs @@ -0,0 +1,39 @@ +use revm::{ + db::{CacheDB, EmptyDB}, + primitives::{address, keccak256, AccountInfo, Bytes, TxEnv, U256}, + Evm, +}; + +fn main() { + let mut cache_db = CacheDB::new(EmptyDB::default()); + + let account = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + let acc_info = AccountInfo { + nonce: 0_u64, + balance: U256::from(1_000_000_000_000_000_000u128), + code_hash: keccak256(Bytes::new()), + code: None, + code_size: 0, + }; + cache_db.insert_account_info(account, acc_info.clone()); + let mut evm = Evm::builder().with_db(cache_db).build(); + + // use erc20 gas token. + let tx = TxEnv { + caller: account, + gas_limit: 21000u64, + transact_to: account_to.into(), + value: U256::from(1_000u64), + data: Bytes::new(), + nonce: None, + chain_id: None, + fee_token_id: Some(1u16), + ..Default::default() + }; + + // process txs in block + evm.context.evm.env.tx = tx; + let _ = evm.transact_commit(); +} diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index ccfb83f5d7..ac4676052c 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -241,6 +241,7 @@ impl Env { pub fn validate_tx_against_state( &self, account: &mut Account, + erc20_balance: U256, ) -> Result<(), InvalidTransaction> { // EIP-3607: Reject transactions from senders with deployed code // This EIP is introduced after london but there was no collision in past @@ -283,7 +284,12 @@ impl Env { // Check if account has enough balance for gas_limit*gas_price and value transfer. // Transfer will be done inside `*_inner` functions. - if balance_check > account.info.balance { + let lack_of_fund_for_max_fee = if self.tx.fee_token_id.unwrap_or_default() != 0 { + balance_check > erc20_balance + } else { + balance_check > account.info.balance + }; + if lack_of_fund_for_max_fee { cfg_if::cfg_if! { if #[cfg(not(feature = "morph"))] { if self.cfg.is_balance_check_disabled() { diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 55781adddd..be88ed0110 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -5,9 +5,11 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, + morph::erc20_fee::L2_FEE_VAULT, primitives::{ - specification::SpecId, BlockEnv, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, - ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, EOF_MAGIC_BYTES, + specification::SpecId, Address, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, + EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, + EOF_MAGIC_BYTES, U256, }, Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; @@ -323,21 +325,39 @@ impl Evm<'_, EXT, DB> { /// Transact pre-verified transaction. fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult { let spec_id = self.spec_id(); - let ctx = &mut self.context; - let pre_exec = self.handler.pre_execution(); - // load access list and beneficiary if needed. - pre_exec.load_accounts(ctx)?; + { + let ctx = &mut self.context; + let pre_exec = self.handler.pre_execution(); + // load access list and beneficiary if needed. + pre_exec.load_accounts(ctx)?; - // load precompiles - let precompiles = pre_exec.load_precompiles(); - ctx.evm.set_precompiles(precompiles); + // load precompiles + let precompiles = pre_exec.load_precompiles(); + ctx.evm.set_precompiles(precompiles); + } // deduce caller balance with its limit. - pre_exec.deduct_caller(ctx)?; + { + let ctx = &mut self.context; + let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + if let Some(token_fee) = erc20_fee_info { + self.deduct_caller_with_erc20( + token_fee.token_address, + token_fee.caller, + L2_FEE_VAULT, + )?; + } else { + self.handler.pre_execution().deduct_caller(ctx)?; + } + } + + let ctx = &mut self.context; let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend; + let pre_exec = self.handler.pre_execution(); + // apply EIP-7702 auth list. let eip7702_gas_refund = pre_exec.apply_eip7702_auth_list(ctx)? as i64; @@ -389,6 +409,62 @@ impl Evm<'_, EXT, DB> { // Returns output of transaction. post_exec.output(ctx, result) } + + #[inline] + pub fn deduct_caller_with_erc20( + &mut self, + token: Address, + from: Address, + to: Address, + ) -> Result<(), EVMError> { + let ctx = &self.context; + let Some(rlp_bytes) = &ctx.evm.inner.env.tx.morph.rlp_bytes else { + return Err(EVMError::Custom( + "[MORPH] Failed to load transaction rlp_bytes.".to_string(), + )); + }; + + let tx_l1_cost = self + .context + .evm + .inner + .l1_block_info + .as_ref() + .expect("L1BlockInfo should be loaded") + .calculate_tx_l1_cost(rlp_bytes, self.spec_id()); + let gas_cost = + U256::from(ctx.evm.env.tx.gas_limit).saturating_mul(ctx.evm.env.effective_gas_price()); + let amount = tx_l1_cost + gas_cost; + + // Call transfer(address,uint256) method via EVM + // Method signature: transfer(address,uint256) -> 0xa9059cbb + let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // Encode calldata: method_id + padded to address + amount + let mut calldata = Vec::with_capacity(68); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes + calldata.extend_from_slice(to.as_slice()); + calldata.extend_from_slice(&amount.to_be_bytes::<32>()); + let tx = TxEnv { + caller: from, + gas_limit: u64::MAX, + gas_price: U256::ZERO, + transact_to: TxKind::Call(token), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + let origin_tx = self.context.evm.inner.env.tx.clone(); + self.context.evm.inner.env.tx = tx; + + let _ = self.transact_preverified_inner(0); + self.context.evm.inner.env.tx = origin_tx; + + Ok(()) + } } #[cfg(test)] diff --git a/crates/revm/src/handler/mainnet/validation.rs b/crates/revm/src/handler/mainnet/validation.rs index 8aa4c86c23..f533123f38 100644 --- a/crates/revm/src/handler/mainnet/validation.rs +++ b/crates/revm/src/handler/mainnet/validation.rs @@ -1,3 +1,4 @@ +use crate::primitives::U256; use revm_interpreter::gas; use crate::{ @@ -25,11 +26,15 @@ pub fn validate_tx_against_state( .journaled_state .load_code(tx_caller, &mut context.evm.inner.db)?; + let erc20_balance = match &context.evm.inner.erc20_fee_info { + Some(fee_info) => fee_info.balance, + None => U256::ZERO, + }; context .evm .inner .env - .validate_tx_against_state::(caller_account.data) + .validate_tx_against_state::(caller_account.data, erc20_balance) .map_err(EVMError::Transaction)?; Ok(()) diff --git a/crates/revm/src/morph.rs b/crates/revm/src/morph.rs index 6285100b3a..c081eed862 100644 --- a/crates/revm/src/morph.rs +++ b/crates/revm/src/morph.rs @@ -1,4 +1,4 @@ -mod erc20_fee; +pub mod erc20_fee; mod handler_register; mod l1block; diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs index 8c6fab9050..4bc5fb3f12 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/erc20_fee.rs @@ -1,7 +1,6 @@ -use crate::primitives::{Bytes, TxEnv, TxKind}; - use crate::morph::L1_GAS_PRICE_ORACLE_ADDRESS; use crate::primitives::{address, Address, U256}; +use crate::primitives::{Bytes, TxEnv, TxKind}; use crate::{Database, Evm}; // TokenAddressMappingSlot is the storage slot for mapping(uint16 => address) @@ -11,7 +10,7 @@ const TOKEN_PRICE_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); // TokenBalanceSlotMappingSlot is the storage slot for mapping(uint16 => bytes32) const TOKEN_BALANCE_SLOT_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); // System address for receiving ERC20 fees -pub(super) const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); +pub const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); #[derive(Clone, Debug, Default)] pub struct Erc20FeeInfo { @@ -29,7 +28,7 @@ pub struct Erc20FeeInfo { impl Erc20FeeInfo { // Get the token information for gas payment from the state db. - pub(super) fn try_fetch( + pub fn try_fetch( db: &mut DB, token_id: u16, caller: Address, @@ -109,14 +108,14 @@ pub(super) fn get_erc20_balance( // Fallback: call balanceOf(address) method // Method signature: balanceOf(address) -> 0x70a08231 let method_id = [0x70u8, 0xa0, 0x82, 0x31]; - + // Encode calldata: method_id + padded address let mut calldata = Vec::with_capacity(36); calldata.extend_from_slice(&method_id); calldata.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes calldata.extend_from_slice(account.as_slice()); - // Create EVM instance and execute the call + let db: &mut dyn Database = db; let mut evm = Evm::builder().with_db(db).build(); let tx = TxEnv { caller: Address::default(), @@ -129,7 +128,7 @@ pub(super) fn get_erc20_balance( ..Default::default() }; evm.context.evm.env.tx = tx; - + // Execute transaction and extract balance from output match evm.transact() { Ok(result) => { @@ -146,37 +145,3 @@ pub(super) fn get_erc20_balance( Err(_) => U256::ZERO, } } - -pub(super) fn transfer_erc20( - db: &mut DB, - token: Address, - from: Address, - to: Address, - amount: U256, -) { - // Call transfer(address,uint256) method via EVM - // Method signature: transfer(address,uint256) -> 0xa9059cbb - let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; - - // Encode calldata: method_id + padded to address + amount - let mut calldata = Vec::with_capacity(68); - calldata.extend_from_slice(&method_id); - calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - calldata.extend_from_slice(to.as_slice()); - calldata.extend_from_slice(&amount.to_be_bytes::<32>()); - - // Create EVM instance and execute the call - let mut evm = Evm::builder().with_db(db).build(); - let tx = TxEnv { - caller: from, - gas_limit: u64::MAX, - transact_to: TxKind::Call(token), - value: U256::ZERO, - data: Bytes::from(calldata), - nonce: None, - chain_id: None, - ..Default::default() - }; - evm.context.evm.env.tx = tx; - let _ = evm.transact(); -} diff --git a/crates/revm/src/morph/handler_register.rs b/crates/revm/src/morph/handler_register.rs index 55c5c82239..669716af80 100644 --- a/crates/revm/src/morph/handler_register.rs +++ b/crates/revm/src/morph/handler_register.rs @@ -1,10 +1,6 @@ //! Handler related to Morph chain - use crate::handler::mainnet; use crate::handler::mainnet::deduct_caller_inner; -use crate::morph::erc20_fee::{transfer_erc20, L2_FEE_VAULT}; -use crate::morph::Erc20FeeInfo; -use crate::primitives::{Account, Env, TxKind}; use crate::{ handler::register::EvmHandler, interpreter::Gas, @@ -38,9 +34,9 @@ pub fn load_accounts( .map_err(EVMError::Database)?; context.evm.inner.l1_block_info = Some(l1_block_info); - let fee_token_id = context.evm.env.tx.fee_token_id.unwrap_or_default(); + let fee_token_id = context.evm.inner.env().tx.fee_token_id.unwrap_or_default(); if fee_token_id != 0 { - let caller = context.evm.env.tx.caller; + let caller = context.evm.inner.env.tx.caller; let erc20_fee_info = crate::morph::Erc20FeeInfo::try_fetch(&mut context.evm.inner.db, fee_token_id, caller) .map_err(EVMError::Database)?; @@ -63,6 +59,10 @@ pub fn deduct_caller( .load_account(context.evm.inner.env.tx.caller, &mut context.evm.inner.db)?; if !context.evm.inner.env.tx.morph.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. + deduct_caller_inner::(caller_account.data, &context.evm.inner.env); + let Some(rlp_bytes) = &context.evm.inner.env.tx.morph.rlp_bytes else { return Err(EVMError::Custom( "[MORPH] Failed to load transaction rlp_bytes.".to_string(), @@ -76,30 +76,16 @@ pub fn deduct_caller( .as_ref() .expect("L1BlockInfo should be loaded") .calculate_tx_l1_cost(rlp_bytes, SPEC::SPEC_ID); - - if let Some(ref erc20_fee_info) = context.evm.inner.erc20_fee_info { - deduct_caller_use_erc20( - &mut context.evm.inner.db, - erc20_fee_info, - caller_account.data, - &context.evm.inner.env, - ); - } else { - // 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. - deduct_caller_inner::(caller_account.data, &context.evm.inner.env); - - if tx_l1_cost.gt(&caller_account.info.balance) { - return Err(EVMError::Transaction( - InvalidTransaction::LackOfFundForMaxFee { - fee: tx_l1_cost.into(), - balance: caller_account.info.balance.into(), - }, - )); - } - caller_account.data.info.balance = - caller_account.data.info.balance.saturating_sub(tx_l1_cost); + if tx_l1_cost.gt(&caller_account.info.balance) { + return Err(EVMError::Transaction( + InvalidTransaction::LackOfFundForMaxFee { + fee: tx_l1_cost.into(), + balance: caller_account.info.balance.into(), + }, + )); } + caller_account.data.info.balance = + caller_account.data.info.balance.saturating_sub(tx_l1_cost); } else { // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. if matches!(context.evm.inner.env.tx.transact_to, TransactTo::Call(_)) { @@ -113,35 +99,6 @@ pub fn deduct_caller( Ok(()) } -/// Helper function that deducts the caller balance. -#[inline] -fn deduct_caller_use_erc20( - db: &mut DB, - erc20_fee_info: &Erc20FeeInfo, - caller_account: &mut Account, - env: &Env, -) { - // Subtract gas costs from the caller's account. - // We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled. - let gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price()); - transfer_erc20( - db, - erc20_fee_info.token_address, - erc20_fee_info.caller, - L2_FEE_VAULT, - gas_cost, - ); - - // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. - if matches!(env.tx.transact_to, TxKind::Call(_)) { - // Nonce is already checked - caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - } - - // touch account so we know it is changed. - caller_account.mark_touch(); -} - /// Reward beneficiary with gas fee. #[inline] pub fn reward_beneficiary( From 182d45e9383cd7e4fa13404cfd4ac5122d81d443 Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:33:57 +0800 Subject: [PATCH 04/14] reimburse_caller_with_erc20 --- crates/revm/src/evm.rs | 145 +++++++++++++++++++++++++---- crates/revm/src/morph/erc20_fee.rs | 117 +++++++++++++++-------- 2 files changed, 206 insertions(+), 56 deletions(-) diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index be88ed0110..6dceca955e 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -1,3 +1,5 @@ +use revm_interpreter::Gas; + use crate::{ builder::{EvmBuilder, HandlerStage, SetGenericStage}, db::{Database, DatabaseCommit, EmptyDB}, @@ -5,11 +7,13 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, - morph::erc20_fee::L2_FEE_VAULT, + morph::{ + erc20_fee::{eth_to_erc20, L2_FEE_VAULT}, + Erc20FeeInfo, + }, primitives::{ - specification::SpecId, Address, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, - EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, - EOF_MAGIC_BYTES, U256, + specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, + ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, EOF_MAGIC_BYTES, U256, }, Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; @@ -342,11 +346,7 @@ impl Evm<'_, EXT, DB> { let ctx = &mut self.context; let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); if let Some(token_fee) = erc20_fee_info { - self.deduct_caller_with_erc20( - token_fee.token_address, - token_fee.caller, - L2_FEE_VAULT, - )?; + self.deduct_caller_with_erc20(token_fee)?; } else { self.handler.pre_execution().deduct_caller(ctx)?; } @@ -403,9 +403,23 @@ impl Evm<'_, EXT, DB> { // calculate final refund and add EIP-7702 refund to gas. post_exec.refund(ctx, result.gas_mut(), eip7702_gas_refund); // Reimburse the caller - post_exec.reimburse_caller(ctx, result.gas())?; + { + let ctx = &mut self.context; + let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + if let Some(token_fee) = erc20_fee_info { + self.reimburse_caller_with_erc20(token_fee, result.gas())?; + } else { + post_exec.reimburse_caller(ctx, result.gas())?; + } + } + + let ctx = &mut self.context; + let post_exec = self.handler.post_execution(); // Reward beneficiary - post_exec.reward_beneficiary(ctx, result.gas())?; + let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + if erc20_fee_info.is_none() { + post_exec.reward_beneficiary(ctx, result.gas())?; + } // Returns output of transaction. post_exec.output(ctx, result) } @@ -413,9 +427,7 @@ impl Evm<'_, EXT, DB> { #[inline] pub fn deduct_caller_with_erc20( &mut self, - token: Address, - from: Address, - to: Address, + erc20_info: Erc20FeeInfo, ) -> Result<(), EVMError> { let ctx = &self.context; let Some(rlp_bytes) = &ctx.evm.inner.env.tx.morph.rlp_bytes else { @@ -435,7 +447,93 @@ impl Evm<'_, EXT, DB> { let gas_cost = U256::from(ctx.evm.env.tx.gas_limit).saturating_mul(ctx.evm.env.effective_gas_price()); let amount = tx_l1_cost + gas_cost; + let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); + if erc20_amount.is_zero() { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate erc20 gas.".to_string(), + )); + } + + if erc20_amount > erc20_info.balance { + return Err(EVMError::Custom( + "[MORPH] Erc20 balance is insufficient to pay gas.".to_string(), + )); + } + // Call transfer(address,uint256) method via EVM + // Method signature: transfer(address,uint256) -> 0xa9059cbb + let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // Encode calldata: method_id + padded to address + amount + let mut calldata = Vec::with_capacity(68); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes + calldata.extend_from_slice(L2_FEE_VAULT.as_slice()); + calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); + let tx = TxEnv { + caller: erc20_info.caller, + gas_limit: 1_000_00u64, + gas_price: U256::ZERO, + transact_to: TxKind::Call(erc20_info.token_address), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + + // let ctx = &mut self.context; + // let exec = self.handler.execution(); + // let call = exec.call( + // ctx, + // CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap(), + // )?; + // let mut _result = match call { + // FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, + // FrameOrResult::Result(result) => result, + // }; + + let origin_tx = self.context.evm.inner.env.tx.clone(); + self.context.evm.inner.env.tx = tx; + + let _ = self.transact_preverified_inner(0); + // load caller's account. + let ctx = &mut self.context; + let caller_account = ctx + .evm + .inner + .journaled_state + .load_account(ctx.evm.inner.env.tx.caller, &mut ctx.evm.inner.db)? + .data; + caller_account.info.nonce = caller_account.info.nonce.saturating_sub(1); + ctx.evm.inner.env.tx = origin_tx; + + // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. + if matches!(ctx.evm.inner.env.tx.transact_to, TxKind::Call(_)) { + // Nonce is already checked + caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); + } + // touch account so we know it is changed. + caller_account.mark_touch(); + + Ok(()) + } + #[inline] + pub fn reimburse_caller_with_erc20( + &mut self, + erc20_info: Erc20FeeInfo, + gas: &Gas, + ) -> Result<(), EVMError> { + let ctx = &self.context; + + let effective_gas_price = ctx.evm.env.effective_gas_price(); + let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); + let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); + if erc20_amount.is_zero() { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate erc20 gas.".to_string(), + )); + } // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; @@ -444,13 +542,13 @@ impl Evm<'_, EXT, DB> { let mut calldata = Vec::with_capacity(68); calldata.extend_from_slice(&method_id); calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - calldata.extend_from_slice(to.as_slice()); - calldata.extend_from_slice(&amount.to_be_bytes::<32>()); + calldata.extend_from_slice(erc20_info.caller.as_slice()); + calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); let tx = TxEnv { - caller: from, - gas_limit: u64::MAX, + caller: L2_FEE_VAULT, + gas_limit: 1_000_00u64, gas_price: U256::ZERO, - transact_to: TxKind::Call(token), + transact_to: TxKind::Call(erc20_info.token_address), value: U256::ZERO, data: Bytes::from(calldata), nonce: None, @@ -461,6 +559,15 @@ impl Evm<'_, EXT, DB> { self.context.evm.inner.env.tx = tx; let _ = self.transact_preverified_inner(0); + // load caller's account. + let ctx = &mut self.context; + let fee_vault_account = ctx + .evm + .inner + .journaled_state + .load_account(L2_FEE_VAULT, &mut ctx.evm.inner.db)? + .data; + fee_vault_account.info.nonce = fee_vault_account.info.nonce.saturating_sub(1); self.context.evm.inner.env.tx = origin_tx; Ok(()) diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs index 4bc5fb3f12..10e66ba176 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/erc20_fee.rs @@ -3,12 +3,10 @@ use crate::primitives::{address, Address, U256}; use crate::primitives::{Bytes, TxEnv, TxKind}; use crate::{Database, Evm}; -// TokenAddressMappingSlot is the storage slot for mapping(uint16 => address) -const TOKEN_ADDRESS_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); -// TokenPriceMappingSlot is the storage slot for mapping(uint16 => uint256) -const TOKEN_PRICE_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); -// TokenBalanceSlotMappingSlot is the storage slot for mapping(uint16 => bytes32) -const TOKEN_BALANCE_SLOT_MAPPING_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +// TokenRegistry is the storage slot for mapping(uint16 => TokenInfo) - slot 0 +const TOKEN_REGISTRY_SLOT: U256 = U256::from_limbs([0u64, 0, 0, 0]); +// PriceRatio is the storage slot for mapping(uint16 => uint256) - slot 2 +const PRICE_RATIO_SLOT: U256 = U256::from_limbs([2u64, 0, 0, 0]); // System address for receiving ERC20 fees pub const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); @@ -16,8 +14,14 @@ pub const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C0 pub struct Erc20FeeInfo { /// The ERC20 token address pub token_address: Address, - /// The price of the token - pub price: U256, + /// Whether the token is active + pub is_active: bool, + /// Token decimals + pub decimals: u8, + /// The price ratio of the token + pub price_ratio: U256, + /// The scale of the token + pub scale: U256, /// The caller address pub caller: Address, /// The token balance of caller @@ -33,62 +37,85 @@ impl Erc20FeeInfo { token_id: u16, caller: Address, ) -> Result, DB::Error> { - // get token address of token_id - let storage_value = load_mapping_value( - db, + // Get the base slot for this token_id in tokenRegistry mapping + let token_registry_base = + get_mapping_slot(TOKEN_REGISTRY_SLOT, token_id.to_be_bytes().to_vec()); + + // TokenInfo struct layout in storage (following Solidity storage packing rules): + // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding + // slot + 1: balanceSlot (bytes32, 32 bytes) + // slot + 2: isActive (bool, 1 byte) + decimals (uint8, 1 byte) + 30 bytes padding + // slot + 3: scale (uint256, 32 bytes) + + // Read tokenAddress from slot + 0 + let slot_0 = db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, token_registry_base)?; + let token_address = Address::from_word(slot_0.into()); + + // Read balanceSlot from slot + 1 + let token_balance_slot = db.storage( L1_GAS_PRICE_ORACLE_ADDRESS, - TOKEN_ADDRESS_MAPPING_SLOT, - token_id.to_be_bytes().to_vec(), + token_registry_base + U256::from(1), )?; - let token_address: Address = Address::from_word(storage_value.into()); - if token_address.is_zero() { - return Ok(None); - } - // get token price of token_id - let token_price = load_mapping_value( - db, + // Read isActive and decimals from slot + 2 + // In big-endian representation, rightmost byte is the lowest position + // isActive is at the rightmost (byte 31), decimals is to its left (byte 30) + let slot_2 = db.storage( L1_GAS_PRICE_ORACLE_ADDRESS, - TOKEN_PRICE_MAPPING_SLOT, - token_id.to_be_bytes().to_vec(), + token_registry_base + U256::from(2), + )?; + let slot_2_bytes = slot_2.to_be_bytes::<32>(); + let is_active = slot_2_bytes[31] != 0; + let decimals = slot_2_bytes[30]; + + // Read scale from slot + 3 + let scale = db.storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + token_registry_base + U256::from(3), )?; - if token_price.is_zero() { - return Ok(None); - } - // get token balance of token_id - let token_balance_slot = load_mapping_value( + // Get price ratio from priceRatio mapping + let price_ratio = load_mapping_value( db, L1_GAS_PRICE_ORACLE_ADDRESS, - TOKEN_BALANCE_SLOT_MAPPING_SLOT, + PRICE_RATIO_SLOT, token_id.to_be_bytes().to_vec(), )?; - // get caller's token balance + // Get caller's token balance let caller_token_balance = get_erc20_balance(db, token_address, caller, token_balance_slot); - let ecc20_fee = Erc20FeeInfo { + let erc20_fee = Erc20FeeInfo { token_address, - price: token_price, + is_active, + decimals, + price_ratio, + scale, caller, balance: caller_token_balance, balance_slot: token_balance_slot, }; - Ok(Some(ecc20_fee)) + Ok(Some(erc20_fee)) } } +/// Calculate the storage slot for a mapping value +fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { + let mut pre_image = slot_index.to_be_bytes_vec(); + pre_image.append(&mut key); + let storage_key = crate::primitives::keccak256(pre_image); + U256::from_be_bytes(storage_key.0) +} + fn load_mapping_value( db: &mut DB, account: Address, slot_index: U256, - mut key: Vec, + key: Vec, ) -> Result::Error> { - let mut pre_image = slot_index.to_be_bytes_vec(); - pre_image.append(&mut key); - let storage_key = crate::primitives::keccak256(pre_image); - let storage_value = db.storage(account, U256::from_be_bytes(storage_key.0))?; + let storage_slot = get_mapping_slot(slot_index, key); + let storage_value = db.storage(account, storage_slot)?; Ok(storage_value) } @@ -145,3 +172,19 @@ pub(super) fn get_erc20_balance( Err(_) => U256::ZERO, } } + +pub fn eth_to_erc20(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { + if rate.is_zero() { + return U256::ZERO; + } + // EthToERC20 erc20Amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate + // Calculate: (eth_amount * token_scale) / rate + let (erc20_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); + + // If there's a remainder, round up by adding 1 + if !remainder.is_zero() { + erc20_amount.saturating_add(U256::from(1)) + } else { + erc20_amount + } +} From 7056898643337ca195abb0d0120479ada258b1ca Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:33:47 +0800 Subject: [PATCH 05/14] eth fee to erc20fee --- bins/revm-test/src/bin/morph.rs | 63 ++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index 3498987d5e..2a0b2633be 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -1,9 +1,68 @@ use revm::{ db::{CacheDB, EmptyDB}, - primitives::{address, keccak256, AccountInfo, Bytes, TxEnv, U256}, + primitives::{address, bytes, keccak256, AccountInfo, Bytecode, Bytes, TxEnv, U256}, Evm, }; +static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006006905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220bd76a0877c61d26a928dd36a2ac3491d00e9086a429df7883853cc988a8c1cbf64736f6c63430008120033"); + +fn erc20_fee_normal() { + let account_from = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); + let mut cache_db = CacheDB::new(EmptyDB::default()); + let token_account = address!("fab77965cAfB593Bd86E2e8073407CAb7fD2f6c4"); + let token_account_info = AccountInfo { + nonce: 0_u64, + balance: U256::from(1_000_000_000_000_000_000u128), + code_hash: keccak256(Bytes::new()), + code: Some(Bytecode::new_legacy(ERC20_DEPLOYED_CODE.clone())), + code_size: 0, + }; + // cache_db.insert_contract(token_account); + cache_db.insert_account_info(token_account, token_account_info.clone()); + + // Calculate the storage location of account_from in the _balances mapping + // Storage location of Solidity mapping = keccak256(abi.encode(key, slot)) + let balance_slot = U256::ZERO; // slot of _balances mapping in ERC20. + let mut data = [0u8; 64]; + data[12..32].copy_from_slice(account_from.as_slice()); // The address occupies 20 bytes, left-padded to 32 bytes. + data[32..64].copy_from_slice(&balance_slot.to_be_bytes::<32>()); // The slot occupies 32 bytes. + + let storage_key = keccak256(&data); + let storage_key_u256 = U256::from_be_bytes(storage_key.0); + + // Set the balance to 100000000 + let balance_value = U256::from(100000000u64); + let _ = cache_db.insert_account_storage(token_account, storage_key_u256, balance_value); + + let acc_info = AccountInfo { + nonce: 0_u64, + balance: U256::from(1_000_000_000_000_000_000u128), + code_hash: keccak256(Bytes::new()), + code: None, + code_size: 0, + }; + cache_db.insert_account_info(account_from, acc_info.clone()); + let mut evm = Evm::builder().with_db(cache_db).build(); + + // use erc20 gas token. + let tx = TxEnv { + caller: account_from, + gas_limit: 21000u64, + transact_to: account_to.into(), + value: U256::from(1_000u64), + data: Bytes::new(), + nonce: None, + chain_id: None, + fee_token_id: Some(1u16), + ..Default::default() + }; + + // process txs in block + evm.context.evm.env.tx = tx; + let _ = evm.transact_commit(); +} + fn main() { let mut cache_db = CacheDB::new(EmptyDB::default()); @@ -36,4 +95,6 @@ fn main() { // process txs in block evm.context.evm.env.tx = tx; let _ = evm.transact_commit(); + + erc20_fee_normal(); } From 45870fc6bd912a34ad507a1e168d0b855925bb03 Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:34:27 +0800 Subject: [PATCH 06/14] eth fee to erc20fee --- crates/primitives/src/env.rs | 28 ++++++++++++++++--- crates/revm/src/evm.rs | 10 +++---- crates/revm/src/handler/mainnet/validation.rs | 9 +++--- crates/revm/src/morph/erc20_fee.rs | 15 ---------- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index ac4676052c..a3e089f937 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -241,7 +241,7 @@ impl Env { pub fn validate_tx_against_state( &self, account: &mut Account, - erc20_balance: U256, + erc20_info: (U256, U256, U256), ) -> Result<(), InvalidTransaction> { // EIP-3607: Reject transactions from senders with deployed code // This EIP is introduced after london but there was no collision in past @@ -269,9 +269,12 @@ impl Env { } } - let mut balance_check = U256::from(self.tx.gas_limit) + let gas_cost = U256::from(self.tx.gas_limit) .checked_mul(self.tx.gas_price) - .and_then(|gas_cost| gas_cost.checked_add(self.tx.value)) + .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; + + let mut balance_check = gas_cost + .checked_add(self.tx.value) .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; if SPEC::enabled(SpecId::CANCUN) { @@ -285,7 +288,8 @@ impl Env { // Check if account has enough balance for gas_limit*gas_price and value transfer. // Transfer will be done inside `*_inner` functions. let lack_of_fund_for_max_fee = if self.tx.fee_token_id.unwrap_or_default() != 0 { - balance_check > erc20_balance + let erc20_check = eth_to_erc20(gas_cost, erc20_info.1, erc20_info.2); + erc20_check > erc20_info.0 || self.tx.value > account.info.balance } else { balance_check > account.info.balance }; @@ -792,6 +796,22 @@ pub enum AnalysisKind { Analyse, } +pub fn eth_to_erc20(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { + if rate.is_zero() { + return U256::ZERO; + } + // EthToERC20 erc20Amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate + // Calculate: (eth_amount * token_scale) / rate + let (erc20_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); + + // If there's a remainder, round up by adding 1 + if !remainder.is_zero() { + erc20_amount.saturating_add(U256::from(1)) + } else { + erc20_amount + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 6dceca955e..4416f1c21c 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -7,13 +7,11 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, - morph::{ - erc20_fee::{eth_to_erc20, L2_FEE_VAULT}, - Erc20FeeInfo, - }, + morph::{erc20_fee::L2_FEE_VAULT, Erc20FeeInfo}, primitives::{ - specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, - ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, EOF_MAGIC_BYTES, U256, + eth_to_erc20, specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, + EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, + EOF_MAGIC_BYTES, U256, }, Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; diff --git a/crates/revm/src/handler/mainnet/validation.rs b/crates/revm/src/handler/mainnet/validation.rs index f533123f38..0a59aa59ca 100644 --- a/crates/revm/src/handler/mainnet/validation.rs +++ b/crates/revm/src/handler/mainnet/validation.rs @@ -26,15 +26,16 @@ pub fn validate_tx_against_state( .journaled_state .load_code(tx_caller, &mut context.evm.inner.db)?; - let erc20_balance = match &context.evm.inner.erc20_fee_info { - Some(fee_info) => fee_info.balance, - None => U256::ZERO, + let erc20_info = match &context.evm.inner.erc20_fee_info { + Some(fee_info) => (fee_info.balance, fee_info.price_ratio, fee_info.scale), + None => (U256::ZERO, U256::ZERO, U256::ZERO), }; + context .evm .inner .env - .validate_tx_against_state::(caller_account.data, erc20_balance) + .validate_tx_against_state::(caller_account.data, erc20_info) .map_err(EVMError::Transaction)?; Ok(()) diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs index 10e66ba176..f01f26e311 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/erc20_fee.rs @@ -173,18 +173,3 @@ pub(super) fn get_erc20_balance( } } -pub fn eth_to_erc20(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { - if rate.is_zero() { - return U256::ZERO; - } - // EthToERC20 erc20Amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate - // Calculate: (eth_amount * token_scale) / rate - let (erc20_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); - - // If there's a remainder, round up by adding 1 - if !remainder.is_zero() { - erc20_amount.saturating_add(U256::from(1)) - } else { - erc20_amount - } -} From d1e3848ee504b4776fec039ba9db5b832d3ce23d Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:32:24 +0800 Subject: [PATCH 07/14] add fee_limit --- bins/revm-test/src/bin/morph.rs | 57 +++++++++++++++++++++++++++++++++ crates/primitives/src/env.rs | 10 +++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index 2a0b2633be..4657ba255d 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -35,6 +35,62 @@ fn erc20_fee_normal() { let balance_value = U256::from(100000000u64); let _ = cache_db.insert_account_storage(token_account, storage_key_u256, balance_value); + // Set ERC20PriceOracle storage + let oracle_address = address!("530000000000000000000000000000000000000F"); + let token_id: u16 = 1; + + // Calculate base slot for tokenRegistry[1] + // tokenRegistry is at slot 0 + let token_registry_slot = U256::ZERO; + let mut token_id_bytes = [0u8; 32]; + token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); + + let mut pre_image = token_registry_slot.to_be_bytes_vec(); + pre_image.extend_from_slice(&token_id_bytes); + let token_registry_base = keccak256(&pre_image); + let token_registry_base_u256 = U256::from_be_bytes(token_registry_base.0); + + // TokenInfo struct layout: + // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding + // slot + 1: balanceSlot (bytes32, 32 bytes) + // slot + 2: isActive (bool, 1 byte) + decimals (uint8, 1 byte) + 30 bytes padding + // slot + 3: scale (uint256, 32 bytes) + + // Set tokenAddress at slot + 0 + let token_address_value = U256::from_be_bytes(token_account.into_word().into()); + let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256, token_address_value); + + // Set balanceSlot at slot + 1 (using slot 0 for ERC20 balance mapping) + let balance_slot_value = U256::ZERO; + let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256 + U256::from(1), balance_slot_value); + + // Set isActive and decimals at slot + 2 + // isActive = true (1), decimals = 18 + // In storage: rightmost byte (byte 31) is isActive, byte 30 is decimals + let mut slot_2_bytes = [0u8; 32]; + slot_2_bytes[30] = 18; // decimals + slot_2_bytes[31] = 1; // isActive = true + let slot_2_value = U256::from_be_bytes(slot_2_bytes); + let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256 + U256::from(2), slot_2_value); + + // Set scale at slot + 3 + // scale = 10^18 (1e18) + let scale_value = U256::from(1_000_000_000_000_000_000u128); + let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256 + U256::from(3), scale_value); + + // Set priceRatio for tokenID 1 + // priceRatio is at slot 2 + let price_ratio_slot = U256::from(2); + let mut price_ratio_pre_image = price_ratio_slot.to_be_bytes_vec(); + price_ratio_pre_image.extend_from_slice(&token_id_bytes); + let price_ratio_storage_slot = keccak256(&price_ratio_pre_image); + let price_ratio_storage_slot_u256 = U256::from_be_bytes(price_ratio_storage_slot.0); + + // Set price ratio to 1e18 (1:1 ratio with ETH for simplicity) + let price_ratio_value = U256::from(1_000_000_000_000_000_000u128); + let _ = cache_db.insert_account_storage(oracle_address, price_ratio_storage_slot_u256, price_ratio_value); + + let acc_info = AccountInfo { nonce: 0_u64, balance: U256::from(1_000_000_000_000_000_000u128), @@ -55,6 +111,7 @@ fn erc20_fee_normal() { nonce: None, chain_id: None, fee_token_id: Some(1u16), + fee_limit: Some(100000u64), ..Default::default() }; diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index a3e089f937..38d238c5d7 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -288,8 +288,12 @@ impl Env { // Check if account has enough balance for gas_limit*gas_price and value transfer. // Transfer will be done inside `*_inner` functions. let lack_of_fund_for_max_fee = if self.tx.fee_token_id.unwrap_or_default() != 0 { + let mut fee_limit = U256::from(self.tx.fee_limit.unwrap_or_default()); + if fee_limit.is_zero() || fee_limit > erc20_info.0 { + fee_limit = erc20_info.0 + } let erc20_check = eth_to_erc20(gas_cost, erc20_info.1, erc20_info.2); - erc20_check > erc20_info.0 || self.tx.value > account.info.balance + erc20_check > fee_limit || self.tx.value > account.info.balance } else { balance_check > account.info.balance }; @@ -649,6 +653,9 @@ pub struct TxEnv { #[cfg(feature = "morph")] /// For ERC20FeeType pub fee_token_id: Option, + #[cfg(feature = "morph")] + /// For ERC20FeeType + pub fee_limit: Option, } pub enum TxType { @@ -695,6 +702,7 @@ impl Default for TxEnv { #[cfg(feature = "morph")] morph: MorphFields::default(), fee_token_id: None, + fee_limit: None, } } } From c6b7c686fbe13dcd31894e8fabd95f7c981d2e43 Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Wed, 12 Nov 2025 22:59:48 +0800 Subject: [PATCH 08/14] alt fee transact --- bins/revm-test/src/bin/morph.rs | 181 +++++++++++++++++----- crates/primitives/src/env.rs | 1 - crates/revm/src/evm.rs | 120 +++++++------- crates/revm/src/morph/erc20_fee.rs | 39 +++-- crates/revm/src/morph/handler_register.rs | 10 -- 5 files changed, 219 insertions(+), 132 deletions(-) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index 4657ba255d..5acb72a8bc 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -1,11 +1,17 @@ use revm::{ db::{CacheDB, EmptyDB}, - primitives::{address, bytes, keccak256, AccountInfo, Bytecode, Bytes, TxEnv, U256}, - Evm, + primitives::{address, bytes, keccak256, AccountInfo, Address, Bytecode, Bytes, TxEnv, U256}, + Database, Evm, }; static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006006905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220bd76a0877c61d26a928dd36a2ac3491d00e9086a429df7883853cc988a8c1cbf64736f6c63430008120033"); +fn main() { + eth_gas_normal(); + + erc20_fee_normal(); +} + fn erc20_fee_normal() { let account_from = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); @@ -31,65 +37,84 @@ fn erc20_fee_normal() { let storage_key = keccak256(&data); let storage_key_u256 = U256::from_be_bytes(storage_key.0); - // Set the balance to 100000000 - let balance_value = U256::from(100000000u64); + // Set the balance to 200000000 + let balance_value = U256::from(200000000u64); let _ = cache_db.insert_account_storage(token_account, storage_key_u256, balance_value); // Set ERC20PriceOracle storage - let oracle_address = address!("530000000000000000000000000000000000000F"); + let oracle_address = address!("5300000000000000000000000000000000000021"); let token_id: u16 = 1; - + // Calculate base slot for tokenRegistry[1] // tokenRegistry is at slot 0 - let token_registry_slot = U256::ZERO; + let token_registry_slot = U256::ZERO.to_be_bytes_vec(); let mut token_id_bytes = [0u8; 32]; token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); - - let mut pre_image = token_registry_slot.to_be_bytes_vec(); - pre_image.extend_from_slice(&token_id_bytes); - let token_registry_base = keccak256(&pre_image); + + let mut token_registry_pre_image = token_id_bytes.to_vec(); + token_registry_pre_image.extend_from_slice(&token_registry_slot); + let token_registry_base = keccak256(&token_registry_pre_image); let token_registry_base_u256 = U256::from_be_bytes(token_registry_base.0); - + // TokenInfo struct layout: // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding // slot + 1: balanceSlot (bytes32, 32 bytes) // slot + 2: isActive (bool, 1 byte) + decimals (uint8, 1 byte) + 30 bytes padding // slot + 3: scale (uint256, 32 bytes) - + // Set tokenAddress at slot + 0 let token_address_value = U256::from_be_bytes(token_account.into_word().into()); - let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256, token_address_value); - + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256, + token_address_value, + ); + // Set balanceSlot at slot + 1 (using slot 0 for ERC20 balance mapping) let balance_slot_value = U256::ZERO; - let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256 + U256::from(1), balance_slot_value); - + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256 + U256::from(1), + balance_slot_value, + ); + // Set isActive and decimals at slot + 2 // isActive = true (1), decimals = 18 // In storage: rightmost byte (byte 31) is isActive, byte 30 is decimals let mut slot_2_bytes = [0u8; 32]; slot_2_bytes[30] = 18; // decimals - slot_2_bytes[31] = 1; // isActive = true + slot_2_bytes[31] = 1; // isActive = true let slot_2_value = U256::from_be_bytes(slot_2_bytes); - let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256 + U256::from(2), slot_2_value); - + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256 + U256::from(2), + slot_2_value, + ); + // Set scale at slot + 3 - // scale = 10^18 (1e18) - let scale_value = U256::from(1_000_000_000_000_000_000u128); - let _ = cache_db.insert_account_storage(oracle_address, token_registry_base_u256 + U256::from(3), scale_value); - + // scale = 10 + let scale_value = U256::from(1u64); + let _ = cache_db.insert_account_storage( + oracle_address, + token_registry_base_u256 + U256::from(3), + scale_value, + ); + // Set priceRatio for tokenID 1 // priceRatio is at slot 2 - let price_ratio_slot = U256::from(2); - let mut price_ratio_pre_image = price_ratio_slot.to_be_bytes_vec(); - price_ratio_pre_image.extend_from_slice(&token_id_bytes); + let price_ratio_slot = U256::from(2).to_be_bytes_vec(); + let mut price_ratio_pre_image = token_id_bytes.to_vec(); + price_ratio_pre_image.extend_from_slice(&price_ratio_slot); + let price_ratio_storage_slot = keccak256(&price_ratio_pre_image); let price_ratio_storage_slot_u256 = U256::from_be_bytes(price_ratio_storage_slot.0); - - // Set price ratio to 1e18 (1:1 ratio with ETH for simplicity) - let price_ratio_value = U256::from(1_000_000_000_000_000_000u128); - let _ = cache_db.insert_account_storage(oracle_address, price_ratio_storage_slot_u256, price_ratio_value); + let price_ratio_value = U256::from(250000000u64); + let _ = cache_db.insert_account_storage( + oracle_address, + price_ratio_storage_slot_u256, + price_ratio_value, + ); let acc_info = AccountInfo { nonce: 0_u64, @@ -99,10 +124,10 @@ fn erc20_fee_normal() { code_size: 0, }; cache_db.insert_account_info(account_from, acc_info.clone()); - let mut evm = Evm::builder().with_db(cache_db).build(); + let mut evm = Evm::builder().with_db(&mut cache_db).build(); - // use erc20 gas token. - let tx = TxEnv { + // use erc20 gas token for txn. + let mut tx = TxEnv { caller: account_from, gas_limit: 21000u64, transact_to: account_to.into(), @@ -110,17 +135,93 @@ fn erc20_fee_normal() { data: Bytes::new(), nonce: None, chain_id: None, - fee_token_id: Some(1u16), - fee_limit: Some(100000u64), + fee_token_id: Some(1), + fee_limit: None, + gas_price: U256::from(10u64.pow(9)), ..Default::default() }; - // process txs in block + // process txn + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); evm.context.evm.env.tx = tx; let _ = evm.transact_commit(); + + let account_from_balance = &evm + .context + .evm + .inner + .db + .load_account(account_from) + .unwrap() + .info + .balance; + println!("account_from_balance: {:?}", account_from_balance); + assert!( + account_from_balance.to::() == 999999999999999000, + "Only 1000wei must have been transferred." + ); //Only the value 1_000 wei was transferred. + + let erc20_value = &evm + .context + .evm + .db + .storage(token_account, storage_key_u256) + .unwrap_or_default(); + println!("account_from_erc20_value: {:?}", erc20_value); + assert!( + erc20_value.to::() == 199916000, + "Gas fees should use: 84,000" + ); //Gas fees used: 84,000 + + let method_id = [0x70u8, 0xa0, 0x82, 0x31]; + let mut calldata = Vec::with_capacity(36); + calldata.extend_from_slice(&method_id); + calldata.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes + calldata.extend_from_slice(account_from.as_slice()); + + let mut token_balance_tx = TxEnv { + caller: Address::default(), + gas_limit: u64::MAX, + transact_to: token_account.into(), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + token_balance_tx.morph.is_l1_msg = false; + token_balance_tx.morph.rlp_bytes = Some(Bytes::default()); + evm.context.evm.env.tx = token_balance_tx; + + let erc20_balance_evm = match evm.transact() { + Ok(result) => { + if result.result.is_success() { + // Parse the returned balance (32 bytes) + if let Some(output) = result.result.output() { + if output.len() >= 32 { + U256::from_be_slice(&output[..32]) + } else { + U256::ZERO + } + } else { + U256::ZERO + } + } else { + U256::ZERO + } + } + Err(_) => { + println!("get_erc20_balance error"); + U256::ZERO + } + }; + println!("account_from_erc20_value_evm: {:?}", erc20_balance_evm); + + assert!(erc20_value.eq(&erc20_balance_evm), "Gas fees used: 84,000") //Gas fees used: 84,000 } -fn main() { +fn eth_gas_normal() { let mut cache_db = CacheDB::new(EmptyDB::default()); let account = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); @@ -149,9 +250,7 @@ fn main() { ..Default::default() }; - // process txs in block + // process txn evm.context.evm.env.tx = tx; let _ = evm.transact_commit(); - - erc20_fee_normal(); } diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 38d238c5d7..6f744c4aa0 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -811,7 +811,6 @@ pub fn eth_to_erc20(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { // EthToERC20 erc20Amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate // Calculate: (eth_amount * token_scale) / rate let (erc20_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); - // If there's a remainder, round up by adding 1 if !remainder.is_zero() { erc20_amount.saturating_add(U256::from(1)) diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 4416f1c21c..db3bf70487 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -231,6 +231,18 @@ impl Evm<'_, EXT, DB> { /// This function will validate the transaction. #[inline] pub fn transact(&mut self) -> EVMResult { + let context = &mut self.context; + let fee_token_id = context.evm.inner.env().tx.fee_token_id.unwrap_or_default(); + if fee_token_id != 0 { + let caller = context.evm.inner.env.tx.caller; + let erc20_fee_info = crate::morph::Erc20FeeInfo::try_fetch( + &mut context.evm.inner.db, + fee_token_id, + caller, + ) + .map_err(EVMError::Database)?; + context.evm.inner.erc20_fee_info = erc20_fee_info; + } let initial_gas_spend = self.preverify_transaction_inner().inspect_err(|_| { self.clear(); })?; @@ -343,8 +355,8 @@ impl Evm<'_, EXT, DB> { { let ctx = &mut self.context; let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); - if let Some(token_fee) = erc20_fee_info { - self.deduct_caller_with_erc20(token_fee)?; + if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { + self.deduct_caller_with_erc20(erc20_fee_info)?; } else { self.handler.pre_execution().deduct_caller(ctx)?; } @@ -404,8 +416,8 @@ impl Evm<'_, EXT, DB> { { let ctx = &mut self.context; let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); - if let Some(token_fee) = erc20_fee_info { - self.reimburse_caller_with_erc20(token_fee, result.gas())?; + if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { + self.reimburse_caller_with_erc20(erc20_fee_info, result.gas())?; } else { post_exec.reimburse_caller(ctx, result.gas())?; } @@ -425,8 +437,14 @@ impl Evm<'_, EXT, DB> { #[inline] pub fn deduct_caller_with_erc20( &mut self, - erc20_info: Erc20FeeInfo, + erc20_info: Option, ) -> Result<(), EVMError> { + let Some(erc20_info) = erc20_info else { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate erc20 gas.".to_string(), + )); + }; + let ctx = &self.context; let Some(rlp_bytes) = &ctx.evm.inner.env.tx.morph.rlp_bytes else { return Err(EVMError::Custom( @@ -451,10 +469,9 @@ impl Evm<'_, EXT, DB> { "[MORPH] Failed to calculate erc20 gas.".to_string(), )); } - if erc20_amount > erc20_info.balance { return Err(EVMError::Custom( - "[MORPH] Erc20 balance is insufficient to pay gas.".to_string(), + "[MORPH] Token balance is insufficient to pay gas.".to_string(), )); } // Call transfer(address,uint256) method via EVM @@ -462,12 +479,14 @@ impl Evm<'_, EXT, DB> { let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; // Encode calldata: method_id + padded to address + amount - let mut calldata = Vec::with_capacity(68); + let mut calldata = Vec::new(); calldata.extend_from_slice(&method_id); - calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - calldata.extend_from_slice(L2_FEE_VAULT.as_slice()); + // calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes + let mut address_bytes = [0u8; 32]; + address_bytes[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); + calldata.extend_from_slice(&address_bytes); calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); - let tx = TxEnv { + let mut tx = TxEnv { caller: erc20_info.caller, gas_limit: 1_000_00u64, gas_price: U256::ZERO, @@ -478,40 +497,16 @@ impl Evm<'_, EXT, DB> { chain_id: None, ..Default::default() }; + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); - // let ctx = &mut self.context; - // let exec = self.handler.execution(); - // let call = exec.call( - // ctx, - // CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap(), - // )?; - // let mut _result = match call { - // FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, - // FrameOrResult::Result(result) => result, - // }; - - let origin_tx = self.context.evm.inner.env.tx.clone(); - self.context.evm.inner.env.tx = tx; - - let _ = self.transact_preverified_inner(0); - // load caller's account. let ctx = &mut self.context; - let caller_account = ctx - .evm - .inner - .journaled_state - .load_account(ctx.evm.inner.env.tx.caller, &mut ctx.evm.inner.db)? - .data; - caller_account.info.nonce = caller_account.info.nonce.saturating_sub(1); - ctx.evm.inner.env.tx = origin_tx; - - // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. - if matches!(ctx.evm.inner.env.tx.transact_to, TxKind::Call(_)) { - // Nonce is already checked - caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - } - // touch account so we know it is changed. - caller_account.mark_touch(); + let exec = self.handler.execution(); + let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; + let mut _result = match call { + FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, + FrameOrResult::Result(result) => result, + }; Ok(()) } @@ -519,18 +514,20 @@ impl Evm<'_, EXT, DB> { #[inline] pub fn reimburse_caller_with_erc20( &mut self, - erc20_info: Erc20FeeInfo, + erc20_info: Option, gas: &Gas, ) -> Result<(), EVMError> { + let Some(erc20_info) = erc20_info else { + return Err(EVMError::Custom( + "[MORPH] Failed to calculate erc20 gas.".to_string(), + )); + }; let ctx = &self.context; - let effective_gas_price = ctx.evm.env.effective_gas_price(); let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); if erc20_amount.is_zero() { - return Err(EVMError::Custom( - "[MORPH] Failed to calculate erc20 gas.".to_string(), - )); + return Ok(()); } // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb @@ -540,9 +537,11 @@ impl Evm<'_, EXT, DB> { let mut calldata = Vec::with_capacity(68); calldata.extend_from_slice(&method_id); calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - calldata.extend_from_slice(erc20_info.caller.as_slice()); + let mut address_bytes = [0u8; 32]; + address_bytes[12..32].copy_from_slice(erc20_info.caller.as_slice()); + calldata.extend_from_slice(&address_bytes); calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); - let tx = TxEnv { + let mut tx = TxEnv { caller: L2_FEE_VAULT, gas_limit: 1_000_00u64, gas_price: U256::ZERO, @@ -553,20 +552,15 @@ impl Evm<'_, EXT, DB> { chain_id: None, ..Default::default() }; - let origin_tx = self.context.evm.inner.env.tx.clone(); - self.context.evm.inner.env.tx = tx; - - let _ = self.transact_preverified_inner(0); - // load caller's account. + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); let ctx = &mut self.context; - let fee_vault_account = ctx - .evm - .inner - .journaled_state - .load_account(L2_FEE_VAULT, &mut ctx.evm.inner.db)? - .data; - fee_vault_account.info.nonce = fee_vault_account.info.nonce.saturating_sub(1); - self.context.evm.inner.env.tx = origin_tx; + let exec = self.handler.execution(); + let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; + let mut _result = match call { + FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, + FrameOrResult::Result(result) => result, + }; Ok(()) } diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs index f01f26e311..573a549140 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/erc20_fee.rs @@ -1,4 +1,3 @@ -use crate::morph::L1_GAS_PRICE_ORACLE_ADDRESS; use crate::primitives::{address, Address, U256}; use crate::primitives::{Bytes, TxEnv, TxKind}; use crate::{Database, Evm}; @@ -9,6 +8,8 @@ const TOKEN_REGISTRY_SLOT: U256 = U256::from_limbs([0u64, 0, 0, 0]); const PRICE_RATIO_SLOT: U256 = U256::from_limbs([2u64, 0, 0, 0]); // System address for receiving ERC20 fees pub const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); +// System address for L2 token registry +pub const L2_TOKEN_REGISTRY_ADDRESS: Address = address!("5300000000000000000000000000000000000021"); #[derive(Clone, Debug, Default)] pub struct Erc20FeeInfo { @@ -38,9 +39,9 @@ impl Erc20FeeInfo { caller: Address, ) -> Result, DB::Error> { // Get the base slot for this token_id in tokenRegistry mapping - let token_registry_base = - get_mapping_slot(TOKEN_REGISTRY_SLOT, token_id.to_be_bytes().to_vec()); - + let mut token_id_bytes = [0u8; 32]; + token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); + let token_registry_base = get_mapping_slot(TOKEN_REGISTRY_SLOT, token_id_bytes.to_vec()); // TokenInfo struct layout in storage (following Solidity storage packing rules): // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding // slot + 1: balanceSlot (bytes32, 32 bytes) @@ -48,12 +49,12 @@ impl Erc20FeeInfo { // slot + 3: scale (uint256, 32 bytes) // Read tokenAddress from slot + 0 - let slot_0 = db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, token_registry_base)?; + let slot_0 = db.storage(L2_TOKEN_REGISTRY_ADDRESS, token_registry_base)?; let token_address = Address::from_word(slot_0.into()); // Read balanceSlot from slot + 1 let token_balance_slot = db.storage( - L1_GAS_PRICE_ORACLE_ADDRESS, + L2_TOKEN_REGISTRY_ADDRESS, token_registry_base + U256::from(1), )?; @@ -61,7 +62,7 @@ impl Erc20FeeInfo { // In big-endian representation, rightmost byte is the lowest position // isActive is at the rightmost (byte 31), decimals is to its left (byte 30) let slot_2 = db.storage( - L1_GAS_PRICE_ORACLE_ADDRESS, + L2_TOKEN_REGISTRY_ADDRESS, token_registry_base + U256::from(2), )?; let slot_2_bytes = slot_2.to_be_bytes::<32>(); @@ -70,21 +71,20 @@ impl Erc20FeeInfo { // Read scale from slot + 3 let scale = db.storage( - L1_GAS_PRICE_ORACLE_ADDRESS, + L2_TOKEN_REGISTRY_ADDRESS, token_registry_base + U256::from(3), )?; // Get price ratio from priceRatio mapping let price_ratio = load_mapping_value( db, - L1_GAS_PRICE_ORACLE_ADDRESS, + L2_TOKEN_REGISTRY_ADDRESS, PRICE_RATIO_SLOT, - token_id.to_be_bytes().to_vec(), + token_id_bytes.to_vec(), )?; // Get caller's token balance let caller_token_balance = get_erc20_balance(db, token_address, caller, token_balance_slot); - let erc20_fee = Erc20FeeInfo { token_address, is_active, @@ -103,8 +103,8 @@ impl Erc20FeeInfo { /// Calculate the storage slot for a mapping value fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { let mut pre_image = slot_index.to_be_bytes_vec(); - pre_image.append(&mut key); - let storage_key = crate::primitives::keccak256(pre_image); + key.append(&mut pre_image); + let storage_key = crate::primitives::keccak256(key); U256::from_be_bytes(storage_key.0) } @@ -125,9 +125,13 @@ pub(super) fn get_erc20_balance( account: Address, token_balance_slot: U256, ) -> U256 { + println!("get_erc20_balance. token: {:?}", token); + // If balance slot is provided, try to read directly from storage if !token_balance_slot.is_zero() { - if let Ok(balance) = load_mapping_value(db, token, token_balance_slot, account.to_vec()) { + let mut data = [0u8; 32]; + data[12..32].copy_from_slice(account.as_slice()); + if let Ok(balance) = load_mapping_value(db, token, token_balance_slot, data.to_vec()) { return balance; } } @@ -144,7 +148,7 @@ pub(super) fn get_erc20_balance( let db: &mut dyn Database = db; let mut evm = Evm::builder().with_db(db).build(); - let tx = TxEnv { + let mut tx = TxEnv { caller: Address::default(), gas_limit: u64::MAX, transact_to: TxKind::Call(token), @@ -154,6 +158,8 @@ pub(super) fn get_erc20_balance( chain_id: None, ..Default::default() }; + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); evm.context.evm.env.tx = tx; // Execute transaction and extract balance from output @@ -169,7 +175,6 @@ pub(super) fn get_erc20_balance( } U256::ZERO } - Err(_) => U256::ZERO, + Err(_e) => U256::ZERO, } } - diff --git a/crates/revm/src/morph/handler_register.rs b/crates/revm/src/morph/handler_register.rs index 669716af80..f828826a29 100644 --- a/crates/revm/src/morph/handler_register.rs +++ b/crates/revm/src/morph/handler_register.rs @@ -33,16 +33,6 @@ pub fn load_accounts( crate::morph::L1BlockInfo::try_fetch(&mut context.evm.inner.db, SPEC::SPEC_ID) .map_err(EVMError::Database)?; context.evm.inner.l1_block_info = Some(l1_block_info); - - let fee_token_id = context.evm.inner.env().tx.fee_token_id.unwrap_or_default(); - if fee_token_id != 0 { - let caller = context.evm.inner.env.tx.caller; - let erc20_fee_info = - crate::morph::Erc20FeeInfo::try_fetch(&mut context.evm.inner.db, fee_token_id, caller) - .map_err(EVMError::Database)?; - context.evm.inner.erc20_fee_info = erc20_fee_info; - } - mainnet::load_accounts::(context) } From dd74329254e6eb6aa32a0606df3296be6979199f Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:54:40 +0800 Subject: [PATCH 09/14] exec_alt_fee_txn --- bins/revm-test/src/bin/morph.rs | 179 ++++++++++++++++++++++++-------- crates/revm/src/evm.rs | 7 ++ 2 files changed, 142 insertions(+), 44 deletions(-) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index 5acb72a8bc..515cd53b72 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -1,20 +1,59 @@ use revm::{ - db::{CacheDB, EmptyDB}, - primitives::{address, bytes, keccak256, AccountInfo, Address, Bytecode, Bytes, TxEnv, U256}, - Database, Evm, + Database, Evm, db::{CacheDB, EmptyDB}, morph::erc20_fee::L2_FEE_VAULT, primitives::{AccountInfo, Address, Bytecode, Bytes, TxEnv, U256, address, bytes, keccak256} }; static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006006905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220bd76a0877c61d26a928dd36a2ac3491d00e9086a429df7883853cc988a8c1cbf64736f6c63430008120033"); fn main() { - eth_gas_normal(); - - erc20_fee_normal(); + println!("start test eth_fee_normal"); + eth_fee_normal(); + println!("start test erc20_fee_normal"); + alt_fee_normal(); } -fn erc20_fee_normal() { +fn alt_fee_normal() { let account_from = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); + + // use erc20 gas token for txn. + let tx = TxEnv { + caller: account_from, + gas_limit: 31000u64, + transact_to: account_to.into(), + value: U256::from(1_000u64), + data: Bytes::new(), + nonce: None, + chain_id: None, + fee_token_id: Some(1), + fee_limit: None, + gas_price: U256::from(10u64.pow(9)), + ..Default::default() + }; + + let (account_from_balance, erc20_value, erc20_balance_evm) = + exec_alt_fee_txn(1, U256::from(1u64), U256::from(250000000u64), tx); + + assert!( + account_from_balance.to::() == 999999999999999000, + "Only 1000wei must have been transferred." + ); //Only the value 1_000 wei was transferred. + + assert!( + erc20_value.to::() == 199916000, + "Gas fees should use: 84,000" + ); //Gas fees used: 84,000 + + assert!(erc20_value.eq(&erc20_balance_evm), "Gas fees used: 84,000") //Gas fees used: 84,000 +} + +fn exec_alt_fee_txn( + token_id: u16, + scale_value: U256, + price_ratio_value: U256, + tx: TxEnv, +) -> (U256, U256, U256) { + let account_from = tx.caller; + let mut cache_db = CacheDB::new(EmptyDB::default()); let token_account = address!("fab77965cAfB593Bd86E2e8073407CAb7fD2f6c4"); let token_account_info = AccountInfo { @@ -41,11 +80,23 @@ fn erc20_fee_normal() { let balance_value = U256::from(200000000u64); let _ = cache_db.insert_account_storage(token_account, storage_key_u256, balance_value); + + + let balance_slot = U256::ZERO; // slot of _balances mapping in ERC20. + let mut data = [0u8; 64]; + data[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); // The address occupies 20 bytes, left-padded to 32 bytes. + data[32..64].copy_from_slice(&balance_slot.to_be_bytes::<32>()); // The slot occupies 32 bytes. + + let storage_key = keccak256(&data); + let storage_key_u256_to = U256::from_be_bytes(storage_key.0); + let balance_value = U256::from(100000000u64); + let _ = cache_db.insert_account_storage(token_account, storage_key_u256_to, balance_value); + + // Set ERC20PriceOracle storage let oracle_address = address!("5300000000000000000000000000000000000021"); - let token_id: u16 = 1; - // Calculate base slot for tokenRegistry[1] + // Calculate base slot for tokenRegistry[token_id] // tokenRegistry is at slot 0 let token_registry_slot = U256::ZERO.to_be_bytes_vec(); let mut token_id_bytes = [0u8; 32]; @@ -92,15 +143,13 @@ fn erc20_fee_normal() { ); // Set scale at slot + 3 - // scale = 10 - let scale_value = U256::from(1u64); let _ = cache_db.insert_account_storage( oracle_address, token_registry_base_u256 + U256::from(3), scale_value, ); - // Set priceRatio for tokenID 1 + // Set priceRatio for tokenID // priceRatio is at slot 2 let price_ratio_slot = U256::from(2).to_be_bytes_vec(); let mut price_ratio_pre_image = token_id_bytes.to_vec(); @@ -109,7 +158,6 @@ fn erc20_fee_normal() { let price_ratio_storage_slot = keccak256(&price_ratio_pre_image); let price_ratio_storage_slot_u256 = U256::from_be_bytes(price_ratio_storage_slot.0); - let price_ratio_value = U256::from(250000000u64); let _ = cache_db.insert_account_storage( oracle_address, price_ratio_storage_slot_u256, @@ -127,19 +175,7 @@ fn erc20_fee_normal() { let mut evm = Evm::builder().with_db(&mut cache_db).build(); // use erc20 gas token for txn. - let mut tx = TxEnv { - caller: account_from, - gas_limit: 21000u64, - transact_to: account_to.into(), - value: U256::from(1_000u64), - data: Bytes::new(), - nonce: None, - chain_id: None, - fee_token_id: Some(1), - fee_limit: None, - gas_price: U256::from(10u64.pow(9)), - ..Default::default() - }; + let mut tx = tx; // process txn tx.morph.is_l1_msg = false; @@ -147,7 +183,7 @@ fn erc20_fee_normal() { evm.context.evm.env.tx = tx; let _ = evm.transact_commit(); - let account_from_balance = &evm + let account_from_balance = evm .context .evm .inner @@ -157,22 +193,33 @@ fn erc20_fee_normal() { .info .balance; println!("account_from_balance: {:?}", account_from_balance); - assert!( - account_from_balance.to::() == 999999999999999000, - "Only 1000wei must have been transferred." - ); //Only the value 1_000 wei was transferred. - let erc20_value = &evm + let erc20_value = evm .context .evm .db .storage(token_account, storage_key_u256) .unwrap_or_default(); println!("account_from_erc20_value: {:?}", erc20_value); - assert!( - erc20_value.to::() == 199916000, - "Gas fees should use: 84,000" - ); //Gas fees used: 84,000 + + + + let erc20_value_to = evm + .context + .evm + .db + .storage(token_account, storage_key_u256_to) + .unwrap_or_default(); + println!("===>account_to_erc20_value: {:?}", erc20_value_to); + println!("===>storage_key_u256: {:?}", storage_key_u256); + + // // Test converting storage_key_u256_to to string and back to U256 + // println!("Testing U256 to string and back"); + // let str_val = storage_key_u256_to.to_string(); + // println!("String value: {}", str_val); + // let back_to_u256: U256 = str_val.parse().unwrap(); + // println!("Back to U256: {:?}", back_to_u256); + // assert_eq!(back_to_u256, storage_key_u256_to, "Conversion should be consistent"); let method_id = [0x70u8, 0xa0, 0x82, 0x31]; let mut calldata = Vec::with_capacity(36); @@ -218,13 +265,13 @@ fn erc20_fee_normal() { }; println!("account_from_erc20_value_evm: {:?}", erc20_balance_evm); - assert!(erc20_value.eq(&erc20_balance_evm), "Gas fees used: 84,000") //Gas fees used: 84,000 + (account_from_balance, erc20_value, erc20_balance_evm) } -fn eth_gas_normal() { +fn eth_fee_normal() { let mut cache_db = CacheDB::new(EmptyDB::default()); - let account = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let account_from = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); let account_to = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"); let acc_info = AccountInfo { @@ -234,23 +281,67 @@ fn eth_gas_normal() { code: None, code_size: 0, }; - cache_db.insert_account_info(account, acc_info.clone()); + cache_db.insert_account_info(account_from, acc_info.clone()); + + let acc_info_to = AccountInfo { + nonce: 0_u64, + balance: U256::ZERO, + code_hash: keccak256(Bytes::new()), + code: None, + code_size: 0, + }; + cache_db.insert_account_info(account_to, acc_info_to.clone()); + let mut evm = Evm::builder().with_db(cache_db).build(); - // use erc20 gas token. - let tx = TxEnv { - caller: account, + // use eth gas token. + let mut tx = TxEnv { + caller: account_from, gas_limit: 21000u64, transact_to: account_to.into(), value: U256::from(1_000u64), data: Bytes::new(), nonce: None, chain_id: None, - fee_token_id: Some(1u16), + fee_token_id: None, ..Default::default() }; // process txn + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); evm.context.evm.env.tx = tx; let _ = evm.transact_commit(); + + let account_from_balance = evm + .context + .evm + .inner + .db + .load_account(account_from) + .unwrap() + .info + .balance; + println!("account_from_balance: {:?}", account_from_balance); + + let account_to_balance = evm + .context + .evm + .inner + .db + .load_account(account_to) + .unwrap() + .info + .balance; + println!("account_to_balance: {:?}", account_to_balance); + + assert!( + account_from_balance.to::() == 999999999999999000, + "Only 1000wei must have been transferred." + ); //Only the value 1_000 wei was transferred. + + assert!( + account_to_balance.to::() == 1000, + "account_to should receive 1000 wei." + ); //account_to should receive 1000 wei. } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index db3bf70487..53efcd188f 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -529,6 +529,13 @@ impl Evm<'_, EXT, DB> { if erc20_amount.is_zero() { return Ok(()); } + println!("------reimburse_caller_with_erc20 erc20_amount: {:?}", erc20_amount); + + let ctx = &mut self.context; + let balance_slot :U256= "110218063311400506153027935500334854487342827447185155285887586702483003113927".parse().unwrap(); + let erc20_fee_vault = ctx.evm.sload(erc20_info.token_address, balance_slot).unwrap_or_default(); + println!("------erc20_fee_vault: {:?}", erc20_fee_vault); + // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; From aedfe8457b0c0ff607f757f1321dee493026466d Mon Sep 17 00:00:00 2001 From: kathy <22675649+anylots@users.noreply.github.com> Date: Fri, 14 Nov 2025 00:12:05 +0800 Subject: [PATCH 10/14] transfer token use sstore --- bins/revm-test/src/bin/morph.rs | 48 ++++--- crates/revm/src/evm.rs | 198 +++++++++++++++++++---------- crates/revm/src/morph.rs | 2 +- crates/revm/src/morph/erc20_fee.rs | 9 +- 4 files changed, 160 insertions(+), 97 deletions(-) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index 515cd53b72..bf781acb1b 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -1,5 +1,8 @@ use revm::{ - Database, Evm, db::{CacheDB, EmptyDB}, morph::erc20_fee::L2_FEE_VAULT, primitives::{AccountInfo, Address, Bytecode, Bytes, TxEnv, U256, address, bytes, keccak256} + db::{CacheDB, EmptyDB}, + morph::erc20_fee::L2_FEE_VAULT, + primitives::{address, bytes, keccak256, AccountInfo, Address, Bytecode, Bytes, TxEnv, U256}, + Database, Evm, }; static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006006905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220bd76a0877c61d26a928dd36a2ac3491d00e9086a429df7883853cc988a8c1cbf64736f6c63430008120033"); @@ -7,7 +10,7 @@ static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b5 fn main() { println!("start test eth_fee_normal"); eth_fee_normal(); - println!("start test erc20_fee_normal"); + println!("start test alt_fee_normal"); alt_fee_normal(); } @@ -30,7 +33,8 @@ fn alt_fee_normal() { ..Default::default() }; - let (account_from_balance, erc20_value, erc20_balance_evm) = + // USDT: + let (account_from_balance, erc20_value, erc20_value_vault, erc20_balance_evm) = exec_alt_fee_txn(1, U256::from(1u64), U256::from(250000000u64), tx); assert!( @@ -43,6 +47,11 @@ fn alt_fee_normal() { "Gas fees should use: 84,000" ); //Gas fees used: 84,000 + assert!( + erc20_value_vault.to::() == 84000, + "Gas fees should use: 84,000" + ); //recive gas fees: 84,000 + assert!(erc20_value.eq(&erc20_balance_evm), "Gas fees used: 84,000") //Gas fees used: 84,000 } @@ -51,7 +60,7 @@ fn exec_alt_fee_txn( scale_value: U256, price_ratio_value: U256, tx: TxEnv, -) -> (U256, U256, U256) { +) -> (U256, U256, U256, U256) { let account_from = tx.caller; let mut cache_db = CacheDB::new(EmptyDB::default()); @@ -80,18 +89,13 @@ fn exec_alt_fee_txn( let balance_value = U256::from(200000000u64); let _ = cache_db.insert_account_storage(token_account, storage_key_u256, balance_value); - - let balance_slot = U256::ZERO; // slot of _balances mapping in ERC20. let mut data = [0u8; 64]; data[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); // The address occupies 20 bytes, left-padded to 32 bytes. data[32..64].copy_from_slice(&balance_slot.to_be_bytes::<32>()); // The slot occupies 32 bytes. let storage_key = keccak256(&data); - let storage_key_u256_to = U256::from_be_bytes(storage_key.0); - let balance_value = U256::from(100000000u64); - let _ = cache_db.insert_account_storage(token_account, storage_key_u256_to, balance_value); - + let storage_key_u256_vault = U256::from_be_bytes(storage_key.0); // Set ERC20PriceOracle storage let oracle_address = address!("5300000000000000000000000000000000000021"); @@ -201,25 +205,14 @@ fn exec_alt_fee_txn( .storage(token_account, storage_key_u256) .unwrap_or_default(); println!("account_from_erc20_value: {:?}", erc20_value); + println!("storage_key_u256: {:?}", storage_key_u256); - - - let erc20_value_to = evm + let erc20_value_vault = evm .context .evm .db - .storage(token_account, storage_key_u256_to) + .storage(token_account, storage_key_u256_vault) .unwrap_or_default(); - println!("===>account_to_erc20_value: {:?}", erc20_value_to); - println!("===>storage_key_u256: {:?}", storage_key_u256); - - // // Test converting storage_key_u256_to to string and back to U256 - // println!("Testing U256 to string and back"); - // let str_val = storage_key_u256_to.to_string(); - // println!("String value: {}", str_val); - // let back_to_u256: U256 = str_val.parse().unwrap(); - // println!("Back to U256: {:?}", back_to_u256); - // assert_eq!(back_to_u256, storage_key_u256_to, "Conversion should be consistent"); let method_id = [0x70u8, 0xa0, 0x82, 0x31]; let mut calldata = Vec::with_capacity(36); @@ -265,7 +258,12 @@ fn exec_alt_fee_txn( }; println!("account_from_erc20_value_evm: {:?}", erc20_balance_evm); - (account_from_balance, erc20_value, erc20_balance_evm) + ( + account_from_balance, + erc20_value, + erc20_value_vault, + erc20_balance_evm, + ) } fn eth_fee_normal() { diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 53efcd188f..e41b373e84 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -7,7 +7,7 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, - morph::{erc20_fee::L2_FEE_VAULT, Erc20FeeInfo}, + morph::{erc20_fee::L2_FEE_VAULT, get_mapping_account_slot, Erc20FeeInfo}, primitives::{ eth_to_erc20, specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, @@ -445,7 +445,7 @@ impl Evm<'_, EXT, DB> { )); }; - let ctx = &self.context; + let ctx: &Context = &self.context; let Some(rlp_bytes) = &ctx.evm.inner.env.tx.morph.rlp_bytes else { return Err(EVMError::Custom( "[MORPH] Failed to load transaction rlp_bytes.".to_string(), @@ -474,39 +474,66 @@ impl Evm<'_, EXT, DB> { "[MORPH] Token balance is insufficient to pay gas.".to_string(), )); } - // Call transfer(address,uint256) method via EVM - // Method signature: transfer(address,uint256) -> 0xa9059cbb - let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; - - // Encode calldata: method_id + padded to address + amount - let mut calldata = Vec::new(); - calldata.extend_from_slice(&method_id); - // calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - let mut address_bytes = [0u8; 32]; - address_bytes[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); - calldata.extend_from_slice(&address_bytes); - calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); - let mut tx = TxEnv { - caller: erc20_info.caller, - gas_limit: 1_000_00u64, - gas_price: U256::ZERO, - transact_to: TxKind::Call(erc20_info.token_address), - value: U256::ZERO, - data: Bytes::from(calldata), - nonce: None, - chain_id: None, - ..Default::default() - }; - tx.morph.is_l1_msg = false; - tx.morph.rlp_bytes = Some(Bytes::default()); + //110218063311400506153027935500334854487342827447185155285887586702483003113927 + //51649299683075463979090664991608549190737649190809275440655607745038800234274 let ctx = &mut self.context; - let exec = self.handler.execution(); - let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; - let mut _result = match call { - FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, - FrameOrResult::Result(result) => result, + // ctx.evm.touch(address); + let mut acc = match ctx.evm.load_account(erc20_info.token_address) { + Ok(acc) => acc, + Err(e) => { + return Err(EVMError::Custom( + "[MORPH] Token balance is insufficient to pay gas.".to_string(), + )) + } }; + acc.mark_touch(); + let mut acc_vault = match ctx.evm.load_account(L2_FEE_VAULT) { + Ok(acc) => acc, + Err(e) => { + return Err(EVMError::Custom( + "[MORPH] Token balance is insufficient to pay gas.".to_string(), + )) + } + }; + acc_vault.mark_touch(); + transfer_token_sstore(erc20_info, erc20_amount, ctx,true); + + // println!("========balance rt: {:?}, erc20_amount: {:?}", rt); + + // Call transfer(address,uint256) method via EVM + // Method signature: transfer(address,uint256) -> 0xa9059cbb + // let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // // Encode calldata: method_id + padded to address + amount + // let mut calldata = Vec::new(); + // calldata.extend_from_slice(&method_id); + // // calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes + // let mut address_bytes = [0u8; 32]; + // address_bytes[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); + // calldata.extend_from_slice(&address_bytes); + // calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); + // let mut tx = TxEnv { + // caller: erc20_info.caller, + // gas_limit: 1_000_00u64, + // gas_price: U256::ZERO, + // transact_to: TxKind::Call(erc20_info.token_address), + // value: U256::ZERO, + // data: Bytes::from(calldata), + // nonce: None, + // chain_id: None, + // ..Default::default() + // }; + // tx.morph.is_l1_msg = false; + // tx.morph.rlp_bytes = Some(Bytes::default()); + + // let ctx = &mut self.context; + // let exec = self.handler.execution(); + // let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; + // let mut _result = match call { + // FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, + // FrameOrResult::Result(result) => result, + // }; Ok(()) } @@ -522,57 +549,90 @@ impl Evm<'_, EXT, DB> { "[MORPH] Failed to calculate erc20 gas.".to_string(), )); }; - let ctx = &self.context; + let ctx = &mut self.context; let effective_gas_price = ctx.evm.env.effective_gas_price(); let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); if erc20_amount.is_zero() { return Ok(()); } - println!("------reimburse_caller_with_erc20 erc20_amount: {:?}", erc20_amount); - - let ctx = &mut self.context; - let balance_slot :U256= "110218063311400506153027935500334854487342827447185155285887586702483003113927".parse().unwrap(); - let erc20_fee_vault = ctx.evm.sload(erc20_info.token_address, balance_slot).unwrap_or_default(); - println!("------erc20_fee_vault: {:?}", erc20_fee_vault); + transfer_token_sstore(erc20_info, erc20_amount, ctx,false); // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb - let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; - - // Encode calldata: method_id + padded to address + amount - let mut calldata = Vec::with_capacity(68); - calldata.extend_from_slice(&method_id); - calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - let mut address_bytes = [0u8; 32]; - address_bytes[12..32].copy_from_slice(erc20_info.caller.as_slice()); - calldata.extend_from_slice(&address_bytes); - calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); - let mut tx = TxEnv { - caller: L2_FEE_VAULT, - gas_limit: 1_000_00u64, - gas_price: U256::ZERO, - transact_to: TxKind::Call(erc20_info.token_address), - value: U256::ZERO, - data: Bytes::from(calldata), - nonce: None, - chain_id: None, - ..Default::default() - }; - tx.morph.is_l1_msg = false; - tx.morph.rlp_bytes = Some(Bytes::default()); - let ctx = &mut self.context; - let exec = self.handler.execution(); - let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; - let mut _result = match call { - FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, - FrameOrResult::Result(result) => result, - }; + // let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // // Encode calldata: method_id + padded to address + amount + // let mut calldata = Vec::with_capacity(68); + // calldata.extend_from_slice(&method_id); + // let mut address_bytes = [0u8; 32]; + // address_bytes[12..32].copy_from_slice(erc20_info.caller.as_slice()); + // calldata.extend_from_slice(&address_bytes); + // calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); + // let mut tx = TxEnv { + // caller: L2_FEE_VAULT, + // gas_limit: 1_000_00u64, + // gas_price: U256::ZERO, + // transact_to: TxKind::Call(erc20_info.token_address), + // value: U256::ZERO, + // data: Bytes::from(calldata), + // nonce: None, + // chain_id: None, + // ..Default::default() + // }; + // tx.morph.is_l1_msg = false; + // tx.morph.rlp_bytes = Some(Bytes::default()); + // let ctx = &mut self.context; + // let exec = self.handler.execution(); + // let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; + // let mut _result = match call { + // FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, + // FrameOrResult::Result(result) => result, + // }; Ok(()) } } +fn transfer_token_sstore(erc20_info: Erc20FeeInfo, erc20_amount: U256, ctx: &mut Context , forward: bool) { + let (from ,to) = if forward{ + (erc20_info.caller, L2_FEE_VAULT) + }else { + (L2_FEE_VAULT, erc20_info.caller) + }; + // sub amount + let balance_slot = get_mapping_account_slot(erc20_info.balance_slot, from); + let balance = ctx + .evm + .sload(erc20_info.token_address, balance_slot) + .unwrap_or_default(); + let rt = ctx.evm.sstore( + erc20_info.token_address, + balance_slot, + balance.saturating_sub(erc20_amount), + ); + match rt { + Ok(_v) => println!("sub amount sstore ok"), + Err(_e) => println!("sstore error"), + } + + // add amount + let balance_slot = get_mapping_account_slot(erc20_info.balance_slot, to); + let balance = ctx + .evm + .sload(erc20_info.token_address, balance_slot) + .unwrap_or_default(); + let rt = ctx.evm.sstore( + erc20_info.token_address, + balance_slot, + balance.saturating_add(erc20_amount), + ); + match rt { + Ok(_v) => println!("add amount sstore ok"), + Err(_e) => println!("sstore error"), + } +} + #[cfg(test)] mod tests { diff --git a/crates/revm/src/morph.rs b/crates/revm/src/morph.rs index c081eed862..c1f3337eef 100644 --- a/crates/revm/src/morph.rs +++ b/crates/revm/src/morph.rs @@ -2,7 +2,7 @@ pub mod erc20_fee; mod handler_register; mod l1block; -pub use crate::morph::erc20_fee::Erc20FeeInfo; +pub use crate::morph::erc20_fee::{Erc20FeeInfo, get_mapping_account_slot}; pub use crate::morph::handler_register::{ deduct_caller, load_accounts, morph_handle_register, reward_beneficiary, }; diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/erc20_fee.rs index 573a549140..783fdf9809 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/erc20_fee.rs @@ -108,6 +108,13 @@ fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { U256::from_be_bytes(storage_key.0) } +/// Calculate the account's storage slot for a mapping value +pub fn get_mapping_account_slot(slot_index: U256, account: Address) -> U256 { + let mut key = [0u8; 32]; + key[12..32].copy_from_slice(account.as_slice()); + get_mapping_slot(slot_index, key.to_vec()) +} + fn load_mapping_value( db: &mut DB, account: Address, @@ -125,8 +132,6 @@ pub(super) fn get_erc20_balance( account: Address, token_balance_slot: U256, ) -> U256 { - println!("get_erc20_balance. token: {:?}", token); - // If balance slot is provided, try to read directly from storage if !token_balance_slot.is_zero() { let mut data = [0u8; 32]; From 449fd46c08b528d639fece05174d2310e4c99299 Mon Sep 17 00:00:00 2001 From: anylots <22675649+anylots@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:23:22 +0800 Subject: [PATCH 11/14] transfer_token_evm --- crates/revm/src/evm.rs | 184 +++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 109 deletions(-) diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index e41b373e84..3b9419ed21 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -475,66 +475,16 @@ impl Evm<'_, EXT, DB> { )); } - //110218063311400506153027935500334854487342827447185155285887586702483003113927 - //51649299683075463979090664991608549190737649190809275440655607745038800234274 - let ctx = &mut self.context; - // ctx.evm.touch(address); - let mut acc = match ctx.evm.load_account(erc20_info.token_address) { - Ok(acc) => acc, - Err(e) => { - return Err(EVMError::Custom( - "[MORPH] Token balance is insufficient to pay gas.".to_string(), - )) - } - }; - acc.mark_touch(); - let mut acc_vault = match ctx.evm.load_account(L2_FEE_VAULT) { - Ok(acc) => acc, - Err(e) => { - return Err(EVMError::Custom( - "[MORPH] Token balance is insufficient to pay gas.".to_string(), - )) - } - }; - acc_vault.mark_touch(); - transfer_token_sstore(erc20_info, erc20_amount, ctx,true); - - // println!("========balance rt: {:?}, erc20_amount: {:?}", rt); - - // Call transfer(address,uint256) method via EVM - // Method signature: transfer(address,uint256) -> 0xa9059cbb - // let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; - - // // Encode calldata: method_id + padded to address + amount - // let mut calldata = Vec::new(); - // calldata.extend_from_slice(&method_id); - // // calldata.extend_from_slice(&[0u8; 12]); // Pad to address to 32 bytes - // let mut address_bytes = [0u8; 32]; - // address_bytes[12..32].copy_from_slice(L2_FEE_VAULT.as_slice()); - // calldata.extend_from_slice(&address_bytes); - // calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); - // let mut tx = TxEnv { - // caller: erc20_info.caller, - // gas_limit: 1_000_00u64, - // gas_price: U256::ZERO, - // transact_to: TxKind::Call(erc20_info.token_address), - // value: U256::ZERO, - // data: Bytes::from(calldata), - // nonce: None, - // chain_id: None, - // ..Default::default() - // }; - // tx.morph.is_l1_msg = false; - // tx.morph.rlp_bytes = Some(Bytes::default()); - - // let ctx = &mut self.context; - // let exec = self.handler.execution(); - // let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; - // let mut _result = match call { - // FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, - // FrameOrResult::Result(result) => result, - // }; - + if erc20_info.balance_slot.is_zero() { + let _ = self.transfer_token_evm(erc20_info, erc20_amount, true)?; + } else { + let ctx = &mut self.context; + let mut acc = ctx.evm.load_account(erc20_info.token_address)?; + acc.mark_touch(); + let mut acc_vault = ctx.evm.load_account(L2_FEE_VAULT)?; + acc_vault.mark_touch(); + transfer_token_sstore(erc20_info.clone(), erc20_amount, ctx, true)? + } Ok(()) } @@ -546,7 +496,7 @@ impl Evm<'_, EXT, DB> { ) -> Result<(), EVMError> { let Some(erc20_info) = erc20_info else { return Err(EVMError::Custom( - "[MORPH] Failed to calculate erc20 gas.".to_string(), + "[MORPH] Failed to calculate token gas.".to_string(), )); }; let ctx = &mut self.context; @@ -554,50 +504,73 @@ impl Evm<'_, EXT, DB> { let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); if erc20_amount.is_zero() { - return Ok(()); + return Err(EVMError::Custom( + "[MORPH] Failed to calculate token reimburse.".to_string(), + )); } - transfer_token_sstore(erc20_info, erc20_amount, ctx,false); + if erc20_info.balance_slot.is_zero() { + let _ = self.transfer_token_evm(erc20_info, erc20_amount, false)?; + } else { + transfer_token_sstore(erc20_info.clone(), erc20_amount, ctx, false)?; + } + + Ok(()) + } + fn transfer_token_evm( + &mut self, + erc20_info: Erc20FeeInfo, + amount: U256, + forward: bool, + ) -> Result> { + let (from, to) = if forward { + (erc20_info.caller, L2_FEE_VAULT) + } else { + (L2_FEE_VAULT, erc20_info.caller) + }; // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb - // let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; - - // // Encode calldata: method_id + padded to address + amount - // let mut calldata = Vec::with_capacity(68); - // calldata.extend_from_slice(&method_id); - // let mut address_bytes = [0u8; 32]; - // address_bytes[12..32].copy_from_slice(erc20_info.caller.as_slice()); - // calldata.extend_from_slice(&address_bytes); - // calldata.extend_from_slice(&erc20_amount.to_be_bytes::<32>()); - // let mut tx = TxEnv { - // caller: L2_FEE_VAULT, - // gas_limit: 1_000_00u64, - // gas_price: U256::ZERO, - // transact_to: TxKind::Call(erc20_info.token_address), - // value: U256::ZERO, - // data: Bytes::from(calldata), - // nonce: None, - // chain_id: None, - // ..Default::default() - // }; - // tx.morph.is_l1_msg = false; - // tx.morph.rlp_bytes = Some(Bytes::default()); - // let ctx = &mut self.context; - // let exec = self.handler.execution(); - // let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; - // let mut _result = match call { - // FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame)?, - // FrameOrResult::Result(result) => result, - // }; - - Ok(()) + let method_id = [0xa9u8, 0x05, 0x9c, 0xbb]; + + // Encode calldata: method_id + padded to address + amount + let mut calldata = Vec::with_capacity(68); + calldata.extend_from_slice(&method_id); + let mut address_bytes = [0u8; 32]; + address_bytes[12..32].copy_from_slice(to.as_slice()); + calldata.extend_from_slice(&address_bytes); + calldata.extend_from_slice(&amount.to_be_bytes::<32>()); + let mut tx = TxEnv { + caller: from, + gas_limit: 1_000_00u64, + gas_price: U256::ZERO, + transact_to: TxKind::Call(erc20_info.token_address), + value: U256::ZERO, + data: Bytes::from(calldata), + nonce: None, + chain_id: None, + ..Default::default() + }; + tx.morph.is_l1_msg = false; + tx.morph.rlp_bytes = Some(Bytes::default()); + let ctx = &mut self.context; + let exec = self.handler.execution(); + let call = exec.call(ctx, CallInputs::new_boxed(&tx, 1_000_000_000u64).unwrap())?; + match call { + FrameOrResult::Frame(first_frame) => self.run_the_loop(first_frame), + FrameOrResult::Result(result) => Ok(result), + } } } -fn transfer_token_sstore(erc20_info: Erc20FeeInfo, erc20_amount: U256, ctx: &mut Context , forward: bool) { - let (from ,to) = if forward{ +fn transfer_token_sstore( + erc20_info: Erc20FeeInfo, + erc20_amount: U256, + ctx: &mut Context, + forward: bool, +) -> Result<(), EVMError> { + let (from, to) = if forward { (erc20_info.caller, L2_FEE_VAULT) - }else { + } else { (L2_FEE_VAULT, erc20_info.caller) }; // sub amount @@ -606,15 +579,11 @@ fn transfer_token_sstore(erc20_info: Erc20FeeInfo, erc20_amou .evm .sload(erc20_info.token_address, balance_slot) .unwrap_or_default(); - let rt = ctx.evm.sstore( + ctx.evm.sstore( erc20_info.token_address, balance_slot, balance.saturating_sub(erc20_amount), - ); - match rt { - Ok(_v) => println!("sub amount sstore ok"), - Err(_e) => println!("sstore error"), - } + )?; // add amount let balance_slot = get_mapping_account_slot(erc20_info.balance_slot, to); @@ -622,15 +591,12 @@ fn transfer_token_sstore(erc20_info: Erc20FeeInfo, erc20_amou .evm .sload(erc20_info.token_address, balance_slot) .unwrap_or_default(); - let rt = ctx.evm.sstore( + ctx.evm.sstore( erc20_info.token_address, balance_slot, balance.saturating_add(erc20_amount), - ); - match rt { - Ok(_v) => println!("add amount sstore ok"), - Err(_e) => println!("sstore error"), - } + )?; + Ok(()) } #[cfg(test)] From 4fcaf4a510ffc3cd300c7c74daa43005e610580e Mon Sep 17 00:00:00 2001 From: anylots <22675649+anylots@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:17:33 +0800 Subject: [PATCH 12/14] transfer_token_evm --- bins/revm-test/src/bin/morph.rs | 9 +- crates/primitives/src/env.rs | 24 ++-- crates/revm/src/context/inner_evm_context.rs | 4 +- crates/revm/src/evm.rs | 119 ++++++++---------- crates/revm/src/morph.rs | 4 +- .../src/morph/{erc20_fee.rs => token_fee.rs} | 19 +-- 6 files changed, 83 insertions(+), 96 deletions(-) rename crates/revm/src/morph/{erc20_fee.rs => token_fee.rs} (94%) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index bf781acb1b..1fb3d26d3b 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -1,6 +1,6 @@ use revm::{ db::{CacheDB, EmptyDB}, - morph::erc20_fee::L2_FEE_VAULT, + morph::token_fee::L2_FEE_VAULT, primitives::{address, bytes, keccak256, AccountInfo, Address, Bytecode, Bytes, TxEnv, U256}, Database, Evm, }; @@ -33,7 +33,7 @@ fn alt_fee_normal() { ..Default::default() }; - // USDT: + // 1 ETH = 4000 USDT. let (account_from_balance, erc20_value, erc20_value_vault, erc20_balance_evm) = exec_alt_fee_txn(1, U256::from(1u64), U256::from(250000000u64), tx); @@ -134,10 +134,10 @@ fn exec_alt_fee_txn( ); // Set isActive and decimals at slot + 2 - // isActive = true (1), decimals = 18 + // isActive = true (1), decimals = 6 // In storage: rightmost byte (byte 31) is isActive, byte 30 is decimals let mut slot_2_bytes = [0u8; 32]; - slot_2_bytes[30] = 18; // decimals + slot_2_bytes[30] = 6; // decimals slot_2_bytes[31] = 1; // isActive = true let slot_2_value = U256::from_be_bytes(slot_2_bytes); let _ = cache_db.insert_account_storage( @@ -205,7 +205,6 @@ fn exec_alt_fee_txn( .storage(token_account, storage_key_u256) .unwrap_or_default(); println!("account_from_erc20_value: {:?}", erc20_value); - println!("storage_key_u256: {:?}", storage_key_u256); let erc20_value_vault = evm .context diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 6f744c4aa0..85c375fcaa 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -241,7 +241,7 @@ impl Env { pub fn validate_tx_against_state( &self, account: &mut Account, - erc20_info: (U256, U256, U256), + token_info: (U256, U256, U256), ) -> Result<(), InvalidTransaction> { // EIP-3607: Reject transactions from senders with deployed code // This EIP is introduced after london but there was no collision in past @@ -289,11 +289,11 @@ impl Env { // Transfer will be done inside `*_inner` functions. let lack_of_fund_for_max_fee = if self.tx.fee_token_id.unwrap_or_default() != 0 { let mut fee_limit = U256::from(self.tx.fee_limit.unwrap_or_default()); - if fee_limit.is_zero() || fee_limit > erc20_info.0 { - fee_limit = erc20_info.0 + if fee_limit.is_zero() || fee_limit > token_info.0 { + fee_limit = token_info.0 } - let erc20_check = eth_to_erc20(gas_cost, erc20_info.1, erc20_info.2); - erc20_check > fee_limit || self.tx.value > account.info.balance + let token_check = eth_to_token(gas_cost, token_info.1, token_info.2); + token_check > fee_limit || self.tx.value > account.info.balance } else { balance_check > account.info.balance }; @@ -651,10 +651,10 @@ pub struct TxEnv { pub morph: MorphFields, #[cfg(feature = "morph")] - /// For ERC20FeeType + /// For AltFeeType pub fee_token_id: Option, #[cfg(feature = "morph")] - /// For ERC20FeeType + /// For AltFeeType pub fee_limit: Option, } @@ -804,18 +804,18 @@ pub enum AnalysisKind { Analyse, } -pub fn eth_to_erc20(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { +pub fn eth_to_token(eth_amount: U256, rate: U256, token_scale: U256) -> U256 { if rate.is_zero() { return U256::ZERO; } - // EthToERC20 erc20Amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate + // EthToToken token_amount = ethAmount / (tokenRate / tokenScale) = ethAmount * tokenScale / tokenRate // Calculate: (eth_amount * token_scale) / rate - let (erc20_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); + let (token_amount, remainder) = eth_amount.saturating_mul(token_scale).div_rem(rate); // If there's a remainder, round up by adding 1 if !remainder.is_zero() { - erc20_amount.saturating_add(U256::from(1)) + token_amount.saturating_add(U256::from(1)) } else { - erc20_amount + token_amount } } diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index 9002595465..fb35a2a041 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -1,5 +1,5 @@ #[cfg(feature = "morph")] -use crate::morph::Erc20FeeInfo; +use crate::morph::TokenFeeInfo; use crate::{ db::Database, interpreter::{ @@ -37,7 +37,7 @@ pub struct InnerEvmContext { pub l1_block_info: Option, /// Used as temporary value holder to store Erc20 fee info. #[cfg(feature = "morph")] - pub erc20_fee_info: Option, + pub erc20_fee_info: Option, } impl Clone for InnerEvmContext diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 3b9419ed21..b66a3657b8 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -7,9 +7,9 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, - morph::{erc20_fee::L2_FEE_VAULT, get_mapping_account_slot, Erc20FeeInfo}, + morph::{get_mapping_account_slot, token_fee::L2_FEE_VAULT, TokenFeeInfo}, primitives::{ - eth_to_erc20, specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, + eth_to_token, specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, EOF_MAGIC_BYTES, U256, }, @@ -235,12 +235,9 @@ impl Evm<'_, EXT, DB> { let fee_token_id = context.evm.inner.env().tx.fee_token_id.unwrap_or_default(); if fee_token_id != 0 { let caller = context.evm.inner.env.tx.caller; - let erc20_fee_info = crate::morph::Erc20FeeInfo::try_fetch( - &mut context.evm.inner.db, - fee_token_id, - caller, - ) - .map_err(EVMError::Database)?; + let erc20_fee_info = + TokenFeeInfo::try_fetch(&mut context.evm.inner.db, fee_token_id, caller) + .map_err(EVMError::Database)?; context.evm.inner.erc20_fee_info = erc20_fee_info; } let initial_gas_spend = self.preverify_transaction_inner().inspect_err(|_| { @@ -340,32 +337,25 @@ impl Evm<'_, EXT, DB> { fn transact_preverified_inner(&mut self, initial_gas_spend: u64) -> EVMResult { let spec_id = self.spec_id(); - { - let ctx = &mut self.context; - let pre_exec = self.handler.pre_execution(); - // load access list and beneficiary if needed. - pre_exec.load_accounts(ctx)?; + let ctx = &mut self.context; + let pre_exec = self.handler.pre_execution(); + // load access list and beneficiary if needed. + pre_exec.load_accounts(ctx)?; - // load precompiles - let precompiles = pre_exec.load_precompiles(); - ctx.evm.set_precompiles(precompiles); - } + // load precompiles + let precompiles = pre_exec.load_precompiles(); + ctx.evm.set_precompiles(precompiles); // deduce caller balance with its limit. - { - let ctx = &mut self.context; - let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); - if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { - self.deduct_caller_with_erc20(erc20_fee_info)?; - } else { - self.handler.pre_execution().deduct_caller(ctx)?; - } + let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { + self.deduct_caller_with_erc20(erc20_fee_info)?; + } else { + self.handler.pre_execution().deduct_caller(ctx)?; } let ctx = &mut self.context; - let gas_limit = ctx.evm.env.tx.gas_limit - initial_gas_spend; - let pre_exec = self.handler.pre_execution(); // apply EIP-7702 auth list. @@ -413,14 +403,13 @@ impl Evm<'_, EXT, DB> { // calculate final refund and add EIP-7702 refund to gas. post_exec.refund(ctx, result.gas_mut(), eip7702_gas_refund); // Reimburse the caller - { - let ctx = &mut self.context; - let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); - if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { - self.reimburse_caller_with_erc20(erc20_fee_info, result.gas())?; - } else { - post_exec.reimburse_caller(ctx, result.gas())?; - } + + let ctx = &mut self.context; + let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { + self.reimburse_caller_with_erc20(erc20_fee_info, result.gas())?; + } else { + post_exec.reimburse_caller(ctx, result.gas())?; } let ctx = &mut self.context; @@ -434,12 +423,11 @@ impl Evm<'_, EXT, DB> { post_exec.output(ctx, result) } - #[inline] pub fn deduct_caller_with_erc20( &mut self, - erc20_info: Option, + token_info: Option, ) -> Result<(), EVMError> { - let Some(erc20_info) = erc20_info else { + let Some(token_info) = token_info else { return Err(EVMError::Custom( "[MORPH] Failed to calculate erc20 gas.".to_string(), )); @@ -463,38 +451,37 @@ impl Evm<'_, EXT, DB> { let gas_cost = U256::from(ctx.evm.env.tx.gas_limit).saturating_mul(ctx.evm.env.effective_gas_price()); let amount = tx_l1_cost + gas_cost; - let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); - if erc20_amount.is_zero() { + let token_amount = eth_to_token(amount, token_info.price_ratio, token_info.scale); + if token_amount.is_zero() { return Err(EVMError::Custom( "[MORPH] Failed to calculate erc20 gas.".to_string(), )); } - if erc20_amount > erc20_info.balance { + if token_amount > token_info.balance { return Err(EVMError::Custom( "[MORPH] Token balance is insufficient to pay gas.".to_string(), )); } - if erc20_info.balance_slot.is_zero() { - let _ = self.transfer_token_evm(erc20_info, erc20_amount, true)?; + if token_info.balance_slot.is_zero() { + let _ = self.transfer_token_evm(token_info, token_amount, true)?; } else { let ctx = &mut self.context; - let mut acc = ctx.evm.load_account(erc20_info.token_address)?; + let mut acc = ctx.evm.load_account(token_info.token_address)?; acc.mark_touch(); let mut acc_vault = ctx.evm.load_account(L2_FEE_VAULT)?; acc_vault.mark_touch(); - transfer_token_sstore(erc20_info.clone(), erc20_amount, ctx, true)? + transfer_token_sstore(token_info.clone(), token_amount, ctx, true)? } Ok(()) } - #[inline] pub fn reimburse_caller_with_erc20( &mut self, - erc20_info: Option, + token_info: Option, gas: &Gas, ) -> Result<(), EVMError> { - let Some(erc20_info) = erc20_info else { + let Some(token_info) = token_info else { return Err(EVMError::Custom( "[MORPH] Failed to calculate token gas.".to_string(), )); @@ -502,16 +489,16 @@ impl Evm<'_, EXT, DB> { let ctx = &mut self.context; let effective_gas_price = ctx.evm.env.effective_gas_price(); let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); - let erc20_amount = eth_to_erc20(amount, erc20_info.price_ratio, erc20_info.scale); - if erc20_amount.is_zero() { + let token_amount = eth_to_token(amount, token_info.price_ratio, token_info.scale); + if token_amount.is_zero() { return Err(EVMError::Custom( "[MORPH] Failed to calculate token reimburse.".to_string(), )); } - if erc20_info.balance_slot.is_zero() { - let _ = self.transfer_token_evm(erc20_info, erc20_amount, false)?; + if token_info.balance_slot.is_zero() { + let _ = self.transfer_token_evm(token_info, token_amount, false)?; } else { - transfer_token_sstore(erc20_info.clone(), erc20_amount, ctx, false)?; + transfer_token_sstore(token_info.clone(), token_amount, ctx, false)?; } Ok(()) @@ -519,14 +506,14 @@ impl Evm<'_, EXT, DB> { fn transfer_token_evm( &mut self, - erc20_info: Erc20FeeInfo, + token_info: TokenFeeInfo, amount: U256, forward: bool, ) -> Result> { let (from, to) = if forward { - (erc20_info.caller, L2_FEE_VAULT) + (token_info.caller, L2_FEE_VAULT) } else { - (L2_FEE_VAULT, erc20_info.caller) + (L2_FEE_VAULT, token_info.caller) }; // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb @@ -543,7 +530,7 @@ impl Evm<'_, EXT, DB> { caller: from, gas_limit: 1_000_00u64, gas_price: U256::ZERO, - transact_to: TxKind::Call(erc20_info.token_address), + transact_to: TxKind::Call(token_info.token_address), value: U256::ZERO, data: Bytes::from(calldata), nonce: None, @@ -563,36 +550,36 @@ impl Evm<'_, EXT, DB> { } fn transfer_token_sstore( - erc20_info: Erc20FeeInfo, + token_info: TokenFeeInfo, erc20_amount: U256, ctx: &mut Context, forward: bool, ) -> Result<(), EVMError> { let (from, to) = if forward { - (erc20_info.caller, L2_FEE_VAULT) + (token_info.caller, L2_FEE_VAULT) } else { - (L2_FEE_VAULT, erc20_info.caller) + (L2_FEE_VAULT, token_info.caller) }; // sub amount - let balance_slot = get_mapping_account_slot(erc20_info.balance_slot, from); + let balance_slot = get_mapping_account_slot(token_info.balance_slot, from); let balance = ctx .evm - .sload(erc20_info.token_address, balance_slot) + .sload(token_info.token_address, balance_slot) .unwrap_or_default(); ctx.evm.sstore( - erc20_info.token_address, + token_info.token_address, balance_slot, balance.saturating_sub(erc20_amount), )?; // add amount - let balance_slot = get_mapping_account_slot(erc20_info.balance_slot, to); + let balance_slot = get_mapping_account_slot(token_info.balance_slot, to); let balance = ctx .evm - .sload(erc20_info.token_address, balance_slot) + .sload(token_info.token_address, balance_slot) .unwrap_or_default(); ctx.evm.sstore( - erc20_info.token_address, + token_info.token_address, balance_slot, balance.saturating_add(erc20_amount), )?; diff --git a/crates/revm/src/morph.rs b/crates/revm/src/morph.rs index c1f3337eef..3b8cd3f782 100644 --- a/crates/revm/src/morph.rs +++ b/crates/revm/src/morph.rs @@ -1,8 +1,8 @@ -pub mod erc20_fee; +pub mod token_fee; mod handler_register; mod l1block; -pub use crate::morph::erc20_fee::{Erc20FeeInfo, get_mapping_account_slot}; +pub use crate::morph::token_fee::{TokenFeeInfo, get_mapping_account_slot}; pub use crate::morph::handler_register::{ deduct_caller, load_accounts, morph_handle_register, reward_beneficiary, }; diff --git a/crates/revm/src/morph/erc20_fee.rs b/crates/revm/src/morph/token_fee.rs similarity index 94% rename from crates/revm/src/morph/erc20_fee.rs rename to crates/revm/src/morph/token_fee.rs index 783fdf9809..db9d14b859 100644 --- a/crates/revm/src/morph/erc20_fee.rs +++ b/crates/revm/src/morph/token_fee.rs @@ -1,19 +1,19 @@ use crate::primitives::{address, Address, U256}; -use crate::primitives::{Bytes, TxEnv, TxKind}; +use crate::primitives::{keccak256, Bytes, TxEnv, TxKind}; use crate::{Database, Evm}; // TokenRegistry is the storage slot for mapping(uint16 => TokenInfo) - slot 0 const TOKEN_REGISTRY_SLOT: U256 = U256::from_limbs([0u64, 0, 0, 0]); // PriceRatio is the storage slot for mapping(uint16 => uint256) - slot 2 const PRICE_RATIO_SLOT: U256 = U256::from_limbs([2u64, 0, 0, 0]); -// System address for receiving ERC20 fees +// System address for receiving Alt Token fees pub const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); // System address for L2 token registry pub const L2_TOKEN_REGISTRY_ADDRESS: Address = address!("5300000000000000000000000000000000000021"); #[derive(Clone, Debug, Default)] -pub struct Erc20FeeInfo { - /// The ERC20 token address +pub struct TokenFeeInfo { + /// The fee token address pub token_address: Address, /// Whether the token is active pub is_active: bool, @@ -31,13 +31,13 @@ pub struct Erc20FeeInfo { pub balance_slot: U256, } -impl Erc20FeeInfo { +impl TokenFeeInfo { // Get the token information for gas payment from the state db. pub fn try_fetch( db: &mut DB, token_id: u16, caller: Address, - ) -> Result, DB::Error> { + ) -> Result, DB::Error> { // Get the base slot for this token_id in tokenRegistry mapping let mut token_id_bytes = [0u8; 32]; token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); @@ -85,7 +85,7 @@ impl Erc20FeeInfo { // Get caller's token balance let caller_token_balance = get_erc20_balance(db, token_address, caller, token_balance_slot); - let erc20_fee = Erc20FeeInfo { + let token_fee = TokenFeeInfo { token_address, is_active, decimals, @@ -96,7 +96,7 @@ impl Erc20FeeInfo { balance_slot: token_balance_slot, }; - Ok(Some(erc20_fee)) + Ok(Some(token_fee)) } } @@ -104,11 +104,12 @@ impl Erc20FeeInfo { fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { let mut pre_image = slot_index.to_be_bytes_vec(); key.append(&mut pre_image); - let storage_key = crate::primitives::keccak256(key); + let storage_key = keccak256(key); U256::from_be_bytes(storage_key.0) } /// Calculate the account's storage slot for a mapping value +#[inline] pub fn get_mapping_account_slot(slot_index: U256, account: Address) -> U256 { let mut key = [0u8; 32]; key[12..32].copy_from_slice(account.as_slice()); From c435b5935cdb50918945e1d2bcab97e8532dfdaf Mon Sep 17 00:00:00 2001 From: anylots <22675649+anylots@users.noreply.github.com> Date: Fri, 28 Nov 2025 00:10:15 +0800 Subject: [PATCH 13/14] dev&test for TokenFeeInfo --- bins/revm-test/src/bin/morph.rs | 29 +++++++++-- crates/primitives/src/env.rs | 2 +- crates/revm/src/context/evm_context.rs | 4 +- crates/revm/src/context/inner_evm_context.rs | 12 ++--- crates/revm/src/evm.rs | 50 ++++++++++++------- crates/revm/src/handler/mainnet/validation.rs | 12 ++++- crates/revm/src/morph.rs | 4 +- crates/revm/src/morph/token_fee.rs | 37 ++++++++------ 8 files changed, 100 insertions(+), 50 deletions(-) diff --git a/bins/revm-test/src/bin/morph.rs b/bins/revm-test/src/bin/morph.rs index 1fb3d26d3b..f9c8d8c5bb 100644 --- a/bins/revm-test/src/bin/morph.rs +++ b/bins/revm-test/src/bin/morph.rs @@ -1,11 +1,13 @@ use revm::{ db::{CacheDB, EmptyDB}, - morph::token_fee::L2_FEE_VAULT, - primitives::{address, bytes, keccak256, AccountInfo, Address, Bytecode, Bytes, TxEnv, U256}, + primitives::{ + address, bytes, keccak256, AccountInfo, Address, BlockEnv, Bytecode, Bytes, TxEnv, U256, + }, Database, Evm, }; static ERC20_DEPLOYED_CODE : Bytes = bytes!("608060405234801561001057600080fd5b50600436106100a95760003560e01c80633950935111610071578063395093511461016857806370a082311461019857806395d89b41146101c8578063a457c2d7146101e6578063a9059cbb14610216578063dd62ed3e14610246576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b6610276565b6040516100c39190610b0c565b60405180910390f35b6100e660048036038101906100e19190610bc7565b610308565b6040516100f39190610c22565b60405180910390f35b61010461032b565b6040516101119190610c4c565b60405180910390f35b610134600480360381019061012f9190610c67565b610335565b6040516101419190610c22565b60405180910390f35b610152610364565b60405161015f9190610cd6565b60405180910390f35b610182600480360381019061017d9190610bc7565b61036d565b60405161018f9190610c22565b60405180910390f35b6101b260048036038101906101ad9190610cf1565b6103a4565b6040516101bf9190610c4c565b60405180910390f35b6101d06103ec565b6040516101dd9190610b0c565b60405180910390f35b61020060048036038101906101fb9190610bc7565b61047e565b60405161020d9190610c22565b60405180910390f35b610230600480360381019061022b9190610bc7565b6104f5565b60405161023d9190610c22565b60405180910390f35b610260600480360381019061025b9190610d1e565b610518565b60405161026d9190610c4c565b60405180910390f35b60606003805461028590610d8d565b80601f01602080910402602001604051908101604052809291908181526020018280546102b190610d8d565b80156102fe5780601f106102d3576101008083540402835291602001916102fe565b820191906000526020600020905b8154815290600101906020018083116102e157829003601f168201915b5050505050905090565b60008061031361059f565b90506103208185856105a7565b600191505092915050565b6000600254905090565b60008061034061059f565b905061034d858285610770565b6103588585856107fc565b60019150509392505050565b60006006905090565b60008061037861059f565b905061039981858561038a8589610518565b6103949190610ded565b6105a7565b600191505092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6060600480546103fb90610d8d565b80601f016020809104026020016040519081016040528092919081815260200182805461042790610d8d565b80156104745780601f1061044957610100808354040283529160200191610474565b820191906000526020600020905b81548152906001019060200180831161045757829003601f168201915b5050505050905090565b60008061048961059f565b905060006104978286610518565b9050838110156104dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104d390610e93565b60405180910390fd5b6104e982868684036105a7565b60019250505092915050565b60008061050061059f565b905061050d8185856107fc565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610616576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060d90610f25565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610685576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161067c90610fb7565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516107639190610c4c565b60405180910390a3505050565b600061077c8484610518565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146107f657818110156107e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107df90611023565b60405180910390fd5b6107f584848484036105a7565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361086b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610862906110b5565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108da576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108d190611147565b60405180910390fd5b6108e5838383610a72565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561096b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610962906111d9565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610a599190610c4c565b60405180910390a3610a6c848484610a77565b50505050565b505050565b505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610ab6578082015181840152602081019050610a9b565b60008484015250505050565b6000601f19601f8301169050919050565b6000610ade82610a7c565b610ae88185610a87565b9350610af8818560208601610a98565b610b0181610ac2565b840191505092915050565b60006020820190508181036000830152610b268184610ad3565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610b5e82610b33565b9050919050565b610b6e81610b53565b8114610b7957600080fd5b50565b600081359050610b8b81610b65565b92915050565b6000819050919050565b610ba481610b91565b8114610baf57600080fd5b50565b600081359050610bc181610b9b565b92915050565b60008060408385031215610bde57610bdd610b2e565b5b6000610bec85828601610b7c565b9250506020610bfd85828601610bb2565b9150509250929050565b60008115159050919050565b610c1c81610c07565b82525050565b6000602082019050610c376000830184610c13565b92915050565b610c4681610b91565b82525050565b6000602082019050610c616000830184610c3d565b92915050565b600080600060608486031215610c8057610c7f610b2e565b5b6000610c8e86828701610b7c565b9350506020610c9f86828701610b7c565b9250506040610cb086828701610bb2565b9150509250925092565b600060ff82169050919050565b610cd081610cba565b82525050565b6000602082019050610ceb6000830184610cc7565b92915050565b600060208284031215610d0757610d06610b2e565b5b6000610d1584828501610b7c565b91505092915050565b60008060408385031215610d3557610d34610b2e565b5b6000610d4385828601610b7c565b9250506020610d5485828601610b7c565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610da557607f821691505b602082108103610db857610db7610d5e565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df882610b91565b9150610e0383610b91565b9250828201905080821115610e1b57610e1a610dbe565b5b92915050565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b6000610e7d602583610a87565b9150610e8882610e21565b604082019050919050565b60006020820190508181036000830152610eac81610e70565b9050919050565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b6000610f0f602483610a87565b9150610f1a82610eb3565b604082019050919050565b60006020820190508181036000830152610f3e81610f02565b9050919050565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b6000610fa1602283610a87565b9150610fac82610f45565b604082019050919050565b60006020820190508181036000830152610fd081610f94565b9050919050565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b600061100d601d83610a87565b915061101882610fd7565b602082019050919050565b6000602082019050818103600083015261103c81611000565b9050919050565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b600061109f602583610a87565b91506110aa82611043565b604082019050919050565b600060208201905081810360008301526110ce81611092565b9050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b6000611131602383610a87565b915061113c826110d5565b604082019050919050565b6000602082019050818103600083015261116081611124565b9050919050565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b60006111c3602683610a87565b91506111ce82611167565b604082019050919050565b600060208201905081810360008301526111f2816111b6565b905091905056fea2646970667358221220bd76a0877c61d26a928dd36a2ac3491d00e9086a429df7883853cc988a8c1cbf64736f6c63430008120033"); +const L2_FEE_VAULT: Address = address!("530000000000000000000000000000000000000a"); fn main() { println!("start test eth_fee_normal"); @@ -62,6 +64,7 @@ fn exec_alt_fee_txn( tx: TxEnv, ) -> (U256, U256, U256, U256) { let account_from = tx.caller; + let account_to = *tx.transact_to.to().unwrap(); let mut cache_db = CacheDB::new(EmptyDB::default()); let token_account = address!("fab77965cAfB593Bd86E2e8073407CAb7fD2f6c4"); @@ -102,7 +105,7 @@ fn exec_alt_fee_txn( // Calculate base slot for tokenRegistry[token_id] // tokenRegistry is at slot 0 - let token_registry_slot = U256::ZERO.to_be_bytes_vec(); + let token_registry_slot = U256::from(151u64).to_be_bytes_vec(); let mut token_id_bytes = [0u8; 32]; token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); @@ -155,7 +158,7 @@ fn exec_alt_fee_txn( // Set priceRatio for tokenID // priceRatio is at slot 2 - let price_ratio_slot = U256::from(2).to_be_bytes_vec(); + let price_ratio_slot = U256::from(153u64).to_be_bytes_vec(); let mut price_ratio_pre_image = token_id_bytes.to_vec(); price_ratio_pre_image.extend_from_slice(&price_ratio_slot); @@ -176,7 +179,12 @@ fn exec_alt_fee_txn( code_size: 0, }; cache_db.insert_account_info(account_from, acc_info.clone()); - let mut evm = Evm::builder().with_db(&mut cache_db).build(); + let mut block_env = BlockEnv::default(); + block_env.coinbase = L2_FEE_VAULT; + let mut evm = Evm::builder() + .with_db(&mut cache_db) + .with_block_env(block_env) + .build(); // use erc20 gas token for txn. let mut tx = tx; @@ -198,6 +206,17 @@ fn exec_alt_fee_txn( .balance; println!("account_from_balance: {:?}", account_from_balance); + let account_to_balance = evm + .context + .evm + .inner + .db + .load_account(account_to) + .unwrap() + .info + .balance; + println!("account_to_balance: {:?}", account_to_balance); + let erc20_value = evm .context .evm diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 85c375fcaa..11318c95c0 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -293,7 +293,7 @@ impl Env { fee_limit = token_info.0 } let token_check = eth_to_token(gas_cost, token_info.1, token_info.2); - token_check > fee_limit || self.tx.value > account.info.balance + token_check.is_zero() || token_check > fee_limit || self.tx.value > account.info.balance } else { balance_check > account.info.balance }; diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 3acee3ab1c..e527917592 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -531,7 +531,7 @@ pub(crate) mod test_utils { #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, #[cfg(feature = "morph")] - erc20_fee_info: None, + token_fee_info: None, }, precompiles: ContextPrecompiles::default(), } @@ -548,7 +548,7 @@ pub(crate) mod test_utils { #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, #[cfg(feature = "morph")] - erc20_fee_info: None, + token_fee_info: None, }, precompiles: ContextPrecompiles::default(), } diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index fb35a2a041..6d54eb4b43 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -35,9 +35,9 @@ pub struct InnerEvmContext { /// Used as temporary value holder to store L1 block info. #[cfg(feature = "morph")] pub l1_block_info: Option, - /// Used as temporary value holder to store Erc20 fee info. + /// Used as temporary value holder to store alt fee info. #[cfg(feature = "morph")] - pub erc20_fee_info: Option, + pub token_fee_info: Option, } impl Clone for InnerEvmContext @@ -53,7 +53,7 @@ where #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: self.l1_block_info.clone(), #[cfg(feature = "morph")] - erc20_fee_info: self.erc20_fee_info.clone(), + token_fee_info: self.token_fee_info.clone(), } } } @@ -68,7 +68,7 @@ impl InnerEvmContext { #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, #[cfg(feature = "morph")] - erc20_fee_info: None, + token_fee_info: None, } } @@ -83,7 +83,7 @@ impl InnerEvmContext { #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: None, #[cfg(feature = "morph")] - erc20_fee_info: None, + token_fee_info: None, } } @@ -100,7 +100,7 @@ impl InnerEvmContext { #[cfg(any(feature = "optimism", feature = "morph"))] l1_block_info: self.l1_block_info, #[cfg(feature = "morph")] - erc20_fee_info: self.erc20_fee_info, + token_fee_info: self.token_fee_info, } } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index b66a3657b8..33eeefcf75 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -1,5 +1,3 @@ -use revm_interpreter::Gas; - use crate::{ builder::{EvmBuilder, HandlerStage, SetGenericStage}, db::{Database, DatabaseCommit, EmptyDB}, @@ -7,7 +5,7 @@ use crate::{ interpreter::{ CallInputs, CreateInputs, EOFCreateInputs, Host, InterpreterAction, SharedMemory, }, - morph::{get_mapping_account_slot, token_fee::L2_FEE_VAULT, TokenFeeInfo}, + morph::{get_mapping_account_slot, TokenFeeInfo}, primitives::{ eth_to_token, specification::SpecId, BlockEnv, Bytes, CfgEnv, EVMError, EVMResult, EnvWithHandlerCfg, ExecutionResult, HandlerCfg, ResultAndState, TxEnv, TxKind, @@ -16,6 +14,7 @@ use crate::{ Context, ContextWithHandlerCfg, Frame, FrameOrResult, FrameResult, }; use core::fmt; +use revm_interpreter::Gas; use std::{boxed::Box, vec::Vec}; /// EVM call stack limit. @@ -235,10 +234,10 @@ impl Evm<'_, EXT, DB> { let fee_token_id = context.evm.inner.env().tx.fee_token_id.unwrap_or_default(); if fee_token_id != 0 { let caller = context.evm.inner.env.tx.caller; - let erc20_fee_info = + let token_fee_info = TokenFeeInfo::try_fetch(&mut context.evm.inner.db, fee_token_id, caller) .map_err(EVMError::Database)?; - context.evm.inner.erc20_fee_info = erc20_fee_info; + context.evm.inner.token_fee_info = token_fee_info; } let initial_gas_spend = self.preverify_transaction_inner().inspect_err(|_| { self.clear(); @@ -347,9 +346,9 @@ impl Evm<'_, EXT, DB> { ctx.evm.set_precompiles(precompiles); // deduce caller balance with its limit. - let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + let token_fee_info = ctx.evm.inner.token_fee_info.clone(); if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { - self.deduct_caller_with_erc20(erc20_fee_info)?; + self.deduct_caller_with_erc20(token_fee_info)?; } else { self.handler.pre_execution().deduct_caller(ctx)?; } @@ -405,9 +404,9 @@ impl Evm<'_, EXT, DB> { // Reimburse the caller let ctx = &mut self.context; - let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); + let token_fee_info = ctx.evm.inner.token_fee_info.clone(); if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { - self.reimburse_caller_with_erc20(erc20_fee_info, result.gas())?; + self.reimburse_caller_with_erc20(token_fee_info, result.gas())?; } else { post_exec.reimburse_caller(ctx, result.gas())?; } @@ -415,8 +414,8 @@ impl Evm<'_, EXT, DB> { let ctx = &mut self.context; let post_exec = self.handler.post_execution(); // Reward beneficiary - let erc20_fee_info = ctx.evm.inner.erc20_fee_info.clone(); - if erc20_fee_info.is_none() { + let token_fee_info = ctx.evm.inner.token_fee_info.clone(); + if token_fee_info.is_none() { post_exec.reward_beneficiary(ctx, result.gas())?; } // Returns output of transaction. @@ -469,10 +468,22 @@ impl Evm<'_, EXT, DB> { let ctx = &mut self.context; let mut acc = ctx.evm.load_account(token_info.token_address)?; acc.mark_touch(); - let mut acc_vault = ctx.evm.load_account(L2_FEE_VAULT)?; - acc_vault.mark_touch(); transfer_token_sstore(token_info.clone(), token_amount, ctx, true)? } + + let ctx = &mut self.context; + let mut caller_account = ctx + .evm + .inner + .journaled_state + .load_account(ctx.evm.inner.env.tx.caller, &mut ctx.evm.inner.db)?; + + if matches!(ctx.evm.inner.env.tx.transact_to, TxKind::Call(_)) { + // Nonce is already checked + caller_account.data.info.nonce = caller_account.data.info.nonce.saturating_add(1); + } + caller_account.mark_touch(); + Ok(()) } @@ -489,6 +500,9 @@ impl Evm<'_, EXT, DB> { let ctx = &mut self.context; let effective_gas_price = ctx.evm.env.effective_gas_price(); let amount = effective_gas_price * U256::from(gas.remaining() + gas.refunded() as u64); + if amount.is_zero() { + return Ok(()); + } let token_amount = eth_to_token(amount, token_info.price_ratio, token_info.scale); if token_amount.is_zero() { return Err(EVMError::Custom( @@ -510,10 +524,11 @@ impl Evm<'_, EXT, DB> { amount: U256, forward: bool, ) -> Result> { + let l2_fee_vault = self.context.env().block.coinbase; let (from, to) = if forward { - (token_info.caller, L2_FEE_VAULT) + (token_info.caller, l2_fee_vault) } else { - (L2_FEE_VAULT, token_info.caller) + (l2_fee_vault, token_info.caller) }; // Call transfer(address,uint256) method via EVM // Method signature: transfer(address,uint256) -> 0xa9059cbb @@ -555,10 +570,11 @@ fn transfer_token_sstore( ctx: &mut Context, forward: bool, ) -> Result<(), EVMError> { + let l2_fee_vault = ctx.env().block.coinbase; let (from, to) = if forward { - (token_info.caller, L2_FEE_VAULT) + (token_info.caller, l2_fee_vault) } else { - (L2_FEE_VAULT, token_info.caller) + (l2_fee_vault, token_info.caller) }; // sub amount let balance_slot = get_mapping_account_slot(token_info.balance_slot, from); diff --git a/crates/revm/src/handler/mainnet/validation.rs b/crates/revm/src/handler/mainnet/validation.rs index 0a59aa59ca..8c80d20899 100644 --- a/crates/revm/src/handler/mainnet/validation.rs +++ b/crates/revm/src/handler/mainnet/validation.rs @@ -26,7 +26,15 @@ pub fn validate_tx_against_state( .journaled_state .load_code(tx_caller, &mut context.evm.inner.db)?; - let erc20_info = match &context.evm.inner.erc20_fee_info { + if context.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 + && context.evm.inner.token_fee_info.is_none() + { + return Err(EVMError::Custom( + "[MORPH] Failed to load token_fee_info.".to_string(), + )); + } + + let token_fee_info = match &context.evm.inner.token_fee_info { Some(fee_info) => (fee_info.balance, fee_info.price_ratio, fee_info.scale), None => (U256::ZERO, U256::ZERO, U256::ZERO), }; @@ -35,7 +43,7 @@ pub fn validate_tx_against_state( .evm .inner .env - .validate_tx_against_state::(caller_account.data, erc20_info) + .validate_tx_against_state::(caller_account.data, token_fee_info) .map_err(EVMError::Transaction)?; Ok(()) diff --git a/crates/revm/src/morph.rs b/crates/revm/src/morph.rs index 3b8cd3f782..5c6c3fdbab 100644 --- a/crates/revm/src/morph.rs +++ b/crates/revm/src/morph.rs @@ -1,9 +1,9 @@ -pub mod token_fee; mod handler_register; mod l1block; +pub mod token_fee; -pub use crate::morph::token_fee::{TokenFeeInfo, get_mapping_account_slot}; pub use crate::morph::handler_register::{ deduct_caller, load_accounts, morph_handle_register, reward_beneficiary, }; pub use crate::morph::l1block::{L1BlockInfo, L1_GAS_PRICE_ORACLE_ADDRESS}; +pub use crate::morph::token_fee::{get_mapping_account_slot, get_mapping_slot, TokenFeeInfo}; diff --git a/crates/revm/src/morph/token_fee.rs b/crates/revm/src/morph/token_fee.rs index db9d14b859..8b7c09da3f 100644 --- a/crates/revm/src/morph/token_fee.rs +++ b/crates/revm/src/morph/token_fee.rs @@ -1,13 +1,12 @@ -use crate::primitives::{address, Address, U256}; +use crate::primitives::{address, Address, EVMError, U256}; use crate::primitives::{keccak256, Bytes, TxEnv, TxKind}; use crate::{Database, Evm}; -// TokenRegistry is the storage slot for mapping(uint16 => TokenInfo) - slot 0 -const TOKEN_REGISTRY_SLOT: U256 = U256::from_limbs([0u64, 0, 0, 0]); -// PriceRatio is the storage slot for mapping(uint16 => uint256) - slot 2 -const PRICE_RATIO_SLOT: U256 = U256::from_limbs([2u64, 0, 0, 0]); -// System address for receiving Alt Token fees -pub const L2_FEE_VAULT: Address = address!("0e87cd091e091562F25CB1cf4641065dA2C049F5"); +// https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/system/L2TokenRegistry.sol +// TokenRegistry is the storage slot for mapping(uint16 => TokenInfo) - slot 151 +const TOKEN_REGISTRY_SLOT: U256 = U256::from_limbs([151u64, 0, 0, 0]); +// PriceRatio is the storage slot for mapping(uint16 => uint256) - slot 153 +const PRICE_RATIO_SLOT: U256 = U256::from_limbs([153u64, 0, 0, 0]); // System address for L2 token registry pub const L2_TOKEN_REGISTRY_ADDRESS: Address = address!("5300000000000000000000000000000000000021"); @@ -51,12 +50,18 @@ impl TokenFeeInfo { // Read tokenAddress from slot + 0 let slot_0 = db.storage(L2_TOKEN_REGISTRY_ADDRESS, token_registry_base)?; let token_address = Address::from_word(slot_0.into()); + if token_address == Address::default() { + return Ok(None); + } // Read balanceSlot from slot + 1 - let token_balance_slot = db.storage( + let mut token_balance_slot = db.storage( L2_TOKEN_REGISTRY_ADDRESS, token_registry_base + U256::from(1), )?; + if !token_balance_slot.is_zero() { + token_balance_slot = token_balance_slot.saturating_sub(U256::from(1u64)); + } // Read isActive and decimals from slot + 2 // In big-endian representation, rightmost byte is the lowest position @@ -84,7 +89,8 @@ impl TokenFeeInfo { )?; // Get caller's token balance - let caller_token_balance = get_erc20_balance(db, token_address, caller, token_balance_slot); + let caller_token_balance = + get_erc20_balance(db, token_address, caller, token_balance_slot)?; let token_fee = TokenFeeInfo { token_address, is_active, @@ -101,7 +107,7 @@ impl TokenFeeInfo { } /// Calculate the storage slot for a mapping value -fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { +pub fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { let mut pre_image = slot_index.to_be_bytes_vec(); key.append(&mut pre_image); let storage_key = keccak256(key); @@ -132,13 +138,13 @@ pub(super) fn get_erc20_balance( token: Address, account: Address, token_balance_slot: U256, -) -> U256 { +) -> Result { // If balance slot is provided, try to read directly from storage if !token_balance_slot.is_zero() { let mut data = [0u8; 32]; data[12..32].copy_from_slice(account.as_slice()); if let Ok(balance) = load_mapping_value(db, token, token_balance_slot, data.to_vec()) { - return balance; + return Ok(balance); } } @@ -175,12 +181,13 @@ pub(super) fn get_erc20_balance( // Parse the returned balance (32 bytes) if let Some(output) = result.result.output() { if output.len() >= 32 { - return U256::from_be_slice(&output[..32]); + return Ok(U256::from_be_slice(&output[..32])); } } } - U256::ZERO + Ok(U256::ZERO) } - Err(_e) => U256::ZERO, + Err(EVMError::Database(db_err)) => Err(db_err), + Err(_) => Ok(U256::ZERO), } } From f0e7bb9f61296d7e560bfcbf7b0de47362f77468 Mon Sep 17 00:00:00 2001 From: anylots <22675649+anylots@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:27:47 +0800 Subject: [PATCH 14/14] rename token gas func_name --- crates/revm/src/evm.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 33eeefcf75..d55926492d 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -348,7 +348,7 @@ impl Evm<'_, EXT, DB> { // deduce caller balance with its limit. let token_fee_info = ctx.evm.inner.token_fee_info.clone(); if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { - self.deduct_caller_with_erc20(token_fee_info)?; + self.deduct_caller_with_token(token_fee_info)?; } else { self.handler.pre_execution().deduct_caller(ctx)?; } @@ -406,7 +406,7 @@ impl Evm<'_, EXT, DB> { let ctx = &mut self.context; let token_fee_info = ctx.evm.inner.token_fee_info.clone(); if ctx.evm.inner.env.tx.fee_token_id.unwrap_or_default() != 0 { - self.reimburse_caller_with_erc20(token_fee_info, result.gas())?; + self.reimburse_caller_with_token(token_fee_info, result.gas())?; } else { post_exec.reimburse_caller(ctx, result.gas())?; } @@ -422,13 +422,13 @@ impl Evm<'_, EXT, DB> { post_exec.output(ctx, result) } - pub fn deduct_caller_with_erc20( + pub fn deduct_caller_with_token( &mut self, token_info: Option, ) -> Result<(), EVMError> { let Some(token_info) = token_info else { return Err(EVMError::Custom( - "[MORPH] Failed to calculate erc20 gas.".to_string(), + "[MORPH] Failed to calculate token gas.".to_string(), )); }; @@ -453,7 +453,7 @@ impl Evm<'_, EXT, DB> { let token_amount = eth_to_token(amount, token_info.price_ratio, token_info.scale); if token_amount.is_zero() { return Err(EVMError::Custom( - "[MORPH] Failed to calculate erc20 gas.".to_string(), + "[MORPH] Failed to calculate token gas.".to_string(), )); } if token_amount > token_info.balance { @@ -487,7 +487,7 @@ impl Evm<'_, EXT, DB> { Ok(()) } - pub fn reimburse_caller_with_erc20( + pub fn reimburse_caller_with_token( &mut self, token_info: Option, gas: &Gas, @@ -566,7 +566,7 @@ impl Evm<'_, EXT, DB> { fn transfer_token_sstore( token_info: TokenFeeInfo, - erc20_amount: U256, + amount: U256, ctx: &mut Context, forward: bool, ) -> Result<(), EVMError> { @@ -585,7 +585,7 @@ fn transfer_token_sstore( ctx.evm.sstore( token_info.token_address, balance_slot, - balance.saturating_sub(erc20_amount), + balance.saturating_sub(amount), )?; // add amount @@ -597,7 +597,7 @@ fn transfer_token_sstore( ctx.evm.sstore( token_info.token_address, balance_slot, - balance.saturating_add(erc20_amount), + balance.saturating_add(amount), )?; Ok(()) }