> ## Documentation Index
> Fetch the complete documentation index at: https://docs.base.org/llms.txt
> Use this file to discover all available pages before exploring further.

# L2 Execution Engine

> Execution engine changes in the Isthmus upgrade, including L2ToL1MessagePasser storage root in block headers and operator fee collection.

[l2-to-l1-mp]: ../../protocol/execution/evm/predeploys#L2ToL1MessagePasser

[output-root]: ../../reference/glossary#l2-output-root

## Overview

The storage root of the `L2ToL1MessagePasser` is included in the block header's
`withdrawalRoot` field.

## Timestamp Activation

Isthmus, like other network upgrades, is activated at a timestamp.
Changes to the L2 Block execution rules are applied when the `L2 Timestamp >= activation time`.

## `L2ToL1MessagePasser` Storage Root in Header

After Isthmus hardfork's activation, the L2 block header's `withdrawalsRoot` field will consist of the 32-byte
[`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root from the world state identified by the stateRoot
field in the block header. The storage root should be the same root that is returned by `eth_getProof`
at the given block number.

### Header Validity Rules

Prior to isthmus activation:

* the L2 block header's `withdrawalsRoot` field must be:
  * `nil` if Canyon has not been activated.
  * `keccak256(rlp(empty_string_code))` if Canyon has been activated.
* the L2 block header's `requestsHash` field must be omitted.

After Isthmus activation, an L2 block header is valid iff:

1. The `withdrawalsRoot` field
   1. Is 32 bytes in length.
   2. Matches the [`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root,
      as committed to in the `storageRoot` within the block header
2. The `requestsHash` field is equal to `sha256('') = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
   indicating no requests in the block.

### Header Withdrawals Root

| Byte offset | Description                                               |
| ----------- | --------------------------------------------------------- |
| `[0, 32)`   | [`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root |

#### Rationale

Currently, to generate [L2 output roots][output-root] for historical blocks, an archival node is required. This directly
places a burden on users of the system in a post-fault-proofs world, where:

1. A proposer must have an archive node to propose an output root at the safe head.
2. A user that is proving their withdrawal must have an archive node to verify that the output root they are proving
   their withdrawal against is indeed valid and included within the safe chain.

Placing the [`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root in the `withdrawalsRoot` field alleviates this burden
for users and protocol participants alike, allowing them to propose and verify other proposals with lower operating costs.

#### Genesis Block

If Isthmus is active at genesis block, the `withdrawalsRoot` in the genesis block header is set to the
[`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root.

#### State Processing

At the time of state processing, the header for which transactions are being validated should not make it's `withdrawalsRoot`
available to the EVM/application layer.

#### P2P

During sync, we expect the withdrawals list in the block body to be empty (OP stack does not make
use of the withdrawals list) and hence the hash of the withdrawals list to be the MPT root of an empty list.
When verifying the header chain using the final header that is synced, the header timestamp is used to
determine whether Isthmus is active at the said block. If it is, we expect that the header `withdrawalsRoot`
MPT hash can be any non-null value (since it is expected to contain the `L2ToL1MessagePasser`'s storage root).

#### Backwards Compatibility Considerations

Beginning at Canyon (which includes Shanghai hardfork support) and prior to Isthmus activation,
the `withdrawalsRoot` field is set to the MPT root of an empty withdrawals list. This is the
same root as an empty storage root. The withdrawals are captured in the L2 state, however
they are not reflected in the `withdrawalsRoot`. Hence, prior to Isthmus activation,
even if a `withdrawalsRoot` is present and a MPT root is present in the header, it should not be used.
Any implementation that calculates output root should be careful not to use the header `withdrawalsRoot`.

Note that there is always nonzero storage in the [`L2ToL1MessagePasser`][l2-to-l1-mp],
because it is a [proxied predeploy](../../protocol/execution/evm/predeploys) -- from genesis it
stores an implementation address and owner address. So from Isthmus,
the `withdrawalsRoot` will always be non-nil and never be the MPT root of an empty list.

#### Forwards Compatibility Considerations

As it stands, the `withdrawalsRoot` field is unused within the Base's header consensus format, and will never be
used for other reasons that are currently planned. Setting this value to the account storage root of the withdrawal
directly fits with Base, and makes use of the existing field in the L1 header consensus format.

#### Client Implementation Considerations

Various EL clients store historical state of accounts differently. If, as a contrived case, Base did not have
an outbound withdrawal for a long period of time, the node may not have access to the account storage root of the
[`L2ToL1MessagePasser`][l2-to-l1-mp]. In this case, the client would be unable to keep consensus. However, most modern
clients are able to at the very least reconstruct the account storage root at a given block on the fly if it does not
directly store this information.

##### Transaction Simulation

In response to RPC methods like `eth_simulateV1` that allow simulation of arbitrary transactions within one or more blocks,
an empty withdrawals root should be included in the header of a block that consists of such simulated transactions. The same
is applicable for scenarios where the actual withdrawals root value is not readily available.

## Deposit Requests

[EIP-6110] shifts deposit to the execution layer, introducing a new [EIP-7685] deposit request of type
`DEPOSIT_REQUEST_TYPE`. Deposit requests then appear in the [EIP-7685] requests list. The Base needs to ignore these
requests. Requests generation must be modified to exclude [EIP-6110] deposit requests. Note that since the [EIP-6110]
request type did *not* exist prior to Pectra on L1 and the Isthmus hardfork on L2, no activation time is needed since these
deposit type requests may always be excluded.

[EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110

[EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685

## Block Body Withdrawals List

Withdrawals list in the block body is encoded as an empty RLP list.

## EVM Changes

### BLS Precompiles

Similar to the `bn256Pairing` precompile in the [granite hardfork](../granite/exec-engine),
[EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) introduces a BLS
precompile that short-circuits depending on input size in the EVM.

The input size limits of the BLS precompile contracts are listed below:

* G1 multiple-scalar-multiply: `input_size <= 513760 bytes`
* G2 multiple-scalar-multiply: `input_size <= 488448 bytes`
* Pairing check: `input_size <= 235008 bytes`

The rest of the BLS precompiles are fixed-size operations which have a fixed gas cost.

## Block Sealing

In the Base, `EIP-7685` is no-op'd, and the `requestsHash` is always set to `sha256('')` (as noted in
[header validity rules](#header-validity-rules)). As such, [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110),
[EIP-7002](https://eips.ethereum.org/EIPS/eip-7002), and [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) are not
enabled either. The Base execution layer must ensure that the post-block filtering of events in the deposit contract
(EIP-6110) as well as the `EIP-7002` + `EIP-7251` system calls are *not invoked* during the block sealing process after
Isthmus activation.

Users of Base may still permissionlessly deploy these smart contracts, but they will not be treated as special
by the Base execution layer, and the system calls introduced in L1's Pectra hardfork are not considered.

## Engine API Updates

### Update to `ExecutionPayload`

`ExecutionPayload` will contain an extra field for `withdrawalsRoot` after Isthmus hard fork.

### `engine_newPayloadV4` API

Post Isthmus, `engine_newPayloadV4` will be used.

The `executionRequests` parameter MUST be an empty array.

## Fees

New OP stack variants have different resource consumption patterns, and thus require a more flexible
pricing model. To enable more customizable fee structures, Isthmus adds a new component to the fee
calculation: the `operatorFee`, which is parameterized by two scalars: the `operatorFeeScalar`
and the `operatorFeeConstant`.

### Operator Fee

The operator fee is integrated directly into the EVM, alongside the standard gas fee and the Base specific L1 data
fee. This fee follows the same semantics of existing fees charged in the EVM[^1], just with a new fee beneficiary account.

#### Fee Formula

$$
\text{operatorFee} = (\text{gas} \times \text{operatorFeeScalar} \div 10^6) + \text{operatorFeeConstant}
$$

Where:

* `gas` is 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 the `gas_limit`. When determining how much gas should be refunded,
  based off of how much of the `gas_limit` the transaction used, this should be the `gas_used`.
* `operatorFeeScalar` is a `uint32` scalar set by the chain operator, scaled by `1e6`.
* `operatorFeeConstant` is a `uint64` scalar set by the chain operator.

Note that the operator fee's maximum value has 77 bits, which can be calculated from the maximum input parameters:

```text theme={null}
operatorFee_max = (uint64_max * uint32_max / 10^6) + uint64_max ≈ 7.924660923989131 * 10^22
```

So implementations don't need to check for overflows if they perform the calculations with `uint256` types.

#### Deposit Operator Fees

Deposit transactions do not get charged operator fees. For all deposit transactions, regardless of the operator fee
parameter configuration, the operator fee should be **zero**. Deposit transactions also do not receive operator fee gas
refunds, since they never buy the operator fee gas to begin with.

#### EVM Fee Semantics

Like other fees in the EVM, the operator fee should be charged following the pattern below:

1. 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](#fee-formula) with `gas` set to the `gas_limit` of the transaction, and add it to the existing
   worst-case transaction fee.
2. When buying gas prior to execution, charge the account the worst-case operator fee. To compute this value, use the
   [fee formula](#fee-formula) with `gas` set to the `gas_limit` of the transaction.
3. 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 $\text{opFeeRefund} = \text{opFeeWorstCase} - \text{opFeeActual}$, where:
   * $\text{opFeeWorstCase}$ is as described in #1 + #2.
   * $\text{opFeeActual}$ is the amount of the operator fee that was actually used. This value is computed using the
     [fee formula](#fee-formula) with `gas` set to the `gas_limit - gas_used + refunded_gas`. `refunded_gas` is as
     described in [EIP-3529](https://eips.ethereum.org/EIPS/eip-3529).
4. After execution, when rewarding the fee beneficiaries, send the *spent operator fee* to the
   [operator fee vault](#fee-vaults). This value is exactly $\text{opFeeActual}$ as described above.

Implementations must ensure ETH is neither minted nor destroyed as a result of the operator fee.

#### Transaction Pool Changes

To account for the additional fee factored into transaction validity mentioned above, the transaction pool must reject
transactions that do not have enough balance to cover the worst-case cost of the transaction fee. This worst-case cost
of a transaction now includes the worst-case operator fee.

#### Configuring Operator Fee Parameters

`operatorFeeScalar` and `operatorFeeConstant` are loaded in a similar way to the `baseFeeScalar` and
`blobBaseFeeScalar` used in the [`L1Fee`](../../protocol/execution/index#ecotone-l1-cost-fee-changes-eip-4844-da).
calculation. In more detail, these parameters can be accessed in two interchangable ways.

* read from the deposited L1 attributes (`operatorFeeScalar` and `operatorFeeConstant`) 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 `uint32` in slot `8` at offset `0`.
    * Operator fee constant as big-endian `uint64` in slot `8` at offset `4`.

### Fee Vaults

These collected fees are sent to a new vault for the `operatorFee`: the [`OperatorFeeVault`](./predeploys#operatorfeevault).

Like the existing vaults, this is a hardcoded address, pointing at a pre-deployed proxy contract.
The proxy is backed by a vault contract deployment, based on `FeeVault`, to route vault funds to L1 securely.

### Receipts

After Isthmus activation, 2 new fields `operatorFeeScalar` and `operatorFeeConstant` are added to transaction receipts
if and only if at least one of them is non zero.

[^1]: Wood, G., & Ethereum Contributors. (n.d.-a). Ethereum Yellow Paper. [https://ethereum.github.io/yellowpaper/paper.pdf](https://ethereum.github.io/yellowpaper/paper.pdf) Page 8, section 5: "Gas and Payment"
