Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion crates/mega-evm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,30 @@ pub mod rex4 {
}

/// Constants for the `REX5` spec.
pub mod rex5 {}
pub mod rex5 {
/// Floor for the gas limit assigned to REX5 pre-block system calls that opt
/// into the live block gas budget — currently only
/// `SequencerRegistry.applyPendingChanges()` via
/// [`crate::MegaEvm::transact_system_call_with_gas_limit`].
///
/// The value (30M) matches the historical revm system-call default at the
/// time REX5 was specified — see the `gas_limit(30_000_000)` literal in
/// [`revm::handler::SystemCallTx::new_system_tx_with_caller`]'s `TxEnv` impl
/// (`revm-handler/src/system_call.rs`).
/// revm does not export this as a `pub const`, so we mirror the literal here
/// instead of aliasing it. Do NOT raise this value to follow upstream
/// changes — the floor must remain at the historical 30M to preserve
/// backward compatibility for REX5 chains whose block gas limit is smaller
/// than any new upstream default. Lifting it would silently increase the
/// minimum guaranteed budget and become observable via the `GAS` opcode
/// inside `applyPendingChanges()`.
///
/// Keeping the floor at the historical default ensures test harnesses or
/// chains configured with a sub-30M block gas limit still receive the
Comment thread
RealiCZ marked this conversation as resolved.
/// budget that pre-REX5 / EIP-2935 / EIP-4788 system calls have always
/// gotten.
pub const SYSTEM_CALL_GAS_LIMIT_FLOOR: u64 = 30_000_000;
}

/// Constants for the `REX` spec.
pub mod rex {
Expand Down
39 changes: 38 additions & 1 deletion crates/mega-evm/src/evm/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ where
/// # Note
///
/// This function copies the logic from `alloy_op_evm::OpEvm::transact_system_call`
/// to maintain compatibility with the Optimism EVM system call interface.
/// to maintain compatibility with the Optimism EVM system call interface. The
/// transaction's gas limit follows revm's upstream-fixed 30M default. Callers that
/// need to use the live block gas budget — e.g. REX5 pre-block helpers whose cost is
/// sensitive to dynamic storage gas — should use
/// [`MegaEvm::transact_system_call_with_gas_limit`] instead.
fn transact_system_call(
&mut self,
caller: Address,
Expand Down Expand Up @@ -197,3 +201,36 @@ where
revm::handler::Handler::run_system_call(&mut h, self)
}
}

impl<DB, INSP, ExtEnvs: ExternalEnvTypes> MegaEvm<DB, INSP, ExtEnvs>
where
DB: Database,
{
/// Transact a system call with an explicit gas limit and finalize.
///
/// Behaves like [`alloy_evm::Evm::transact_system_call`] but lets the caller specify
/// the gas limit instead of relying on revm's upstream-fixed 30M default. This is
/// intended for REX5+ pre-block helpers (e.g. `transact_apply_pending_changes` in
/// `system::sequencer_registry`) whose real cost is sensitive to dynamic storage gas
/// and is no longer guaranteed to fit within 30M on activation blocks.
///
/// The recommended argument is
/// `block.gas_limit.max(crate::constants::rex5::SYSTEM_CALL_GAS_LIMIT_FLOOR)`.
pub fn transact_system_call_with_gas_limit(
&mut self,
caller: Address,
contract: Address,
data: Bytes,
gas_limit: u64,
) -> Result<ResultAndState<MegaHaltReason>, EVMError<DB::Error, MegaTransactionError>> {
let mut tx =
<MegaTransaction as SystemCallTx>::new_system_tx_with_caller(caller, contract, data);
tx.base.gas_limit = gas_limit;
self.ctx().set_tx(tx);
let mut h = MegaHandler::<_, _, EthFrame<EthInterpreter>>::new();
revm::handler::Handler::run_system_call(&mut h, self).map(|result| {
let state = self.inner.ctx.journal_mut().finalize();
ResultAndState { result, state }
})
}
}
40 changes: 40 additions & 0 deletions crates/mega-evm/src/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,46 @@ mod tests {
assert!(system_call.is_success());
}

#[test]
fn test_transact_system_call_with_gas_limit_uses_passed_value() {
let mut db = MemoryDatabase::default()
.account_balance(CALLER, U256::from(1_000_000))
.account_code(CALLEE, Bytes::new());
let mut evm = MegaEvm::new(configure_context(&mut db));

let result = evm
.transact_system_call_with_gas_limit(CALLER, CALLEE, Bytes::new(), 123_456_789)
.unwrap();
assert!(result.result.is_success());
// The custom gas limit must be applied to the underlying tx.
assert_eq!(evm.inner.ctx.tx.base.gas_limit, 123_456_789);
}

#[test]
fn test_default_system_call_keeps_upstream_30m_gas_limit() {
Comment thread
Troublor marked this conversation as resolved.
let mut db = MemoryDatabase::default()
.account_balance(CALLER, U256::from(1_000_000))
.account_code(CALLEE, Bytes::new());
let mut context = MegaContext::new(&mut db, MegaSpecId::REX5);
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::ZERO);
chain.operator_fee_constant = Some(U256::ZERO);
});
context.block.gas_limit = 100_000_000;
let mut evm = MegaEvm::new(context);

// The default system-call entry point must NOT be widened by REX5 — only the
// explicit `transact_system_call_with_gas_limit` path should pick up the live
// block budget. This preserves byte-level behavior of EIP-2935 / EIP-4788
// pre-block calls across all specs.
SystemCallEvm::transact_system_call_with_caller(&mut evm, CALLER, CALLEE, Bytes::new())
.unwrap();
// Literal, not `SYSTEM_CALL_GAS_LIMIT_FLOOR`: this assertion verifies revm's
// upstream hardcoded default. If upstream ever drifts from our floor, this
// test should fail loudly rather than be auto-aligned by our constant.
assert_eq!(evm.inner.ctx.tx.base.gas_limit, 30_000_000);
Comment thread
RealiCZ marked this conversation as resolved.
}

#[test]
fn test_mega_evm_exposes_state_wrapper_block_hashes() {
let mut db = MemoryDatabase::default();
Expand Down
78 changes: 74 additions & 4 deletions crates/mega-evm/src/system/sequencer_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,26 @@ pub(crate) fn is_apply_pending_changes_due<DB: Database>(
/// change if they are due in the current block.
/// Caller should gate this with [`is_apply_pending_changes_due`] to avoid an EVM
/// call on every block.
pub(crate) fn transact_apply_pending_changes<Halt>(
evm: &mut impl alloy_evm::Evm<HaltReason = Halt>,
) -> Result<ResultAndState<Halt>, BlockExecutionError> {
///
/// The system call is issued with `max(block.gas_limit, SYSTEM_CALL_GAS_LIMIT_FLOOR)`
/// instead of the upstream-fixed 30M. `applyPendingChanges()` writes role-rotation
/// slots whose actual cost depends on REX dynamic storage gas, so the upstream default
/// is no longer guaranteed to be enough on activation blocks.
pub(crate) fn transact_apply_pending_changes<DB, INSP, ExtEnvs>(
evm: &mut crate::MegaEvm<DB, INSP, ExtEnvs>,
) -> Result<ResultAndState<crate::MegaHaltReason>, BlockExecutionError>
where
DB: alloy_evm::Database,
ExtEnvs: crate::ExternalEnvTypes,
{
let calldata = ISequencerRegistry::applyPendingChangesCall {}.abi_encode();
let result_and_state = match evm.transact_system_call(
let gas_limit =
evm.block_env_ref().gas_limit.max(crate::constants::rex5::SYSTEM_CALL_GAS_LIMIT_FLOOR);
let result_and_state = match evm.transact_system_call_with_gas_limit(
alloy_eips::eip4788::SYSTEM_ADDRESS,
SEQUENCER_REGISTRY_ADDRESS,
Bytes::from(calldata),
gas_limit,
) {
Ok(res) => res,
Err(e) => {
Expand Down Expand Up @@ -943,6 +955,64 @@ mod tests {
);
}

#[test]
fn test_transact_apply_pending_changes_uses_block_gas_limit() {
Comment thread
Troublor marked this conversation as resolved.
// Block gas_limit > 30M must be passed through to the system call so that
// applyPendingChanges() can absorb the variable cost from REX dynamic
// storage gas instead of being capped at the upstream-fixed 30M.
let mut db = InMemoryDB::default();
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);

let block =
BlockEnv { number: U256::from(1000), gas_limit: 250_000_000, ..Default::default() };
let mut context = crate::MegaContext::new(&mut db, MegaSpecId::REX5).with_block(block);
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::ZERO);
chain.operator_fee_constant = Some(U256::ZERO);
});
let mut evm = crate::MegaEvm::new(context);

transact_apply_pending_changes(&mut evm).expect("system call should succeed");
assert_eq!(revm::handler::EvmTr::ctx_ref(&evm).tx.base.gas_limit, 250_000_000);
}

#[test]
fn test_transact_apply_pending_changes_respects_30m_floor() {
// When the block gas limit is below the 30M floor, the system call must
// still receive at least the historical default budget.
let mut db = InMemoryDB::default();
db.insert_account_info(
SEQUENCER_REGISTRY_ADDRESS,
AccountInfo {
code_hash: SEQUENCER_REGISTRY_CODE_HASH,
code: Some(Bytecode::new_raw(SEQUENCER_REGISTRY_CODE)),
..Default::default()
},
);

let block =
BlockEnv { number: U256::from(1000), gas_limit: 1_000_000, ..Default::default() };
let mut context = crate::MegaContext::new(&mut db, MegaSpecId::REX5).with_block(block);
context.modify_chain(|chain| {
chain.operator_fee_scalar = Some(U256::ZERO);
chain.operator_fee_constant = Some(U256::ZERO);
});
let mut evm = crate::MegaEvm::new(context);

transact_apply_pending_changes(&mut evm).expect("system call should succeed");
assert_eq!(
revm::handler::EvmTr::ctx_ref(&evm).tx.base.gas_limit,
crate::constants::rex5::SYSTEM_CALL_GAS_LIMIT_FLOOR,
);
}

#[test]
fn test_transact_apply_pending_changes_errors_when_registry_reverts() {
let revert_code = Bytecode::new_legacy(Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0xfd]));
Expand Down
Loading
Loading