#![allow(clippy::doc_markdown)]
use std::convert::Infallible;
use alloy_primitives::{address, Address, Bytes, TxKind, U256};
use mega_evm::{
test_utils::MemoryDatabase, EthHaltReason, EvmTxRuntimeLimits, MegaContext, MegaEvm,
MegaHaltReason, MegaSpecId, MegaTransaction, OpHaltReason, TestExternalEnvs,
};
use revm::{
context::{
result::{ExecutionResult, Output, ResultAndState},
TxEnv,
},
state::EvmState,
};
const CALLER: Address = address!("2000000000000000000000000000000000000002");
fn return_zeros_initcode(code_len: u32) -> Bytes {
let bytes = code_len.to_be_bytes();
let mut code = Vec::with_capacity(7);
code.push(0x62); code.extend_from_slice(&bytes[1..]); code.push(0x60); code.push(0x00);
code.push(0xf3); Bytes::from(code)
}
fn return_ef_prefix_initcode() -> Bytes {
Bytes::from(vec![0x60, 0xef, 0x60, 0x00, 0x53, 0x60, 0x10, 0x60, 0x00, 0xf3])
}
fn run_create(
spec: MegaSpecId,
tx_compute_gas_limit: u64,
init_code: Bytes,
inspector: bool,
) -> ResultAndState<mega_evm::MegaHaltReason> {
run_create_with_gas_limit(spec, tx_compute_gas_limit, 100_000_000, init_code, inspector)
}
fn run_create_with_gas_limit(
spec: MegaSpecId,
tx_compute_gas_limit: u64,
tx_gas_limit: u64,
init_code: Bytes,
inspector: bool,
) -> ResultAndState<mega_evm::MegaHaltReason> {
let mut db = MemoryDatabase::default();
db.set_account_balance(CALLER, U256::from(10_000_000_000_000_000_000u128));
let limits = EvmTxRuntimeLimits::no_limits().with_tx_compute_gas_limit(tx_compute_gas_limit);
let mut context = MegaContext::new(&mut db, spec)
.with_external_envs(TestExternalEnvs::<Infallible>::new().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 tx_env = TxEnv {
caller: CALLER,
kind: TxKind::Create,
gas_limit: tx_gas_limit,
gas_price: 0,
data: init_code,
value: U256::ZERO,
..Default::default()
};
let mut tx = MegaTransaction::new(tx_env);
tx.enveloped_tx = Some(Bytes::new());
if inspector {
let mut evm = MegaEvm::new(context).with_inspector(revm::inspector::NoOpInspector {});
alloy_evm::Evm::transact_raw(&mut evm, tx)
.expect("transact (inspector) should not surface EVMError")
} else {
let mut evm = MegaEvm::new(context);
alloy_evm::Evm::transact_raw(&mut evm, tx).expect("transact should not surface EVMError")
}
}
fn deployed_account_has_code(state: &EvmState, deployed_address: Address) -> bool {
state
.get(&deployed_address)
.map(|acc| {
acc.info.code.as_ref().map(|c| !c.is_empty()).unwrap_or(false) ||
acc.info.code_hash != revm::primitives::KECCAK_EMPTY
})
.unwrap_or(false)
}
const CODE_LEN: u32 = 8_000;
const TIGHT_COMPUTE_BUDGET: u64 = 1_000_000;
const GENEROUS_COMPUTE_BUDGET: u64 = 4_000_000;
#[test]
fn test_create_compute_gas_exceed_during_codedeposit_does_not_split_outcome() {
let res =
run_create(MegaSpecId::REX5, TIGHT_COMPUTE_BUDGET, return_zeros_initcode(CODE_LEN), false);
let result_is_revert = matches!(&res.result, ExecutionResult::Revert { .. });
let result_is_halt = matches!(&res.result, ExecutionResult::Halt { .. });
assert!(result_is_revert || result_is_halt, "got: {:?}", res.result);
let deployed_address = CALLER.create(0);
assert!(
!deployed_account_has_code(&res.state, deployed_address),
"deployed account at {deployed_address} must not have code; state entry: {:?}",
res.state.get(&deployed_address)
);
}
#[test]
fn test_pre_rex5_preserves_split_outcome() {
let res =
run_create(MegaSpecId::REX4, TIGHT_COMPUTE_BUDGET, return_zeros_initcode(CODE_LEN), false);
let result_is_revert = matches!(&res.result, ExecutionResult::Revert { .. });
let result_is_halt = matches!(&res.result, ExecutionResult::Halt { .. });
assert!(result_is_revert || result_is_halt, "got: {:?}", res.result);
let deployed_address = CALLER.create(0);
assert!(
deployed_account_has_code(&res.state, deployed_address),
"REX4 split outcome: deployed account at {deployed_address} must have code; \
state entry: {:?}",
res.state.get(&deployed_address)
);
}
#[test]
fn test_create_compute_gas_within_budget_still_commits_normally() {
let res = run_create(
MegaSpecId::REX5,
GENEROUS_COMPUTE_BUDGET,
return_zeros_initcode(CODE_LEN),
false,
);
assert!(res.result.is_success(), "got: {:?}", res.result);
let deployed_address = match &res.result {
ExecutionResult::Success { output: Output::Create(_, Some(addr)), .. } => *addr,
other => panic!("Expected Create output with address, got: {other:?}"),
};
assert_eq!(deployed_address, CALLER.create(0));
assert!(deployed_account_has_code(&res.state, deployed_address));
}
#[test]
fn test_inspect_frame_run_path_matches_frame_run() {
let res = run_create(
MegaSpecId::REX5,
TIGHT_COMPUTE_BUDGET,
return_zeros_initcode(CODE_LEN),
true, );
let result_is_revert = matches!(&res.result, ExecutionResult::Revert { .. });
let result_is_halt = matches!(&res.result, ExecutionResult::Halt { .. });
assert!(result_is_revert || result_is_halt, "got: {:?}", res.result);
let deployed_address = CALLER.create(0);
assert!(!deployed_account_has_code(&res.state, deployed_address));
}
#[test]
fn test_create_with_eip3541_prefix_skips_pre_charge() {
let res =
run_create(MegaSpecId::REX5, GENEROUS_COMPUTE_BUDGET, return_ef_prefix_initcode(), false);
assert!(
matches!(&res.result, ExecutionResult::Halt { .. } | ExecutionResult::Revert { .. }),
"got: {:?}",
res.result
);
let deployed_address = CALLER.create(0);
assert!(!deployed_account_has_code(&res.state, deployed_address));
}
#[test]
fn test_create_insufficient_gas_for_code_deposit_skips_pre_charge() {
let res = run_create_with_gas_limit(
MegaSpecId::REX5,
TIGHT_COMPUTE_BUDGET,
1_650_000,
return_zeros_initcode(CODE_LEN),
false,
);
let reason = match &res.result {
ExecutionResult::Halt { reason, .. } => reason.clone(),
other => panic!("Expected Halt, got: {other:?}"),
};
assert!(
matches!(reason, MegaHaltReason::Base(OpHaltReason::Base(EthHaltReason::OutOfGas(_)))),
"got: {reason:?}",
);
let deployed_address = CALLER.create(0);
assert!(!deployed_account_has_code(&res.state, deployed_address));
}
#[test]
fn test_create_with_oversized_code_skips_pre_charge() {
const OVERSIZE_CODE_LEN: u32 = 64_000;
let res = run_create(
MegaSpecId::REX5,
GENEROUS_COMPUTE_BUDGET,
return_zeros_initcode(OVERSIZE_CODE_LEN),
false,
);
assert!(!res.result.is_success(), "got: {:?}", res.result);
let deployed_address = CALLER.create(0);
assert!(!deployed_account_has_code(&res.state, deployed_address));
}