use std::convert::Infallible;
use alloy_consensus::{Signed, Transaction, TxLegacy};
use alloy_eips::eip2718::Encodable2718;
use alloy_evm::{block::BlockExecutor, Evm, EvmEnv, EvmFactory};
use alloy_op_evm::block::receipt_builder::OpAlloyReceiptBuilder;
use alloy_primitives::{address, Bytes, Signature, TxKind, B256, U256};
use mega_evm::{
test_utils::{BytecodeBuilder, MemoryDatabase},
BlockLimits, MegaBlockExecutionCtx, MegaBlockExecutor, MegaEvmFactory, MegaHardforkConfig,
MegaSpecId, MegaTxEnvelope, TestExternalEnvs,
};
use revm::{
bytecode::opcode::{ADD, DUP1, LOG0, PUSH0, SLOAD, SSTORE},
context::BlockEnv,
database::{Database, State},
};
const CALLER: alloy_primitives::Address = address!("2000000000000000000000000000000000000002");
const CONTRACT: alloy_primitives::Address = address!("1000000000000000000000000000000000000001");
fn create_transaction(
nonce: u64,
gas_limit: u64,
) -> alloy_consensus::transaction::Recovered<MegaTxEnvelope> {
let tx_legacy = TxLegacy {
chain_id: Some(8453), nonce,
gas_price: 1_000_000,
gas_limit,
to: TxKind::Call(CONTRACT),
value: U256::ZERO,
input: Bytes::new(),
};
let signed = Signed::new_unchecked(tx_legacy, Signature::test_signature(), Default::default());
let tx = MegaTxEnvelope::Legacy(signed);
alloy_consensus::transaction::Recovered::new_unchecked(tx, CALLER)
}
fn create_log_generating_contract(data_size: usize) -> Bytes {
let mut builder = BytecodeBuilder::default();
if data_size <= 0xFF {
builder = builder.push_number(data_size as u8);
} else if data_size <= 0xFFFF {
builder = builder.push_number(data_size as u16);
} else {
builder = builder.push_number(data_size as u32);
}
builder = builder.append(PUSH0);
builder = builder.append(LOG0);
builder.stop().build()
}
fn create_sstore_contract(num_writes: usize) -> Bytes {
let mut builder = BytecodeBuilder::default();
for i in 1..=num_writes {
if i <= 0xFF {
builder = builder.push_number(i as u8);
} else if i <= 0xFFFF {
builder = builder.push_number(i as u16);
} else {
builder = builder.push_number(i as u32);
}
builder = builder.append(DUP1);
builder = builder.append(SLOAD);
builder = builder.push_number(1u8);
builder = builder.append(ADD);
builder = builder.append(SSTORE);
}
builder.stop().build()
}
#[test]
fn test_block_custom_data_limit() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(2000); db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_txs_data_limit(2_500),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let tx1 = create_transaction(0, 1_000_000);
let result1 = executor.execute_transaction(&tx1);
assert!(result1.is_ok(), "First transaction should succeed");
assert!(result1.unwrap() < tx1.gas_limit(), "Gas used should be less than gas limit");
let tx2 = create_transaction(1, 1_000_000);
let result2 = executor.execute_transaction(&tx2);
assert!(result2.is_ok(), "Second transaction should succeed (last tx can exceed limit)");
assert!(result2.unwrap() < tx2.gas_limit(), "Gas used should be less than gas limit");
let tx3 = create_transaction(2, 1_000_000);
let result3 = executor.execute_transaction(&tx3);
assert!(result3.is_err(), "Third transaction should fail due to block data limit");
let err_msg = format!("{:?}", result3.unwrap_err());
assert!(
err_msg.contains("TransactionDataLimit"),
"Error should mention TransactionDataLimit, got: {}",
err_msg
);
}
#[test]
fn test_block_custom_kv_update_limit() {
let mut db = MemoryDatabase::default();
let bytecode = create_sstore_contract(50); db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_kv_update_limit(1),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let result1 = executor.execute_transaction(&create_transaction(0, 10_000_000));
assert!(result1.is_ok(), "First transaction should succeed (last tx can exceed limit)");
let result2 = executor.execute_transaction(&create_transaction(1, 10_000_000));
assert!(result2.is_err(), "Second transaction should fail due to block KV update limit");
let err_msg = format!("{:?}", result2.unwrap_err());
assert!(
err_msg.contains("KVUpdateLimit"),
"Error should mention KVUpdateLimit, got: {}",
err_msg
);
}
#[test]
fn test_block_multiple_transactions_within_limits() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100); db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits()
.with_block_txs_data_limit(10_000)
.with_block_kv_update_limit(1_000),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
for nonce in 0..5 {
let tx = create_transaction(nonce, 1_000_000);
let result = executor.execute_transaction(&tx);
assert!(result.is_ok(), "Transaction {} should succeed", nonce);
assert!(result.unwrap() < tx.gas_limit(), "Gas used should be less than gas limit");
}
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 5, "Should have 5 receipts");
}
#[test]
fn test_block_data_limit_exceeded_mid_block() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(2000);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_txs_data_limit(6_000),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let result1 = executor.execute_transaction(&create_transaction(0, 1_000_000));
assert!(result1.is_ok(), "First transaction should succeed");
let result2 = executor.execute_transaction(&create_transaction(1, 1_000_000));
assert!(result2.is_ok(), "Second transaction should succeed");
let result3 = executor.execute_transaction(&create_transaction(2, 1_000_000));
assert!(result3.is_ok(), "Third transaction should succeed (last tx can exceed limit)");
let result4 = executor.execute_transaction(&create_transaction(3, 1_000_000));
assert!(result4.is_err(), "Fourth transaction should fail due to block data limit");
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 3, "Should have 3 receipts (4th tx failed)");
}
#[test]
fn test_block_kv_limit_exceeded_mid_block() {
let mut db = MemoryDatabase::default();
let bytecode = create_sstore_contract(1); db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_kv_update_limit(1),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let tx1 = create_transaction(0, 10_000_000);
let result1 = executor.execute_transaction(&tx1);
assert!(result1.is_ok(), "First transaction should succeed (last tx can exceed limit)");
assert!(result1.unwrap() < tx1.gas_limit(), "Gas used should be less than gas limit");
let tx2 = create_transaction(1, 10_000_000);
let result2 = executor.execute_transaction(&tx2);
assert!(result2.is_err(), "Second transaction should fail due to block KV update limit");
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 1, "Should have 1 receipt (2nd tx failed)");
}
#[test]
fn test_block_no_state_commit_on_limit_exceeded() {
let mut db = MemoryDatabase::default();
let bytecode = BytecodeBuilder::default()
.push_number(42u8) .append(PUSH0) .append(SSTORE) .stop()
.build();
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_kv_update_limit(0),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let result = executor.execute_transaction(&create_transaction(0, 10_000_000));
assert!(result.is_err(), "Transaction should fail due to block KV update limit of 0");
let db_state = executor.evm_mut().db_mut();
let _ = db_state.load_cache_account(CONTRACT);
let value = db_state.storage(CONTRACT, U256::ZERO).expect("Storage access should not fail");
assert_eq!(value, U256::ZERO, "Storage should not be committed");
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 0, "Should have 0 receipts (tx rejected in pre-execution)");
}
#[test]
fn test_block_tx_size_limit_default_unlimited() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx =
MegaBlockExecutionCtx::new(B256::ZERO, None, Bytes::new(), BlockLimits::no_limits());
assert_eq!(
block_ctx.block_limits.block_txs_encode_size_limit,
u64::MAX,
"Default tx size limit should be u64::MAX"
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
for nonce in 0..10 {
let tx = create_transaction(nonce, 1_000_000);
let result = executor.execute_transaction(&tx);
assert!(
result.is_ok(),
"Transaction {} should succeed with unlimited tx size limit",
nonce
);
}
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 10, "Should have 10 receipts");
}
#[test]
fn test_block_tx_size_limit_allows_multiple_transactions() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let sample_tx = create_transaction(0, 1_000_000);
let tx_size = sample_tx.encode_2718_len() as u64;
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_txs_encode_size_limit(tx_size * 5),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
for nonce in 0..5 {
let tx = create_transaction(nonce, 1_000_000);
let result = executor.execute_transaction(&tx);
assert!(result.is_ok(), "Transaction {} should succeed", nonce);
}
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 5, "Should have 5 receipts");
}
#[test]
fn test_block_tx_size_limit_exceeded_first_transaction() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_txs_encode_size_limit(10),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let result = executor.execute_transaction(&create_transaction(0, 1_000_000));
assert!(result.is_err(), "Transaction should fail due to tx size limit");
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
err_msg.contains("TransactionEncodeSizeLimit"),
"Error should mention TransactionEncodeSizeLimit, got: {}",
err_msg
);
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 0, "Should have 0 receipts (tx failed)");
}
#[test]
fn test_block_tx_size_limit_exceeded_mid_block() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let sample_tx = create_transaction(0, 1_000_000);
let tx_size = sample_tx.encode_2718_len() as u64;
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_txs_encode_size_limit(tx_size * 3),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
for nonce in 0..3 {
let tx = create_transaction(nonce, 1_000_000);
let result = executor.execute_transaction(&tx);
assert!(result.is_ok(), "Transaction {} should succeed", nonce);
}
let result = executor.execute_transaction(&create_transaction(3, 1_000_000));
assert!(result.is_err(), "Fourth transaction should fail due to tx size limit");
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
err_msg.contains("TransactionEncodeSizeLimit"),
"Error should mention TransactionEncodeSizeLimit, got: {}",
err_msg
);
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 3, "Should have 3 receipts (4th tx failed)");
}
const CALLER2: alloy_primitives::Address = address!("3000000000000000000000000000000000000003");
const CALLER3: alloy_primitives::Address = address!("4000000000000000000000000000000000000004");
fn create_transaction_with_caller(
caller: alloy_primitives::Address,
nonce: u64,
gas_limit: u64,
) -> alloy_consensus::transaction::Recovered<MegaTxEnvelope> {
let tx_legacy = TxLegacy {
chain_id: Some(8453), nonce,
gas_price: 1_000_000,
gas_limit,
to: TxKind::Call(CONTRACT),
value: U256::ZERO,
input: Bytes::new(),
};
let signed = Signed::new_unchecked(tx_legacy, Signature::test_signature(), Default::default());
let tx = MegaTxEnvelope::Legacy(signed);
alloy_consensus::transaction::Recovered::new_unchecked(tx, caller)
}
#[test]
fn test_commit_time_pre_execution_check_parallel_simulation() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
db.set_account_balance(CALLER2, U256::from(1_000_000_000_000_000u64));
db.set_account_balance(CALLER3, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let tx_gas_limit = 100_000u64;
let block_gas_limit = 150_000u64;
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: block_gas_limit,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_gas_limit(block_gas_limit),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let tx1 = create_transaction_with_caller(CALLER, 0, tx_gas_limit);
let tx2 = create_transaction_with_caller(CALLER2, 0, tx_gas_limit);
let tx3 = create_transaction_with_caller(CALLER3, 0, tx_gas_limit);
let outcome1 = executor.run_transaction(&tx1);
assert!(outcome1.is_ok(), "run_transaction(tx1) should succeed");
let outcome1 = outcome1.unwrap();
let outcome2 = executor.run_transaction(&tx2);
assert!(outcome2.is_ok(), "run_transaction(tx2) should succeed (tx1 not committed yet)");
let outcome2 = outcome2.unwrap();
let outcome3 = executor.run_transaction(&tx3);
assert!(outcome3.is_ok(), "run_transaction(tx3) should succeed (no tx committed yet)");
let outcome3 = outcome3.unwrap();
let commit2 = executor.commit_transaction_outcome(outcome2);
assert!(commit2.is_ok(), "commit_transaction_outcome(tx2) should succeed");
let commit3 = executor.commit_transaction_outcome(outcome3);
assert!(commit3.is_ok(), "commit_transaction_outcome(tx3) should succeed");
let commit1 = executor.commit_transaction_outcome(outcome1);
assert!(commit1.is_err(), "commit_transaction_outcome(tx1) should fail - block at capacity");
let err_msg = format!("{:?}", commit1.unwrap_err());
assert!(
err_msg.contains("TransactionGasLimitMoreThanAvailableBlockGas"),
"Error should mention TransactionGasLimitMoreThanAvailableBlockGas, got: {}",
err_msg
);
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(
receipts.receipts.len(),
2,
"Should have 2 receipts (tx1 failed at commit time due to race condition)"
);
}
#[test]
fn test_block_tx_size_limit_with_varying_sizes() {
let mut db = MemoryDatabase::default();
let bytecode = create_log_generating_contract(100);
db.set_account_code(CONTRACT, bytecode);
db.set_account_balance(CALLER, U256::from(1_000_000_000_000_000u64));
let mut state = State::builder().with_database(&mut db).build();
let external_envs = TestExternalEnvs::<Infallible>::new();
let evm_factory = MegaEvmFactory::new().with_external_env_factory(external_envs);
let mut cfg_env = revm::context::CfgEnv::default();
cfg_env.spec = MegaSpecId::MINI_REX;
let block_env = BlockEnv {
number: U256::from(1000),
timestamp: U256::from(1_800_000_000),
gas_limit: 30_000_000,
..Default::default()
};
let evm_env = EvmEnv::new(cfg_env, block_env);
let evm = evm_factory.create_evm(&mut state, evm_env);
let small_tx = create_transaction(0, 100_000);
let large_tx = create_transaction(0, 10_000_000);
let small_size = small_tx.encode_2718_len() as u64;
let large_size = large_tx.encode_2718_len() as u64;
let block_ctx = MegaBlockExecutionCtx::new(
B256::ZERO,
None,
Bytes::new(),
BlockLimits::no_limits().with_block_txs_encode_size_limit(small_size * 2 + large_size),
);
use alloy_hardforks::ForkCondition;
use mega_evm::MegaHardfork;
let chain_spec =
MegaHardforkConfig::default().with(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
let receipt_builder = OpAlloyReceiptBuilder::default();
let mut executor = MegaBlockExecutor::new(evm, block_ctx, chain_spec, receipt_builder);
let result1 = executor.execute_transaction(&create_transaction(0, 100_000));
assert!(result1.is_ok(), "First small transaction should succeed");
let result2 = executor.execute_transaction(&create_transaction(1, 100_000));
assert!(result2.is_ok(), "Second small transaction should succeed");
let result3 = executor.execute_transaction(&create_transaction(2, 10_000_000));
assert!(result3.is_ok(), "Large transaction should succeed");
let result4 = executor.execute_transaction(&create_transaction(3, 100_000));
assert!(result4.is_err(), "Fourth transaction should fail due to tx size limit");
let block_result = executor.finish();
assert!(block_result.is_ok(), "Block should finish successfully");
let (_, receipts) = block_result.unwrap();
assert_eq!(receipts.receipts.len(), 3, "Should have 3 receipts (4th tx failed)");
}