Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions crates/mega-evm/tests/rex4/deployment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Tests for Rex4 system contract deployment wiring.

use alloy_evm::{block::BlockExecutor, Database, Evm, EvmEnv, EvmFactory};
use alloy_hardforks::ForkCondition;
use alloy_op_evm::block::receipt_builder::OpAlloyReceiptBuilder;
use alloy_primitives::{Address, B256, Bytes};
use mega_evm::{
test_utils::MemoryDatabase, BlockLimits, MegaBlockExecutionCtx, MegaBlockExecutor,
MegaEvmFactory, MegaHardfork, MegaHardforkConfig, MegaSpecId, ACCESS_CONTROL_ADDRESS,
ACCESS_CONTROL_CODE, ACCESS_CONTROL_CODE_HASH, LIMIT_CONTROL_ADDRESS, LIMIT_CONTROL_CODE,
LIMIT_CONTROL_CODE_HASH,
};
use revm::{context::BlockEnv, database::State, primitives::U256};

fn rex4_chain_spec() -> MegaHardforkConfig {
MegaHardforkConfig::default().with(MegaHardfork::Rex4, ForkCondition::Timestamp(0))
}

Comment on lines +17 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rex4_chain_spec() only activates MegaHardfork::Rex4 without enabling prerequisite hardforks (Rex, Rex1, Rex2, Rex3). In production, all prior hardforks would be active. If apply_pre_execution_changes() deploys earlier system contracts (Oracle, HP Timestamp, Keyless Deploy) conditionally on their hardfork activation, this test won't exercise those paths, potentially masking ordering dependencies.

Consider using MegaHardforkConfig::default().with_all_activated() or chaining all prerequisite hardforks to better reflect production conditions.

fn rex3_chain_spec() -> MegaHardforkConfig {
MegaHardforkConfig::default().with(MegaHardfork::Rex3, ForkCondition::Timestamp(0))
}

fn make_block_env(timestamp: u64) -> BlockEnv {
BlockEnv {
number: U256::from(1_u64),
timestamp: U256::from(timestamp),
gas_limit: 30_000_000,
..Default::default()
}
}

fn assert_contract_deployed<DB: Database>(
db: &mut State<DB>,
address: Address,
expected_code_hash: B256,
expected_code: Bytes,
name: &str,
) {
let cache_acc = db.load_cache_account(address).expect("should load cache account");
let acc_info = cache_acc.account_info().expect("system contract account should exist");

assert_eq!(
acc_info.code_hash, expected_code_hash,
"{name} code hash should match expected bytecode"
);
assert!(acc_info.code.is_some(), "{name} code should be set");
let deployed_code = acc_info.code.as_ref().expect("code should be present");
assert_eq!(
deployed_code.original_bytes(),
expected_code,
"{name} bytecode should match the embedded system contract code"
);
}

fn assert_contract_not_deployed<DB: Database>(
db: &mut State<DB>,
address: Address,
name: &str,
) {
let cache_acc = db.load_cache_account(address).expect("should load cache account");
assert!(
cache_acc.account_info().is_none(),
"{name} should not be deployed for this spec boundary"
);
}
Comment on lines +60 to +65
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert_contract_not_deployed checks cache_acc.account_info().is_none(), which asserts the entire account doesn't exist. This is correct for a fresh MemoryDatabase, but a more precise check would verify the code_hash is KECCAK_EMPTY (no code deployed) rather than asserting the account doesn't exist at all. If the executor ever touches the account for a non-code reason, this assertion would break even though the contract isn't actually deployed.


#[test]
fn test_rex4_system_contracts_deployed_on_activation() {
let mut db = MemoryDatabase::default();
let mut state = State::builder().with_database(&mut db).build();

let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::REX4;
let evm_env = EvmEnv::new(cfg_env, make_block_env(0));

let evm_factory = MegaEvmFactory::new();
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Default::default(),
BlockLimits::no_limits(),
);
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, rex4_chain_spec(), receipt_builder);

executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");

let db_ref = executor.evm_mut().db_mut();
assert_contract_deployed(
db_ref,
ACCESS_CONTROL_ADDRESS,
ACCESS_CONTROL_CODE_HASH,
ACCESS_CONTROL_CODE,
"MegaAccessControl",
);
assert_contract_deployed(
db_ref,
LIMIT_CONTROL_ADDRESS,
LIMIT_CONTROL_CODE_HASH,
LIMIT_CONTROL_CODE,
"MegaLimitControl",
);
}

#[test]
fn test_rex4_system_contract_deployment_is_idempotent() {
let mut db = MemoryDatabase::default();
let mut state = State::builder().with_database(&mut db).build();

let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::REX4;
let evm_env = EvmEnv::new(cfg_env, make_block_env(0));

let evm_factory = MegaEvmFactory::new();
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Default::default(),
BlockLimits::no_limits(),
);
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, rex4_chain_spec(), receipt_builder);

executor.apply_pre_execution_changes().expect("first pre-execution changes should succeed");

{
let db_ref = executor.evm_mut().db_mut();
assert_contract_deployed(
db_ref,
ACCESS_CONTROL_ADDRESS,
ACCESS_CONTROL_CODE_HASH,
ACCESS_CONTROL_CODE,
"MegaAccessControl",
);
assert_contract_deployed(
db_ref,
LIMIT_CONTROL_ADDRESS,
LIMIT_CONTROL_CODE_HASH,
LIMIT_CONTROL_CODE,
"MegaLimitControl",
);
}

executor
.apply_pre_execution_changes()
.expect("second pre-execution changes should also succeed");

let db_ref = executor.evm_mut().db_mut();
assert_contract_deployed(
db_ref,
ACCESS_CONTROL_ADDRESS,
ACCESS_CONTROL_CODE_HASH,
ACCESS_CONTROL_CODE,
"MegaAccessControl after second apply",
);
assert_contract_deployed(
db_ref,
LIMIT_CONTROL_ADDRESS,
LIMIT_CONTROL_CODE_HASH,
LIMIT_CONTROL_CODE,
"MegaLimitControl after second apply",
);
}

#[test]
fn test_rex3_boundary_does_not_deploy_rex4_system_contracts() {
let mut db = MemoryDatabase::default();
let mut state = State::builder().with_database(&mut db).build();

let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::REX3;
let evm_env = EvmEnv::new(cfg_env, make_block_env(0));

let evm_factory = MegaEvmFactory::new();
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Default::default(),
BlockLimits::no_limits(),
);
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, rex3_chain_spec(), receipt_builder);

executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");

let db_ref = executor.evm_mut().db_mut();
assert_contract_not_deployed(db_ref, ACCESS_CONTROL_ADDRESS, "MegaAccessControl");
assert_contract_not_deployed(db_ref, LIMIT_CONTROL_ADDRESS, "MegaLimitControl");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Significant setup duplication across all three test functions (~20 lines of identical boilerplate each). The existing test suite typically extracts setup into helper functions. Consider a helper like fn make_executor(spec: MegaSpecId, hardforks: MegaHardforkConfig) -> MegaBlockExecutor<...> to reduce repetition.

1 change: 1 addition & 0 deletions crates/mega-evm/tests/rex4/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Tests for `Rex4` hardfork features.

mod access_control;
mod deployment;
mod frame_limits;
mod frame_state_growth;
mod gas_detention;
Expand Down