#[cfg(not(feature = "std"))]
use alloc as std;
use std::{rc::Rc, vec::Vec};
use alloy_evm::Database;
use alloy_primitives::Address;
use core::cell::RefCell;
use delegate::delegate;
use op_revm::{DefaultOp, L1BlockInfo, OpContext, OpSpecId};
use revm::{
context::{BlockEnv, CfgEnv, ContextSetters, ContextTr, LocalContext},
context_interface::context::ContextError,
database::EmptyDB,
Journal,
};
use crate::{
constants, AdditionalLimit, BucketId, DynamicGasCost, EmptyExternalEnv, EvmTxRuntimeLimits,
ExternalEnvTypes, ExternalEnvs, MegaSpecId, TxRuntimeLimit, VolatileDataAccess,
VolatileDataAccessTracker, VolatileDataAccessType,
};
#[derive(Debug, derive_more::Deref, derive_more::DerefMut)]
pub struct MegaContext<DB: Database, ExtEnvs: ExternalEnvTypes> {
#[deref]
#[deref_mut]
pub(crate) inner: OpContext<DB>,
pub(crate) spec: MegaSpecId,
pub(crate) disable_beneficiary: bool,
pub additional_limit: Rc<RefCell<AdditionalLimit>>,
pub(crate) salt_env: Rc<ExtEnvs::SaltEnv>,
pub dynamic_storage_gas_cost: Rc<RefCell<DynamicGasCost<Rc<ExtEnvs::SaltEnv>>>>,
pub oracle_env: Rc<RefCell<ExtEnvs::OracleEnv>>,
pub volatile_data_tracker: Rc<RefCell<VolatileDataAccessTracker>>,
pub(crate) inside_sandbox: Rc<RefCell<bool>>,
pub(crate) system_address: Address,
}
impl Default for MegaContext<EmptyDB, EmptyExternalEnv> {
fn default() -> Self {
Self::new(EmptyDB::default(), MegaSpecId::EQUIVALENCE)
}
}
impl<DB: Database> MegaContext<DB, EmptyExternalEnv> {
pub fn new(db: DB, spec: MegaSpecId) -> Self {
let salt_env = Rc::new(EmptyExternalEnv);
Self::new_with_shared_ext_envs(db, spec, salt_env, Rc::new(RefCell::new(EmptyExternalEnv)))
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> MegaContext<DB, ExtEnvs> {
pub(crate) fn new_with_shared_ext_envs(
db: DB,
spec: MegaSpecId,
salt_env: Rc<ExtEnvs::SaltEnv>,
oracle_env: Rc<RefCell<ExtEnvs::OracleEnv>>,
) -> Self {
let mut inner =
revm::Context::op().with_db(db).with_cfg(CfgEnv::new_with_spec(spec.into_op_spec()));
if spec.is_enabled(MegaSpecId::MINI_REX) {
inner.cfg.limit_contract_code_size = Some(constants::mini_rex::MAX_CONTRACT_SIZE);
inner.cfg.limit_contract_initcode_size = Some(constants::mini_rex::MAX_INITCODE_SIZE);
}
let tx_limits = EvmTxRuntimeLimits::from_spec(spec);
Self {
spec,
disable_beneficiary: false,
additional_limit: Rc::new(RefCell::new(AdditionalLimit::new(spec, tx_limits))),
salt_env: Rc::clone(&salt_env),
dynamic_storage_gas_cost: Rc::new(RefCell::new(DynamicGasCost::new(
spec,
salt_env,
inner.block.number.to::<u64>().saturating_sub(1),
))),
oracle_env,
volatile_data_tracker: Rc::new(RefCell::new(VolatileDataAccessTracker::new(
tx_limits.block_env_access_compute_gas_limit,
tx_limits.oracle_access_compute_gas_limit,
))),
inside_sandbox: Rc::new(RefCell::new(false)),
system_address: crate::MEGA_SYSTEM_ADDRESS,
inner,
}
}
}
impl<DB: Database, ExtEnvTypes: ExternalEnvTypes> MegaContext<DB, ExtEnvTypes> {
#[deprecated(note = "Use `MegaContext::new` instead")]
pub fn new_with_context(
context: OpContext<DB>,
spec: MegaSpecId,
external_envs: ExternalEnvs<ExtEnvTypes>,
) -> Self {
let mut inner = context;
inner.cfg.spec = spec.into_op_spec();
if spec.is_enabled(MegaSpecId::MINI_REX) {
if inner.cfg.limit_contract_code_size.is_none() {
inner.cfg.limit_contract_code_size = Some(constants::mini_rex::MAX_CONTRACT_SIZE);
}
if inner.cfg.limit_contract_initcode_size.is_none() {
inner.cfg.limit_contract_initcode_size =
Some(constants::mini_rex::MAX_INITCODE_SIZE);
}
}
let tx_limits = EvmTxRuntimeLimits::from_spec(spec);
let salt_env = Rc::new(external_envs.salt_env);
Self {
spec,
disable_beneficiary: false,
additional_limit: Rc::new(RefCell::new(AdditionalLimit::new(spec, tx_limits))),
salt_env: Rc::clone(&salt_env),
dynamic_storage_gas_cost: Rc::new(RefCell::new(DynamicGasCost::new(
spec,
salt_env,
inner.block.number.to::<u64>().saturating_sub(1),
))),
oracle_env: Rc::new(RefCell::new(external_envs.oracle_env)),
volatile_data_tracker: Rc::new(RefCell::new(VolatileDataAccessTracker::new(
tx_limits.block_env_access_compute_gas_limit,
tx_limits.oracle_access_compute_gas_limit,
))),
inside_sandbox: Rc::new(RefCell::new(false)),
system_address: crate::MEGA_SYSTEM_ADDRESS,
inner,
}
}
pub fn with_db<ODB: Database>(self, db: ODB) -> MegaContext<ODB, ExtEnvTypes> {
MegaContext {
inner: self.inner.with_db(db),
spec: self.spec,
disable_beneficiary: self.disable_beneficiary,
additional_limit: self.additional_limit,
salt_env: self.salt_env,
dynamic_storage_gas_cost: self.dynamic_storage_gas_cost,
oracle_env: self.oracle_env,
volatile_data_tracker: self.volatile_data_tracker,
inside_sandbox: self.inside_sandbox,
system_address: self.system_address,
}
}
pub fn with_tx(mut self, tx: crate::MegaTransaction) -> Self {
self.inner = self.inner.with_tx(tx);
self
}
pub fn with_block(mut self, block: BlockEnv) -> Self {
self.inner = self.inner.with_block(block);
self.on_new_block();
self
}
pub fn with_cfg(mut self, cfg: CfgEnv<MegaSpecId>) -> Self {
self.spec = cfg.spec;
self.inner = self.inner.with_cfg(cfg.into_op_cfg());
if self.spec.is_enabled(MegaSpecId::MINI_REX) {
if self.inner.cfg.limit_contract_code_size.is_none() {
self.inner.cfg.limit_contract_code_size =
Some(constants::mini_rex::MAX_CONTRACT_SIZE);
}
if self.inner.cfg.limit_contract_initcode_size.is_none() {
self.inner.cfg.limit_contract_initcode_size =
Some(constants::mini_rex::MAX_INITCODE_SIZE);
}
}
self
}
pub fn with_external_envs<NewExtEnvTypes: ExternalEnvTypes>(
self,
external_envs: ExternalEnvs<NewExtEnvTypes>,
) -> MegaContext<DB, NewExtEnvTypes> {
let parent_block_number = self.inner.block.number.to::<u64>().saturating_sub(1);
let spec = self.spec;
let salt_env = Rc::new(external_envs.salt_env);
MegaContext {
inner: self.inner,
spec,
disable_beneficiary: self.disable_beneficiary,
additional_limit: self.additional_limit,
salt_env: Rc::clone(&salt_env),
dynamic_storage_gas_cost: Rc::new(RefCell::new(DynamicGasCost::new(
spec,
salt_env,
parent_block_number,
))),
oracle_env: Rc::new(RefCell::new(external_envs.oracle_env)),
volatile_data_tracker: self.volatile_data_tracker,
inside_sandbox: self.inside_sandbox,
system_address: self.system_address,
}
}
pub fn with_chain(mut self, chain: L1BlockInfo) -> Self {
self.inner = self.inner.with_chain(chain);
self
}
pub fn with_tx_runtime_limits(mut self, tx_limits: EvmTxRuntimeLimits) -> Self {
self.additional_limit = Rc::new(RefCell::new(AdditionalLimit::new(self.spec, tx_limits)));
self.volatile_data_tracker = Rc::new(RefCell::new(VolatileDataAccessTracker::new(
tx_limits.block_env_access_compute_gas_limit,
tx_limits.oracle_access_compute_gas_limit,
)));
self
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> MegaContext<DB, ExtEnvs> {
pub fn mega_spec(&self) -> MegaSpecId {
self.spec
}
pub fn system_address(&self) -> Address {
self.system_address
}
pub(crate) fn set_system_address(&mut self, address: Address) {
self.system_address = address;
}
#[inline]
pub fn is_inside_sandbox(&self) -> bool {
*self.inside_sandbox.borrow()
}
#[inline]
pub(crate) fn set_inside_sandbox(&self, value: bool) {
*self.inside_sandbox.borrow_mut() = value;
}
#[inline]
pub fn with_inside_sandbox(self, value: bool) -> Self {
self.set_inside_sandbox(value);
self
}
pub fn generated_data_size(&self) -> u64 {
self.additional_limit.borrow().data_size.tx_usage()
}
pub fn kv_update_count(&self) -> u64 {
self.additional_limit.borrow().kv_update.tx_usage()
}
pub fn accessed_bucket_ids(&self) -> Vec<BucketId> {
self.dynamic_storage_gas_cost.borrow().get_bucket_ids()
}
pub fn into_inner(self) -> OpContext<DB> {
self.inner
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> MegaContext<DB, ExtEnvs> {
pub fn get_block_env_accesses(&self) -> VolatileDataAccess {
self.volatile_data_tracker.borrow().get_block_env_accesses()
}
pub fn reset_volatile_data_access(&mut self) {
self.volatile_data_tracker.borrow_mut().reset();
}
pub(crate) fn mark_block_env_accessed(&self, access_type: VolatileDataAccessType) {
self.volatile_data_tracker.borrow_mut().mark_block_env_accessed(access_type);
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> MegaContext<DB, ExtEnvs> {
pub fn disable_beneficiary(&mut self) {
self.disable_beneficiary = true;
}
pub(crate) fn check_and_mark_beneficiary_balance_access(&self, address: &Address) -> bool {
if self.inner.block.beneficiary == *address {
self.volatile_data_tracker.borrow_mut().mark_beneficiary_balance_accessed();
true
} else {
false
}
}
pub(crate) fn check_tx_beneficiary_access(&self) {
let tx = &self.inner.tx;
let beneficiary = self.inner.block.beneficiary;
if tx.base.caller == beneficiary {
self.volatile_data_tracker.borrow_mut().mark_beneficiary_balance_accessed();
}
if let revm::primitives::TxKind::Call(recipient) = tx.base.kind {
if recipient == beneficiary {
self.volatile_data_tracker.borrow_mut().mark_beneficiary_balance_accessed();
}
}
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> MegaContext<DB, ExtEnvs> {
pub(crate) fn on_new_block(&self) {
if self.spec.is_enabled(MegaSpecId::MINI_REX) {
self.dynamic_storage_gas_cost.borrow_mut().on_new_block(&self.inner.block);
}
}
pub(crate) fn on_new_tx(&mut self) {
self.reset_volatile_data_access();
if self.spec.is_enabled(MegaSpecId::MINI_REX) {
self.additional_limit.borrow_mut().reset();
self.additional_limit.borrow_mut().before_tx_start(&self.inner.tx);
}
self.check_tx_beneficiary_access();
if self.spec.is_enabled(MegaSpecId::REX4) {
let compute_gas_limit = self.volatile_data_tracker.borrow().get_compute_gas_limit();
if let Some(limit) = compute_gas_limit {
self.additional_limit.borrow_mut().set_compute_gas_limit(limit);
}
}
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> ContextTr for MegaContext<DB, ExtEnvs> {
type Block = BlockEnv;
type Tx = crate::MegaTransaction;
type Cfg = CfgEnv<OpSpecId>;
type Db = DB;
type Journal = Journal<DB>;
type Chain = L1BlockInfo;
type Local = LocalContext;
delegate! {
to self.inner {
fn tx(&self) -> &Self::Tx;
fn block(&self) -> &Self::Block;
fn cfg(&self) -> &Self::Cfg;
fn journal(&self) -> &Self::Journal;
fn journal_mut(&mut self) -> &mut Self::Journal;
fn journal_ref(&self) -> &Self::Journal;
fn db(&self) -> &Self::Db;
fn db_mut(&mut self) -> &mut Self::Db;
fn chain(&self) -> &Self::Chain;
fn chain_mut(&mut self) -> &mut Self::Chain;
fn local(&self) -> &Self::Local;
fn local_mut(&mut self) -> &mut Self::Local;
fn error(&mut self) -> &mut Result<(), ContextError<<Self::Db as revm::Database>::Error>>;
fn tx_journal_mut(&mut self) -> (&Self::Tx, &mut Self::Journal);
fn tx_local_mut(&mut self) -> (&Self::Tx, &mut Self::Local);
}
}
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> ContextSetters for MegaContext<DB, ExtEnvs> {
delegate! {
to self.inner {
fn set_block(&mut self, block: Self::Block);
fn set_tx(&mut self, tx: Self::Tx);
}
}
}
pub trait IntoMegaethCfgEnv {
fn into_megaeth_cfg(self, spec: MegaSpecId) -> CfgEnv<MegaSpecId>;
}
pub trait IntoOpCfgEnv {
fn into_op_cfg(self) -> CfgEnv<OpSpecId>;
}
impl IntoOpCfgEnv for CfgEnv<MegaSpecId> {
fn into_op_cfg(self) -> CfgEnv<OpSpecId> {
let mut op_cfg = CfgEnv::new_with_spec(OpSpecId::from(self.spec));
op_cfg.chain_id = self.chain_id;
op_cfg.tx_chain_id_check = self.tx_chain_id_check;
op_cfg.limit_contract_code_size = self.limit_contract_code_size;
op_cfg.limit_contract_initcode_size = self.limit_contract_initcode_size;
op_cfg.disable_nonce_check = self.disable_nonce_check;
op_cfg.max_blobs_per_tx = self.max_blobs_per_tx;
op_cfg.blob_base_fee_update_fraction = self.blob_base_fee_update_fraction;
op_cfg.tx_gas_limit_cap = self.tx_gas_limit_cap;
op_cfg.memory_limit = self.memory_limit;
op_cfg.disable_balance_check = self.disable_balance_check;
op_cfg.disable_block_gas_limit = self.disable_block_gas_limit;
op_cfg.disable_eip3541 = self.disable_eip3541;
op_cfg.disable_eip3607 = self.disable_eip3607;
op_cfg.disable_base_fee = self.disable_base_fee;
op_cfg
}
}
impl IntoMegaethCfgEnv for CfgEnv<OpSpecId> {
fn into_megaeth_cfg(self, spec: MegaSpecId) -> CfgEnv<MegaSpecId> {
let mut cfg = CfgEnv::new_with_spec(spec);
cfg.chain_id = self.chain_id;
cfg.tx_chain_id_check = self.tx_chain_id_check;
cfg.limit_contract_code_size = self.limit_contract_code_size;
cfg.limit_contract_initcode_size = self.limit_contract_initcode_size;
cfg.disable_nonce_check = self.disable_nonce_check;
cfg.max_blobs_per_tx = self.max_blobs_per_tx;
cfg.blob_base_fee_update_fraction = self.blob_base_fee_update_fraction;
cfg.tx_gas_limit_cap = self.tx_gas_limit_cap;
cfg.memory_limit = self.memory_limit;
cfg.disable_balance_check = self.disable_balance_check;
cfg.disable_block_gas_limit = self.disable_block_gas_limit;
cfg.disable_eip3541 = self.disable_eip3541;
cfg.disable_eip3607 = self.disable_eip3607;
cfg.disable_base_fee = self.disable_base_fee;
cfg
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::address;
use revm::{context::CfgEnv, database::EmptyDB};
use crate::TestExternalEnvs;
#[test]
fn test_with_cfg_updates_spec() {
let mut context = MegaContext::new(EmptyDB::default(), MegaSpecId::EQUIVALENCE);
assert_eq!(context.mega_spec(), MegaSpecId::EQUIVALENCE);
assert_eq!(context.inner.cfg.spec, OpSpecId::from(MegaSpecId::EQUIVALENCE));
let new_cfg = CfgEnv::new_with_spec(MegaSpecId::MINI_REX);
context = context.with_cfg(new_cfg);
assert_eq!(context.mega_spec(), MegaSpecId::MINI_REX);
assert_eq!(context.inner.cfg.spec, OpSpecId::from(MegaSpecId::MINI_REX));
}
#[test]
fn test_with_cfg_spec_consistency() {
let context = MegaContext::new(EmptyDB::default(), MegaSpecId::EQUIVALENCE);
let specs_to_test = [MegaSpecId::MINI_REX, MegaSpecId::EQUIVALENCE];
let mut current_context = context;
for spec in specs_to_test {
let cfg = CfgEnv::new_with_spec(spec);
current_context = current_context.with_cfg(cfg);
assert_eq!(current_context.mega_spec(), spec);
assert_eq!(current_context.inner.cfg.spec, OpSpecId::from(spec));
}
}
#[test]
fn test_shared_salt_env_keeps_dynamic_gas_cache_isolated() {
let external_envs = TestExternalEnvs::new();
let parent = MegaContext::new(EmptyDB::default(), MegaSpecId::REX4)
.with_external_envs(external_envs.into());
let parent_address = address!("0000000000000000000000000000000000100001");
let sandbox_address = address!("0000000000000000000000000000000000100002");
parent
.dynamic_storage_gas_cost
.borrow_mut()
.new_account_gas(parent_address)
.expect("parent bucket lookup should succeed");
let parent_bucket_ids = parent.accessed_bucket_ids();
let sandbox =
MegaContext::<_, TestExternalEnvs<std::convert::Infallible>>::new_with_shared_ext_envs(
EmptyDB::default(),
MegaSpecId::REX4,
Rc::clone(&parent.salt_env),
Rc::clone(&parent.oracle_env),
)
.with_block(parent.block().clone())
.with_chain(parent.chain().clone())
.with_inside_sandbox(true);
sandbox
.dynamic_storage_gas_cost
.borrow_mut()
.new_account_gas(sandbox_address)
.expect("sandbox bucket lookup should succeed");
assert_eq!(parent.accessed_bucket_ids(), parent_bucket_ids);
assert_ne!(sandbox.accessed_bucket_ids(), parent_bucket_ids);
}
}