use std::convert::Infallible;
use alloy_primitives::{Address, Bytes, TxKind, U256};
use mega_evm::{
revm::context::result::ResultAndState,
test_utils::{BytecodeBuilder, MemoryDatabase},
BucketId, EVMError, EmptyExternalEnv, EvmTxRuntimeLimits, ExternalEnvs, MegaContext, MegaEvm,
MegaHaltReason, MegaSpecId, MegaTransaction, MegaTransactionError, SaltEnv,
};
use revm::{
bytecode::opcode::{CREATE, CREATE2, SSTORE, STOP},
context::TxEnv,
};
use crate::common::{transact_default, CALLER, CONTRACT};
const ONE_ETH: u128 = 1_000_000_000_000_000_000;
#[derive(Debug)]
struct FailingSaltEnv;
impl SaltEnv for FailingSaltEnv {
type Error = String;
fn get_bucket_capacity(&self, _bucket_id: BucketId) -> Result<u64, String> {
Err("injected salt error".into())
}
fn bucket_id_for_account(_account: Address) -> BucketId {
0
}
fn bucket_id_for_slot(_address: Address, _key: U256) -> BucketId {
0
}
}
fn transact_with_failing_salt(
spec: MegaSpecId,
code: Bytes,
) -> Result<ResultAndState<MegaHaltReason>, EVMError<Infallible, MegaTransactionError>> {
let mut db = MemoryDatabase::default()
.account_balance(CALLER, U256::from(10 * ONE_ETH))
.account_code(CONTRACT, code);
let envs: ExternalEnvs<(FailingSaltEnv, EmptyExternalEnv)> =
ExternalEnvs { salt_env: FailingSaltEnv, oracle_env: EmptyExternalEnv };
let mut context =
MegaContext::new(&mut db, spec).with_external_envs(envs).with_tx_runtime_limits(
EvmTxRuntimeLimits::no_limits()
.with_tx_data_size_limit(u64::MAX)
.with_tx_kv_updates_limit(u64::MAX),
);
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 = TxEnv {
caller: CALLER,
kind: TxKind::Call(CONTRACT),
data: Bytes::new(),
value: U256::ZERO,
gas_limit: 100_000_000,
..Default::default()
};
let mut tx = MegaTransaction::new(tx);
tx.enveloped_tx = Some(Bytes::new());
alloy_evm::Evm::transact_raw(&mut evm, tx)
}
fn assert_injected_salt_custom_error(
result: Result<ResultAndState<MegaHaltReason>, EVMError<Infallible, MegaTransactionError>>,
) {
match result {
Err(EVMError::Custom(msg)) => {
assert!(
msg.contains("injected salt error"),
"error should carry the injected SALT failure, got: {msg}"
);
}
Err(other) => panic!("expected EVMError::Custom, got: {other:?}"),
Ok(result) => panic!("expected a fatal DB-error halt, got success: {:?}", result.result),
}
}
#[test]
fn test_rex6_create_salt_error_on_create_contract_storage_gas() {
let code = BytecodeBuilder::default()
.push_number(0u64) .push_number(0u64) .push_number(0u64) .append(CREATE)
.append(STOP)
.build();
assert_injected_salt_custom_error(transact_with_failing_salt(MegaSpecId::REX6, code));
}
#[test]
fn test_rex6_sstore_salt_error_on_sstore_set_storage_gas() {
let code = BytecodeBuilder::default()
.push_number(1u64) .push_number(0u64) .append(SSTORE)
.append(STOP)
.build();
assert_injected_salt_custom_error(transact_with_failing_salt(MegaSpecId::REX6, code));
}
fn is_stack_underflow(result: &crate::common::Outcome) -> bool {
matches!(&result.result, revm::context::result::ExecutionResult::Halt { reason, .. }
if format!("{reason:?}").contains("StackUnderflow"))
}
#[test]
fn test_rex6_create2_missing_offset_stack_underflow() {
let code = BytecodeBuilder::default().push_number(0u64).append(CREATE2).append(STOP).build();
let db = MemoryDatabase::default()
.account_balance(CALLER, U256::from(10 * ONE_ETH))
.account_code(CONTRACT, code);
let outcome = transact_default(MegaSpecId::REX6, db);
assert!(is_stack_underflow(&outcome), "expected StackUnderflow, got {:?}", outcome.result);
}
#[test]
fn test_rex6_create2_missing_length_stack_underflow() {
let code = BytecodeBuilder::default()
.push_number(0u64) .push_number(0u64) .append(CREATE2)
.append(STOP)
.build();
let db = MemoryDatabase::default()
.account_balance(CALLER, U256::from(10 * ONE_ETH))
.account_code(CONTRACT, code);
let outcome = transact_default(MegaSpecId::REX6, db);
assert!(is_stack_underflow(&outcome), "expected StackUnderflow, got {:?}", outcome.result);
}