use std::convert::Infallible;
use alloy_primitives::{address, Address, Bytes, TxKind, U256};
use alloy_sol_types::SolCall;
use mega_evm::{
revm::context::result::ExecutionResult,
sandbox::{
decode_error_result,
tests::{CREATE2_FACTORY_CONTRACT, CREATE2_FACTORY_DEPLOYER, CREATE2_FACTORY_TX},
KeylessDeployError,
},
test_utils::{ErrorInjectingDatabase, MemoryDatabase},
EVMError, IKeylessDeploy, MegaContext, MegaEvm, MegaSpecId, MegaTransaction, TestExternalEnvs,
KEYLESS_DEPLOY_ADDRESS, MEGA_SYSTEM_ADDRESS, ORACLE_CONTRACT_ADDRESS,
};
use revm::{context::TxEnv, inspector::NoOpInspector};
const RELAYER: Address = address!("0000000000000000000000000000000000990000");
#[test]
fn test_keyless_deploy_address_db_error_maps_to_internal_error_revert() {
let mut inner = MemoryDatabase::default();
inner.set_account_balance(
CREATE2_FACTORY_DEPLOYER,
U256::from(1_000_000_000_000_000_000_000u128),
);
inner.set_account_balance(RELAYER, U256::from(1_000_000_000u64));
let mut db = ErrorInjectingDatabase::new(inner);
db.fail_on_account = Some(CREATE2_FACTORY_CONTRACT);
let external_envs = TestExternalEnvs::<Infallible>::new();
let call_data = IKeylessDeploy::keylessDeployCall {
keylessDeploymentTransaction: Bytes::from_static(CREATE2_FACTORY_TX),
gasLimitOverride: U256::from(10_000_000u64),
}
.abi_encode();
let mut context =
MegaContext::new(db, MegaSpecId::REX5).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: 30_000_000,
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);
let res = alloy_evm::Evm::transact(&mut evm, tx).expect("outer EVM error not expected");
let revert_output = match res.result {
ExecutionResult::Revert { output, .. } => output,
other => panic!("expected outer Revert, got {other:?}"),
};
let decoded = decode_error_result(&revert_output)
.expect("revert payload must decode to a known KeylessDeployError");
assert!(
matches!(decoded, KeylessDeployError::InternalError),
"DB read failure on deploy address must surface as selector-only InternalError, got {decoded:?}",
);
let signer_after =
res.state.get(&CREATE2_FACTORY_DEPLOYER).cloned().map(|acc| acc.info).unwrap_or_default();
assert_eq!(
signer_after.nonce, 0,
"DB error before sandbox must not bump signer nonce (no replay barrier)",
);
}
#[test]
fn test_keyless_signer_nonce_db_error_maps_to_internal_error_revert() {
let mut inner = MemoryDatabase::default();
inner.set_account_balance(
CREATE2_FACTORY_DEPLOYER,
U256::from(1_000_000_000_000_000_000_000u128),
);
inner.set_account_balance(RELAYER, U256::from(1_000_000_000u64));
let mut db = ErrorInjectingDatabase::new(inner);
db.fail_on_account = Some(CREATE2_FACTORY_DEPLOYER);
let external_envs = TestExternalEnvs::<Infallible>::new();
let call_data = IKeylessDeploy::keylessDeployCall {
keylessDeploymentTransaction: Bytes::from_static(CREATE2_FACTORY_TX),
gasLimitOverride: U256::from(10_000_000u64),
}
.abi_encode();
let mut context =
MegaContext::new(db, MegaSpecId::REX5).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: 30_000_000,
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);
let res = alloy_evm::Evm::transact(&mut evm, tx).expect("outer EVM error not expected");
let revert_output = match res.result {
ExecutionResult::Revert { output, .. } => output,
other => panic!("expected outer Revert, got {other:?}"),
};
let decoded = decode_error_result(&revert_output)
.expect("revert payload must decode to a known KeylessDeployError");
assert!(
matches!(decoded, KeylessDeployError::InternalError),
"signer nonce DB read failure must surface as InternalError, got {decoded:?}",
);
let signer_after =
res.state.get(&CREATE2_FACTORY_DEPLOYER).cloned().map(|acc| acc.info).unwrap_or_default();
assert_eq!(
signer_after.nonce, 0,
"DB error before sandbox must not bump signer nonce (no replay barrier)",
);
}
#[test]
fn test_system_tx_validate_inspect_account_db_error_surfaces_as_custom() {
let mut inner = MemoryDatabase::default();
inner.set_account_balance(MEGA_SYSTEM_ADDRESS, U256::from(10u128.pow(18)));
let mut db = ErrorInjectingDatabase::new(inner);
db.fail_on_account = Some(MEGA_SYSTEM_ADDRESS);
let mut context = MegaContext::new(db, MegaSpecId::REX5);
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::ZERO);
chain.operator_fee_constant = Some(U256::ZERO);
});
let chain_id = revm::context::ContextTr::cfg(&context).chain_id;
let tx = TxEnv {
caller: MEGA_SYSTEM_ADDRESS,
kind: TxKind::Call(ORACLE_CONTRACT_ADDRESS),
data: Bytes::new(),
value: U256::ZERO,
gas_limit: 1_000_000,
gas_price: 0,
chain_id: Some(chain_id),
nonce: 0,
..Default::default()
};
let mut tx = MegaTransaction::new(tx);
tx.enveloped_tx = Some(Bytes::new());
let mut evm = MegaEvm::new(context).with_inspector(NoOpInspector);
let res = alloy_evm::Evm::transact_raw(&mut evm, tx);
match res {
Err(EVMError::Custom(msg)) => {
assert!(
msg.contains("Mega system transaction state read failed"),
"expected wrapped DB error, got: {msg}",
);
}
other => panic!(
"expected EVMError::Custom for system-tx inspect_account DB error, got {other:?}"
),
}
}