use std::vec::Vec;
use alloy_primitives::{address, hex, Address, Bytes, Signature, TxKind, B256, U256};
use alloy_sol_types::SolCall;
use mega_evm::{
alloy_consensus::{Signed, TxLegacy},
revm::context::result::ExecutionResult,
sandbox::{calculate_keyless_deploy_address, decode_error_result, KeylessDeployError},
test_utils::{BytecodeBuilder, MemoryDatabase},
IKeylessDeploy, MegaContext, MegaEvm, MegaHaltReason, MegaSpecId, MegaTransaction,
TestExternalEnvs, KEYLESS_DEPLOY_ADDRESS,
};
use revm::{
bytecode::opcode::{LOG1, MSTORE, RETURN, STOP},
context::TxEnv,
inspector::NoOpInspector,
};
const RELAYER: Address = address!("0000000000000000000000000000000000990000");
const SIGNED_GAS_PRICE: u128 = 100_000_000_000;
const SIGNED_GAS_LIMIT: u64 = 100_000;
const OUTER_GAS_LIMIT: u64 = 30_000_000;
const LARGE_GAS_LIMIT_OVERRIDE: u64 = 10_000_000_000;
fn build_log1_then_empty_return_init_code(topic: B256) -> Bytes {
let log_data_word: u32 = 0xdead_beef;
BytecodeBuilder::default()
.push_bytes(log_data_word.to_be_bytes())
.push_number(0_u8)
.append(MSTORE)
.push_bytes(topic.as_slice())
.push_number(4_u8)
.push_number(0x1c_u8)
.append(LOG1)
.push_number(0_u8)
.push_number(0_u8)
.append(RETURN)
.build()
}
fn build_no_logs_empty_return_init_code() -> Bytes {
BytecodeBuilder::default()
.push_number(0_u8) .push_number(0_u8) .append(RETURN)
.append(STOP) .build()
}
fn build_keyless_tx_with_init_code(init_code: Bytes) -> (Bytes, Address) {
let tx = TxLegacy {
nonce: 0,
gas_price: SIGNED_GAS_PRICE,
gas_limit: SIGNED_GAS_LIMIT,
to: TxKind::Create,
value: U256::ZERO,
input: init_code,
chain_id: None,
};
let r = U256::from_be_bytes(hex!(
"2222222222222222222222222222222222222222222222222222222222222222"
));
let s = U256::from_be_bytes(hex!(
"2222222222222222222222222222222222222222222222222222222222222222"
));
let sig = Signature::new(r, s, false);
let signed = Signed::new_unchecked(tx, sig, B256::ZERO);
let mut buf = Vec::new();
signed.rlp_encode(&mut buf);
let tx_bytes = Bytes::from(buf);
let signer = signed.recover_signer().expect("should recover signer");
(tx_bytes, signer)
}
fn run_keyless_outer(
spec: MegaSpecId,
db: &mut MemoryDatabase,
keyless_tx_bytes: Bytes,
gas_limit_override: u64,
) -> ExecutionResult<MegaHaltReason> {
let call_data = IKeylessDeploy::keylessDeployCall {
keylessDeploymentTransaction: keyless_tx_bytes,
gasLimitOverride: U256::from(gas_limit_override),
}
.abi_encode();
let external_envs = TestExternalEnvs::<std::convert::Infallible>::new();
let mut context = MegaContext::new(db, spec).with_external_envs(external_envs.into());
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::ZERO);
chain.operator_fee_constant = Some(U256::ZERO);
});
let tx = TxEnv {
caller: RELAYER,
kind: TxKind::Call(KEYLESS_DEPLOY_ADDRESS),
data: call_data.into(),
value: U256::ZERO,
gas_limit: OUTER_GAS_LIMIT,
gas_price: 0,
..Default::default()
};
let mut tx = MegaTransaction::new(tx);
tx.enveloped_tx = Some(Bytes::new());
let mut evm = MegaEvm::new(context).with_inspector(NoOpInspector);
alloy_evm::Evm::transact_commit(&mut evm, tx)
.expect("outer keyless call should not fail at the EVM-error level")
}
fn extract_error_data(result: &ExecutionResult<MegaHaltReason>) -> Bytes {
let output = match result {
ExecutionResult::Success { output, .. } => output.data().clone(),
other => panic!(
"keyless empty-code path must return Success-style (errorData carries the failure); \
got {other:?}",
),
};
let decoded = IKeylessDeploy::keylessDeployCall::abi_decode_returns(&output)
.expect("outer Success output must decode as keylessDeployReturn");
decoded.errorData
}
#[test]
fn test_rex5_keyless_empty_code_forwards_log_to_parent_receipt() {
let topic =
B256::from_slice(&hex!("ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11"));
let init_code = build_log1_then_empty_return_init_code(topic);
let (keyless_tx_bytes, signer) = build_keyless_tx_with_init_code(init_code);
let deploy_address = calculate_keyless_deploy_address(signer);
let mut db = MemoryDatabase::default();
db.set_account_balance(RELAYER, U256::from(1_000_000_000u64));
let result =
run_keyless_outer(MegaSpecId::REX5, &mut db, keyless_tx_bytes, LARGE_GAS_LIMIT_OVERRIDE);
let error_data = extract_error_data(&result);
let decoded_err = decode_error_result(&error_data);
assert!(
matches!(decoded_err, Some(KeylessDeployError::EmptyCodeDeployed { .. })),
"REX5 empty-code path must surface EmptyCodeDeployed in errorData; got {decoded_err:?}",
);
let signer_after = {
use revm::Database as _;
db.basic(signer).expect("db read should succeed").unwrap_or_default()
};
assert_eq!(
signer_after.nonce, 1,
"REX5 empty-code path must consume the replay barrier (signer nonce bump)",
);
let logs = result.logs();
assert_eq!(
logs.len(),
1,
"REX5 empty-code path must forward the constructor's LOG1 into the parent receipt; \
got logs = {logs:?}",
);
let log = &logs[0];
assert_eq!(
log.address, deploy_address,
"forwarded log must carry the deploy address as emitter",
);
let topics = log.data.topics();
assert_eq!(topics.len(), 1, "LOG1 must have exactly one topic; got {topics:?}");
assert_eq!(topics[0], topic, "LOG1 topic must match the value pushed in init code");
let expected_data = 0xdead_beef_u32.to_be_bytes();
assert_eq!(
log.data.data.as_ref(),
&expected_data,
"LOG1 data must match the 4-byte word the init code emitted",
);
}
#[test]
fn test_rex4_keyless_empty_code_drops_logs_for_replay_parity() {
let topic =
B256::from_slice(&hex!("ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11ee11"));
let init_code = build_log1_then_empty_return_init_code(topic);
let (keyless_tx_bytes, signer) = build_keyless_tx_with_init_code(init_code);
let mut db = MemoryDatabase::default();
db.set_account_balance(RELAYER, U256::from(1_000_000_000u64));
db.set_account_balance(signer, U256::from(1_000_000_000_000_000_000u128));
let rex4_gas_limit_override: u64 = 1_000_000;
let result =
run_keyless_outer(MegaSpecId::REX4, &mut db, keyless_tx_bytes, rex4_gas_limit_override);
let error_data = extract_error_data(&result);
let decoded_err = decode_error_result(&error_data);
assert!(
matches!(decoded_err, Some(KeylessDeployError::EmptyCodeDeployed { .. })),
"REX4 empty-code path must surface EmptyCodeDeployed in errorData; got {decoded_err:?}",
);
let logs = result.logs();
assert!(
logs.is_empty(),
"REX4 empty-code path must NOT forward sandbox logs (frozen for replay parity); \
got logs = {logs:?}",
);
}
#[test]
fn test_rex5_keyless_empty_code_no_logs_emitted_no_logs_forwarded() {
let init_code = build_no_logs_empty_return_init_code();
let (keyless_tx_bytes, _signer) = build_keyless_tx_with_init_code(init_code);
let mut db = MemoryDatabase::default();
db.set_account_balance(RELAYER, U256::from(1_000_000_000u64));
let result =
run_keyless_outer(MegaSpecId::REX5, &mut db, keyless_tx_bytes, LARGE_GAS_LIMIT_OVERRIDE);
let error_data = extract_error_data(&result);
let decoded_err = decode_error_result(&error_data);
assert!(
matches!(decoded_err, Some(KeylessDeployError::EmptyCodeDeployed { .. })),
"REX5 empty-code path must surface EmptyCodeDeployed in errorData; got {decoded_err:?}",
);
let logs = result.logs();
assert!(
logs.is_empty(),
"REX5 empty-code path with no LOG opcodes must forward zero logs; got logs = {logs:?}",
);
}