use alloy_primitives::{address, Address, Bytes, U256};
use mega_evm::{
test_utils::{BytecodeBuilder, MemoryDatabase},
EvmTxRuntimeLimits, MegaContext, MegaEvm, MegaHaltReason, MegaSpecId, MegaTransaction,
};
use revm::{
bytecode::opcode::{CREATE2, STOP},
context::{result::ExecutionResult, tx::TxEnvBuilder},
handler::EvmTr,
};
const CALLER: Address = address!("0000000000000000000000000000000000200000");
const CONTRACT: Address = address!("0000000000000000000000000000000000200001");
fn transact_create2_with_code(
spec: MegaSpecId,
code: Bytes,
) -> (ExecutionResult<MegaHaltReason>, u64) {
let mut db = MemoryDatabase::default()
.account_balance(CALLER, U256::from(10_000_000_000_000_000_000u128))
.account_code(CONTRACT, code);
let mut context =
MegaContext::new(&mut db, spec).with_tx_runtime_limits(EvmTxRuntimeLimits::from_spec(spec));
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::from(0));
chain.operator_fee_constant = Some(U256::from(0));
});
let tx =
TxEnvBuilder::default().caller(CALLER).call(CONTRACT).gas_limit(100_000_000).build_fill();
let mut tx = MegaTransaction::new(tx);
tx.enveloped_tx = Some(Bytes::new());
let mut evm = MegaEvm::new(context);
let result =
alloy_evm::Evm::transact_raw(&mut evm, tx).expect("tx should not surface EVMError");
let compute = evm.ctx_ref().additional_limit.borrow().get_usage().compute_gas;
(result.result, compute)
}
fn build_create2_empty_initcode_at_offset(offset: U256) -> Bytes {
BytecodeBuilder::default()
.push_number(0_u64) .push_number(0_u64) .push_u256(offset) .push_number(0_u64) .append(CREATE2)
.append(STOP)
.build()
}
#[test]
fn test_rex5_create2_len_zero_offset_max_succeeds() {
let code = build_create2_empty_initcode_at_offset(U256::MAX);
let (result, _compute) = transact_create2_with_code(MegaSpecId::REX5, code);
assert!(
result.is_success(),
"REX5 CREATE2(len=0, offset=U256::MAX) must succeed; got {result:?}",
);
}
#[test]
fn test_rex5_create2_len_nonzero_offset_max_still_halts() {
let code = BytecodeBuilder::default()
.push_number(0_u64) .push_number(1_u64) .push_u256(U256::MAX) .push_number(0_u64) .append(CREATE2)
.append(STOP)
.build();
let (result, _compute) = transact_create2_with_code(MegaSpecId::REX5, code);
assert!(
matches!(result, ExecutionResult::Halt { .. }),
"REX5 CREATE2(len=1, offset=U256::MAX) must halt — the len==0 short-circuit \
must not engage for nonzero len; got {result:?}",
);
}
#[test]
fn test_rex4_create2_len_zero_offset_max_halts() {
let code = build_create2_empty_initcode_at_offset(U256::MAX);
let (result, _compute) = transact_create2_with_code(MegaSpecId::REX4, code);
assert!(
matches!(result, ExecutionResult::Halt { .. }),
"REX4 CREATE2(len=0, offset=U256::MAX) must halt (frozen stable-spec behavior); \
got {result:?}",
);
}
#[test]
fn test_rex5_create2_len_zero_large_offset_skips_memory_expansion() {
let offset = U256::from(1u64 << 30);
let code = build_create2_empty_initcode_at_offset(offset);
let (result, compute) = transact_create2_with_code(MegaSpecId::REX5, code);
assert!(
result.is_success(),
"REX5 CREATE2(len=0, offset={offset}) must succeed; got {result:?}",
);
assert!(
compute < 100_000,
"REX5 CREATE2(len=0) should not record memory-expansion compute_gas; got {compute}",
);
}
#[test]
fn test_rex5_create2_len_zero_offset_zero_succeeds() {
let code = build_create2_empty_initcode_at_offset(U256::ZERO);
let (result, _compute) = transact_create2_with_code(MegaSpecId::REX5, code);
assert!(result.is_success(), "REX5 CREATE2(len=0, offset=0) must succeed; got {result:?}",);
}
#[test]
fn test_rex4_create2_len_zero_offset_zero_succeeds() {
let code = build_create2_empty_initcode_at_offset(U256::ZERO);
let (result, _compute) = transact_create2_with_code(MegaSpecId::REX4, code);
assert!(result.is_success(), "REX4 CREATE2(len=0, offset=0) must succeed; got {result:?}",);
}
#[test]
fn test_create2_len_zero_offset_zero_succeeds_on_both_specs() {
let code = build_create2_empty_initcode_at_offset(U256::ZERO);
let (result_rex5, _) = transact_create2_with_code(MegaSpecId::REX5, code.clone());
let (result_rex4, _) = transact_create2_with_code(MegaSpecId::REX4, code);
assert!(result_rex5.is_success(), "REX5 must succeed; got {result_rex5:?}");
assert!(result_rex4.is_success(), "REX4 must succeed; got {result_rex4:?}");
}