diff --git a/crates/context/interface/src/cfg.rs b/crates/context/interface/src/cfg.rs index cb107693d6..e96641c84c 100644 --- a/crates/context/interface/src/cfg.rs +++ b/crates/context/interface/src/cfg.rs @@ -70,6 +70,18 @@ pub trait Cfg { /// Returns whether the fee charge is disabled. fn is_fee_charge_disabled(&self) -> bool; + /// Returns whether EIP-7708 (ETH transfers emit logs) is disabled. + fn is_eip7708_disabled(&self) -> bool; + + /// Returns whether EIP-7708 delayed burn logging is disabled. + /// + /// When enabled, revm tracks all self-destructed addresses and emits logs for + /// accounts that still have remaining balance at the end of the transaction. + /// This can be disabled for performance reasons as it requires storing and + /// iterating over all self-destructed accounts. When disabled, the logging + /// can be done outside of revm when applying accounts to database state. + fn is_eip7708_delayed_burn_disabled(&self) -> bool; + /// Returns the limit in bytes for the memory buffer. fn memory_limit(&self) -> u64; diff --git a/crates/context/interface/src/journaled_state.rs b/crates/context/interface/src/journaled_state.rs index 9a9b9a9fe7..60f9075b40 100644 --- a/crates/context/interface/src/journaled_state.rs +++ b/crates/context/interface/src/journaled_state.rs @@ -119,6 +119,17 @@ pub trait JournalTr { /// Sets the spec id. fn set_spec_id(&mut self, spec_id: SpecId); + /// Sets EIP-7708 configuration flags. + /// + /// - `disabled`: Whether EIP-7708 (ETH transfers emit logs) is completely disabled. + /// - `delayed_burn_disabled`: Whether delayed burn logging is disabled. When enabled, + /// revm tracks all self-destructed addresses and emits logs for accounts that still + /// have remaining balance at the end of the transaction. This can be disabled for + /// performance reasons as it requires storing and iterating over all self-destructed + /// accounts. When disabled, the logging can be done outside of revm when applying + /// accounts to database state. + fn set_eip7708_config(&mut self, disabled: bool, delayed_burn_disabled: bool); + /// Touches the account. fn touch_account(&mut self, address: Address); diff --git a/crates/context/src/cfg.rs b/crates/context/src/cfg.rs index 5a320ea660..f494756ba1 100644 --- a/crates/context/src/cfg.rs +++ b/crates/context/src/cfg.rs @@ -130,6 +130,20 @@ pub struct CfgEnv { /// By default, it is set to `false`. #[cfg(feature = "optional_fee_charge")] pub disable_fee_charge: bool, + /// Disables EIP-7708 (ETH transfers emit logs). + /// + /// By default, it is set to `false`. + pub amsterdam_eip7708_disabled: bool, + /// Disables EIP-7708 delayed burn logging. + /// + /// When enabled, revm tracks all self-destructed addresses and emits logs for + /// accounts that still have remaining balance at the end of the transaction. + /// This can be disabled for performance reasons as it requires storing and + /// iterating over all self-destructed accounts. When disabled, the logging + /// can be done outside of revm when applying accounts to database state. + /// + /// By default, it is set to `false`. + pub amsterdam_eip7708_delayed_burn_disabled: bool, } impl CfgEnv { @@ -171,6 +185,8 @@ impl CfgEnv { disable_priority_fee_check: false, #[cfg(feature = "optional_fee_charge")] disable_fee_charge: false, + amsterdam_eip7708_disabled: false, + amsterdam_eip7708_delayed_burn_disabled: false, } } @@ -273,6 +289,8 @@ impl CfgEnv { disable_priority_fee_check: self.disable_priority_fee_check, #[cfg(feature = "optional_fee_charge")] disable_fee_charge: self.disable_fee_charge, + amsterdam_eip7708_disabled: self.amsterdam_eip7708_disabled, + amsterdam_eip7708_delayed_burn_disabled: self.amsterdam_eip7708_delayed_burn_disabled, } } @@ -488,6 +506,14 @@ impl + Clone> Cfg for CfgEnv { } } + fn is_eip7708_disabled(&self) -> bool { + self.amsterdam_eip7708_disabled + } + + fn is_eip7708_delayed_burn_disabled(&self) -> bool { + self.amsterdam_eip7708_delayed_burn_disabled + } + fn memory_limit(&self) -> u64 { cfg_if::cfg_if! { if #[cfg(feature = "memory_limit")] { diff --git a/crates/context/src/context.rs b/crates/context/src/context.rs index 8ba2cb2c34..1afec6ec78 100644 --- a/crates/context/src/context.rs +++ b/crates/context/src/context.rs @@ -141,12 +141,17 @@ impl< /// /// This will create a new [`Journal`] object. pub fn new(db: DB, spec: SPEC) -> Self { + let cfg = CfgEnv::new_with_spec(spec); let mut journaled_state = JOURNAL::new(db); - journaled_state.set_spec_id(spec.clone().into()); + journaled_state.set_spec_id(cfg.spec.clone().into()); + journaled_state.set_eip7708_config( + cfg.amsterdam_eip7708_disabled, + cfg.amsterdam_eip7708_delayed_burn_disabled, + ); Self { tx: TX::default(), block: BLOCK::default(), - cfg: CfgEnv::new_with_spec(spec), + cfg, local: LOCAL::default(), journaled_state, chain: Default::default(), @@ -170,6 +175,10 @@ where mut journal: OJOURNAL, ) -> Context { journal.set_spec_id(self.cfg.spec().into()); + journal.set_eip7708_config( + self.cfg.is_eip7708_disabled(), + self.cfg.is_eip7708_delayed_burn_disabled(), + ); Context { tx: self.tx, block: self.block, @@ -188,9 +197,12 @@ where self, db: ODB, ) -> Context, CHAIN, LOCAL> { - let spec = self.cfg.spec().into(); let mut journaled_state = Journal::new(db); - journaled_state.set_spec_id(spec); + journaled_state.set_spec_id(self.cfg.spec().into()); + journaled_state.set_eip7708_config( + self.cfg.is_eip7708_disabled(), + self.cfg.is_eip7708_delayed_burn_disabled(), + ); Context { tx: self.tx, block: self.block, @@ -208,9 +220,12 @@ where db: ODB, ) -> Context, Journal>, CHAIN, LOCAL> { - let spec = self.cfg.spec().into(); let mut journaled_state = Journal::new(WrapDatabaseRef(db)); - journaled_state.set_spec_id(spec); + journaled_state.set_spec_id(self.cfg.spec().into()); + journaled_state.set_eip7708_config( + self.cfg.is_eip7708_disabled(), + self.cfg.is_eip7708_delayed_burn_disabled(), + ); Context { tx: self.tx, block: self.block, @@ -272,6 +287,10 @@ where cfg: OCFG, ) -> Context { self.journaled_state.set_spec_id(cfg.spec().into()); + self.journaled_state.set_eip7708_config( + cfg.is_eip7708_disabled(), + cfg.is_eip7708_delayed_burn_disabled(), + ); Context { tx: self.tx, block: self.block, @@ -307,6 +326,10 @@ where { f(&mut self.cfg); self.journaled_state.set_spec_id(self.cfg.spec().into()); + self.journaled_state.set_eip7708_config( + self.cfg.is_eip7708_disabled(), + self.cfg.is_eip7708_delayed_burn_disabled(), + ); self } @@ -383,6 +406,10 @@ where { f(&mut self.cfg); self.journaled_state.set_spec_id(self.cfg.spec().into()); + self.journaled_state.set_eip7708_config( + self.cfg.is_eip7708_disabled(), + self.cfg.is_eip7708_delayed_burn_disabled(), + ); } /// Modifies the context chain. diff --git a/crates/context/src/journal.rs b/crates/context/src/journal.rs index 580a7699e6..0d966c2f19 100644 --- a/crates/context/src/journal.rs +++ b/crates/context/src/journal.rs @@ -6,7 +6,7 @@ pub mod inner; pub mod warm_addresses; pub use context_interface::journaled_state::entry::{JournalEntry, JournalEntryTr}; -pub use inner::JournalInner; +pub use inner::{JournalCfg, JournalInner}; use bytecode::Bytecode; use context_interface::{ @@ -196,7 +196,13 @@ impl JournalTr for Journal { #[inline] fn set_spec_id(&mut self, spec_id: SpecId) { - self.inner.spec = spec_id; + self.inner.cfg.spec = spec_id; + } + + #[inline] + fn set_eip7708_config(&mut self, disabled: bool, delayed_burn_disabled: bool) { + self.inner + .set_eip7708_config(disabled, delayed_burn_disabled); } #[inline] @@ -384,7 +390,7 @@ impl JournalTr for Journal { load_code: bool, skip_cold_load: bool, ) -> Result, JournalLoadError<::Error>> { - let spec = self.inner.spec; + let spec = self.inner.cfg.spec; self.inner .load_account_optional(&mut self.database, address, load_code, skip_cold_load) .map(|a| { diff --git a/crates/context/src/journal/inner.rs b/crates/context/src/journal/inner.rs index b5504dd61f..af795041b5 100644 --- a/crates/context/src/journal/inner.rs +++ b/crates/context/src/journal/inner.rs @@ -20,6 +20,37 @@ use primitives::{ }; use state::{Account, EvmState, TransientStorage}; use std::vec::Vec; + +/// Configuration for the journal that affects EVM execution behavior. +/// +/// This struct bundles the spec ID and EIP-7708 configuration flags. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct JournalCfg { + /// The spec ID for the EVM. Spec is required for some journal entries and needs to be set for + /// JournalInner to be functional. + /// + /// If spec is set it assumed that precompile addresses are set as well for this particular spec. + /// + /// This spec is used for two things: + /// + /// - [EIP-161]: Prior to this EIP, Ethereum had separate definitions for empty and non-existing accounts. + /// - [EIP-6780]: `SELFDESTRUCT` only in same transaction + /// + /// [EIP-161]: https://eips.ethereum.org/EIPS/eip-161 + /// [EIP-6780]: https://eips.ethereum.org/EIPS/eip-6780 + pub spec: SpecId, + /// Whether EIP-7708 (ETH transfers emit logs) is disabled. + pub eip7708_disabled: bool, + /// Whether EIP-7708 delayed burn logging is disabled. + /// + /// When enabled, revm tracks all self-destructed addresses and emits logs for + /// accounts that still have remaining balance at the end of the transaction. + /// This can be disabled for performance reasons as it requires storing and + /// iterating over all self-destructed accounts. When disabled, the logging + /// can be done outside of revm when applying accounts to database state. + pub eip7708_delayed_burn_disabled: bool, +} /// Inner journal state that contains journal and state changes. /// /// Spec Id is a essential information for the Journal. @@ -44,19 +75,8 @@ pub struct JournalInner { /// /// This ID is used in `Self::state` to determine if account/storage is touched/warm/cold. pub transaction_id: usize, - /// The spec ID for the EVM. Spec is required for some journal entries and needs to be set for - /// JournalInner to be functional. - /// - /// If spec is set it assumed that precompile addresses are set as well for this particular spec. - /// - /// This spec is used for two things: - /// - /// - [EIP-161]: Prior to this EIP, Ethereum had separate definitions for empty and non-existing accounts. - /// - [EIP-6780]: `SELFDESTRUCT` only in same transaction - /// - /// [EIP-161]: https://eips.ethereum.org/EIPS/eip-161 - /// [EIP-6780]: https://eips.ethereum.org/EIPS/eip-6780 - pub spec: SpecId, + /// Journal configuration containing spec ID and EIP-7708 flags. + pub cfg: JournalCfg, /// Warm addresses containing both coinbase and current precompiles. pub warm_addresses: WarmAddresses, /// Addresses that were self-destructed for the first time in this transaction. @@ -90,7 +110,7 @@ impl JournalInner { journal: Vec::default(), transaction_id: 0, depth: 0, - spec: SpecId::default(), + cfg: JournalCfg::default(), warm_addresses: WarmAddresses::new(), selfdestructed_addresses: Vec::new(), } @@ -124,12 +144,12 @@ impl JournalInner { depth, journal, transaction_id, - spec, + cfg, warm_addresses, selfdestructed_addresses, } = self; - // Spec, precompiles, BAL and state are not changed. It is always set again execution. - let _ = spec; + // Cfg and state are not changed. They are always set again before execution. + let _ = cfg; let _ = state; transient_storage.clear(); *depth = 0; @@ -156,11 +176,11 @@ impl JournalInner { depth, journal, transaction_id, - spec, + cfg, warm_addresses, selfdestructed_addresses, } = self; - let is_spurious_dragon_enabled = spec.is_enabled_in(SPURIOUS_DRAGON); + let is_spurious_dragon_enabled = cfg.spec.is_enabled_in(SPURIOUS_DRAGON); // iterate over all journals entries and revert our global state journal.drain(..).rev().for_each(|entry| { entry.revert(state, None, is_spurious_dragon_enabled); @@ -190,12 +210,12 @@ impl JournalInner { depth, journal, transaction_id, - spec, + cfg, warm_addresses, selfdestructed_addresses, } = self; - // Spec is not changed. And it is always set again in execution. - let _ = spec; + // Cfg is not changed. It is always set again before execution. + let _ = cfg; // Clear coinbase address warming for next tx warm_addresses.clear_coinbase_and_access_list(); selfdestructed_addresses.clear(); @@ -227,7 +247,10 @@ impl JournalInner { /// [EIP-7708](https://eips.ethereum.org/EIPS/eip-7708) #[inline] pub fn eip7708_emit_selfdestruct_remaining_balance_logs(&mut self) { - if !self.spec.is_enabled_in(AMSTERDAM) { + if !self.cfg.spec.is_enabled_in(AMSTERDAM) + || self.cfg.eip7708_disabled + || self.cfg.eip7708_delayed_burn_disabled + { return; } @@ -261,7 +284,14 @@ impl JournalInner { /// Sets SpecId. #[inline] pub fn set_spec_id(&mut self, spec: SpecId) { - self.spec = spec; + self.cfg.spec = spec; + } + + /// Sets EIP-7708 configuration flags. + #[inline] + pub fn set_eip7708_config(&mut self, disabled: bool, delayed_burn_disabled: bool) { + self.cfg.eip7708_disabled = disabled; + self.cfg.eip7708_delayed_burn_disabled = delayed_burn_disabled; } /// Mark account as touched as only touched accounts will be added to state. @@ -542,7 +572,7 @@ impl JournalInner { /// Reverts all changes to state until given checkpoint. #[inline] pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) { - let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON); + let is_spurious_dragon_enabled = self.cfg.spec.is_enabled_in(SPURIOUS_DRAGON); let state = &mut self.state; let transient_storage = &mut self.transient_storage; self.depth = self.depth.saturating_sub(1); @@ -581,7 +611,7 @@ impl JournalInner { target: Address, skip_cold_load: bool, ) -> Result, JournalLoadError> { - let spec = self.spec; + let spec = self.cfg.spec; let account_load = self.load_account_optional(db, target, false, skip_cold_load)?; let is_cold = account_load.is_cold; let is_empty = account_load.state_clear_aware_is_empty(spec); @@ -612,8 +642,10 @@ impl JournalInner { // EIP-6780 (Cancun hard-fork): selfdestruct only if contract is created in the same tx let journal_entry = if acc.is_created_locally() || !is_cancun_enabled { // EIP-7708: Track first self-destruction for remaining balance log. - // Only track when account is actually destroyed. - if destroyed_status == SelfdestructionRevertStatus::GloballySelfdestroyed { + // Only track when account is actually destroyed and delayed burn is not disabled. + if destroyed_status == SelfdestructionRevertStatus::GloballySelfdestroyed + && !self.cfg.eip7708_delayed_burn_disabled + { self.selfdestructed_addresses.push(address); } @@ -690,7 +722,7 @@ impl JournalInner { db: &mut DB, address: Address, ) -> Result, DB::Error> { - let spec = self.spec; + let spec = self.cfg.spec; let is_eip7702_enabled = spec.is_enabled_in(SpecId::PRAGUE); let account = self .load_account_optional(db, address, is_eip7702_enabled, false) @@ -1030,7 +1062,8 @@ impl JournalInner { #[inline] pub fn eip7708_transfer_log(&mut self, from: Address, to: Address, balance: U256) { // Only emit log if EIP-7708 is enabled and balance is non-zero - if !self.spec.is_enabled_in(AMSTERDAM) || balance.is_zero() { + if !self.cfg.spec.is_enabled_in(AMSTERDAM) || self.cfg.eip7708_disabled || balance.is_zero() + { return; } @@ -1061,7 +1094,8 @@ impl JournalInner { #[inline] pub fn eip7708_selfdestruct_to_self_log(&mut self, address: Address, balance: U256) { // Only emit log if EIP-7708 is enabled and balance is non-zero - if !self.spec.is_enabled_in(AMSTERDAM) || balance.is_zero() { + if !self.cfg.spec.is_enabled_in(AMSTERDAM) || self.cfg.eip7708_disabled || balance.is_zero() + { return; } diff --git a/examples/cheatcode_inspector/src/main.rs b/examples/cheatcode_inspector/src/main.rs index f10af4e291..577f29f647 100644 --- a/examples/cheatcode_inspector/src/main.rs +++ b/examples/cheatcode_inspector/src/main.rs @@ -140,6 +140,11 @@ impl JournalTr for Backend { self.journaled_state.set_spec_id(spec_id); } + fn set_eip7708_config(&mut self, disabled: bool, delayed_burn_disabled: bool) { + self.journaled_state + .set_eip7708_config(disabled, delayed_burn_disabled); + } + fn touch_account(&mut self, address: Address) { self.journaled_state.touch_account(address); }