Isthmus L2 Execution Engine
Isthmus reworks several execution-layer rules. The headline change is that the block header’s withdrawalsRoot field now carries the storage root of the L2ToL1MessagePasser predeploy. The upgrade also adjusts deposit-request handling, adds BLS precompiles, and introduces a configurable operator fee.
Timestamp Activation
Section titled “Timestamp Activation”Like every Base network upgrade, Isthmus turns on at a timestamp. The new L2 block execution rules take effect once L2 Timestamp >= activation time.
L2ToL1MessagePasser Storage Root in Header
Section titled “L2ToL1MessagePasser Storage Root in Header”From Isthmus activation onward, the L2 block header’s withdrawalsRoot field holds the 32-byte L2ToL1MessagePasser account storage root, taken from the world state identified by the header’s stateRoot. That root is the same value eth_getProof returns for the account at the given block number.
Header Validity Rules
Section titled “Header Validity Rules”Before Isthmus activates:
- the L2 block header’s
withdrawalsRootfield must be:nilif Canyon has not been activated.keccak256(rlp(empty_string_code))if Canyon has been activated.
- the L2 block header’s
requestsHashfield must be omitted.
After Isthmus activates, an L2 block header is valid if and only if:
- The
withdrawalsRootfield- Is 32 bytes in length.
- Matches the
L2ToL1MessagePasseraccount storage root, as committed to in thestorageRootwithin the block header
- The
requestsHashfield is equal tosha256('') = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, indicating no requests in the block.
Header Withdrawals Root
Section titled “Header Withdrawals Root”| Byte offset | Description |
|---|---|
[0, 32) | L2ToL1MessagePasser account storage root |
Rationale
Section titled “Rationale”Generating L2 output roots for historical blocks otherwise requires an archive node, which pushes real cost onto the people who keep the system running. In a world secured by fault proofs that means a proposer needs an archive node to propose an output root at the safe head, and a user proving a withdrawal needs one to confirm that the output root they are proving against is genuinely valid and part of the safe chain. Committing the L2ToL1MessagePasser storage root directly into the withdrawalsRoot field removes that requirement, so both proposers and verifiers can do their work at lower operating cost.
Genesis Block
Section titled “Genesis Block”If Isthmus is active at the genesis block, the genesis header’s withdrawalsRoot is set to the L2ToL1MessagePasser account storage root.
State Processing
Section titled “State Processing”While transactions are being validated, the header’s withdrawalsRoot must not be exposed to the EVM or the application layer.
During sync the withdrawals list in the block body is expected to be empty (OP Stack does not use it), so its hash is the MPT root of an empty list. When the synced header chain is verified against the final synced header, the header timestamp decides whether Isthmus is active at that block. If it is, the header’s withdrawalsRoot MPT hash may be any non-null value, since it is expected to hold the L2ToL1MessagePasser storage root.
Backwards Compatibility Considerations
Section titled “Backwards Compatibility Considerations”From Canyon (which brings in Shanghai support) up until Isthmus activation, withdrawalsRoot is set to the MPT root of an empty withdrawals list — the same value as an empty storage root. Withdrawals are captured in the L2 state during this window but are not reflected in withdrawalsRoot, so even when a header carries a non-trivial MPT root in that field before Isthmus, it must not be relied on. Any output-root calculation has to avoid using the header withdrawalsRoot in this range.
There is always nonzero storage in the L2ToL1MessagePasser, because it is a proxied predeploy and stores an implementation address and owner address from genesis. As a result, from Isthmus the withdrawalsRoot is always non-nil and never equal to the MPT root of an empty list.
Forwards Compatibility Considerations
Section titled “Forwards Compatibility Considerations”The withdrawalsRoot field is otherwise unused in Base’s header consensus format and is not slated for any other use. Pointing it at the withdrawal account’s storage root fits Base cleanly and reuses a field that already exists in the L1 header format.
Client Implementation Considerations
Section titled “Client Implementation Considerations”Execution clients keep historical account state in different ways. In a contrived case where Base produced no outbound withdrawal for a long stretch, a node might not retain the L2ToL1MessagePasser account storage root and would then be unable to stay in consensus. In practice most modern clients can reconstruct an account’s storage root at a given block on demand even when they do not persist it directly.
Transaction Simulation
Section titled “Transaction Simulation”For RPC methods such as eth_simulateV1 that simulate arbitrary transactions across one or more blocks, the header of a simulated block should carry an empty withdrawals root. The same applies whenever the real withdrawals-root value is not readily available.
Deposit Requests
Section titled “Deposit Requests”EIP-6110 moves deposits to the execution layer and defines a new EIP-7685 deposit request of type DEPOSIT_REQUEST_TYPE, which would otherwise show up in the EIP-7685 requests list. Base ignores these: requests generation is modified to leave out EIP-6110 deposit requests. Because the EIP-6110 request type did not exist before Pectra on L1 and Isthmus on L2, no activation time is needed — these deposit-type requests can always be excluded.
Block Body Withdrawals List
Section titled “Block Body Withdrawals List”The withdrawals list in the block body is encoded as an empty RLP list.
EVM Changes
Section titled “EVM Changes”BLS Precompiles
Section titled “BLS Precompiles”As with the bn256Pairing precompile in the Granite hardfork, EIP-2537 adds a BLS precompile that short-circuits based on input size in the EVM.
The input-size limits for the BLS precompile contracts are:
- G1 multiple-scalar-multiply:
input_size <= 513760 bytes - G2 multiple-scalar-multiply:
input_size <= 488448 bytes - Pairing check:
input_size <= 235008 bytes
The remaining BLS precompiles are fixed-size operations with a fixed gas cost.
Block Sealing
Section titled “Block Sealing”On Base, EIP-7685 is no-op’d and requestsHash is always set to sha256(''), as covered in the header validity rules. That also means EIP-6110, EIP-7002, and EIP-7251 are not enabled. After Isthmus activation, the Base execution layer must not run the post-block deposit-contract event filtering (EIP-6110) or the EIP-7002 + EIP-7251 system calls during block sealing.
Users remain free to deploy these contracts permissionlessly, but the Base execution layer gives them no special treatment, and the system calls L1 introduced in Pectra are not considered.
Engine API Updates
Section titled “Engine API Updates”Update to ExecutionPayload
Section titled “Update to ExecutionPayload”After Isthmus, ExecutionPayload carries an additional withdrawalsRoot field.
engine_newPayloadV4 API
Section titled “engine_newPayloadV4 API”From Isthmus onward, engine_newPayloadV4 is used. The executionRequests parameter MUST be an empty array.
Different OP Stack variants consume resources differently and need a more flexible pricing model. To allow more customizable fee structures, Isthmus adds a new term to the fee calculation — the operatorFee — parameterized by two scalars, the operatorFeeScalar and the operatorFeeConstant.
Operator Fee
Section titled “Operator Fee”The operator fee is wired directly into the EVM, alongside the standard gas fee and Base’s L1 data fee. It behaves like the EVM’s existing fees, just with a different beneficiary account.
Fee Formula
Section titled “Fee Formula”operatorFee = (gas * operatorFeeScalar / 10^6) + operatorFeeConstantWhere:
gasis the amount of gas that the transaction used. When calculating the amount of gas that is bought at the beginning of the transaction, this should be thegas_limit. When determining how much gas should be refunded, based off of how much of thegas_limitthe transaction used, this should be thegas_used.operatorFeeScalaris auint32scalar set by the chain operator, scaled by1e6.operatorFeeConstantis auint64scalar set by the chain operator.
The operator fee’s maximum value fits in 77 bits, which follows from the maximum input parameters:
operatorFee_max = (uint64_max * uint32_max / 10^6) + uint64_max ≈ 7.924660923989131 * 10^22So implementations that compute it with uint256 types do not need to check for overflow.
Deposit Operator Fees
Section titled “Deposit Operator Fees”Deposit transactions are never charged an operator fee: whatever the operator fee parameters are, it is zero for them. They also receive no operator-fee gas refund, since they never bought that gas in the first place.
EVM Fee Semantics
Section titled “EVM Fee Semantics”Like the EVM’s other fees, the operator fee is charged following this pattern:
- During pre-execution validation, the account must have enough ETH to cover the existing worst-case gas + L1 data fees as well as the worst-case operator fee (for deposits, the worst-case fee is
0). To compute this value, use the fee formula withgasset to thegas_limitof the transaction, and add it to the existing worst-case transaction fee. - When buying gas prior to execution, charge the account the worst-case operator fee. To compute this value, use the fee formula with
gasset to thegas_limitof the transaction. - After execution, when issuing refunds, transactions that bought operator fee gas should be refunded the operator fee gas that was unused (i.e., the caller should only be charged the effective operator fee.) The refund should be calculated as
opFeeRefund = opFeeWorstCase - opFeeActual, where:opFeeWorstCaseis as described in #1 + #2.opFeeActualis the amount of the operator fee that was actually used. This value is computed using the fee formula withgasset to thegas_limit - gas_used + refunded_gas.refunded_gasis as described in EIP-3529.
- After execution, when rewarding the fee beneficiaries, send the spent operator fee to the operator fee vault. This value is exactly
opFeeActualas described above.
Implementations must ensure ETH is neither minted nor destroyed as a result of the operator fee.
Transaction Pool Changes
Section titled “Transaction Pool Changes”Because this extra fee feeds into transaction validity, the transaction pool must reject any transaction whose balance cannot cover the worst-case cost — and that worst-case cost now includes the worst-case operator fee.
Configuring Operator Fee Parameters
Section titled “Configuring Operator Fee Parameters”operatorFeeScalar and operatorFeeConstant are loaded much like the baseFeeScalar and blobBaseFeeScalar used in the L1Fee calculation. They can be read in two interchangeable ways:
- read from the deposited L1 attributes (
operatorFeeScalarandoperatorFeeConstant) of the current L2 block - read from the L1 Block Info contract (
0x4200000000000000000000000000000000000015)- using the respective solidity getter functions (
operatorFeeScalar,operatorFeeConstant) - using direct storage-reads:
- Operator fee scalar as big-endian
uint32in slot8at offset0. - Operator fee constant as big-endian
uint64in slot8at offset4.
- Operator fee scalar as big-endian
- using the respective solidity getter functions (
Fee Vaults
Section titled “Fee Vaults”The collected operator fees go to a new vault, the OperatorFeeVault. As with the existing vaults, this is a hardcoded address pointing at a pre-deployed proxy, backed by a FeeVault-based deployment that routes vault funds to L1 securely.
Receipts
Section titled “Receipts”After Isthmus activation, two new fields — operatorFeeScalar and operatorFeeConstant — are added to transaction receipts whenever at least one of them is non-zero.