#![allow(clippy::doc_markdown)]
use std::convert::Infallible;
use alloy_primitives::{address, Address, Bytes, TxKind, U256};
use mega_evm::{
test_utils::{BytecodeBuilder, MemoryDatabase},
EvmTxRuntimeLimits, MegaContext, MegaEvm, MegaHaltReason, MegaSpecId, MegaTransaction,
TestExternalEnvs,
};
use revm::{
bytecode::opcode::*,
context::{result::ExecutionResult, TxEnv},
handler::EvmTr,
state::{AccountInfo, Bytecode},
};
const CALLER: Address = address!("1111111111111111111111111111111111111111");
const CONTRACT_WITH_BAD_CREATE2: Address = address!("2222222222222222222222222222222222222222");
fn memory_oog_create2_runtime() -> Bytes {
BytecodeBuilder::default()
.push_number(0u8) .push_number(500_000u32) .push_number(0u8) .push_number(0u8) .append(CREATE2)
.append(STOP)
.build()
}
#[test]
fn test_create2_with_oversize_initcode_len_does_not_panic() {
let mut db = MemoryDatabase::default();
db.set_account_balance(CALLER, U256::from(10_000_000_000_000_000_000u128));
let runtime = memory_oog_create2_runtime();
let bytecode = Bytecode::new_raw(runtime);
db.insert_account_info(
CONTRACT_WITH_BAD_CREATE2,
AccountInfo { code_hash: bytecode.hash_slow(), code: Some(bytecode), ..Default::default() },
);
let envs = TestExternalEnvs::<Infallible>::new();
let limits = EvmTxRuntimeLimits::no_limits();
let mut context = MegaContext::new(&mut db, MegaSpecId::REX4)
.with_external_envs(envs.into())
.with_tx_runtime_limits(limits);
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::from(0));
chain.operator_fee_constant = Some(U256::from(0));
});
let mut evm = MegaEvm::new(context);
let tx_env = TxEnv {
caller: CALLER,
kind: TxKind::Call(CONTRACT_WITH_BAD_CREATE2),
gas_limit: 200_000,
gas_price: 0,
..Default::default()
};
let mut tx = MegaTransaction::new(tx_env);
tx.enveloped_tx = Some(Bytes::new());
let res =
alloy_evm::Evm::transact_raw(&mut evm, tx).expect("transact should not surface EVMError");
assert!(
matches!(
res.result,
ExecutionResult::Success { .. } |
ExecutionResult::Halt { .. } |
ExecutionResult::Revert { .. }
),
"got: {:?}",
res.result
);
}
const COMPUTE_GAS_TEST_CONTRACT: Address = address!("3333333333333333333333333333333333333333");
fn create2_bytecode(initcode_len: u64) -> Bytes {
BytecodeBuilder::default()
.push_number(0u64) .push_number(initcode_len) .push_number(0u64) .push_number(0u64) .append(CREATE2)
.append(STOP)
.build()
}
fn memory_expansion_gas(len: u64) -> u64 {
let words = len.div_ceil(32);
3 * words + words * words / 512
}
fn run_create2_and_get_compute_gas(
initcode_len: u64,
gas_limit: u64,
) -> (ExecutionResult<MegaHaltReason>, u64) {
let mut db = MemoryDatabase::default();
db.set_account_balance(CALLER, U256::from(10_000_000_000_000_000_000u128));
let bytecode = Bytecode::new_raw(create2_bytecode(initcode_len));
db.insert_account_info(
COMPUTE_GAS_TEST_CONTRACT,
AccountInfo { code_hash: bytecode.hash_slow(), code: Some(bytecode), ..Default::default() },
);
let envs = TestExternalEnvs::<Infallible>::new();
let mut context = MegaContext::new(&mut db, MegaSpecId::REX4)
.with_external_envs(envs.into())
.with_tx_runtime_limits(EvmTxRuntimeLimits::no_limits());
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::from(0));
chain.operator_fee_constant = Some(U256::from(0));
});
let mut evm = MegaEvm::new(context);
let tx_env = TxEnv {
caller: CALLER,
kind: TxKind::Call(COMPUTE_GAS_TEST_CONTRACT),
gas_limit,
gas_price: 0,
..Default::default()
};
let mut tx = MegaTransaction::new(tx_env);
tx.enveloped_tx = Some(Bytes::new());
let res = alloy_evm::Evm::transact_raw(&mut evm, tx).expect("transact should not fail");
let compute_gas = evm.ctx_ref().additional_limit.borrow().get_usage().compute_gas;
(res.result, compute_gas)
}
#[test]
fn test_create2_memory_expansion_recorded_as_compute_gas() {
const INITCODE_LEN: u64 = 32_000;
let words = INITCODE_LEN.div_ceil(32);
let expected_expansion = memory_expansion_gas(INITCODE_LEN);
let expected_hash = 6 * words;
let expected_init_word = 2 * words;
let expected_extra = expected_expansion + expected_hash + expected_init_word;
let (baseline_result, baseline_compute_gas) = run_create2_and_get_compute_gas(0, 10_000_000);
assert!(
matches!(baseline_result, ExecutionResult::Success { .. }),
"baseline CREATE2 with empty initcode should succeed: {:?}",
baseline_result
);
let (big_result, big_compute_gas) = run_create2_and_get_compute_gas(INITCODE_LEN, 10_000_000);
assert!(
matches!(big_result, ExecutionResult::Success { .. }),
"large-initcode CREATE2 should succeed: {:?}",
big_result
);
let delta = big_compute_gas - baseline_compute_gas;
let diff = delta.abs_diff(expected_extra);
assert!(
diff < 50,
"compute gas delta ({}) should ~equal expansion ({}) + hash ({}) + init_word ({}) = {}; \
baseline={}, big={}, diff={}. If diff is close to {}, the wrapper's resize_memory! \
consumed interpreter gas but never recorded it into the compute gas tracker.",
delta,
expected_expansion,
expected_hash,
expected_init_word,
expected_extra,
baseline_compute_gas,
big_compute_gas,
diff,
expected_expansion,
);
}
#[test]
fn test_create2_resize_gas_skipped_when_canonical_create2_oogs() {
const INITCODE_LEN: u64 = 32_000;
let expected_expansion = memory_expansion_gas(INITCODE_LEN);
let tight_gas: u64 = 91_000;
let (small_result, small_compute_gas) = run_create2_and_get_compute_gas(0, tight_gas);
let (big_result, big_compute_gas) = run_create2_and_get_compute_gas(INITCODE_LEN, tight_gas);
assert!(
matches!(small_result, ExecutionResult::Halt { .. }),
"small-initcode run should OOG inside canonical CREATE2 at tight_gas={}: {:?}",
tight_gas,
small_result
);
assert!(
matches!(big_result, ExecutionResult::Halt { .. }),
"big-initcode run should OOG inside canonical CREATE2 at tight_gas={}: {:?}",
tight_gas,
big_result
);
let delta = big_compute_gas.abs_diff(small_compute_gas);
assert!(
delta < 50,
"compute gas delta on OOG ({}) should be ~0; got small={}, big={}. If delta is \
close to expected_expansion ({}), the resize gas leaked into the tracker even \
though the inner CREATE2 errored — the deferred-recording skip path is broken.",
delta,
small_compute_gas,
big_compute_gas,
expected_expansion,
);
}