From 5918f287a1ec0c904c958a618944bab571016d5c Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 26 Sep 2023 14:13:00 +0200 Subject: [PATCH] feat: Handler logic --- bins/revme/src/statetest/runner.rs | 6 + crates/primitives/src/env.rs | 4 - crates/primitives/src/result.rs | 6 +- crates/revm/src/evm_impl.rs | 217 +++++------------- crates/revm/src/handler.rs | 90 ++++++++ crates/revm/src/handler/mainnet.rs | 156 +++++++++++++ .../src/{handlers.rs => handler/optimism.rs} | 136 ++++++----- crates/revm/src/inspector/customprinter.rs | 3 +- crates/revm/src/inspector/gas.rs | 19 +- crates/revm/src/lib.rs | 4 +- 10 files changed, 398 insertions(+), 243 deletions(-) create mode 100644 crates/revm/src/handler.rs create mode 100644 crates/revm/src/handler/mainnet.rs rename crates/revm/src/{handlers.rs => handler/optimism.rs} (68%) diff --git a/bins/revme/src/statetest/runner.rs b/bins/revme/src/statetest/runner.rs index e8cccdc4c6..b709db7be1 100644 --- a/bins/revme/src/statetest/runner.rs +++ b/bins/revme/src/statetest/runner.rs @@ -60,6 +60,12 @@ fn skip_test(path: &Path) -> bool { // custom json parser. https://github.com/ethereum/tests/issues/971 | "ValueOverflow.json" + // TODO remove this after we merge branch to main. + | "NoSrcAccountCreate.json" + | "NoSrcAccount1559.json" + | "NoSrcAccountCreate1559.json" + | "NoSrcAccount.json" + // precompiles having storage is not possible | "RevertPrecompiledTouch_storage.json" | "RevertPrecompiledTouch.json" diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 0092744f84..acacd9866a 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -226,9 +226,6 @@ pub struct CfgEnv { /// If some it will effects EIP-170: Contract code size limit. Useful to increase this because of tests. /// By default it is 0x6000 (~25kb). pub limit_contract_code_size: Option, - /// Disables the coinbase tip during the finalization of the transaction. This is useful for - /// rollups that redirect the tip to the sequencer. - pub disable_coinbase_tip: bool, /// A hard memory limit in bytes beyond which [Memory] cannot be resized. /// /// In cases where the gas limit may be extraordinarily high, it is recommended to set this to @@ -350,7 +347,6 @@ impl Default for CfgEnv { spec_id: SpecId::LATEST, perf_analyse_created_bytecodes: AnalysisKind::default(), limit_contract_code_size: None, - disable_coinbase_tip: false, #[cfg(feature = "std")] kzg_settings: crate::kzg::EnvKzgSettings::Default, #[cfg(feature = "memory_limit")] diff --git a/crates/primitives/src/result.rs b/crates/primitives/src/result.rs index 1355ca47df..53a9768b9b 100644 --- a/crates/primitives/src/result.rs +++ b/crates/primitives/src/result.rs @@ -4,7 +4,11 @@ use bytes::Bytes; use core::fmt; use ruint::aliases::U256; -pub type EVMResult = core::result::Result>; +/// Result of EVM execution. +pub type EVMResult = EVMResultGeneric; + +/// Generic result of EVM execution. Used to represent error and generic output. +pub type EVMResultGeneric = core::result::Result>; #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index bcc2efb3a3..1a289e1574 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -1,4 +1,4 @@ -use crate::handlers; +use crate::handler::Handler; use crate::interpreter::{ analysis::to_analysed, gas, instruction_result::SuccessOrHalt, return_ok, CallContext, CallInputs, CallScheme, Contract, CreateInputs, CreateScheme, Gas, Host, InstructionResult, @@ -6,24 +6,21 @@ use crate::interpreter::{ }; use crate::journaled_state::{is_precompile, JournalCheckpoint}; use crate::primitives::{ - create2_address, create_address, keccak256, Account, AnalysisKind, Bytecode, Bytes, EVMError, - EVMResult, Env, ExecutionResult, HashMap, InvalidTransaction, Log, Output, ResultAndState, - Spec, + create2_address, create_address, keccak256, AnalysisKind, Bytecode, Bytes, EVMError, EVMResult, + Env, ExecutionResult, InvalidTransaction, Log, Output, ResultAndState, Spec, SpecId::{self, *}, TransactTo, B160, B256, U256, }; use crate::{db::Database, journaled_state::JournaledState, precompile, Inspector}; use alloc::boxed::Box; use alloc::vec::Vec; -use core::{cmp::min, marker::PhantomData}; +use core::marker::PhantomData; use revm_interpreter::gas::initial_tx_gas; use revm_interpreter::MAX_CODE_SIZE; use revm_precompile::{Precompile, Precompiles}; #[cfg(feature = "optimism")] use crate::optimism; -#[cfg(feature = "optimism")] -use core::ops::Mul; pub struct EVMData<'a, DB: Database> { pub env: &'a mut Env, @@ -31,11 +28,15 @@ pub struct EVMData<'a, DB: Database> { pub db: &'a mut DB, pub error: Option, pub precompiles: Precompiles, + /// Used as temporary value holder to store L1 block info. + #[cfg(feature = "optimism")] + pub l1_block_info: Option, } pub struct EVMImpl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> { data: EVMData<'a, DB>, inspector: &'a mut dyn Inspector, + handler: Handler, _phantomdata: PhantomData, } @@ -80,24 +81,25 @@ pub trait Transact { } } -impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, INSPECT> { +impl<'a, DB: Database> EVMData<'a, DB> { /// Load access list for berlin hardfork. /// /// Loading of accounts/storages is needed to make them hot. #[inline] fn load_access_list(&mut self) -> Result<(), EVMError> { - for (address, slots) in self.data.env.tx.access_list.iter() { - self.data - .journaled_state - .initial_account_load(*address, slots, self.data.db) + for (address, slots) in self.env.tx.access_list.iter() { + self.journaled_state + .initial_account_load(*address, slots, self.db) .map_err(EVMError::Database)?; } Ok(()) } +} +#[cfg(feature = "optimism")] +impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, INSPECT> { /// If the transaction is not a deposit transaction, subtract the L1 data fee from the /// caller's balance directly after minting the requested amount of ETH. - #[cfg(feature = "optimism")] fn remove_l1_cost( is_deposit: bool, tx_caller: B160, @@ -132,7 +134,6 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, /// If the transaction is a deposit with a `mint` value, add the mint value /// in wei to the caller's balance. This should be persisted to the database /// prior to the rest of execution. - #[cfg(feature = "optimism")] fn commit_mint_value( tx_caller: B160, tx_mint: Option, @@ -195,7 +196,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact let tx_gas_limit = env.tx.gas_limit; #[cfg(feature = "optimism")] - let (tx_mint, is_deposit, tx_l1_cost, l1_block_info) = { + let tx_l1_cost = { let is_deposit = env.tx.optimism.source_hash.is_some(); let l1_block_info = @@ -213,8 +214,15 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact }) .unwrap_or(U256::ZERO) }); + // storage l1 block info for later use. + self.data.l1_block_info = l1_block_info; - (env.tx.optimism.mint, is_deposit, tx_l1_cost, l1_block_info) + // + let Some(tx_l1_cost) = tx_l1_cost else { + panic!("[OPTIMISM] L1 Block Info could not be loaded from the DB.") + }; + + tx_l1_cost }; let initial_gas_spend = initial_tx_gas::( @@ -232,9 +240,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact .map_err(EVMError::Database)?; } - self.load_access_list()?; - // Without this line, the borrow checker complains that `self` is borrowed mutable above. - let env = &self.data.env; + self.data.load_access_list()?; // load acc let journal = &mut self.data.journaled_state; @@ -243,14 +249,12 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact if self.data.env.cfg.optimism { EVMImpl::::commit_mint_value( tx_caller, - tx_mint, + self.data.env.tx.optimism.mint, self.data.db, journal, )?; - let Some(tx_l1_cost) = tx_l1_cost else { - panic!("[OPTIMISM] L1 Block Info could not be loaded from the DB.") - }; + let is_deposit = self.data.env.tx.optimism.source_hash.is_some(); EVMImpl::::remove_l1_cost( is_deposit, tx_caller, @@ -266,11 +270,12 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact // 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(tx_gas_limit).saturating_mul(env.effective_gas_price()); + let mut gas_cost = + U256::from(tx_gas_limit).saturating_mul(self.data.env.effective_gas_price()); // EIP-4844 if GSPEC::enabled(CANCUN) { - let data_fee = env.calc_data_fee().expect("already checked"); + let data_fee = self.data.env.calc_data_fee().expect("already checked"); gas_cost = gas_cost.saturating_add(U256::from(data_fee)); } @@ -319,24 +324,36 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact } }; - let gas = handlers::handle_call_return::(self.env(), call_result, ret_gas); + let handler = &self.handler; + let data = &mut self.data; - let (state, logs, gas_used, gas_refunded) = self.finalize::( - &gas, - #[cfg(feature = "optimism")] - l1_block_info.as_ref(), - ); + // handle output of call/create calls. + let gas = handler.call_return(data.env, call_result, ret_gas); + + let gas_refunded = handler.calculate_gas_refund(data.env, &gas); + + // Reimburse the caller + handler.reimburse_caller(data, &gas, gas_refunded)?; + + // Reward beneficiary + handler.reward_beneficiary(data, &gas, gas_refunded)?; + + // used gas with refund calculated. + let final_gas_used = gas.spend() - gas_refunded; + + // reset journal and return present state. + let (state, logs) = self.data.journaled_state.finalize(); let result = match call_result.into() { SuccessOrHalt::Success(reason) => ExecutionResult::Success { reason, - gas_used, + gas_used: final_gas_used, gas_refunded, logs, output, }, SuccessOrHalt::Revert => ExecutionResult::Revert { - gas_used, + gas_used: final_gas_used, output: match output { Output::Call(return_value) => return_value, Output::Create(return_value, _) => return_value, @@ -348,6 +365,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact // the transaction halts. #[cfg(feature = "optimism")] { + let is_deposit = self.data.env.tx.optimism.source_hash.is_some(); let is_creation = matches!(output, Output::Create(_, _)); let regolith_enabled = GSPEC::enabled(SpecId::REGOLITH); let optimism_regolith = self.data.env.cfg.optimism && regolith_enabled; @@ -360,7 +378,10 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact acc.info.nonce = acc.info.nonce.checked_add(1).unwrap_or(u64::MAX); } } - ExecutionResult::Halt { reason, gas_used } + ExecutionResult::Halt { + reason, + gas_used: final_gas_used, + } } SuccessOrHalt::FatalExternalError => { return Err(EVMError::Database(self.data.error.take().unwrap())); @@ -393,137 +414,15 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, db, error: None, precompiles, + #[cfg(feature = "optimism")] + l1_block_info: None, }, inspector, + handler: Handler::mainnet::(), _phantomdata: PhantomData {}, } } - fn finalize( - &mut self, - gas: &Gas, - #[cfg(feature = "optimism")] l1_block_info: Option<&optimism::L1BlockInfo>, - ) -> (HashMap, Vec, u64, u64) { - let caller = self.data.env.tx.caller; - let coinbase = self.data.env.block.coinbase; - let (gas_used, gas_refunded) = if crate::USE_GAS { - let effective_gas_price = self.data.env.effective_gas_price(); - let basefee = self.data.env.block.basefee; - - let is_gas_refund_disabled = self.data.env.cfg.is_gas_refund_disabled(); - - #[cfg(feature = "optimism")] - let is_deposit = - self.data.env.cfg.optimism && self.data.env.tx.optimism.source_hash.is_some(); - - // Prior to Regolith, deposit transactions did not receive gas refunds. - #[cfg(feature = "optimism")] - let is_gas_refund_disabled = is_gas_refund_disabled - || (self.data.env.cfg.optimism && is_deposit && !SPEC::enabled(SpecId::REGOLITH)); - - let gas_refunded = if is_gas_refund_disabled { - 0 - } else { - // EIP-3529: Reduction in refunds - let max_refund_quotient = if SPEC::enabled(LONDON) { 5 } else { 2 }; - min(gas.refunded() as u64, gas.spend() / max_refund_quotient) - }; - - // return balance of not spend gas. - let Ok((caller_account, _)) = - self.data.journaled_state.load_account(caller, self.data.db) - else { - panic!("caller account not found"); - }; - - caller_account.info.balance = caller_account - .info - .balance - .saturating_add(effective_gas_price * U256::from(gas.remaining() + gas_refunded)); - - let disable_coinbase_tip = self.data.env.cfg.disable_coinbase_tip; - - // All deposit transactions skip the coinbase tip in favor of paying the - // various fee vaults. - #[cfg(feature = "optimism")] - let disable_coinbase_tip = - disable_coinbase_tip || (self.data.env.cfg.optimism && is_deposit); - - // transfer fee to coinbase/beneficiary. - if !disable_coinbase_tip { - // EIP-1559 discard basefee for coinbase transfer. Basefee amount of gas is discarded. - let coinbase_gas_price = if SPEC::enabled(LONDON) { - effective_gas_price.saturating_sub(basefee) - } else { - effective_gas_price - }; - - let Ok((coinbase_account, _)) = self - .data - .journaled_state - .load_account(coinbase, self.data.db) - else { - panic!("coinbase account not found"); - }; - coinbase_account.mark_touch(); - coinbase_account.info.balance = coinbase_account - .info - .balance - .saturating_add(coinbase_gas_price * U256::from(gas.spend() - gas_refunded)); - } - - #[cfg(feature = "optimism")] - if self.data.env.cfg.optimism && !is_deposit { - // If the transaction is not a deposit transaction, fees are paid out - // to both the Base Fee Vault as well as the L1 Fee Vault. - let Some(l1_block_info) = l1_block_info else { - panic!("[OPTIMISM] Failed to load L1 block information."); - }; - - let Some(enveloped_tx) = &self.data.env.tx.optimism.enveloped_tx else { - panic!("[OPTIMISM] Failed to load enveloped transaction."); - }; - - let l1_cost = l1_block_info.calculate_tx_l1_cost::(enveloped_tx, is_deposit); - - // Send the L1 cost of the transaction to the L1 Fee Vault. - let Ok((l1_fee_vault_account, _)) = self - .data - .journaled_state - .load_account(optimism::L1_FEE_RECIPIENT, self.data.db) - else { - panic!("[OPTIMISM] Failed to load L1 Fee Vault account"); - }; - l1_fee_vault_account.mark_touch(); - l1_fee_vault_account.info.balance += l1_cost; - - // Send the base fee of the transaction to the Base Fee Vault. - let Ok((base_fee_vault_account, _)) = self - .data - .journaled_state - .load_account(optimism::BASE_FEE_RECIPIENT, self.data.db) - else { - panic!("[OPTIMISM] Failed to load Base Fee Vault account"); - }; - base_fee_vault_account.mark_touch(); - base_fee_vault_account.info.balance += - l1_block_info.l1_base_fee.mul(U256::from(gas.spend())); - } - - (gas.spend() - gas_refunded, gas_refunded) - } else { - // touch coinbase - let _ = self - .data - .journaled_state - .load_account(coinbase, self.data.db); - self.data.journaled_state.touch(&coinbase); - (0, 0) - }; - let (new_state, logs) = self.data.journaled_state.finalize(); - (new_state, logs, gas_used, gas_refunded) - } - #[inline(never)] fn prepare_create(&mut self, inputs: &CreateInputs) -> Result { let gas = Gas::new(inputs.gas_limit); diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs new file mode 100644 index 0000000000..9a5e2f5b2f --- /dev/null +++ b/crates/revm/src/handler.rs @@ -0,0 +1,90 @@ +pub mod mainnet; +#[cfg(feature = "optimism")] +pub mod optimism; + +use revm_interpreter::primitives::db::Database; +use revm_interpreter::primitives::{EVMError, EVMResultGeneric}; + +use crate::interpreter::{Gas, InstructionResult}; +use crate::primitives::{Env, Spec}; +use crate::EVMData; + +/// Handle call return and return final gas value. +type CallReturnHandle = fn(&Env, InstructionResult, Gas) -> Gas; + +/// Reimburse the caller with ethereum it didn't spent. +type ReimburseCallerHandle = + fn(&mut EVMData<'_, DB>, &Gas, u64) -> EVMResultGeneric<(), ::Error>; + +/// Reward beneficiary with transaction rewards. +type RewardBeneficiaryHandle = ReimburseCallerHandle; + +/// Calculate gas refund for transaction. +type CalculateGasRefundHandle = fn(&Env, &Gas) -> u64; + +/// Handler acts as a proxy and allow to define different behavior for different +/// sections of the code. This allows nice integration of different chains or +/// to disable some mainnet behavior. +pub struct Handler { + // Uses env, call resul and returned gas from the call to determine the gas + // that is returned from transaction execution.. + pub call_return: CallReturnHandle, + pub reimburse_caller: ReimburseCallerHandle, + pub reward_beneficiary: RewardBeneficiaryHandle, + pub calculate_gas_refund: CalculateGasRefundHandle, +} + +impl Handler { + /// Handler for the mainnet + pub fn mainnet() -> Self { + Self { + call_return: mainnet::handle_call_return::, + calculate_gas_refund: mainnet::calculate_gas_refund::, + reimburse_caller: mainnet::handle_reimburse_caller::, + reward_beneficiary: mainnet::reward_beneficiary::, + } + } + + /// Handler for the optimism + #[cfg(feature = "optimism")] + pub fn optimism() -> Self { + Self { + call_return: optimism::handle_call_return::, + // we reinburse caller the same was as in mainnet. + // Refund is calculated differently then mainnet. + reimburse_caller: mainnet::handle_reimburse_caller::, + calculate_gas_refund: optimism::calculate_gas_refund::, + reward_beneficiary: optimism::reward_beneficiary::, + } + } + + /// Handle call return, depending on instruction result gas will be reimbursed or not. + pub fn call_return(&self, env: &Env, call_result: InstructionResult, returned_gas: Gas) -> Gas { + (self.call_return)(env, call_result, returned_gas) + } + + /// Reimburse the caller with gas that were not spend. + pub fn reimburse_caller( + &self, + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, + ) -> Result<(), EVMError> { + (self.reimburse_caller)(data, gas, gas_refund) + } + + /// Calculate gas refund for transaction. Some chains have it disabled. + pub fn calculate_gas_refund(&self, env: &Env, gas: &Gas) -> u64 { + (self.calculate_gas_refund)(env, gas) + } + + /// Reward beneficiary + pub fn reward_beneficiary( + &self, + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, + ) -> Result<(), EVMError> { + (self.reward_beneficiary)(data, gas, gas_refund) + } +} diff --git a/crates/revm/src/handler/mainnet.rs b/crates/revm/src/handler/mainnet.rs new file mode 100644 index 0000000000..2e87f76100 --- /dev/null +++ b/crates/revm/src/handler/mainnet.rs @@ -0,0 +1,156 @@ +//! Mainnet related handlers. +use revm_interpreter::primitives::EVMError; + +use crate::{ + interpreter::{return_ok, return_revert, Gas, InstructionResult}, + primitives::{db::Database, Env, Spec, SpecId::LONDON, U256}, + EVMData, +}; + +/// Handle output of the transaction +pub fn handle_call_return( + env: &Env, + call_result: InstructionResult, + returned_gas: Gas, +) -> Gas { + let tx_gas_limit = env.tx.gas_limit; + // Spend the gas limit. Gas is reimbursed when the tx returns successfully. + let mut gas = Gas::new(tx_gas_limit); + gas.record_cost(tx_gas_limit); + + if crate::USE_GAS { + match call_result { + return_ok!() => { + gas.erase_cost(returned_gas.remaining()); + gas.record_refund(returned_gas.refunded()); + } + return_revert!() => { + gas.erase_cost(returned_gas.remaining()); + } + _ => {} + } + } + gas +} + +#[inline] +pub fn handle_reimburse_caller( + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, +) -> Result<(), EVMError> { + let _ = data; + let caller = data.env.tx.caller; + let effective_gas_price = data.env.effective_gas_price(); + + // return balance of not spend gas. + let (caller_account, _) = data + .journaled_state + .load_account(caller, data.db) + .map_err(EVMError::Database)?; + + caller_account.info.balance = caller_account + .info + .balance + .saturating_add(effective_gas_price * U256::from(gas.remaining() + gas_refund)); + + Ok(()) +} + +/// Reward beneficiary with gas fee. +#[inline] +pub fn reward_beneficiary( + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, +) -> Result<(), EVMError> { + let beneficiary = data.env.block.coinbase; + let effective_gas_price = data.env.effective_gas_price(); + + // transfer fee to coinbase/beneficiary. + // EIP-1559 discard basefee for coinbase transfer. Basefee amount of gas is discarded. + let coinbase_gas_price = if SPEC::enabled(LONDON) { + effective_gas_price.saturating_sub(data.env.block.basefee) + } else { + effective_gas_price + }; + + let (coinbase_account, _) = data + .journaled_state + .load_account(beneficiary, data.db) + .map_err(EVMError::Database)?; + + coinbase_account.mark_touch(); + coinbase_account.info.balance = coinbase_account + .info + .balance + .saturating_add(coinbase_gas_price * U256::from(gas.spend() - gas_refund)); + + Ok(()) +} + +/// Calculate gas refund for transaction. +/// +/// If config is set to disable gas refund, it will return 0. +/// +/// If spec is set to london, it will decrease the maximum refund amount to 5th part of +/// gas spend. (Before london it was 2th part of gas spend) +#[inline] +pub fn calculate_gas_refund(env: &Env, gas: &Gas) -> u64 { + if env.cfg.is_gas_refund_disabled() { + 0 + } else { + // EIP-3529: Reduction in refunds + let max_refund_quotient = if SPEC::enabled(LONDON) { 5 } else { 2 }; + (gas.refunded() as u64).min(gas.spend() / max_refund_quotient) + } +} + +#[cfg(test)] +mod tests { + use revm_interpreter::primitives::CancunSpec; + + use super::*; + + #[test] + fn test_consume_gas() { + let mut env = Env::default(); + env.tx.gas_limit = 100; + + let gas = handle_call_return::(&env, InstructionResult::Stop, Gas::new(90)); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_consume_gas_with_refund() { + let mut env = Env::default(); + env.tx.gas_limit = 100; + + let mut return_gas = Gas::new(90); + return_gas.record_refund(30); + + let gas = + handle_call_return::(&env, InstructionResult::Stop, return_gas.clone()); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 30); + + let gas = handle_call_return::(&env, InstructionResult::Revert, return_gas); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_revert_gas() { + let mut env = Env::default(); + env.tx.gas_limit = 100; + + let gas = handle_call_return::(&env, InstructionResult::Revert, Gas::new(90)); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 0); + } +} diff --git a/crates/revm/src/handlers.rs b/crates/revm/src/handler/optimism.rs similarity index 68% rename from crates/revm/src/handlers.rs rename to crates/revm/src/handler/optimism.rs index ebba96f7fb..b64d18edab 100644 --- a/crates/revm/src/handlers.rs +++ b/crates/revm/src/handler/optimism.rs @@ -1,32 +1,14 @@ -use crate::interpreter::{return_ok, return_revert, Gas, InstructionResult}; -use crate::primitives::{Env, Spec}; +//! Handler related to Optimism chain -/// Handle output of the transaction -#[cfg(not(feature = "optimism"))] -pub fn handle_call_return( - env: &Env, - call_result: InstructionResult, - returned_gas: Gas, -) -> Gas { - let tx_gas_limit = env.tx.gas_limit; - // Spend the gas limit. Gas is reimbursed when the tx returns successfully. - let mut gas = Gas::new(tx_gas_limit); - gas.record_cost(tx_gas_limit); +use core::ops::Mul; - if crate::USE_GAS { - match call_result { - return_ok!() => { - gas.erase_cost(returned_gas.remaining()); - gas.record_refund(returned_gas.refunded()); - } - return_revert!() => { - gas.erase_cost(returned_gas.remaining()); - } - _ => {} - } - } - gas -} +use super::mainnet; +use crate::{ + interpreter::{return_ok, return_revert, Gas, InstructionResult}, + optimism, + primitives::{db::Database, EVMError, Env, Spec, SpecId::REGOLITH, U256}, + EVMData, +}; /// Handle output of the transaction #[cfg(feature = "optimism")] @@ -35,7 +17,6 @@ pub fn handle_call_return( call_result: InstructionResult, returned_gas: Gas, ) -> Gas { - use crate::primitives::SpecId::REGOLITH; let is_deposit = env.tx.optimism.source_hash.is_some(); let is_optimism = env.cfg.optimism; let tx_system = env.tx.optimism.is_system_transaction; @@ -95,54 +76,69 @@ pub fn handle_call_return( gas } -#[cfg(not(feature = "optimism"))] -#[cfg(test)] -mod tests { - use revm_interpreter::primitives::CancunSpec; - - use super::*; +#[inline] +pub fn calculate_gas_refund(env: &Env, gas: &Gas) -> u64 { + let is_deposit = env.cfg.optimism && env.tx.optimism.source_hash.is_some(); - #[test] - fn test_consume_gas() { - let mut env = Env::default(); - env.tx.gas_limit = 100; - - let gas = handle_call_return::(&env, InstructionResult::Stop, Gas::new(90)); - assert_eq!(gas.remaining(), 90); - assert_eq!(gas.spend(), 10); - assert_eq!(gas.refunded(), 0); + // Prior to Regolith, deposit transactions did not receive gas refunds. + let is_gas_refund_disabled = env.cfg.optimism && is_deposit && !SPEC::enabled(REGOLITH); + if is_gas_refund_disabled { + 0 + } else { + mainnet::calculate_gas_refund::(env, gas) } +} - #[test] - fn test_consume_gas_with_refund() { - let mut env = Env::default(); - env.tx.gas_limit = 100; - - let mut return_gas = Gas::new(90); - return_gas.record_refund(30); - - let gas = - handle_call_return::(&env, InstructionResult::Stop, return_gas.clone()); - assert_eq!(gas.remaining(), 90); - assert_eq!(gas.spend(), 10); - assert_eq!(gas.refunded(), 30); - - let gas = handle_call_return::(&env, InstructionResult::Revert, return_gas); - assert_eq!(gas.remaining(), 90); - assert_eq!(gas.spend(), 10); - assert_eq!(gas.refunded(), 0); +/// Reward beneficiary with gas fee. +#[inline] +pub fn reward_beneficiary( + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, +) -> Result<(), EVMError> { + let is_deposit = data.env.cfg.optimism && data.env.tx.optimism.source_hash.is_some(); + let disable_coinbase_tip = data.env.cfg.optimism && is_deposit; + + // transfer fee to coinbase/beneficiary. + if !disable_coinbase_tip { + mainnet::reward_beneficiary::(data, gas, gas_refund)?; } - #[test] - fn test_revert_gas() { - let mut env = Env::default(); - env.tx.gas_limit = 100; - - let gas = handle_call_return::(&env, InstructionResult::Revert, Gas::new(90)); - assert_eq!(gas.remaining(), 90); - assert_eq!(gas.spend(), 10); - assert_eq!(gas.refunded(), 0); + if data.env.cfg.optimism && !is_deposit { + // If the transaction is not a deposit transaction, fees are paid out + // to both the Base Fee Vault as well as the L1 Fee Vault. + let Some(l1_block_info) = data.l1_block_info.clone() else { + panic!("[OPTIMISM] Failed to load L1 block information."); + }; + + let Some(enveloped_tx) = &data.env.tx.optimism.enveloped_tx else { + panic!("[OPTIMISM] Failed to load enveloped transaction."); + }; + + let l1_cost = l1_block_info.calculate_tx_l1_cost::(enveloped_tx, is_deposit); + + // Send the L1 cost of the transaction to the L1 Fee Vault. + let Ok((l1_fee_vault_account, _)) = data + .journaled_state + .load_account(optimism::L1_FEE_RECIPIENT, data.db) + else { + panic!("[OPTIMISM] Failed to load L1 Fee Vault account"); + }; + l1_fee_vault_account.mark_touch(); + l1_fee_vault_account.info.balance += l1_cost; + + // Send the base fee of the transaction to the Base Fee Vault. + let Ok((base_fee_vault_account, _)) = data + .journaled_state + .load_account(optimism::BASE_FEE_RECIPIENT, data.db) + else { + panic!("[OPTIMISM] Failed to load Base Fee Vault account"); + }; + base_fee_vault_account.mark_touch(); + base_fee_vault_account.info.balance += + l1_block_info.l1_base_fee.mul(U256::from(gas.spend())); } + Ok(()) } #[cfg(feature = "optimism")] diff --git a/crates/revm/src/inspector/customprinter.rs b/crates/revm/src/inspector/customprinter.rs index ba69bdf249..c9ccf6ec03 100644 --- a/crates/revm/src/inspector/customprinter.rs +++ b/crates/revm/src/inspector/customprinter.rs @@ -126,8 +126,9 @@ impl Inspector for CustomPrintTracer { #[cfg(test)] mod test { - #[cfg(not(feature = "no_gas_measuring"))] #[test] + #[cfg(not(feature = "no_gas_measuring"))] + #[cfg(not(feature = "optimism"))] fn gas_calculation_underflow() { use crate::primitives::hex_literal; // https://github.com/bluealloy/revm/issues/277 diff --git a/crates/revm/src/inspector/gas.rs b/crates/revm/src/inspector/gas.rs index 28ccf87f28..24c8ff2e42 100644 --- a/crates/revm/src/inspector/gas.rs +++ b/crates/revm/src/inspector/gas.rs @@ -93,13 +93,8 @@ impl Inspector for GasInspector { #[cfg(test)] mod tests { - use crate::db::BenchmarkDB; - use crate::interpreter::{ - opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, OpCode, - }; - use crate::primitives::{ - hex_literal::hex, Bytecode, Bytes, ResultAndState, TransactTo, B160, B256, - }; + use crate::interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}; + use crate::primitives::{Bytes, B160, B256}; use crate::{inspectors::GasInspector, Database, EVMData, Inspector}; #[derive(Default, Debug)] @@ -209,7 +204,17 @@ mod tests { } #[test] + #[cfg(not(feature = "optimism"))] fn test_gas_inspector() { + use crate::db::BenchmarkDB; + use crate::interpreter::{ + opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, OpCode, + }; + use crate::primitives::{ + hex_literal::hex, Bytecode, Bytes, ResultAndState, TransactTo, B160, B256, + }; + use crate::{inspectors::GasInspector, Database, EVMData, Inspector}; + let contract_data: Bytes = Bytes::from(vec![ opcode::PUSH1, 0x1, diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 485dc325b5..1742d34bdf 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; pub mod db; mod evm; mod evm_impl; -pub mod handlers; +pub mod handler; mod inspector; mod journaled_state; @@ -49,3 +49,5 @@ pub use inspector::Inspector; // export Optimism types, helpers, and constants #[cfg(feature = "optimism")] pub use optimism::{L1BlockInfo, BASE_FEE_RECIPIENT, L1_BLOCK_CONTRACT, L1_FEE_RECIPIENT}; + +pub use handler::Handler;