use std::convert::Infallible;
use alloy_consensus::{Signed, TxLegacy};
use alloy_evm::{block::BlockExecutor, EvmEnv};
use alloy_hardforks::ForkCondition;
use alloy_op_evm::block::receipt_builder::OpAlloyReceiptBuilder;
use alloy_primitives::{address, Address, Bytes, Signature, TxKind, B256, U256};
use alloy_sol_types::SolCall;
use mega_evm::{
test_utils::MemoryDatabase, BlockLimits, IOracle, ISequencerRegistry, MegaBlockExecutionCtx,
MegaBlockExecutorFactory, MegaEvmFactory, MegaHardfork, MegaHardforkConfig, MegaSpecId,
MegaTxEnvelope, SequencerRegistryConfig, TestExternalEnvs, MEGA_SYSTEM_ADDRESS,
ORACLE_CONTRACT_ADDRESS, SEQUENCER_REGISTRY_ADDRESS, SEQUENCER_REGISTRY_CODE,
SEQUENCER_REGISTRY_CODE_HASH,
};
use mega_system_contracts::sequencer_registry::storage_slots::{
ADMIN, CURRENT_SEQUENCER, CURRENT_SYSTEM_ADDRESS, PENDING_ADMIN, PENDING_SEQUENCER,
PENDING_SYSTEM_ADDRESS, SEQUENCER_ACTIVATION_BLOCK, SYSTEM_ADDRESS_ACTIVATION_BLOCK,
};
use revm::{
context::BlockEnv,
database::State,
handler::EvmTr,
state::{AccountInfo, Bytecode},
};
const NEW_SYSTEM_ADDRESS: Address = address!("3000000000000000000000000000000000000003");
const BOOTSTRAP_SEQUENCER: Address = address!("0x4000000000000000000000000000000000000004");
const BOOTSTRAP_ADMIN: Address = address!("0x5000000000000000000000000000000000000005");
const NEW_ADMIN: Address = address!("0x6000000000000000000000000000000000000006");
fn sequencer_registry_config() -> SequencerRegistryConfig {
SequencerRegistryConfig {
rex5_initial_sequencer: BOOTSTRAP_SEQUENCER,
rex5_initial_admin: BOOTSTRAP_ADMIN,
}
}
fn create_evm_env() -> EvmEnv<MegaSpecId> {
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::REX5;
cfg_env.chain_id = 8453;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
EvmEnv::new(cfg_env, block_env)
}
fn create_tx_from(
sender: Address,
nonce: u64,
target: Address,
data: Bytes,
) -> alloy_consensus::transaction::Recovered<MegaTxEnvelope> {
let tx_legacy = TxLegacy {
chain_id: Some(8453),
nonce,
gas_price: 0,
gas_limit: 1_000_000_000,
to: TxKind::Call(target),
value: U256::ZERO,
input: data,
};
let signed = Signed::new_unchecked(tx_legacy, Signature::test_signature(), Default::default());
let tx = MegaTxEnvelope::Legacy(signed);
alloy_consensus::transaction::Recovered::new_unchecked(tx, sender)
}
fn create_tx_from_with_gas_limit(
sender: Address,
nonce: u64,
target: Address,
data: Bytes,
gas_limit: u64,
) -> alloy_consensus::transaction::Recovered<MegaTxEnvelope> {
let tx_legacy = TxLegacy {
chain_id: Some(8453),
nonce,
gas_price: 0,
gas_limit,
to: TxKind::Call(target),
value: U256::ZERO,
input: data,
};
let signed = Signed::new_unchecked(tx_legacy, Signature::test_signature(), Default::default());
let tx = MegaTxEnvelope::Legacy(signed);
alloy_consensus::transaction::Recovered::new_unchecked(tx, sender)
}
#[test]
fn test_bootstrap_block_resolves_system_address() {
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let system_address = executor.evm().ctx_ref().system_address();
assert_eq!(
system_address, MEGA_SYSTEM_ADDRESS,
"Bootstrap block should resolve system_address to MEGA_SYSTEM_ADDRESS \
(the genesis-hardcoded initial system address)"
);
}
#[test]
fn test_system_tx_uses_resolved_system_address() {
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let calldata = IOracle::getSlotCall { slot: U256::ZERO }.abi_encode();
let tx = create_tx_from(MEGA_SYSTEM_ADDRESS, 0, ORACLE_CONTRACT_ADDRESS, Bytes::from(calldata));
let result = executor.execute_transaction(&tx);
assert!(result.is_ok(), "System tx should succeed: {:?}", result.err());
}
#[test]
fn test_system_address_change() {
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.set_account_balance(NEW_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SYSTEM_ADDRESS,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
PENDING_SYSTEM_ADDRESS,
NEW_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
SYSTEM_ADDRESS_ACTIVATION_BLOCK,
U256::from(1000),
)
.unwrap();
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let system_address = executor.evm().ctx_ref().system_address();
assert_eq!(system_address, NEW_SYSTEM_ADDRESS, "system_address should switch after change");
let calldata = IOracle::getSlotCall { slot: U256::ZERO }.abi_encode();
let tx = create_tx_from(NEW_SYSTEM_ADDRESS, 0, ORACLE_CONTRACT_ADDRESS, Bytes::from(calldata));
let result = executor.execute_transaction(&tx);
assert!(result.is_ok(), "System tx from new system address should succeed: {:?}", result.err());
let calldata = IOracle::getSlotCall { slot: U256::ZERO }.abi_encode();
let tx = create_tx_from(MEGA_SYSTEM_ADDRESS, 0, ORACLE_CONTRACT_ADDRESS, Bytes::from(calldata));
let result = executor.execute_transaction(&tx);
assert!(result.is_err(), "Tx from old system address should fail after change");
}
#[test]
fn test_sequencer_change_does_not_affect_system_address() {
let new_sequencer = address!("4000000000000000000000000000000000000004");
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SYSTEM_ADDRESS,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SEQUENCER,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
PENDING_SEQUENCER,
new_sequencer.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
SEQUENCER_ACTIVATION_BLOCK,
U256::from(1000),
)
.unwrap();
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let system_address = executor.evm().ctx_ref().system_address();
assert_eq!(
system_address, MEGA_SYSTEM_ADDRESS,
"Sequencer change should not affect system_address"
);
let calldata = IOracle::getSlotCall { slot: U256::ZERO }.abi_encode();
let tx = create_tx_from(MEGA_SYSTEM_ADDRESS, 0, ORACLE_CONTRACT_ADDRESS, Bytes::from(calldata));
let result = executor.execute_transaction(&tx);
assert!(
result.is_ok(),
"System tx from original system address should still work after sequencer change"
);
}
#[test]
fn test_dual_change_in_same_block() {
let new_sequencer = address!("4000000000000000000000000000000000000004");
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.set_account_balance(NEW_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SYSTEM_ADDRESS,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SEQUENCER,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
PENDING_SYSTEM_ADDRESS,
NEW_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
SYSTEM_ADDRESS_ACTIVATION_BLOCK,
U256::from(1000),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
PENDING_SEQUENCER,
new_sequencer.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
SEQUENCER_ACTIVATION_BLOCK,
U256::from(1000),
)
.unwrap();
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let system_address = executor.evm().ctx_ref().system_address();
assert_eq!(
system_address, NEW_SYSTEM_ADDRESS,
"Dual change: system_address should be NEW_SYSTEM_ADDRESS"
);
let calldata = IOracle::getSlotCall { slot: U256::ZERO }.abi_encode();
let tx = create_tx_from(NEW_SYSTEM_ADDRESS, 0, ORACLE_CONTRACT_ADDRESS, Bytes::from(calldata));
let result = executor.execute_transaction(&tx);
assert!(result.is_ok(), "New system address should work after dual change");
}
#[test]
fn test_pending_not_yet_due_is_noop() {
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SYSTEM_ADDRESS,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
PENDING_SYSTEM_ADDRESS,
NEW_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
SYSTEM_ADDRESS_ACTIVATION_BLOCK,
U256::from(9999),
)
.unwrap();
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let system_address = executor.evm().ctx_ref().system_address();
assert_eq!(
system_address, MEGA_SYSTEM_ADDRESS,
"Pending not-yet-due change should not change system_address"
);
}
#[test]
fn test_missing_sequencer_sequencer_registry_config_errors() {
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::Rex5, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
let err = executor
.apply_pre_execution_changes()
.expect_err("Rex5 without SequencerRegistryConfig must fail");
assert!(
err.to_string().contains("SequencerRegistryConfig not configured"),
"unexpected error: {err}"
);
}
#[test]
fn test_admin_handoff_via_block_executor() {
let mut db = MemoryDatabase::default();
db.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(1_000_000_000_000_000u64));
db.set_account_balance(BOOTSTRAP_ADMIN, U256::from(1_000_000_000_000_000u64));
db.set_account_balance(NEW_ADMIN, U256::from(1_000_000_000_000_000u64));
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
CURRENT_SYSTEM_ADDRESS,
MEGA_SYSTEM_ADDRESS.into_word().into(),
)
.unwrap();
db.insert_account_storage(
SEQUENCER_REGISTRY_ADDRESS,
ADMIN,
BOOTSTRAP_ADMIN.into_word().into(),
)
.unwrap();
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let chain_spec = MegaHardforkConfig::default()
.with(MegaHardfork::Rex5, ForkCondition::Timestamp(0))
.with_params(sequencer_registry_config());
let receipt_builder = OpAlloyReceiptBuilder::default();
let block_executor_factory =
MegaBlockExecutorFactory::new(chain_spec, evm_factory, receipt_builder);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
Some(B256::ZERO),
Bytes::new(),
BlockLimits::no_limits(),
);
let mut executor =
block_executor_factory.create_executor(&mut state, block_ctx, create_evm_env());
executor.apply_pre_execution_changes().expect("pre-execution changes should succeed");
let calldata = ISequencerRegistry::transferAdminCall { newAdmin: NEW_ADMIN }.abi_encode();
let tx = create_tx_from_with_gas_limit(
BOOTSTRAP_ADMIN,
0,
SEQUENCER_REGISTRY_ADDRESS,
Bytes::from(calldata),
1_000_000,
);
let receipt = executor
.execute_transaction(&tx)
.expect("transferAdmin tx should be accepted by the executor");
assert!(receipt > 0, "transferAdmin tx should report non-zero gas used");
let calldata = ISequencerRegistry::acceptAdminCall {}.abi_encode();
let tx = create_tx_from_with_gas_limit(
NEW_ADMIN,
0,
SEQUENCER_REGISTRY_ADDRESS,
Bytes::from(calldata),
1_000_000,
);
let receipt = executor
.execute_transaction(&tx)
.expect("acceptAdmin tx should be accepted by the executor");
assert!(receipt > 0, "acceptAdmin tx should report non-zero gas used");
drop(executor);
let admin_slot = revm::Database::storage(&mut state, SEQUENCER_REGISTRY_ADDRESS, ADMIN)
.expect("read ADMIN slot");
assert_eq!(
admin_slot,
U256::from_be_bytes(NEW_ADMIN.into_word().0),
"_admin must be promoted to NEW_ADMIN after acceptAdmin",
);
let pending_slot =
revm::Database::storage(&mut state, SEQUENCER_REGISTRY_ADDRESS, PENDING_ADMIN)
.expect("read PENDING_ADMIN slot");
assert_eq!(pending_slot, U256::ZERO, "_pendingAdmin must be cleared after acceptAdmin");
}