use core::cell::RefCell;
#[cfg(not(feature = "std"))]
use alloc as std;
use mega_system_contracts::access_control::IMegaAccessControl::VolatileDataAccessType;
use std::{format, rc::Rc};
use crate::{
AdditionalLimit, ExternalEnvTypes, MegaContext, MegaSpecId, OracleEnv,
VolatileDataAccessTracker, ORACLE_CONTRACT_ADDRESS,
};
use alloy_evm::Database;
use alloy_primitives::{Address, Bytes, Log, B256, U256};
use delegate::delegate;
use revm::{
context::{ContextTr, JournalTr},
context_interface::{context::ContextError, journaled_state::AccountLoad},
interpreter::{Host, SStoreResult, SelfDestructResult, StateLoad},
primitives::{hash_map::Entry, StorageKey, KECCAK_EMPTY},
state::{Account, Bytecode, EvmStorageSlot},
Journal,
};
impl<DB: Database, ExtEnvs: ExternalEnvTypes> Host for MegaContext<DB, ExtEnvs> {
fn basefee(&self) -> U256 {
self.mark_block_env_accessed(VolatileDataAccessType::BaseFee);
self.inner.basefee()
}
fn gas_limit(&self) -> U256 {
self.mark_block_env_accessed(VolatileDataAccessType::GasLimit);
self.inner.gas_limit()
}
fn difficulty(&self) -> U256 {
self.mark_block_env_accessed(VolatileDataAccessType::Difficulty);
self.inner.difficulty()
}
fn prevrandao(&self) -> Option<U256> {
self.mark_block_env_accessed(VolatileDataAccessType::PrevRandao);
self.inner.prevrandao()
}
fn block_number(&self) -> U256 {
self.mark_block_env_accessed(VolatileDataAccessType::BlockNumber);
self.inner.block_number()
}
fn timestamp(&self) -> U256 {
self.mark_block_env_accessed(VolatileDataAccessType::Timestamp);
self.inner.timestamp()
}
fn beneficiary(&self) -> Address {
self.mark_block_env_accessed(VolatileDataAccessType::Coinbase);
self.inner.beneficiary()
}
fn block_hash(&mut self, number: u64) -> Option<B256> {
self.mark_block_env_accessed(VolatileDataAccessType::BlockHash);
self.inner.block_hash(number)
}
fn blob_gasprice(&self) -> U256 {
self.mark_block_env_accessed(VolatileDataAccessType::BlobBaseFee);
self.inner.blob_gasprice()
}
fn blob_hash(&self, number: usize) -> Option<U256> {
self.mark_block_env_accessed(VolatileDataAccessType::BlobHash);
self.inner.blob_hash(number)
}
delegate! {
to self.inner {
fn chain_id(&self) -> U256;
fn effective_gas_price(&self) -> U256;
fn log(&mut self, log: Log);
fn caller(&self) -> Address;
fn max_initcode_size(&self) -> usize;
fn sstore(
&mut self,
address: Address,
key: U256,
value: U256,
) -> Option<StateLoad<SStoreResult>>;
fn tstore(&mut self, address: Address, key: U256, value: U256);
fn tload(&mut self, address: Address, key: U256) -> U256;
}
}
fn selfdestruct(
&mut self,
address: Address,
target: Address,
) -> Option<StateLoad<SelfDestructResult>> {
if self.spec.is_enabled(MegaSpecId::REX4) {
self.check_and_mark_beneficiary_balance_access(&target);
}
let selfdestruct_refund = if self.spec.is_enabled(MegaSpecId::REX4) {
let journal = &mut self.inner.journaled_state;
inspect_account(journal, address, false).ok().and_then(|account| {
if !account.status.contains(revm::state::AccountStatus::CreatedLocal) {
return None;
}
let slot_count = account
.storage
.values()
.filter(|slot| {
slot.original_value().is_zero() && !slot.present_value().is_zero()
})
.count() as u64;
Some(1 + slot_count)
})
} else {
None
};
let result = self.inner.selfdestruct(address, target);
if let Some(refund) = selfdestruct_refund {
if let Some(ref state_load) = result {
if !state_load.data.previously_destroyed {
self.additional_limit.borrow_mut().on_selfdestruct(refund);
}
}
}
result
}
fn sload(&mut self, address: Address, key: U256) -> Option<StateLoad<U256>> {
if self.spec.is_enabled(MegaSpecId::MINI_REX) && address == ORACLE_CONTRACT_ADDRESS {
if self.spec.is_enabled(MegaSpecId::REX3) && self.caller() != self.system_address {
self.volatile_data_tracker.borrow_mut().check_and_mark_oracle_access(&address);
}
if let Some(value) = self.oracle_env.borrow().get_oracle_storage(key) {
return Some(StateLoad::new(value, true));
}
}
let state_load = self.inner.sload(address, key);
state_load.map(|mut state_load| {
if self.spec.is_enabled(MegaSpecId::MINI_REX) && address == ORACLE_CONTRACT_ADDRESS {
state_load.is_cold = true;
}
state_load
})
}
fn balance(&mut self, address: Address) -> Option<StateLoad<U256>> {
self.check_and_mark_beneficiary_balance_access(&address);
self.inner.balance(address)
}
fn load_account_delegated(&mut self, address: Address) -> Option<StateLoad<AccountLoad>> {
self.check_and_mark_beneficiary_balance_access(&address);
if self.spec.is_enabled(MegaSpecId::REX6) {
let resolved = self.best_effort_resolve_eip7702_delegate_address(address);
if resolved != address {
self.check_and_mark_beneficiary_balance_access(&resolved);
}
}
self.inner.load_account_delegated(address)
}
fn load_account_code(&mut self, address: Address) -> Option<StateLoad<Bytes>> {
self.check_and_mark_beneficiary_balance_access(&address);
self.inner.load_account_code(address)
}
fn load_account_code_hash(&mut self, address: Address) -> Option<StateLoad<B256>> {
self.check_and_mark_beneficiary_balance_access(&address);
self.inner.load_account_code_hash(address)
}
}
pub trait HostExt: Host {
fn spec_id(&self) -> MegaSpecId;
fn additional_limit(&self) -> &Rc<RefCell<AdditionalLimit>>;
fn sstore_set_storage_gas(&mut self, address: Address, key: U256) -> Option<u64>;
fn new_account_storage_gas(&mut self, address: Address) -> Option<u64>;
fn create_contract_storage_gas(&mut self, address: Address) -> Option<u64>;
fn volatile_data_tracker(&self) -> &Rc<RefCell<VolatileDataAccessTracker>>;
fn volatile_access_disabled(&self) -> bool;
fn beneficiary_address(&self) -> Address;
fn best_effort_resolve_eip7702_delegate_address(&mut self, address: Address) -> Address;
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> HostExt for MegaContext<DB, ExtEnvs> {
#[inline]
fn spec_id(&self) -> MegaSpecId {
self.spec
}
#[inline]
fn additional_limit(&self) -> &Rc<RefCell<AdditionalLimit>> {
debug_assert!(self.spec.is_enabled(MegaSpecId::MINI_REX));
&self.additional_limit
}
#[inline]
fn sstore_set_storage_gas(&mut self, address: Address, key: U256) -> Option<u64> {
debug_assert!(self.spec.is_enabled(MegaSpecId::MINI_REX));
if self.additional_limit.borrow().has_exceeded_limit.is_exempt() {
return Some(self.dynamic_storage_gas_cost.borrow().sstore_set_gas_unscaled());
}
let result = self.dynamic_storage_gas_cost.borrow_mut().sstore_set_gas(address, key);
result
.map_err(|e| {
*self.error() = Err(ContextError::Custom(format!("{e}")));
})
.ok()
}
#[inline]
fn new_account_storage_gas(&mut self, address: Address) -> Option<u64> {
debug_assert!(self.spec.is_enabled(MegaSpecId::MINI_REX));
if self.additional_limit.borrow().has_exceeded_limit.is_exempt() {
return Some(self.dynamic_storage_gas_cost.borrow().new_account_gas_unscaled());
}
let result = self.dynamic_storage_gas_cost.borrow_mut().new_account_gas(address);
result
.map_err(|e| {
*self.error() = Err(ContextError::Custom(format!("{e}")));
})
.ok()
}
#[inline]
fn create_contract_storage_gas(&mut self, address: Address) -> Option<u64> {
debug_assert!(self.spec.is_enabled(MegaSpecId::REX));
if self.additional_limit.borrow().has_exceeded_limit.is_exempt() {
return Some(self.dynamic_storage_gas_cost.borrow().create_contract_gas_unscaled());
}
let result = self.dynamic_storage_gas_cost.borrow_mut().create_contract_gas(address);
result
.map_err(|e| {
*self.error() = Err(ContextError::Custom(format!("{e}")));
})
.ok()
}
#[inline]
fn volatile_data_tracker(&self) -> &Rc<RefCell<VolatileDataAccessTracker>> {
&self.volatile_data_tracker
}
#[inline]
fn volatile_access_disabled(&self) -> bool {
let current_depth = self.journal_ref().depth();
self.volatile_data_tracker.borrow().volatile_access_disabled(current_depth)
}
#[inline]
fn beneficiary_address(&self) -> Address {
self.inner.block.beneficiary
}
#[inline]
fn best_effort_resolve_eip7702_delegate_address(&mut self, address: Address) -> Address {
let spec = self.spec;
self.inner
.journaled_state
.resolve_eip7702_delegate_address(spec, address)
.unwrap_or(address)
}
}
pub trait JournalInspectTr {
type DBError: core::fmt::Debug;
fn inspect_account(
&mut self,
address: Address,
load_code: bool,
) -> Result<&mut Account, Self::DBError>;
fn inspect_account_delegated(
&mut self,
spec: MegaSpecId,
address: Address,
) -> Result<&mut Account, Self::DBError>;
fn inspect_storage(
&mut self,
spec: MegaSpecId,
address: Address,
key: StorageKey,
) -> Result<&EvmStorageSlot, Self::DBError>;
fn resolve_eip7702_delegate_address(
&mut self,
spec: MegaSpecId,
address: Address,
) -> Result<Address, Self::DBError> {
let load_code = spec.is_enabled(MegaSpecId::REX5);
let account = self.inspect_account(address, load_code)?;
let delegate = account.info.code.as_ref().and_then(|code| match code {
Bytecode::Eip7702(c) => Some(c.address()),
_ => None,
});
Ok(delegate.unwrap_or(address))
}
}
fn inspect_account<DB: revm::Database>(
journal: &mut Journal<DB>,
address: Address,
load_code: bool,
) -> Result<&mut Account, <DB as revm::Database>::Error> {
let transaction_id = journal.transaction_id;
match journal.inner.state.entry(address) {
Entry::Occupied(entry) => {
let account = entry.into_mut();
if account.info.code_hash != KECCAK_EMPTY && account.info.code.is_none() {
account.info.code = Some(journal.database.code_by_hash(account.info.code_hash)?);
}
Ok(account)
}
Entry::Vacant(entry) => {
let mut account = journal
.database
.basic(address)?
.map(|info| info.into())
.unwrap_or_else(|| Account::new_not_existing(transaction_id));
if load_code && account.info.code_hash != KECCAK_EMPTY && account.info.code.is_none() {
account.info.code = Some(journal.database.code_by_hash(account.info.code_hash)?);
}
account.mark_cold();
Ok(entry.insert(account))
}
}
}
pub(crate) fn inspect_account_code_hash<DB: revm::Database>(
journal: &mut Journal<DB>,
address: Address,
) -> Result<B256, <DB as revm::Database>::Error> {
let transaction_id = journal.transaction_id;
match journal.inner.state.entry(address) {
Entry::Occupied(entry) => Ok(entry.get().info.code_hash),
Entry::Vacant(entry) => {
let mut account = journal
.database
.basic(address)?
.map(|info| info.into())
.unwrap_or_else(|| Account::new_not_existing(transaction_id));
account.mark_cold();
Ok(entry.insert(account).info.code_hash)
}
}
}
impl<DB: revm::Database> JournalInspectTr for Journal<DB> {
type DBError = <DB as revm::Database>::Error;
fn inspect_account(
&mut self,
address: Address,
load_code: bool,
) -> Result<&mut Account, Self::DBError> {
inspect_account(self, address, load_code)
}
fn inspect_account_delegated(
&mut self,
spec: MegaSpecId,
address: Address,
) -> Result<&mut Account, Self::DBError> {
let is_rex5_enabled = spec.is_enabled(MegaSpecId::REX5);
let account = inspect_account(self, address, is_rex5_enabled)?;
let delegated_address = account.info.code.as_ref().and_then(|code| match code {
Bytecode::Eip7702(code) => Some(code.address()),
_ => None,
});
let Some(delegated_address) = delegated_address else {
let account = self.inner.state.get_mut(&address).unwrap();
return Ok(account);
};
if spec.is_enabled(MegaSpecId::REX4) {
return inspect_account(self, delegated_address, is_rex5_enabled);
}
let mut current = delegated_address;
let mut visited = std::vec![address];
loop {
let account = inspect_account(self, current, false)?;
let next = account.info.code.as_ref().and_then(|code| match code {
Bytecode::Eip7702(code) => Some(code.address()),
_ => None,
});
let Some(next) = next else {
let account = self.inner.state.get_mut(¤t).unwrap();
return Ok(account);
};
if visited.contains(&next) {
let account = self.inner.state.get_mut(¤t).unwrap();
return Ok(account);
}
visited.push(current);
current = next;
}
}
fn inspect_storage(
&mut self,
spec: MegaSpecId,
address: Address,
key: StorageKey,
) -> Result<&EvmStorageSlot, Self::DBError> {
let transaction_id = self.transaction_id;
let is_rex4_enabled = spec.is_enabled(MegaSpecId::REX4);
let is_newly_created;
let account = if is_rex4_enabled {
let account = inspect_account(self, address, true)?;
is_newly_created = account.is_created();
debug_assert!(account.info.code_hash == KECCAK_EMPTY || account.info.code.is_some());
account
} else {
is_newly_created = inspect_account(self, address, false)?.is_created();
self.inspect_account_delegated(spec, address)?
};
if is_rex4_enabled {
let account = self.inner.state.get_mut(&address).unwrap();
return match account.storage.entry(key) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => {
let slot_value = if is_newly_created {
U256::ZERO
} else {
self.database.storage(address, key)?
};
let mut slot = EvmStorageSlot::new(slot_value, transaction_id);
slot.mark_cold();
Ok(entry.insert(slot))
}
};
}
if account.storage.contains_key(&key) {
let account = self.inspect_account_delegated(spec, address)?;
return Ok(account.storage.get(&key).unwrap());
}
let slot_value =
if is_newly_created { U256::ZERO } else { self.database.storage(address, key)? };
let mut slot = EvmStorageSlot::new(slot_value, transaction_id);
slot.mark_cold();
let account = self.inspect_account_delegated(spec, address)?;
account.storage.insert(key, slot);
Ok(account.storage.get(&key).expect("slot should exist"))
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> JournalInspectTr for MegaContext<DB, ExtEnvs> {
type DBError = ();
fn inspect_account(&mut self, address: Address, load_code: bool) -> Result<&mut Account, ()> {
let journal = &mut self.inner.journaled_state;
let error = &mut self.inner.error;
journal.inspect_account(address, load_code).map_err(|e| {
*error = Err(ContextError::Custom(format!("{e}")));
})
}
fn inspect_account_delegated(
&mut self,
spec: MegaSpecId,
address: Address,
) -> Result<&mut Account, ()> {
let journal = &mut self.inner.journaled_state;
let error = &mut self.inner.error;
journal.inspect_account_delegated(spec, address).map_err(|e| {
*error = Err(ContextError::Custom(format!("{e}")));
})
}
fn inspect_storage(
&mut self,
spec: MegaSpecId,
address: Address,
key: StorageKey,
) -> Result<&EvmStorageSlot, ()> {
let journal = &mut self.inner.journaled_state;
let error = &mut self.inner.error;
journal.inspect_storage(spec, address, key).map_err(|e| {
*error = Err(ContextError::Custom(format!("{e}")));
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, keccak256};
use core::cell::Cell;
use revm::{
primitives::HashMap,
state::{AccountInfo, Bytecode},
Database,
};
#[derive(Default)]
struct LazyCodeDatabase {
accounts: HashMap<Address, AccountInfo>,
codes: HashMap<B256, Bytecode>,
storage_calls: Cell<usize>,
}
impl LazyCodeDatabase {
fn with_account_code(mut self, address: Address, bytecode: Bytes) -> Self {
let code = Bytecode::new_raw(bytecode);
let code_hash = code.hash_slow();
self.accounts.insert(
address,
AccountInfo { balance: U256::ZERO, nonce: 0, code_hash, code: None },
);
self.codes.insert(code_hash, code);
self
}
fn with_eip7702_delegation(mut self, address: Address, delegate: Address) -> Self {
let code = Bytecode::new_eip7702(delegate);
let code_hash = code.hash_slow();
self.accounts.insert(
address,
AccountInfo { balance: U256::ZERO, nonce: 0, code_hash, code: None },
);
self.codes.insert(code_hash, code);
self
}
fn storage_calls(&self) -> usize {
self.storage_calls.get()
}
}
impl revm::Database for LazyCodeDatabase {
type Error = core::convert::Infallible;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
Ok(self.accounts.get(&address).cloned())
}
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
Ok(self.codes.get(&code_hash).cloned().unwrap_or_default())
}
fn storage(&mut self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
self.storage_calls.set(self.storage_calls.get() + 1);
Ok(U256::ZERO)
}
fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
Ok(B256::ZERO)
}
}
#[test]
fn test_inspect_account_vacant_path_does_not_hydrate_code() {
const ADDR: Address = address!("00000000000000000000000000000000000000aa");
let bytecode = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]); let expected_hash = keccak256(&bytecode);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
let account =
inspect_account(&mut journal, ADDR, false).expect("inspect_account must succeed");
assert_eq!(
account.info.code_hash, expected_hash,
"code_hash must propagate from the database's `basic()` result",
);
assert!(
account.info.code.is_none(),
"`load_code = false` must leave `info.code` as-is on the vacant branch",
);
}
#[test]
fn test_inspect_account_with_load_code_hydrates_lazy_bytecode_on_first_touch() {
const ADDR: Address = address!("00000000000000000000000000000000000000aa");
let bytecode = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode.clone());
let mut journal = Journal::new(db);
let account = inspect_account(&mut journal, ADDR, true)
.expect("inspect_account must succeed on first cold-touch");
let hydrated = account
.info
.code
.as_ref()
.expect("`load_code = true` must populate `info.code` from code_by_hash");
assert_eq!(
hydrated.original_bytes().as_ref(),
bytecode.as_ref(),
"hydrated bytecode must match what `code_by_hash` would return",
);
}
#[test]
fn test_inspect_account_occupied_branch_hydrates_on_second_inspection() {
const ADDR: Address = address!("00000000000000000000000000000000000000bb");
let bytecode = Bytes::from_static(&[0x5b]); let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
let first_code_hash = inspect_account(&mut journal, ADDR, false)
.expect("first inspection must succeed")
.info
.code_hash;
let second =
inspect_account(&mut journal, ADDR, false).expect("second inspection must succeed");
assert_eq!(
second.info.code_hash, first_code_hash,
"code_hash must be identical across cache miss and cache hit",
);
assert!(
second.info.code.is_some(),
"second inspection must observe the hydrated code via the occupied-branch \
`code_by_hash` load",
);
}
#[test]
fn test_inspect_account_with_load_code_leaves_eoa_code_empty() {
const EOA: Address = address!("00000000000000000000000000000000000000cc");
let mut db = LazyCodeDatabase::default();
db.accounts.insert(
EOA,
AccountInfo {
balance: U256::from(1_000_000u64),
nonce: 5,
code_hash: KECCAK_EMPTY,
code: None,
},
);
let mut journal = Journal::new(db);
let account = inspect_account(&mut journal, EOA, true)
.expect("inspect_account must succeed and be a no-op on EOAs");
assert_eq!(account.info.code_hash, KECCAK_EMPTY, "EOA code_hash must remain KECCAK_EMPTY");
assert!(
account.info.code.is_none(),
"EOA code must stay `None`; the `code_hash != KECCAK_EMPTY` guard keeps \
`code_by_hash` off the hot path for accounts without on-chain code",
);
}
#[test]
fn test_inspect_account_code_hash_never_hydrates_code() {
const ADDR: Address = address!("00000000000000000000000000000000000000dd");
let bytecode = Bytes::from_static(&[0x5b]); let expected_hash = keccak256(&bytecode);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
let vacant_hash =
inspect_account_code_hash(&mut journal, ADDR).expect("vacant read must succeed");
assert_eq!(vacant_hash, expected_hash, "vacant branch must return the code_hash");
assert!(
journal.inner.state.get(&ADDR).is_some_and(|a| a.info.code.is_none()),
"vacant branch must not hydrate info.code",
);
let occupied_hash =
inspect_account_code_hash(&mut journal, ADDR).expect("occupied read must succeed");
assert_eq!(
occupied_hash, expected_hash,
"occupied branch must return the cached code_hash"
);
assert!(
journal.inner.state.get(&ADDR).is_some_and(|a| a.info.code.is_none()),
"inspect_account_code_hash must NOT hydrate info.code on the occupied branch",
);
}
#[test]
fn test_resolve_eip7702_delegate_loads_code_from_rex5_exactly() {
const DELEGATOR: Address = address!("00000000000000000000000000000000000000e1");
const DELEGATE: Address = address!("00000000000000000000000000000000000000e2");
let db = LazyCodeDatabase::default().with_eip7702_delegation(DELEGATOR, DELEGATE);
let mut journal = Journal::new(db);
let resolved = journal
.resolve_eip7702_delegate_address(MegaSpecId::REX4, DELEGATOR)
.expect("resolve must succeed");
assert_eq!(resolved, DELEGATOR, "REX4 must not load code, so the hop is not followed");
assert!(
journal.inner.state.get(&DELEGATOR).is_some_and(|a| a.info.code.is_none()),
"REX4 resolution must leave the delegator's code lazy",
);
let db = LazyCodeDatabase::default().with_eip7702_delegation(DELEGATOR, DELEGATE);
let mut journal = Journal::new(db);
let resolved = journal
.resolve_eip7702_delegate_address(MegaSpecId::REX5, DELEGATOR)
.expect("resolve must succeed");
assert_eq!(resolved, DELEGATE, "REX5 must hydrate the code and follow the hop");
}
#[test]
fn test_inspect_account_delegated_follows_eip7702_on_cold_first_touch() {
use revm::context::JournalTr;
const DELEGATOR: Address = address!("00000000000000000000000000000000000000d1");
const DELEGATE: Address = address!("00000000000000000000000000000000000000d2");
let delegate_bytecode = Bytes::from_static(&[0x60, 0x42, 0x60, 0x00, 0x55]);
let db = LazyCodeDatabase::default()
.with_eip7702_delegation(DELEGATOR, DELEGATE)
.with_account_code(DELEGATE, delegate_bytecode.clone());
let mut journal = Journal::new(db);
let resolved = journal
.inspect_account_delegated(MegaSpecId::REX5, DELEGATOR)
.expect("inspect_account_delegated must succeed on a cold-cache first touch");
let hydrated = resolved.info.code.as_ref().expect(
"delegate's bytecode must be hydrated by the inner inspect_account call — \
without the vacant-path hydration, the cold-touch on DELEGATE would leave \
code as None and any subsequent EIP-7702 walk would see a wrongly-empty target",
);
assert!(
!matches!(hydrated, Bytecode::Eip7702(_)),
"resolved account must NOT be the delegator (whose code is the EIP-7702 \
designation); got: {hydrated:?}",
);
assert_eq!(
hydrated.original_bytes().as_ref(),
delegate_bytecode.as_ref(),
"resolved account's code must match the delegate's raw bytecode — confirms \
the delegation was followed exactly one hop",
);
}
#[test]
fn test_inspect_account_delegated_does_not_hydrate_pre_rex5() {
use revm::context::JournalTr;
const DELEGATOR: Address = address!("00000000000000000000000000000000000000d1");
const DELEGATE: Address = address!("00000000000000000000000000000000000000d2");
let delegate_bytecode = Bytes::from_static(&[0x60, 0x42, 0x60, 0x00, 0x55]);
let db = LazyCodeDatabase::default()
.with_eip7702_delegation(DELEGATOR, DELEGATE)
.with_account_code(DELEGATE, delegate_bytecode);
let mut journal = Journal::new(db);
let resolved = journal
.inspect_account_delegated(MegaSpecId::REX4, DELEGATOR)
.expect("inspect_account_delegated must succeed on pre-REX5");
assert!(
resolved.info.code.is_none(),
"pre-REX5: `inspect_account_delegated` must NOT hydrate `info.code` — the \
latent lazy-DB EIP-7702 detection gap on stable specs is intentionally \
preserved. Resolved account's code: {:?}",
resolved.info.code,
);
}
#[test]
fn test_lazy_code_database_fixture_pins_reth_style_contract() {
const KNOWN: Address = address!("00000000000000000000000000000000000000ee");
let bytecode = Bytes::from_static(&[0x00]);
let mut db = LazyCodeDatabase::default().with_account_code(KNOWN, bytecode);
let known = db.basic(KNOWN).unwrap().expect("known account must resolve");
assert!(
known.code.is_none(),
"LazyCodeDatabase::basic must NOT pre-populate code — that is the \
behavior `inspect_account` is being tested against",
);
assert!(
db.basic(Address::ZERO).unwrap().is_none(),
"unknown address must return None from basic()",
);
let unknown_hash = keccak256([0xffu8]);
assert_eq!(
db.code_by_hash(unknown_hash).unwrap().original_bytes().len(),
0,
"unknown code_hash must fall back to empty bytecode",
);
assert_eq!(db.storage(KNOWN, U256::ZERO).unwrap(), U256::ZERO);
assert_eq!(db.block_hash(0).unwrap(), B256::ZERO);
}
#[test]
fn test_inspect_storage_rex4_slot_hit_returns_existing_value() {
const ADDR: Address = address!("00000000000000000000000000000000000000bb");
let bytecode = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
let key = U256::from(3);
let expected_value = U256::from(42);
{
let tid = journal.transaction_id;
let account = inspect_account(&mut journal, ADDR, false).unwrap();
let mut slot = EvmStorageSlot::new(expected_value, tid);
slot.mark_cold();
account.storage.insert(key, slot);
}
let spec = MegaSpecId::REX4;
let slot = journal
.inspect_storage(spec, ADDR, key)
.expect("inspect_storage must succeed on existing slot");
assert_eq!(
slot.present_value, expected_value,
"REX4 slot hit must return the pre-seeded value"
);
assert!(slot.is_cold, "inspected slot must remain cold");
}
#[test]
fn test_inspect_storage_rex4_slot_miss_inserts_and_returns_db_value() {
const ADDR: Address = address!("00000000000000000000000000000000000000cc");
let bytecode = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
let key = U256::from(7);
let spec = MegaSpecId::REX4;
let slot = journal
.inspect_storage(spec, ADDR, key)
.expect("inspect_storage must succeed on absent slot");
assert_eq!(
slot.present_value,
U256::ZERO,
"absent slot on non-created account must return ZERO from database"
);
assert!(slot.is_cold, "newly inserted slot must be marked cold");
let calls_after_first = journal.database.storage_calls();
let slot2 =
journal.inspect_storage(spec, ADDR, key).expect("second inspect_storage must succeed");
assert_eq!(slot2.present_value, U256::ZERO, "second call must return the same value");
assert_eq!(
journal.database.storage_calls(),
calls_after_first,
"second inspect_storage on the same slot must hit the cache, not the DB",
);
}
#[test]
fn test_inspect_storage_rex4_newly_created_short_circuits_db() {
const ADDR: Address = address!("00000000000000000000000000000000000000dd");
let bytecode = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
{
let account = inspect_account(&mut journal, ADDR, false).unwrap();
account.mark_created();
}
let key = U256::from(1);
let spec = MegaSpecId::REX4;
let slot = journal
.inspect_storage(spec, ADDR, key)
.expect("inspect_storage must succeed on newly-created account");
assert_eq!(
slot.present_value,
U256::ZERO,
"newly-created account must return ZERO without querying database"
);
assert!(slot.is_cold, "slot must be marked cold");
}
#[test]
fn test_inspect_storage_pre_rex4_uses_delegation_path() {
const ADDR: Address = address!("00000000000000000000000000000000000000ee");
const DELEGATE: Address = address!("00000000000000000000000000000000000000ed");
let delegate_code = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default()
.with_eip7702_delegation(ADDR, DELEGATE)
.with_account_code(DELEGATE, delegate_code);
let mut journal = Journal::new(db);
let key = U256::from(5);
let expected = U256::from(123);
let spec = MegaSpecId::MINI_REX;
let _ = inspect_account(&mut journal, ADDR, false).unwrap();
{
let tid = journal.transaction_id;
let account = inspect_account(&mut journal, DELEGATE, false).unwrap();
let mut slot = EvmStorageSlot::new(expected, tid);
slot.mark_cold();
account.storage.insert(key, slot);
}
let slot = journal
.inspect_storage(spec, ADDR, key)
.expect("inspect_storage must succeed pre-REX4");
assert_eq!(
slot.present_value, expected,
"pre-REX4 storage inspection must follow the delegator to the delegate's slot",
);
assert!(slot.is_cold, "slot must be marked cold");
assert_eq!(
journal.database.storage_calls(),
0,
"delegate slot pre-seeded in the cache must be returned without hitting the DB",
);
}
#[test]
fn test_inspect_storage_pre_rex4_newly_created_short_circuits_db() {
const ADDR: Address = address!("00000000000000000000000000000000000000ef");
let bytecode = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default().with_account_code(ADDR, bytecode);
let mut journal = Journal::new(db);
{
let account = inspect_account(&mut journal, ADDR, false).unwrap();
account.mark_created();
}
let key = U256::from(6);
let spec = MegaSpecId::MINI_REX;
let slot = journal
.inspect_storage(spec, ADDR, key)
.expect("inspect_storage must succeed pre-REX4 on newly-created account");
assert_eq!(
slot.present_value,
U256::ZERO,
"pre-REX4 newly-created account must return ZERO without querying database"
);
assert!(slot.is_cold, "slot must be marked cold");
assert_eq!(
journal.database.storage_calls(),
0,
"newly-created pre-REX4 path must not hit the database storage lookup",
);
}
#[test]
fn test_inspect_storage_rex4_ignores_eip7702_delegation() {
const DELEGATOR: Address = address!("0000000000000000000000000000000000000d01");
const DELEGATE: Address = address!("0000000000000000000000000000000000000d02");
let delegate_code = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0x01]);
let db = LazyCodeDatabase::default()
.with_eip7702_delegation(DELEGATOR, DELEGATE)
.with_account_code(DELEGATE, delegate_code);
let mut journal = Journal::new(db);
let key = U256::from(2);
let expected = U256::from(99);
{
let tid = journal.transaction_id;
let account = inspect_account(&mut journal, DELEGATOR, false).unwrap();
let mut slot = EvmStorageSlot::new(expected, tid);
slot.mark_cold();
account.storage.insert(key, slot);
}
let spec = MegaSpecId::REX4;
let slot = journal
.inspect_storage(spec, DELEGATOR, key)
.expect("inspect_storage must succeed for REX4 delegator");
assert_eq!(
slot.present_value, expected,
"REX4 must read storage from delegator (original address), not delegate"
);
}
}