#[cfg(not(feature = "std"))]
use alloc as std;
use std::vec::Vec;
use alloy_evm::Database as AlloyDatabase;
use alloy_primitives::{Address, U256};
use revm::{context::ContextTr, primitives::KECCAK_EMPTY, state::EvmState, Journal, JournalEntry};
use tracing::error;
use crate::{
merge_evm_state_optional_status, ExternalEnvTypes, JournalInspectTr, MegaContext, MegaSpecId,
};
use super::error::KeylessDeployError;
pub(super) fn apply_sandbox_state<DB: AlloyDatabase, ExtEnvs: ExternalEnvTypes>(
ctx: &mut MegaContext<DB, ExtEnvs>,
sandbox_state: EvmState,
_deploy_signer: Address,
) -> Result<(), KeylessDeployError> {
if ctx.spec.is_enabled(MegaSpecId::REX5) {
apply_sandbox_state_journaled(ctx, sandbox_state)
} else {
apply_sandbox_state_legacy(ctx, &sandbox_state);
Ok(())
}
}
fn apply_sandbox_state_legacy<DB: AlloyDatabase, ExtEnvs: ExternalEnvTypes>(
ctx: &mut MegaContext<DB, ExtEnvs>,
sandbox_state: &EvmState,
) {
let journal = ctx.journal_mut();
merge_evm_state_optional_status(&mut journal.state, sandbox_state, false);
}
fn apply_sandbox_state_journaled<DB: AlloyDatabase, ExtEnvs: ExternalEnvTypes>(
ctx: &mut MegaContext<DB, ExtEnvs>,
sandbox_state: EvmState,
) -> Result<(), KeylessDeployError> {
let journal = ctx.journal_mut();
let checkpoint = journal.inner.checkpoint();
match apply_sandbox_state_journaled_inner(journal, sandbox_state) {
Ok(()) => {
journal.inner.checkpoint_commit();
Ok(())
}
Err(error) => {
journal.inner.checkpoint_revert(checkpoint);
Err(error)
}
}
}
fn apply_sandbox_state_journaled_inner<DB: AlloyDatabase>(
journal: &mut Journal<DB>,
sandbox_state: EvmState,
) -> Result<(), KeylessDeployError> {
for (address, sandbox_account) in &sandbox_state {
journal.inspect_account(*address, false).map_err(|e| {
error!(
error = %e,
address = ?address,
"sandbox merge inspect_account failed",
);
KeylessDeployError::InternalError
})?;
if sandbox_account.is_selfdestructed() {
apply_sandbox_created_selfdestruct(journal, *address, sandbox_account)?;
continue;
}
let parent_balance = journal.inner.state.get(address).unwrap().info.balance;
let parent_nonce = journal.inner.state.get(address).unwrap().info.nonce;
let parent_code_hash = journal.inner.state.get(address).unwrap().info.code_hash;
if sandbox_account.is_created() {
ensure_sandbox_create_can_merge(parent_nonce, parent_code_hash)?;
mark_account_created_for_sandbox_merge(journal, *address);
zero_parent_storage_absent_from_sandbox(journal, *address, sandbox_account);
}
if sandbox_account.info.balance != parent_balance {
journal.inner.journal.push(JournalEntry::BalanceChange {
address: *address,
old_balance: parent_balance,
});
journal.inner.state.get_mut(address).unwrap().info.balance =
sandbox_account.info.balance;
}
if sandbox_account.info.nonce < parent_nonce {
error!(
address = ?address,
parent_nonce,
sandbox_nonce = sandbox_account.info.nonce,
"sandbox merge would decrease parent account nonce",
);
return Err(KeylessDeployError::InternalError);
}
let nonce_diff = sandbox_account.info.nonce - parent_nonce;
for _ in 0..nonce_diff {
journal.inner.journal.push(JournalEntry::NonceChange { address: *address });
}
if nonce_diff > 0 {
journal.inner.state.get_mut(address).unwrap().info.nonce = sandbox_account.info.nonce;
}
if sandbox_account.info.code_hash == parent_code_hash {
let parent_code_missing = journal
.inner
.state
.get(address)
.unwrap()
.info
.code
.as_ref()
.is_none_or(|code| code.is_empty());
if parent_code_hash != KECCAK_EMPTY && parent_code_missing {
if let Some(code) = &sandbox_account.info.code {
journal.inner.state.get_mut(address).unwrap().info.code = Some(code.clone());
}
}
} else {
if parent_code_hash != KECCAK_EMPTY {
error!(
address = ?address,
"sandbox merge would replace non-empty parent code",
);
return Err(KeylessDeployError::InternalError);
}
let Some(code) = &sandbox_account.info.code else {
error!(
address = ?address,
"sandbox account changed code hash without bytecode",
);
return Err(KeylessDeployError::InternalError);
};
journal.inner.set_code_with_hash(
*address,
code.clone(),
sandbox_account.info.code_hash,
);
}
if sandbox_account.is_touched() {
journal.inner.touch(*address);
}
for (key, sandbox_slot) in &sandbox_account.storage {
let parent_slot =
journal.inner.state.get(address).and_then(|a| a.storage.get(key)).cloned();
let had_value = parent_slot
.as_ref()
.map(|slot| slot.present_value)
.unwrap_or_else(|| sandbox_slot.original_value());
if sandbox_slot.present_value != had_value {
journal.inner.journal.push(JournalEntry::StorageChanged {
key: *key,
had_value,
address: *address,
});
}
if let Some(parent_slot) =
journal.inner.state.get_mut(address).unwrap().storage.get_mut(key)
{
parent_slot.present_value = sandbox_slot.present_value;
} else {
let mut merged_slot = sandbox_slot.clone();
merged_slot.mark_cold();
journal.inner.state.get_mut(address).unwrap().storage.insert(*key, merged_slot);
}
}
}
Ok(())
}
fn ensure_sandbox_create_can_merge(
parent_nonce: u64,
parent_code_hash: alloy_primitives::B256,
) -> Result<(), KeylessDeployError> {
if parent_nonce != 0 || parent_code_hash != KECCAK_EMPTY {
error!(
parent_nonce,
parent_code_hash = ?parent_code_hash,
"sandbox merge would create over non-empty parent account",
);
return Err(KeylessDeployError::InternalError);
}
Ok(())
}
fn mark_account_created_for_sandbox_merge<DB: AlloyDatabase>(
journal: &mut Journal<DB>,
address: Address,
) {
let is_created_globally = journal.inner.state.get_mut(&address).unwrap().mark_created_locally();
journal.inner.journal.push(JournalEntry::AccountCreated { address, is_created_globally });
}
fn zero_parent_storage_absent_from_sandbox<DB: AlloyDatabase>(
journal: &mut Journal<DB>,
address: Address,
sandbox_account: &revm::state::Account,
) {
let parent_keys =
journal.inner.state.get(&address).unwrap().storage.keys().copied().collect::<Vec<_>>();
for key in parent_keys {
if sandbox_account.storage.contains_key(&key) {
continue;
}
zero_parent_storage_slot(journal, address, key);
}
}
fn zero_parent_storage_slot<DB: AlloyDatabase>(
journal: &mut Journal<DB>,
address: Address,
key: U256,
) {
let had_value = journal
.inner
.state
.get(&address)
.and_then(|account| account.storage.get(&key))
.map(|slot| slot.present_value())
.unwrap_or_default();
if had_value.is_zero() {
return;
}
journal.inner.journal.push(JournalEntry::StorageChanged { key, had_value, address });
journal.inner.state.get_mut(&address).unwrap().storage.get_mut(&key).unwrap().present_value =
U256::ZERO;
}
fn apply_sandbox_created_selfdestruct<DB: AlloyDatabase>(
journal: &mut Journal<DB>,
address: Address,
sandbox_account: &revm::state::Account,
) -> Result<(), KeylessDeployError> {
if !sandbox_account.is_created() {
error!(
address = ?address,
"sandbox selfdestructed account was not created in the sandbox",
);
return Err(KeylessDeployError::InternalError);
}
let parent = journal.inner.state.get(&address).unwrap();
ensure_sandbox_create_can_merge(parent.info.nonce, parent.info.code_hash)?;
mark_account_created_for_sandbox_merge(journal, address);
journal.inner.touch(address);
let old_balance = journal.inner.state.get(&address).unwrap().info.balance;
if !old_balance.is_zero() {
journal.inner.journal.push(JournalEntry::BalanceChange { address, old_balance });
journal.inner.state.get_mut(&address).unwrap().info.balance = U256::ZERO;
}
let parent_keys =
journal.inner.state.get(&address).unwrap().storage.keys().copied().collect::<Vec<_>>();
for key in parent_keys {
zero_parent_storage_slot(journal, address, key);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, Bytes};
use revm::{
context_interface::journaled_state::JournalTr,
database::EmptyDB,
state::{Account, AccountInfo, EvmStorageSlot},
Database, DatabaseCommit,
};
use crate::{test_utils::MemoryDatabase, EmptyExternalEnv};
fn sandbox_created_account(code: Bytes) -> Account {
let code = revm::bytecode::Bytecode::new_raw(code);
let code_hash = revm::primitives::keccak256(code.bytes_slice());
let mut account = Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 1,
code_hash,
code: Some(code),
});
account.mark_touch();
account.mark_created();
account
}
#[test]
fn test_rex4_apply_sandbox_state_uses_legacy_direct_merge() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let deploy_addr = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX4);
let checkpoint = JournalTr::checkpoint(ctx.journal_mut());
let mut sandbox_state = EvmState::default();
sandbox_state.insert(deploy_addr, {
let mut account = sandbox_created_account(Bytes::from_static(&[0x60, 0x00]));
account
.storage
.insert(U256::from(0), EvmStorageSlot::new_changed(U256::ZERO, U256::from(42), 0));
account
});
apply_sandbox_state(&mut ctx, sandbox_state, signer).expect("apply should succeed");
let journal = ctx.journal_mut();
assert!(
journal.inner.state.get(&deploy_addr).unwrap().is_created(),
"legacy merge should preserve created status for a newly inserted account"
);
JournalTr::checkpoint_revert(journal, checkpoint);
assert!(
journal.inner.state.contains_key(&deploy_addr),
"pre-Rex5 keeps the legacy direct-merge semantics instead of journal rollback"
);
}
#[test]
fn test_rex5_apply_sandbox_state_revert_restores_parent() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let deploy_addr = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let getter_addr = address!("cccccccccccccccccccccccccccccccccccc0003");
let original_balance = U256::from(1_000_000u64);
let read_only_slot = U256::from(7);
let read_only_value = U256::from(123);
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
let journal = ctx.journal_mut();
journal.inner.state.insert(signer, {
let mut acc = Account::from(AccountInfo {
balance: original_balance,
nonce: 0,
code_hash: KECCAK_EMPTY,
code: None,
});
acc.mark_touch();
acc
});
let getter_code = revm::bytecode::Bytecode::new_raw(Bytes::from_static(&[0x60, 0x01]));
let getter_code_hash = revm::primitives::keccak256(getter_code.bytes_slice());
journal.inner.state.insert(getter_addr, {
let mut acc = Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 1,
code_hash: getter_code_hash,
code: None,
});
acc.mark_cold();
acc
});
let checkpoint = JournalTr::checkpoint(journal);
let mut sandbox_state = EvmState::default();
sandbox_state.insert(signer, {
let mut acc = Account::from(AccountInfo {
balance: U256::from(500_000u64),
nonce: 1,
code_hash: KECCAK_EMPTY,
code: None,
});
acc.mark_touch();
acc
});
sandbox_state.insert(deploy_addr, {
let mut acc = sandbox_created_account(Bytes::from_static(&[0x60, 0x00]));
acc.storage
.insert(U256::from(0), EvmStorageSlot::new_changed(U256::ZERO, U256::from(42), 0));
acc
});
sandbox_state.insert(getter_addr, {
let mut acc = Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 1,
code_hash: getter_code_hash,
code: Some(getter_code),
});
acc.storage.insert(read_only_slot, EvmStorageSlot::new(read_only_value, 0));
acc
});
apply_sandbox_state(&mut ctx, sandbox_state, signer).expect("apply should succeed");
let journal = ctx.journal_mut();
assert_eq!(
journal.inner.state.get(&signer).unwrap().info.balance,
U256::from(500_000u64),
"signer balance should reflect sandbox deduction"
);
assert_eq!(
journal.inner.state.get(&signer).unwrap().info.nonce,
1,
"signer nonce should be bumped"
);
assert!(
journal.inner.state.contains_key(&deploy_addr),
"deploy address should exist after merge"
);
assert!(
journal.inner.state.get(&deploy_addr).unwrap().is_created(),
"created account status must be preserved for final commit"
);
assert!(
journal.inner.state.get(&signer).unwrap().is_touched(),
"stateful signer diff should leave signer touched"
);
assert!(
journal.inner.state.get(&deploy_addr).unwrap().is_touched(),
"stateful deploy diff should leave deployed account touched"
);
assert!(
journal
.inner
.state
.get(&getter_addr)
.unwrap()
.info
.code
.as_ref()
.is_some_and(|code| !code.is_empty()),
"read-only bytecode loaded by sandbox should be cached for witnesses"
);
assert_eq!(
journal
.inner
.state
.get(&getter_addr)
.unwrap()
.storage
.get(&read_only_slot)
.map(|slot| slot.present_value()),
Some(read_only_value),
"read-only storage loaded by sandbox should be cached for witnesses"
);
JournalTr::checkpoint_revert(journal, checkpoint);
assert_eq!(
journal.inner.state.get(&signer).unwrap().info.balance,
original_balance,
"signer balance should be restored after revert"
);
assert_eq!(
journal.inner.state.get(&signer).unwrap().info.nonce,
0,
"signer nonce should be restored after revert"
);
if let Some(deploy_account) = journal.inner.state.get(&deploy_addr) {
assert_eq!(
deploy_account.info.code_hash, KECCAK_EMPTY,
"deployed code should be reverted"
);
assert_eq!(deploy_account.info.nonce, 0, "deploy address nonce should be reverted");
assert_eq!(
deploy_account.storage.get(&U256::from(0)).map(|slot| slot.present_value()),
Some(U256::ZERO),
"deployed storage should be reverted to the pre-sandbox value"
);
}
assert!(
journal
.inner
.state
.get(&getter_addr)
.unwrap()
.info
.code
.as_ref()
.is_some_and(|code| !code.is_empty()),
"read-only bytecode cache may remain after revert"
);
assert_eq!(
journal
.inner
.state
.get(&getter_addr)
.unwrap()
.storage
.get(&read_only_slot)
.map(|slot| slot.present_value()),
Some(read_only_value),
"read-only storage cache may remain after revert without becoming a stateful diff"
);
}
#[test]
fn test_rex5_apply_sandbox_state_error_reverts_partial_merge() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let target = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let original_balance = U256::from(1_000_000u64);
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
let journal = ctx.journal_mut();
journal.inner.state.insert(target, {
Account::from(AccountInfo {
balance: original_balance,
nonce: 1,
code_hash: KECCAK_EMPTY,
code: None,
})
});
let journal_len_before = journal.inner.journal.len();
let depth_before = journal.inner.depth;
let mut sandbox_state = EvmState::default();
sandbox_state.insert(target, {
let mut account = Account::from(AccountInfo {
balance: U256::from(500_000u64),
nonce: 0,
code_hash: KECCAK_EMPTY,
code: None,
});
account.mark_touch();
account
});
let error = apply_sandbox_state(&mut ctx, sandbox_state, signer)
.expect_err("nonce decrease should fail defensively");
assert!(matches!(error, KeylessDeployError::InternalError), "unexpected error: {error:?}");
let journal = ctx.journal_mut();
assert_eq!(
journal.inner.state.get(&target).unwrap().info.balance,
original_balance,
"partial balance diff must be reverted on merge error"
);
assert_eq!(
journal.inner.state.get(&target).unwrap().info.nonce,
1,
"parent nonce must be restored on merge error"
);
assert_eq!(
journal.inner.journal.len(),
journal_len_before,
"partial journal entries must be removed on merge error"
);
assert_eq!(journal.inner.depth, depth_before, "checkpoint depth must be balanced");
}
#[test]
fn test_rex5_apply_sandbox_state_created_commit_clears_stale_storage() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let deploy_addr = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let stale_slot = U256::from(9);
let deployed_slot = U256::from(0);
let mut db = MemoryDatabase::default();
db.set_account_storage(deploy_addr, stale_slot, U256::from(99));
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(db, MegaSpecId::REX5);
let mut sandbox_state = EvmState::default();
sandbox_state.insert(deploy_addr, {
let mut account = sandbox_created_account(Bytes::from_static(&[0x60, 0x00]));
account
.storage
.insert(deployed_slot, EvmStorageSlot::new_changed(U256::ZERO, U256::from(42), 0));
account
});
apply_sandbox_state(&mut ctx, sandbox_state, signer).expect("apply should succeed");
let state = JournalTr::finalize(ctx.journal_mut());
ctx.db_mut().commit(state);
assert_eq!(
ctx.db_mut().storage(deploy_addr, deployed_slot).unwrap(),
U256::from(42),
"deployed storage should be committed"
);
assert_eq!(
ctx.db_mut().storage(deploy_addr, stale_slot).unwrap(),
U256::ZERO,
"created-account commit must clear stale database storage"
);
}
#[test]
fn test_rex5_apply_sandbox_state_created_selfdestruct_revert_restores_parent() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let deploy_addr = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let original_balance = U256::from(7);
let storage_slot = U256::from(3);
let original_storage = U256::from(11);
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
let journal = ctx.journal_mut();
journal.inner.state.insert(deploy_addr, {
let mut account = Account::from(AccountInfo {
balance: original_balance,
nonce: 0,
code_hash: KECCAK_EMPTY,
code: None,
});
account.storage.insert(storage_slot, EvmStorageSlot::new(original_storage, 0));
account
});
let checkpoint = JournalTr::checkpoint(journal);
let mut sandbox_state = EvmState::default();
sandbox_state.insert(deploy_addr, {
let mut account = Account::from(AccountInfo::default());
account.mark_touch();
account.mark_created();
account.mark_selfdestruct();
account
});
apply_sandbox_state(&mut ctx, sandbox_state, signer).expect("apply should succeed");
let journal = ctx.journal_mut();
assert_eq!(
journal.inner.state.get(&deploy_addr).unwrap().info.balance,
U256::ZERO,
"created+selfdestructed account should be empty after merge"
);
assert_eq!(
journal
.inner
.state
.get(&deploy_addr)
.unwrap()
.storage
.get(&storage_slot)
.unwrap()
.present_value(),
U256::ZERO,
"created+selfdestructed account should clear loaded parent storage"
);
JournalTr::checkpoint_revert(journal, checkpoint);
let restored = journal.inner.state.get(&deploy_addr).unwrap();
assert_eq!(
restored.info.balance, original_balance,
"balance should be restored after revert"
);
assert_eq!(
restored.storage.get(&storage_slot).unwrap().present_value(),
original_storage,
"storage should be restored after revert"
);
assert!(!restored.is_created(), "created marker should be reverted with the checkpoint");
}
#[test]
fn test_rex5_apply_sandbox_state_rejects_non_empty_parent_code_replacement() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let target = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let parent_code = revm::bytecode::Bytecode::new_raw(Bytes::from_static(&[0x60, 0x01]));
let parent_code_hash = revm::primitives::keccak256(parent_code.bytes_slice());
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
ctx.journal_mut().inner.state.insert(target, {
Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 0,
code_hash: parent_code_hash,
code: Some(parent_code),
})
});
let sandbox_code = revm::bytecode::Bytecode::new_raw(Bytes::from_static(&[0x60, 0x02]));
let sandbox_code_hash = revm::primitives::keccak256(sandbox_code.bytes_slice());
let mut sandbox_state = EvmState::default();
sandbox_state.insert(target, {
let mut acc = Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 0,
code_hash: sandbox_code_hash,
code: Some(sandbox_code),
});
acc.mark_touch();
acc
});
let error = apply_sandbox_state(&mut ctx, sandbox_state, signer)
.expect_err("merge must reject code replacement");
assert!(matches!(error, KeylessDeployError::InternalError), "unexpected error: {error:?}");
}
#[test]
fn test_rex5_apply_sandbox_state_rejects_code_hash_without_bytecode() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let target = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
let mut sandbox_state = EvmState::default();
sandbox_state.insert(target, {
let mut acc = Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 0,
code_hash: revm::primitives::keccak256([0x60u8, 0x00]),
code: None,
});
acc.mark_touch();
acc
});
let error = apply_sandbox_state(&mut ctx, sandbox_state, signer)
.expect_err("merge must reject mismatched code_hash without bytecode");
assert!(matches!(error, KeylessDeployError::InternalError), "unexpected error: {error:?}");
}
#[test]
fn test_rex5_apply_sandbox_state_rejects_create_over_non_empty_parent_nonce() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let deploy_addr = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
ctx.journal_mut().inner.state.insert(deploy_addr, {
Account::from(AccountInfo {
balance: U256::ZERO,
nonce: 5,
code_hash: KECCAK_EMPTY,
code: None,
})
});
let mut sandbox_state = EvmState::default();
sandbox_state
.insert(deploy_addr, sandbox_created_account(Bytes::from_static(&[0x60, 0x00])));
let error = apply_sandbox_state(&mut ctx, sandbox_state, signer)
.expect_err("merge must reject CREATE over non-empty parent");
assert!(matches!(error, KeylessDeployError::InternalError), "unexpected error: {error:?}");
}
#[test]
fn test_rex5_apply_sandbox_state_db_error_during_inspect_maps_to_internal_error() {
use crate::test_utils::ErrorInjectingDatabase;
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let target = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let mut db = ErrorInjectingDatabase::new(MemoryDatabase::default());
db.fail_on_account = Some(target);
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(db, MegaSpecId::REX5);
let mut sandbox_state = EvmState::default();
sandbox_state.insert(target, {
let mut acc = Account::from(AccountInfo::default());
acc.mark_touch();
acc
});
let error = apply_sandbox_state(&mut ctx, sandbox_state, signer)
.expect_err("inspect_account DB error must surface as InternalError");
assert!(matches!(error, KeylessDeployError::InternalError), "unexpected error: {error:?}");
}
#[test]
fn test_rex5_apply_sandbox_state_rejects_selfdestruct_without_created_marker() {
let signer = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0001");
let target = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0002");
let mut ctx = MegaContext::<_, EmptyExternalEnv>::new(EmptyDB::default(), MegaSpecId::REX5);
ctx.journal_mut().inner.state.insert(target, {
Account::from(AccountInfo {
balance: U256::from(1u64),
nonce: 0,
code_hash: KECCAK_EMPTY,
code: None,
})
});
let mut sandbox_state = EvmState::default();
sandbox_state.insert(target, {
let mut acc = Account::from(AccountInfo::default());
acc.mark_touch();
acc.mark_selfdestruct();
acc
});
let error = apply_sandbox_state(&mut ctx, sandbox_state, signer)
.expect_err("merge must reject selfdestruct without created marker");
assert!(matches!(error, KeylessDeployError::InternalError), "unexpected error: {error:?}");
}
}