Skip to content
Open
Changes from 1 commit
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
177 changes: 113 additions & 64 deletions specs/experimental/standard-l2-genesis.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@
- [Initialization](#initialization)
- [Interface](#interface)
- [Fee Vault Config](#fee-vault-config)
- [`setBaseFeeVaultConfig`](#setbasefeevaultconfig)
- [`setL1FeeVaultConfig`](#setl1feevaultconfig)
- [`setSequencerFeeVaultConfig`](#setsequencerfeevaultconfig)
- [`setFeeVaultConfig`](#setfeevaultconfig)
- [Fee Admin](#fee-admin)
- [`feeAdmin`](#feeadmin)
- [`setFeeVaultAdmin`](#setfeevaultadmin)
- [Invariants](#invariants)
- [`OptimismPortal`](#optimismportal)
- [Interface](#interface-1)
- [`setConfig`](#setconfig)
- [`upgrade`](#upgrade)
- [Invariants](#invariants-1)
- [SuperchainConfig](#superchainconfig)
- [Constants](#constants-1)
- [Interface](#interface-2)
- [Initialization](#initialization-1)
- [Predeploys](#predeploys)
- [Constants](#constants-2)
- [L1Block values](#l1block-values)
- [L1Block slots](#l1block-slots)
- [Predeploys](#predeploys-1)
- [ProxyAdmin](#proxyadmin)
- [Rationale](#rationale)
Expand All @@ -38,12 +42,10 @@
- [Interface](#interface-4)
- [`config`](#config)
- [L2CrossDomainMessenger](#l2crossdomainmessenger)
- [Interface](#interface-5)
- [L2ERC721Bridge](#l2erc721bridge)
- [Interface](#interface-6)
- [L2StandardBridge](#l2standardbridge)
- [Interface](#interface-7)
- [OptimismMintableERC721Factory](#optimismmintableerc721factory)
- [OptimismMintableERC20Factory](#optimismmintableerc20factory)
- [Security Considerations](#security-considerations)
- [GovernanceToken](#governancetoken)

Expand All @@ -60,30 +62,31 @@ configurability.

The `ConfigType` enum represents configuration that can be modified.

| Name | Value | Description |
| ---- | ----- | --- |
| `GAS_PAYING_TOKEN` | `uint8(0)` | Modifies the gas paying token for the chain |
| `BASE_FEE_VAULT_CONFIG` | `uint8(1)` | Sets the Fee Vault Config for the `BaseFeeVault` |
| `L1_FEE_VAULT_CONFIG` | `uint8(2)` | Sets the Fee Vault Config for the `L1FeeVault` |
| `SEQUENCER_FEE_VAULT_CONFIG` | `uint8(3)` | Sets the Fee Vault Config for the `SequencerFeeVault` |
| `L1_CROSS_DOMAIN_MESSENGER_ADDRESS` | `uint8(4)` | Sets the `L1CrossDomainMessenger` address |
| `L1_ERC_721_BRIDGE_ADDRESS` | `uint8(5)` | Sets the `L1ERC721Bridge` address |
| `L1_STANDARD_BRIDGE_ADDRESS` | `uint8(6)` | Sets the `L1StandardBridge` address |
| `REMOTE_CHAIN_ID` | `uint8(7)` | Sets the chain id of the base chain |
| Name | Value | Description |
| ----------------------------------- | ---------- | ----------------------------------------------------- |
| `GAS_PAYING_TOKEN` | `uint8(0)` | Modifies the gas paying token for the chain |
| `BASE_FEE_VAULT_CONFIG` | `uint8(1)` | Sets the Fee Vault Config for the `BaseFeeVault` |
| `L1_FEE_VAULT_CONFIG` | `uint8(2)` | Sets the Fee Vault Config for the `L1FeeVault` |
| `SEQUENCER_FEE_VAULT_CONFIG` | `uint8(3)` | Sets the Fee Vault Config for the `SequencerFeeVault` |
| `L1_CROSS_DOMAIN_MESSENGER_ADDRESS` | `uint8(4)` | Sets the `L1CrossDomainMessenger` address |
| `L1_ERC_721_BRIDGE_ADDRESS` | `uint8(5)` | Sets the `L1ERC721Bridge` address |
| `L1_STANDARD_BRIDGE_ADDRESS` | `uint8(6)` | Sets the `L1StandardBridge` address |
| `REMOTE_CHAIN_ID` | `uint8(7)` | Sets the chain id of the base chain |

## `SystemConfig`

### `ConfigUpdate`

The following `ConfigUpdate` event is defined where the `CONFIG_VERSION` is `uint256(0)`:

| Name | Value | Definition | Usage |
| ---- | ----- | --- | -- |
| `BATCHER` | `uint8(0)` | `abi.encode(address)` | Modifies the account that is authorized to progress the safe chain |
| `FEE_SCALARS` | `uint8(1)` | `(uint256(0x01) << 248) \| (uint256(_blobbasefeeScalar) << 32) \| _basefeeScalar` | Modifies the fee scalars |
| `GAS_LIMIT` | `uint8(2)` | `abi.encode(uint64 _gasLimit)` | Modifies the L2 gas limit |
| `UNSAFE_BLOCK_SIGNER` | `uint8(3)` | `abi.encode(address)` | Modifies the account that is authorized to progress the unsafe chain |
| `EIP_1559_PARAMS` | `uint8(4)` | `uint256(uint64(uint32(_denominator))) << 32 \| uint64(uint32(_elasticity))` | Modifies the EIP-1559 denominator and elasticity |
| Name | Value | Definition | Usage |
| --------------------- | ---------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `BATCHER` | `uint8(0)` | `abi.encode(address)` | Modifies the account that is authorized to progress the safe chain |
| `FEE_SCALARS` | `uint8(1)` | `(uint256(0x01) << 248) \| (uint256(_blobbasefeeScalar) << 32) \| _basefeeScalar` | Modifies the fee scalars |
| `GAS_LIMIT` | `uint8(2)` | `abi.encode(uint64 _gasLimit)` | Modifies the L2 gas limit |
| `UNSAFE_BLOCK_SIGNER` | `uint8(3)` | `abi.encode(address)` | Modifies the account that is authorized to progress the unsafe chain |
| `EIP_1559_PARAMS` | `uint8(4)` | `uint256(uint64(uint32(_denominator))) << 32 \| uint64(uint32(_elasticity))` | Modifies the EIP-1559 denominator and elasticity |
| `FEE_VAULT_ADMIN` | `uint8(5)` | `abi.encode(address)` | Modifies the fee vault admin |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right, why is fee vault admin added here? These are generally values that go into L1 attributes tx and we dont want to put the fee vault admin in there. The ConfigUpdate events influence L2 consensus


### Initialization

Expand All @@ -94,6 +97,7 @@ The following actions should happen during the initialization of the `SystemConf
- `emit ConfigUpdate.GAS_LIMIT`
- `emit ConfigUpdate.UNSAFE_BLOCK_SIGNER`
- `emit ConfigUpdate.EIP_1559_PARAMS`
- `emit ConfigUpdate.FEE_VAULT_ADMIN`
- `setConfig(SET_GAS_PAYING_TOKEN)`
- `setConfig(SET_BASE_FEE_VAULT_CONFIG)`
- `setConfig(SET_L1_FEE_VAULT_CONFIG)`
Expand All @@ -111,28 +115,42 @@ These actions MAY only be triggered if there is a diff to the value.

For each `FeeVault`, there is a setter for its config. The arguments to the setter include
the `RECIPIENT`, the `MIN_WITHDRAWAL_AMOUNT` and the `WithdrawalNetwork`.
Each of these functions should be `public` and only callable by the chain governor.
This function should be `public` and only callable by the fee admin.

Each function calls `OptimismPortal.setConfig(ConfigType,bytes)` with its corresponding `ConfigType`.

##### `setBaseFeeVaultConfig`
##### `setFeeVaultConfig`

```solidity
function setBaseFeeVaultConfig(address,uint256,WithdrawalNetwork)
function setFeeVaultConfig(ConfigType,address,uint256,WithdrawalNetwork)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MUST revert if the config type doesn't correspond to a fee vault config type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! We are reverting in the implementation but it should be here too

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here 6cf6dec

```

##### `setL1FeeVaultConfig`
#### Fee Admin

A new role is introduced to call the vault config setters. This role is updated at the `initialize` function.

##### `feeAdmin`

```solidity
function setL1FeeVaultConfig(address,uint256,WithdrawalNetwork)
function feeAdmin() returns (address)
```

##### `setSequencerFeeVaultConfig`
##### `setFeeVaultAdmin`

The `setFeeVaultAdmin` function MUST only be callable by the system config owner.

```solidity
function setSequencerFeeVaultConfig(address,uint256,WithdrawalNetwork)
function setFeeVaultAdmin(address)
```

### Invariants

- Only the fee admin MUST be able to update a fee vault config

- Updating a fee vault config MUST emit a system deposit tx through the `OptimismPortal`

- Only the fee admin MUST be able to update the fee vault admin

## `OptimismPortal`

The `OptimismPortal` is updated to emit a special system `TransactionDeposited` event.
Expand Down Expand Up @@ -160,15 +178,15 @@ The following fields are included:
- `to` is `Predeploys.L1Block`
- `version` is `uint256(0)`
- `opaqueData` is the tightly packed transaction data where `mint` is `0`, `value` is `0`, the `gasLimit`
is `200_000`, `isCreation` is `false` and the `data` is `abi.encodeCall(L1Block.setConfig, (_type, _value))`
is `200_000`, `isCreation` is `false` and the `data` is `abi.encodeCall(L1Block.setConfig, (_type, _value))`

#### `upgrade`

The `upgrade` function MUST only be callable by the `UPGRADER` role as defined
in the [`SuperchainConfig`](#superchainconfig).

```solidity
function upgrade(bytes memory _data) external
function upgrade(uint32 _gasLimit, bytes memory _data) external
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will the _gasLimit value be set? Will it be hardcoded in the SystemConfig?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is upgrade() the right name for this? If "to is Predeploys.L1Block", them I think something like updateConfig() might be more appropriate.

```

This function emits a `TransactionDeposited` event.
Expand All @@ -180,10 +198,18 @@ event TransactionDeposited(address indexed from, address indexed to, uint256 ind
The following fields are included:

- `from` is the `DEPOSITOR_ACCOUNT`
- `to` is `Predeploys.ProxyAdmin`
- `to` is `Predeploys.L2_PROXY_ADMIN`
- `version` is `uint256(0)`
- `opaqueData` is the tightly packed transaction data where `mint` is `0`, `value` is `0`, the `gasLimit`
is `200_000`, `isCreation` is `false` and the `data` is the data passed into `upgrade`.
is `200_000`, `isCreation` is `false` and the `data` is the data passed into `upgrade`.
Copy link
Copy Markdown
Contributor

@maurelian maurelian Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the gasLimit presumably needs to be updated if it becomes an input.

I'm also curious what the current thinking is around the correct behaviour of useGas():

CleanShot 2025-02-19 at 15 56 14@2x

Note that I don't agree with the recommended fix, as it will enables anyone to DoS a system deposit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We believe that we shouldnt add the check in useGas since this can cause further DoS issues by front-running the chain-operator. To compensate for this we agree with adding a test to make sure we dont exceed the buffer gas limit like mentioned here ethereum-optimism/design-docs#97 (comment)


### Invariants

- Only the `SystemConfig` MUST be able to call `setConfig`

- Only the `UPGRADER` role MUST be able to call `upgrade`

- `setConfig` and `upgrade` MUST emit a system deposit tx through `TransactionDeposited` event

## SuperchainConfig

Expand All @@ -193,8 +219,8 @@ that call the L2 `ProxyAdmin`.

### Constants

| Name | Value | Definition |
| --------- | ------------------------- | -- |
| Name | Value | Definition |
| --------------- | -------------------------------------------------------------- | ----------------------------------------- |
| `UPGRADER_SLOT` | `bytes32(uint256(keccak256("superchainConfig.upgrader")) - 1)` | Account that can call the L2 `ProxyAdmin` |

### Interface
Expand All @@ -214,22 +240,27 @@ specific configuration out of the initial L2 genesis state. All network specific
configuration is sourced from deposit transactions during the initialization
of the `SystemConfig`.

## Constants
### L1Block values

| Name | Value | Definition |
| ----------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `ConfigType` | `uint8` | An enum representing the type of config being set |
| `WithdrawalNetwork` | `uint8(0)` or `uint8(1)` | `0` means withdraw to L1, `1` means withdraw to L2 |
| `RECIPIENT` | `address` | The account that will receive funds sent out of the `FeeVault` |
| `MIN_WITHDRAWAL_AMOUNT` | `uint256` | The minimum amount of native asset held in the `FeeVault` before withdrawal is authorized |
| Fee Vault Config | `bytes32` | `bytes32((WithdrawalNetwork << 248) \|\| uint256(uint88(MIN_WITHDRAWAL_AMOUNT)) \|\| uint256(uint160(RECIPIENT)))` |

| Name | Value | Definition |
| --------- | ------------------------- | -- |
| `ConfigType` | `uint8` | An enum representing the type of config being set |
| `WithdrawalNetwork` | `uint8(0)` or `uint8(1)` | `0` means withdraw to L1, `1` means withdraw to L2 |
| `RECIPIENT` | `address` | The account that will receive funds sent out of the `FeeVault` |
| `MIN_WITHDRAWAL_AMOUNT` | `uint256` | The minimum amount of native asset held in the `FeeVault` before withdrawal is authorized |
| Fee Vault Config | `bytes32` | `bytes32((WithdrawalNetwork << 248) \|\| uint256(uint88(MIN_WITHDRAWAL_AMOUNT)) \|\| uint256(uint160(RECIPIENT)))` |
| `BASE_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.basefeevaultconfig")) - 1)` | The Fee Vault Config for the `BaseFeeVault` |
| `L1_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.l1feevaultconfig")) - 1)` | The Fee Vault Config for the `L1FeeVault` |
| `SEQUENCER_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.sequencerfeevaultconfig")) - 1)` | The Fee Vault Config for the `SequencerFeeVault` |
### L1Block slots

| Name | Value | Definition |
| ----------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------- |
| `BASE_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.basefeevaultconfig")) - 1)` | The Fee Vault Config for the `BaseFeeVault` |
| `L1_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.l1feevaultconfig")) - 1)` | The Fee Vault Config for the `L1FeeVault` |
| `SEQUENCER_FEE_VAULT_CONFIG` | `bytes32(uint256(keccak256("opstack.sequencerfeevaultconfig")) - 1)` | The Fee Vault Config for the `SequencerFeeVault` |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we land the operator fee in ethereum-optimism/optimism#12166, we will need to rebase on top of that and then add that to the specs, both here and in the mermaid diagram

| `L1_CROSS_DOMAIN_MESSENGER_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1crossdomainmessengeraddress")) - 1)` | `abi.encode(address(L1CrossDomainMessengerProxy))` |
| `L1_ERC_721_BRIDGE_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1erc721bridgeaddress")) - 1)` | `abi.encode(address(L1ERC721BridgeProxy))` |
| `L1_STANDARD_BRIDGE_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1standardbridgeaddress")) - 1)` | `abi.encode(address(L1StandardBridgeProxy))` |
| `REMOTE_CHAIN_ID` | `bytes32(uint256(keccak256("opstack.remotechainid")) - 1)` | Chain ID of the remote chain |
| `L1_ERC_721_BRIDGE_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1erc721bridgeaddress")) - 1)` | `abi.encode(address(L1ERC721BridgeProxy))` |
| `L1_STANDARD_BRIDGE_ADDRESS` | `bytes32(uint256(keccak256("opstack.l1standardbridgeaddress")) - 1)` | `abi.encode(address(L1StandardBridgeProxy))` |
| `REMOTE_CHAIN_ID` | `bytes32(uint256(keccak256("opstack.remotechainid")) - 1)` | Chain ID of the remote chain |

## Predeploys

Expand Down Expand Up @@ -290,9 +321,9 @@ via a deposit transaction from the `DEPOSITOR_ACCOUNT`.

##### `setIsthmus`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine to leave for now but for clarity, we will need to modify this, unclear which hardfork this will land in exactly right now


This function is meant to be called once on the activation block of the holocene network upgrade.
This function is meant to be called once on the activation block of the Isthmus network upgrade.
It MUST only be callable by the `DEPOSITOR_ACCOUNT` once. When it is called, it MUST call
call each getter for the network specific config and set the returndata into storage.
each getter for the network specific config and set the returndata into storage.

##### `setConfig`

Expand Down Expand Up @@ -331,11 +362,11 @@ The following functions are updated to read from the `L1Block` contract:
- `minWithdrawalAmount()(uint256)`
- `withdraw()`

| Name | Call |
| ---- | -------- |
| `BaseFeeVault` | `L1Block.getConfig(ConfigType.BASE_FEE_VAULT_CONFIG)` |
| Name | Call |
| ------------------- | ---------------------------------------------------------- |
| `BaseFeeVault` | `L1Block.getConfig(ConfigType.BASE_FEE_VAULT_CONFIG)` |
| `SequencerFeeVault` | `L1Block.getConfig(ConfigType.SEQUENCER_FEE_VAULT_CONFIG)` |
| `L1FeeVault` | `L1Block.getConfig(ConfigType.L1_FEE_VAULT_CONFIG)` |
| `L1FeeVault` | `L1Block.getConfig(ConfigType.L1_FEE_VAULT_CONFIG)` |

##### `config`

Expand All @@ -347,16 +378,18 @@ function config()(address,uint256,WithdrawalNetwork)

### L2CrossDomainMessenger

#### Interface

The following functions are updated to read from the `L1Block` contract by calling `L1Block.getConfig(ConfigType.L1_CROSS_DOMAIN_MESSENGER_ADDRESS)`:
To make this contract not initializable, the universal `CrossDomainMessenger` contract is updated to no longer be initializable.
However, the L1 version continues to be.

- `otherMessenger()(address)`
- `OTHER_MESSENGER()(address)`
The `otherMessenger()(address)` function is updated to read from the `L1Block` contract
by calling `L1Block.getConfig(ConfigType.L1_CROSS_DOMAIN_MESSENGER_ADDRESS)`.

### L2ERC721Bridge

#### Interface
To make this contract not initializable, the universal `ERC721Bridge` contract is updated to no longer be initializable.
However, the L1 version continues to be.

The `messenger()` function is updated to return `Predeploys.L2_CROSS_DOMAIN_MESSENGER`.

The following functions are updated to read from the `L1Block` contract by calling `L1Block.getConfig(ConfigType.L1_ERC721_BRIDGE_ADDRESS)`:

Expand All @@ -365,7 +398,10 @@ The following functions are updated to read from the `L1Block` contract by calli

### L2StandardBridge

#### Interface
To make this contract not initializable, the universal `StandardBridge` contract is updated to no longer be initializable.
However, the L1 version continues to be.

The `messenger()` function is updated to return `Predeploys.L2_CROSS_DOMAIN_MESSENGER`.

The following functions are updated to read from the `L1Block` contract by calling `L1Block.getConfig(ConfigType.L1_STANDARD_BRIDGE_ADDRESS)`:

Expand All @@ -374,9 +410,22 @@ The following functions are updated to read from the `L1Block` contract by calli

### OptimismMintableERC721Factory

This contract is updated to remove its constructor.

The chain id is no longer read from storage but instead is read from the `L1Block` contract by calling
`L1Block.getConfig(ConfigType.REMOTE_CHAIN_ID)`

The bridge is no longer set in the constructor but instead it uses the `Predeploys.L2_ERC721_BRIDGE`

### OptimismMintableERC20Factory

The universal contract is updated to be abstract and no longer initializable.
Two new contracts are derived from it: `L1OptimismMintableERC20Factory` and `L2OptimismMintableERC20Factory`

In L2, the bridge is no longer set in the constructor but instead it uses the `Predeploys.L2_STANDARD_BRIDGE`

The L1 version continues to be initializable.

## Security Considerations

### GovernanceToken
Expand Down