From ac15c874bb0fdd309f9e165053cb6845d2f6b4ef Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 5 Aug 2025 14:18:22 +0200 Subject: [PATCH 1/5] chore: remove warm_addresses from JournalInner --- .../context/interface/src/journaled_state.rs | 3 --- crates/context/src/journal.rs | 7 ------ crates/context/src/journal/inner.rs | 23 ++++--------------- crates/handler/src/pre_execution.rs | 12 ++++------ examples/cheatcode_inspector/src/main.rs | 6 ----- 5 files changed, 8 insertions(+), 43 deletions(-) diff --git a/crates/context/interface/src/journaled_state.rs b/crates/context/interface/src/journaled_state.rs index e8ff56adc0..9d45323f14 100644 --- a/crates/context/interface/src/journaled_state.rs +++ b/crates/context/interface/src/journaled_state.rs @@ -66,9 +66,6 @@ pub trait JournalTr { storage_keys: impl IntoIterator, ) -> Result<(), ::Error>; - /// Warms the account. - fn warm_account(&mut self, address: Address); - /// Warms the coinbase account. fn warm_coinbase_account(&mut self, address: Address); diff --git a/crates/context/src/journal.rs b/crates/context/src/journal.rs index 891b6907f3..b92b0c09f3 100644 --- a/crates/context/src/journal.rs +++ b/crates/context/src/journal.rs @@ -140,19 +140,12 @@ impl JournalTr for Journal { self.inner.selfdestruct(&mut self.database, address, target) } - fn warm_account(&mut self, address: Address) { - self.inner.warm_preloaded_addresses.insert(address); - } - fn warm_coinbase_account(&mut self, address: Address) { self.inner.warm_coinbase_address = Some(address); } fn warm_precompiles(&mut self, precompiles: HashSet
) { self.inner.precompiles = precompiles; - self.inner - .warm_preloaded_addresses - .clone_from(&self.inner.precompiles); } #[inline] diff --git a/crates/context/src/journal/inner.rs b/crates/context/src/journal/inner.rs index fc45b372e2..6c286c565d 100644 --- a/crates/context/src/journal/inner.rs +++ b/crates/context/src/journal/inner.rs @@ -53,13 +53,6 @@ pub struct JournalInner { /// [EIP-161]: https://eips.ethereum.org/EIPS/eip-161 /// [EIP-6780]: https://eips.ethereum.org/EIPS/eip-6780 pub spec: SpecId, - /// Warm loaded addresses are used to check if loaded address - /// should be considered cold or warm loaded when the account - /// is first accessed. - /// - /// Note that this not include newly loaded accounts, account and storage - /// is considered warm if it is found in the `State`. - pub warm_preloaded_addresses: HashSet
, /// Warm coinbase address, stored separately to avoid cloning preloaded addresses. pub warm_coinbase_address: Option
, /// Precompile addresses @@ -86,7 +79,6 @@ impl JournalInner { transaction_id: 0, depth: 0, spec: SpecId::default(), - warm_preloaded_addresses: HashSet::default(), precompiles: HashSet::default(), warm_coinbase_address: None, } @@ -116,7 +108,6 @@ impl JournalInner { journal, transaction_id, spec, - warm_preloaded_addresses, precompiles, warm_coinbase_address, } = self; @@ -132,10 +123,6 @@ impl JournalInner { // Clear coinbase address warming for next tx *warm_coinbase_address = None; - // Load precompiles into warm_preloaded_addresses. - // TODO for precompiles we can use max transaction_id so they are always touched warm loaded. - // at least after state clear EIP. - reset_preloaded_addresses(warm_preloaded_addresses, precompiles); // increment transaction id. *transaction_id += 1; logs.clear(); @@ -152,10 +139,11 @@ impl JournalInner { journal, transaction_id, spec, - warm_preloaded_addresses, warm_coinbase_address, precompiles, } = self; + // precompiles are not changed. + let _ = precompiles; let is_spurious_dragon_enabled = spec.is_enabled_in(SPURIOUS_DRAGON); // iterate over all journals entries and revert our global state @@ -168,7 +156,6 @@ impl JournalInner { *transaction_id += 1; // Clear coinbase address warming for next tx *warm_coinbase_address = None; - reset_preloaded_addresses(warm_preloaded_addresses, precompiles); } /// Take the [`EvmState`] and clears the journal by resetting it to initial state. @@ -187,16 +174,14 @@ impl JournalInner { journal, transaction_id, spec, - warm_preloaded_addresses, warm_coinbase_address, precompiles, } = self; // Spec is not changed. And it is always set again in execution. let _ = spec; + let _ = precompiles; // Clear coinbase address warming for next tx *warm_coinbase_address = None; - // Load precompiles into warm_preloaded_addresses. - reset_preloaded_addresses(warm_preloaded_addresses, precompiles); let state = mem::take(state); logs.clear(); @@ -679,7 +664,7 @@ impl JournalInner { }; // Precompiles among some other account(coinbase included) are warm loaded so we need to take that into account - let is_cold = !self.warm_preloaded_addresses.contains(&address) + let is_cold = !self.precompiles.contains(&address) && self.warm_coinbase_address.as_ref() != Some(&address); StateLoad { diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index 7a7debca4b..4ebb46237a 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -57,14 +57,10 @@ pub fn load_accounts< for item in access_list { let address = item.address(); let mut storage = item.storage_slots().peekable(); - if storage.peek().is_none() { - journal.warm_account(*address); - } else { - journal.warm_account_and_storage( - *address, - storage.map(|i| StorageKey::from_be_bytes(i.0)), - )?; - } + journal.warm_account_and_storage( + *address, + storage.map(|i| StorageKey::from_be_bytes(i.0)), + )?; } } } diff --git a/examples/cheatcode_inspector/src/main.rs b/examples/cheatcode_inspector/src/main.rs index 5ca73b86d9..a4fa72ba9f 100644 --- a/examples/cheatcode_inspector/src/main.rs +++ b/examples/cheatcode_inspector/src/main.rs @@ -117,12 +117,6 @@ impl JournalTr for Backend { .warm_account_and_storage(address, storage_keys) } - fn warm_account(&mut self, address: Address) { - self.journaled_state - .warm_preloaded_addresses - .insert(address); - } - fn warm_coinbase_account(&mut self, address: Address) { self.journaled_state.warm_coinbase_address = Some(address) } From c570c5e0d80714882cbc91641b94bb7bad068564 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 5 Aug 2025 17:42:17 +0200 Subject: [PATCH 2/5] feat: short address for journal cold/warm check --- Cargo.lock | 1 + crates/context/Cargo.toml | 1 + crates/context/src/journal.rs | 9 +- crates/context/src/journal/inner.rs | 54 ++--- crates/context/src/journal/warm_addresses.rs | 233 +++++++++++++++++++ crates/handler/src/pre_execution.rs | 6 +- crates/interpreter/src/lib.rs | 1 - crates/precompile/src/lib.rs | 22 +- crates/primitives/src/lib.rs | 18 ++ examples/cheatcode_inspector/src/main.rs | 2 +- 10 files changed, 282 insertions(+), 65 deletions(-) create mode 100644 crates/context/src/journal/warm_addresses.rs diff --git a/Cargo.lock b/Cargo.lock index fe633e46f1..520e764388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3806,6 +3806,7 @@ dependencies = [ name = "revm-context" version = "8.0.4" dependencies = [ + "bitvec", "cfg-if", "derive-where", "revm-bytecode", diff --git a/crates/context/Cargo.toml b/crates/context/Cargo.toml index 6a61e6efd9..ab41e6600f 100644 --- a/crates/context/Cargo.toml +++ b/crates/context/Cargo.toml @@ -28,6 +28,7 @@ bytecode.workspace = true # misc derive-where.workspace = true cfg-if.workspace = true +bitvec.workspace = true # Optional serde = { workspace = true, features = ["derive", "rc"], optional = true } diff --git a/crates/context/src/journal.rs b/crates/context/src/journal.rs index b92b0c09f3..206abbb4e8 100644 --- a/crates/context/src/journal.rs +++ b/crates/context/src/journal.rs @@ -4,6 +4,7 @@ //! and inner submodule contains [`JournalInner`] struct that contains state. pub mod entry; pub mod inner; +pub mod warm_addresses; pub use entry::{JournalEntry, JournalEntryTr}; pub use inner::JournalInner; @@ -141,16 +142,18 @@ impl JournalTr for Journal { } fn warm_coinbase_account(&mut self, address: Address) { - self.inner.warm_coinbase_address = Some(address); + self.inner.warm_addresses.set_coinbase(address); } fn warm_precompiles(&mut self, precompiles: HashSet
) { - self.inner.precompiles = precompiles; + self.inner + .warm_addresses + .set_precompile_addresses(precompiles); } #[inline] fn precompile_addresses(&self) -> &HashSet
{ - &self.inner.precompiles + self.inner.warm_addresses.precompiles() } /// Returns call depth. diff --git a/crates/context/src/journal/inner.rs b/crates/context/src/journal/inner.rs index 6c286c565d..4de397ae39 100644 --- a/crates/context/src/journal/inner.rs +++ b/crates/context/src/journal/inner.rs @@ -1,5 +1,5 @@ //! Module containing the [`JournalInner`] that is part of [`crate::Journal`]. -use crate::entry::SelfdestructionRevertStatus; +use crate::{entry::SelfdestructionRevertStatus, warm_addresses::WarmAddresses}; use super::JournalEntryTr; use bytecode::Bytecode; @@ -12,7 +12,7 @@ use database_interface::Database; use primitives::{ hardfork::SpecId::{self, *}, hash_map::Entry, - Address, HashMap, HashSet, Log, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256, + Address, HashMap, Log, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256, }; use state::{Account, EvmState, EvmStorageSlot, TransientStorage}; use std::vec::Vec; @@ -53,10 +53,12 @@ pub struct JournalInner { /// [EIP-161]: https://eips.ethereum.org/EIPS/eip-161 /// [EIP-6780]: https://eips.ethereum.org/EIPS/eip-6780 pub spec: SpecId, - /// Warm coinbase address, stored separately to avoid cloning preloaded addresses. - pub warm_coinbase_address: Option
, - /// Precompile addresses - pub precompiles: HashSet
, + // /// Warm coinbase address, stored separately to avoid cloning preloaded addresses. + // pub warm_coinbase_address: Option
, + // /// Precompile addresses + // pub precompiles: HashSet
, + /// Warm addresses + pub warm_addresses: WarmAddresses, } impl Default for JournalInner { @@ -79,8 +81,7 @@ impl JournalInner { transaction_id: 0, depth: 0, spec: SpecId::default(), - precompiles: HashSet::default(), - warm_coinbase_address: None, + warm_addresses: WarmAddresses::new(), } } @@ -108,12 +109,10 @@ impl JournalInner { journal, transaction_id, spec, - precompiles, - warm_coinbase_address, + warm_addresses, } = self; // Spec precompiles and state are not changed. It is always set again execution. let _ = spec; - let _ = precompiles; let _ = state; transient_storage.clear(); *depth = 0; @@ -122,7 +121,7 @@ impl JournalInner { journal.clear(); // Clear coinbase address warming for next tx - *warm_coinbase_address = None; + warm_addresses.clear_coinbase(); // increment transaction id. *transaction_id += 1; logs.clear(); @@ -139,12 +138,8 @@ impl JournalInner { journal, transaction_id, spec, - warm_coinbase_address, - precompiles, + warm_addresses, } = self; - // precompiles are not changed. - let _ = precompiles; - let is_spurious_dragon_enabled = spec.is_enabled_in(SPURIOUS_DRAGON); // iterate over all journals entries and revert our global state journal.drain(..).rev().for_each(|entry| { @@ -154,8 +149,9 @@ impl JournalInner { *depth = 0; logs.clear(); *transaction_id += 1; + // Clear coinbase address warming for next tx - *warm_coinbase_address = None; + warm_addresses.clear_coinbase(); } /// Take the [`EvmState`] and clears the journal by resetting it to initial state. @@ -174,14 +170,12 @@ impl JournalInner { journal, transaction_id, spec, - warm_coinbase_address, - precompiles, + warm_addresses, } = self; // Spec is not changed. And it is always set again in execution. let _ = spec; - let _ = precompiles; // Clear coinbase address warming for next tx - *warm_coinbase_address = None; + warm_addresses.clear_coinbase(); let state = mem::take(state); logs.clear(); @@ -664,8 +658,7 @@ impl JournalInner { }; // Precompiles among some other account(coinbase included) are warm loaded so we need to take that into account - let is_cold = !self.precompiles.contains(&address) - && self.warm_coinbase_address.as_ref() != Some(&address); + let is_cold = self.warm_addresses.is_cold(&address); StateLoad { data: vac.insert(account), @@ -866,16 +859,3 @@ pub fn sload_with_account( Ok(StateLoad::new(value, is_cold)) } - -fn reset_preloaded_addresses( - warm_preloaded_addresses: &mut HashSet
, - precompiles: &HashSet
, -) { - // `warm_preloaded_addresses` is append-only, and is initialized with `precompiles`. - // Avoid unnecessarily cloning if it hasn't changed. - if warm_preloaded_addresses.len() == precompiles.len() { - debug_assert_eq!(warm_preloaded_addresses, precompiles); - return; - } - warm_preloaded_addresses.clone_from(precompiles); -} diff --git a/crates/context/src/journal/warm_addresses.rs b/crates/context/src/journal/warm_addresses.rs new file mode 100644 index 0000000000..e9236f6534 --- /dev/null +++ b/crates/context/src/journal/warm_addresses.rs @@ -0,0 +1,233 @@ +//! This module contains [`WarmAddresses`] struct that stores addresses that are warm loaded. +//! +//! It is used to optimize access to precompile addresses. + +use bitvec::{bitvec, order::Lsb0, vec::BitVec}; +use primitives::{short_address, Address, HashSet, SHORT_ADDRESS_CAP}; + +/// Stores addresses that are warm loaded. Contains precompiles and coinbase address. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WarmAddresses { + /// Set of warm loaded precompile addresses. + pub precompile_set: HashSet
, + /// Bit vector of short address access. + pub precompile_short_addresses: BitVec, + /// Coinbase address. + pub coinbase: Option
, +} + +impl WarmAddresses { + /// Create a new warm addresses instance. + #[inline] + pub fn new() -> Self { + Self { + precompile_set: HashSet::new(), + precompile_short_addresses: BitVec::new(), + coinbase: None, + } + } + + /// Set the precompile addresses and short addresses. + #[inline] + pub fn set_precompile_addresses(&mut self, addresses: HashSet
) { + // short address is always smaller than SHORT_ADDRESS_CAP + self.precompile_short_addresses = bitvec![usize, Lsb0; 0; SHORT_ADDRESS_CAP]; + + for address in addresses.iter() { + if let Some(short_address) = short_address(address) { + self.precompile_short_addresses.set(short_address, true); + } + } + + self.precompile_set = addresses; + } + + /// Set the coinbase address. + #[inline] + pub fn set_coinbase(&mut self, address: Address) { + self.coinbase = Some(address); + } + + /// Clear the coinbase address. + #[inline] + pub fn clear_coinbase(&mut self) { + self.coinbase = None; + } + + /// Returns the precompile addresses. + #[inline] + pub fn precompiles(&self) -> &HashSet
{ + &self.precompile_set + } + + /// Returns true if the address is warm loaded. + #[inline] + pub fn is_warm(&self, address: &Address) -> bool { + // check if it is coinbase + if Some(*address) == self.coinbase { + return true; + } + + // check if it is short precompile address + if let Some(short_address) = short_address(address) { + return self.precompile_short_addresses[short_address]; + } + + // finaly check if it is precompile + self.precompile_set.contains(address) + } + + /// Returns true if the address is cold loaded. + #[inline] + pub fn is_cold(&self, address: &Address) -> bool { + !self.is_warm(address) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use primitives::{address, Address}; + + #[test] + fn test_initialization() { + let warm_addresses = WarmAddresses::new(); + assert!(warm_addresses.precompile_set.is_empty()); + assert!(warm_addresses.precompile_short_addresses.is_empty()); + assert!(warm_addresses.coinbase.is_none()); + + // Test Default trait + let default_addresses = WarmAddresses::default(); + assert_eq!(warm_addresses, default_addresses); + } + + #[test] + fn test_coinbase_management() { + let mut warm_addresses = WarmAddresses::new(); + let coinbase_addr = address!("1234567890123456789012345678901234567890"); + + // Test setting coinbase + warm_addresses.set_coinbase(coinbase_addr); + assert_eq!(warm_addresses.coinbase, Some(coinbase_addr)); + assert!(warm_addresses.is_warm(&coinbase_addr)); + + // Test clearing coinbase + warm_addresses.clear_coinbase(); + assert!(warm_addresses.coinbase.is_none()); + assert!(!warm_addresses.is_warm(&coinbase_addr)); + } + + #[test] + fn test_short_address_precompiles() { + let mut warm_addresses = WarmAddresses::new(); + + // Create short addresses (18 leading zeros, last 2 bytes < 300) + let mut bytes1 = [0u8; 20]; + bytes1[19] = 1u8; + let short_addr1 = Address::from(bytes1); + + let mut bytes2 = [0u8; 20]; + bytes2[19] = 5u8; + let short_addr2 = Address::from(bytes2); + + let mut precompiles = HashSet::new(); + precompiles.insert(short_addr1); + precompiles.insert(short_addr2); + + warm_addresses.set_precompile_addresses(precompiles.clone()); + + // Verify storage + assert_eq!(warm_addresses.precompile_set, precompiles); + assert_eq!(warm_addresses.precompile_short_addresses.len(), SHORT_ADDRESS_CAP); + + // Verify bitvec optimization + assert!(warm_addresses.precompile_short_addresses[1]); + assert!(warm_addresses.precompile_short_addresses[5]); + assert!(!warm_addresses.precompile_short_addresses[0]); + + // Verify warmth detection + assert!(warm_addresses.is_warm(&short_addr1)); + assert!(warm_addresses.is_warm(&short_addr2)); + + // Test non-existent short address + let mut other_bytes = [0u8; 20]; + other_bytes[19] = 20u8; + let other_short_addr = Address::from(other_bytes); + assert!(!warm_addresses.is_warm(&other_short_addr)); + } + + #[test] + fn test_regular_address_precompiles() { + let mut warm_addresses = WarmAddresses::new(); + + // Create non-short addresses + let regular_addr = address!("1234567890123456789012345678901234567890"); + let mut bytes = [0u8; 20]; + bytes[18] = 1u8; + bytes[19] = 44u8; // 300 + let boundary_addr = Address::from(bytes); + + let mut precompiles = HashSet::new(); + precompiles.insert(regular_addr); + precompiles.insert(boundary_addr); + + warm_addresses.set_precompile_addresses(precompiles.clone()); + + // Verify storage + assert_eq!(warm_addresses.precompile_set, precompiles); + assert!(!warm_addresses.precompile_short_addresses.any()); + + // Verify warmth detection + assert!(warm_addresses.is_warm(®ular_addr)); + assert!(warm_addresses.is_warm(&boundary_addr)); + + // Test non-existent regular address + let other_addr = address!("0987654321098765432109876543210987654321"); + assert!(!warm_addresses.is_warm(&other_addr)); + } + + #[test] + fn test_mixed_address_types() { + let mut warm_addresses = WarmAddresses::new(); + + let mut short_bytes = [0u8; 20]; + short_bytes[19] = 7u8; + let short_addr = Address::from(short_bytes); + let regular_addr = address!("1234567890123456789012345678901234567890"); + + let mut precompiles = HashSet::new(); + precompiles.insert(short_addr); + precompiles.insert(regular_addr); + + warm_addresses.set_precompile_addresses(precompiles); + + // Both types should be warm + assert!(warm_addresses.is_warm(&short_addr)); + assert!(warm_addresses.is_warm(®ular_addr)); + + // Verify short address optimization is used + assert!(warm_addresses.precompile_short_addresses[7]); + assert!(!warm_addresses.precompile_short_addresses[8]); + } + + #[test] + fn test_short_address_boundary() { + let mut warm_addresses = WarmAddresses::new(); + + // Address at boundary (SHORT_ADDRESS_CAP - 1) + let mut boundary_bytes = [0u8; 20]; + let boundary_val = (SHORT_ADDRESS_CAP - 1) as u16; + boundary_bytes[18] = (boundary_val >> 8) as u8; + boundary_bytes[19] = boundary_val as u8; + let boundary_addr = Address::from(boundary_bytes); + + let mut precompiles = HashSet::new(); + precompiles.insert(boundary_addr); + + warm_addresses.set_precompile_addresses(precompiles); + + assert!(warm_addresses.is_warm(&boundary_addr)); + assert!(warm_addresses.precompile_short_addresses[SHORT_ADDRESS_CAP - 1]); + } +} diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index 4ebb46237a..0c9bf87b9e 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -55,11 +55,9 @@ pub fn load_accounts< if tx.tx_type() != TransactionType::Legacy { if let Some(access_list) = tx.access_list() { for item in access_list { - let address = item.address(); - let mut storage = item.storage_slots().peekable(); journal.warm_account_and_storage( - *address, - storage.map(|i| StorageKey::from_be_bytes(i.0)), + *item.address(), + item.storage_slots().map(|i| StorageKey::from_be_bytes(i.0)), )?; } } diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index e860126833..8c0a6b87b1 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -43,4 +43,3 @@ pub use interpreter_action::{ FrameInput, InterpreterAction, }; pub use interpreter_types::InterpreterTypes; -pub use primitives::{eip7907::MAX_CODE_SIZE, eip7907::MAX_INITCODE_SIZE}; diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index b9f81d1345..c4e6fe9a20 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -55,7 +55,9 @@ cfg_if::cfg_if! { use aurora_engine_modexp as _; use core::hash::Hash; -use primitives::{hardfork::SpecId, Address, HashMap, HashSet, OnceLock}; +use primitives::{ + hardfork::SpecId, short_address, Address, HashMap, HashSet, OnceLock, SHORT_ADDRESS_CAP, +}; use std::vec::Vec; /// Calculate the linear cost of a precompile. @@ -63,9 +65,6 @@ pub fn calc_linear_cost_u32(len: usize, base: u64, word: u64) -> u64 { (len as u64).div_ceil(32) * word + base } -/// Optimize short address access. -pub const SHORT_ADDRESS_CAP: usize = 300; - /// Precompiles contain map of precompile addresses to functions and HashSet of precompile addresses. #[derive(Clone, Debug)] pub struct Precompiles { @@ -309,21 +308,6 @@ impl Precompiles { } } -/// Returns the short address from Address. -/// -/// Short address is considered address that has 18 leading zeros -/// and last two bytes are less than [`SHORT_ADDRESS_CAP`]. -pub fn short_address(address: &Address) -> Option { - if address.0[..18].iter().all(|b| *b == 0) { - let short_address = u16::from_be_bytes(address.0[18..].try_into().unwrap()) as usize; - if short_address < SHORT_ADDRESS_CAP { - return Some(short_address); - } - } - - None -} - /// Precompile with address and function. #[derive(Clone, Debug)] pub struct PrecompileWithAddress(pub Address, pub PrecompileFn); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index d5ea735c68..fd1f0b8c14 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -45,3 +45,21 @@ pub type StorageKey = U256; /// Type alias for EVM storage values (256-bit unsigned integers). /// Used to store data values in smart contract storage slots. pub type StorageValue = U256; + +/// Optimize short address access. +pub const SHORT_ADDRESS_CAP: usize = 300; + +/// Returns the short address from Address. +/// +/// Short address is considered address that has 18 leading zeros +/// and last two bytes are less than [`SHORT_ADDRESS_CAP`]. +#[inline] +pub fn short_address(address: &Address) -> Option { + if address.0[..18].iter().all(|b| *b == 0) { + let short_address = u16::from_be_bytes(address.0[18..].try_into().unwrap()) as usize; + if short_address < SHORT_ADDRESS_CAP { + return Some(short_address); + } + } + None +} diff --git a/examples/cheatcode_inspector/src/main.rs b/examples/cheatcode_inspector/src/main.rs index a4fa72ba9f..7337d1ef18 100644 --- a/examples/cheatcode_inspector/src/main.rs +++ b/examples/cheatcode_inspector/src/main.rs @@ -118,7 +118,7 @@ impl JournalTr for Backend { } fn warm_coinbase_account(&mut self, address: Address) { - self.journaled_state.warm_coinbase_address = Some(address) + self.journaled_state.warm_coinbase_account(address) } fn warm_precompiles(&mut self, addresses: HashSet
) { From 1b832f7b5cb9e15786338ad00400f9568504986c Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 5 Aug 2025 18:03:50 +0200 Subject: [PATCH 3/5] cleanup --- crates/context/src/journal/inner.rs | 6 +--- crates/context/src/journal/warm_addresses.rs | 37 ++++++++++++++------ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/crates/context/src/journal/inner.rs b/crates/context/src/journal/inner.rs index 4de397ae39..a7cefb75a3 100644 --- a/crates/context/src/journal/inner.rs +++ b/crates/context/src/journal/inner.rs @@ -53,11 +53,7 @@ pub struct JournalInner { /// [EIP-161]: https://eips.ethereum.org/EIPS/eip-161 /// [EIP-6780]: https://eips.ethereum.org/EIPS/eip-6780 pub spec: SpecId, - // /// Warm coinbase address, stored separately to avoid cloning preloaded addresses. - // pub warm_coinbase_address: Option
, - // /// Precompile addresses - // pub precompiles: HashSet
, - /// Warm addresses + /// Warm addresses containing both coinbase and current precompiles. pub warm_addresses: WarmAddresses, } diff --git a/crates/context/src/journal/warm_addresses.rs b/crates/context/src/journal/warm_addresses.rs index e9236f6534..52e6004c00 100644 --- a/crates/context/src/journal/warm_addresses.rs +++ b/crates/context/src/journal/warm_addresses.rs @@ -10,11 +10,12 @@ use primitives::{short_address, Address, HashSet, SHORT_ADDRESS_CAP}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct WarmAddresses { /// Set of warm loaded precompile addresses. - pub precompile_set: HashSet
, - /// Bit vector of short address access. - pub precompile_short_addresses: BitVec, + precompile_set: HashSet
, + /// Bit vector of precompile short addressess. If address is shorter than [`SHORT_ADDRESS_CAP`] it + /// will be stored in this bit vector for faster access. + precompile_short_addresses: BitVec, /// Coinbase address. - pub coinbase: Option
, + coinbase: Option
, } impl WarmAddresses { @@ -28,6 +29,18 @@ impl WarmAddresses { } } + /// Returns the precompile addresses. + #[inline] + pub fn precompiles(&self) -> &HashSet
{ + &self.precompile_set + } + + /// Returns the coinbase address. + #[inline] + pub fn coinbase(&self) -> Option
{ + self.coinbase + } + /// Set the precompile addresses and short addresses. #[inline] pub fn set_precompile_addresses(&mut self, addresses: HashSet
) { @@ -55,12 +68,6 @@ impl WarmAddresses { self.coinbase = None; } - /// Returns the precompile addresses. - #[inline] - pub fn precompiles(&self) -> &HashSet
{ - &self.precompile_set - } - /// Returns true if the address is warm loaded. #[inline] pub fn is_warm(&self, address: &Address) -> bool { @@ -69,6 +76,11 @@ impl WarmAddresses { return true; } + // if there are no precompiles, it is cold loaded and bitvec is not set. + if self.precompile_set.is_empty() { + return false; + } + // check if it is short precompile address if let Some(short_address) = short_address(address) { return self.precompile_short_addresses[short_address]; @@ -139,7 +151,10 @@ mod tests { // Verify storage assert_eq!(warm_addresses.precompile_set, precompiles); - assert_eq!(warm_addresses.precompile_short_addresses.len(), SHORT_ADDRESS_CAP); + assert_eq!( + warm_addresses.precompile_short_addresses.len(), + SHORT_ADDRESS_CAP + ); // Verify bitvec optimization assert!(warm_addresses.precompile_short_addresses[1]); From 4626baddfeb998f98d3c36a97491072071ae35d4 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 5 Aug 2025 18:14:10 +0200 Subject: [PATCH 4/5] typos --- crates/context/Cargo.toml | 2 ++ crates/context/src/journal/warm_addresses.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/context/Cargo.toml b/crates/context/Cargo.toml index ab41e6600f..c7f5131e19 100644 --- a/crates/context/Cargo.toml +++ b/crates/context/Cargo.toml @@ -46,6 +46,7 @@ std = [ "database-interface/std", "primitives/std", "state/std", + "bitvec/std", ] serde = [ "dep:serde", @@ -56,6 +57,7 @@ serde = [ "database/serde", "database-interface/serde", "derive-where/serde", + "bitvec/serde", ] dev = [ "memory_limit", diff --git a/crates/context/src/journal/warm_addresses.rs b/crates/context/src/journal/warm_addresses.rs index 52e6004c00..257cea12d0 100644 --- a/crates/context/src/journal/warm_addresses.rs +++ b/crates/context/src/journal/warm_addresses.rs @@ -11,7 +11,7 @@ use primitives::{short_address, Address, HashSet, SHORT_ADDRESS_CAP}; pub struct WarmAddresses { /// Set of warm loaded precompile addresses. precompile_set: HashSet
, - /// Bit vector of precompile short addressess. If address is shorter than [`SHORT_ADDRESS_CAP`] it + /// Bit vector of precompile short addresses. If address is shorter than [`SHORT_ADDRESS_CAP`] it /// will be stored in this bit vector for faster access. precompile_short_addresses: BitVec, /// Coinbase address. @@ -86,7 +86,7 @@ impl WarmAddresses { return self.precompile_short_addresses[short_address]; } - // finaly check if it is precompile + // in the end check if it is inside precompile set self.precompile_set.contains(address) } From 0823edd51505f2d479dd3680b89b06821f149eaa Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 5 Aug 2025 21:36:36 +0200 Subject: [PATCH 5/5] all all_short_address flag --- crates/context/src/journal/warm_addresses.rs | 20 +++++++++++++++++++- crates/precompile/src/lib.rs | 5 +++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/context/src/journal/warm_addresses.rs b/crates/context/src/journal/warm_addresses.rs index 257cea12d0..47dbd32e02 100644 --- a/crates/context/src/journal/warm_addresses.rs +++ b/crates/context/src/journal/warm_addresses.rs @@ -6,7 +6,7 @@ use bitvec::{bitvec, order::Lsb0, vec::BitVec}; use primitives::{short_address, Address, HashSet, SHORT_ADDRESS_CAP}; /// Stores addresses that are warm loaded. Contains precompiles and coinbase address. -#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct WarmAddresses { /// Set of warm loaded precompile addresses. @@ -14,10 +14,18 @@ pub struct WarmAddresses { /// Bit vector of precompile short addresses. If address is shorter than [`SHORT_ADDRESS_CAP`] it /// will be stored in this bit vector for faster access. precompile_short_addresses: BitVec, + /// `true` if all precompiles are short addresses. + all_short_addresses: bool, /// Coinbase address. coinbase: Option
, } +impl Default for WarmAddresses { + fn default() -> Self { + Self::new() + } +} + impl WarmAddresses { /// Create a new warm addresses instance. #[inline] @@ -25,6 +33,7 @@ impl WarmAddresses { Self { precompile_set: HashSet::new(), precompile_short_addresses: BitVec::new(), + all_short_addresses: true, coinbase: None, } } @@ -47,12 +56,16 @@ impl WarmAddresses { // short address is always smaller than SHORT_ADDRESS_CAP self.precompile_short_addresses = bitvec![usize, Lsb0; 0; SHORT_ADDRESS_CAP]; + let mut all_short_addresses = true; for address in addresses.iter() { if let Some(short_address) = short_address(address) { self.precompile_short_addresses.set(short_address, true); + } else { + all_short_addresses = false; } } + self.all_short_addresses = all_short_addresses; self.precompile_set = addresses; } @@ -86,6 +99,11 @@ impl WarmAddresses { return self.precompile_short_addresses[short_address]; } + // if all precompiles are short addresses, it is cold loaded. + if self.all_short_addresses { + return false; + } + // in the end check if it is inside precompile set self.precompile_set.contains(address) } diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index c4e6fe9a20..64104bd6c4 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -74,6 +74,8 @@ pub struct Precompiles { addresses: HashSet
, /// Optimized addresses filter. optimized_access: Vec>, + /// `true` if all precompiles are short addresses. + all_short_addresses: bool, } impl Default for Precompiles { @@ -82,6 +84,7 @@ impl Default for Precompiles { inner: HashMap::new(), addresses: HashSet::new(), optimized_access: vec![None; SHORT_ADDRESS_CAP], + all_short_addresses: true, } } } @@ -266,6 +269,8 @@ impl Precompiles { for item in items.iter() { if let Some(short_address) = short_address(&item.0) { self.optimized_access[short_address] = Some(item.1); + } else { + self.all_short_addresses = false; } }