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, MEGA_SYSTEM_ADDRESS, 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).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() != MEGA_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);
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;
}
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));
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));
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));
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
}
}
pub trait JournalInspectTr {
type DBError: core::fmt::Debug;
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 inspect_account<DB: revm::Database>(
journal: &mut Journal<DB>,
address: Address,
) -> 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));
account.mark_cold();
Ok(entry.insert(account))
}
}
}
impl<DB: revm::Database> JournalInspectTr for Journal<DB> {
type DBError = <DB as revm::Database>::Error;
fn inspect_account_delegated(
&mut self,
spec: MegaSpecId,
address: Address,
) -> Result<&mut Account, Self::DBError> {
let account = inspect_account(self, address)?;
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);
}
let mut current = delegated_address;
let mut visited = std::vec![address];
loop {
let account = inspect_account(self, current)?;
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 account = if is_rex4_enabled {
inspect_account(self, address)?
} else {
self.inspect_account_delegated(spec, address)?
};
if account.storage.contains_key(&key) {
let account = if is_rex4_enabled {
inspect_account(self, address)?
} else {
self.inspect_account_delegated(spec, address)?
};
return Ok(account.storage.get(&key).unwrap());
}
let slot = self.database.storage(address, key)?;
let mut slot = EvmStorageSlot::new(slot, transaction_id);
slot.mark_cold();
let account = if is_rex4_enabled {
inspect_account(self, address)?
} else {
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_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}")));
})
}
}