Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opcm upgrades spec #468

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Changes from 6 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
268 changes: 192 additions & 76 deletions specs/experimental/op-contracts-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,90 @@ of governance approved [contract releases] can be found on the

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

**Table of Contents**

- [Overview](#overview)
- [Getter Methods](#getter-methods)
- [Deployment](#deployment)
- [Interface](#interface)
- [`Proxy.sol`](#proxysol)
- [`deploy`](#deploy)
- [Getter Methods](#getter-methods)
- [Implementation](#implementation)
- [Batch Inbox Address](#batch-inbox-address)
- [Contract Deployments](#contract-deployments)
- [Interface](#interface)
- [`deploy`](#deploy)
- [Implementation](#implementation)
- [Batch Inbox Address](#batch-inbox-address)
- [Contract Deployments](#contract-deployments)
- [Upgrading](#upgrading)
- [Interface](#interface-1)
- [`upgrade`](#upgrade)
- [Implementation](#implementation-1)
- [`NewChainConfig` struct](#newchainconfig-struct)
- [Requirements on the OP Chain contracts](#requirements-on-the-op-chain-contracts)
- [Adding game types](#adding-game-types)
- [Interface](#interface-2)
- [`addGameType`](#addgametype)
- [Implementation](#implementation-2)
- [Security Considerations](#security-considerations)
- [Chain ID Source of Truth](#chain-id-source-of-truth)
- [Chain ID Frontrunning](#chain-id-frontrunning)
- [Chain ID Value](#chain-id-value)
- [Proxy Admin Owner](#proxy-admin-owner)
- [Upgradeability (ABI Changes)](#upgradeability-abi-changes)
- [Safely using `DELEGATECALL`](#safely-using-delegatecall)
- [Atomicity of upgrades](#atomicity-of-upgrades)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Deployment
## Overview

The OP Contracts Manager refers to a series of contracts, of which a new singleton is deployed
for each new release of the OP Stack contracts.

The OP Contracts Manager corresponding to each release can be used to:

1. Deploy a new OP chain.
2. Upgrade the contracts for an existing OP chain from the previous release to the new release.
3. Orchestrate adding a new game type on a per-chain basis

## Getter Methods

The following interface defines the available getter methods:

```solidity
/// @notice The logic address and initializer selector for an implementation contract.
struct Implementation {
blmalone marked this conversation as resolved.
Show resolved Hide resolved
address logic; // Address containing the deployed logic contract.
bytes4 initializer; // Function selector for the initializer.
}

The OP Contracts Manager is a proxied contract deployed at `0xTODO`. It can be deployed as follows:
/// @notice Returns the latest approved release of the OP Stack contracts.
/// @notice Release strings follow semver and are named with the
/// format `op-contracts/vX.Y.Z`.
function latestRelease() external view returns (string memory);
blmalone marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Returns the implementation contract addresses.
function implementations() public view returns (Implementations memory);

TODO.
/// @notice Maps an L2 Chain ID to the SystemConfig address for that chain.
/// @notice All contracts for a chain can be found from its SystemConfig.
function systemConfigs(uint256 chainId) external view returns (SystemConfig);
maurelian marked this conversation as resolved.
Show resolved Hide resolved

## Interface
/// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard
function chainIdToBatchInboxAddress(uint256 _l2ChainId) public pure returns (address);

Version 1.0.0 of the OP Contracts Manager deploys the `op-contracts/v1.6.0`
contracts release.
/// @notice Returns the blueprint contract addresses.
function blueprints() public view returns (Blueprints memory);
```
blmalone marked this conversation as resolved.
Show resolved Hide resolved

### `Proxy.sol`
## Deployment

The OP Contracts Manager is a proxied contract using the standard `Proxy.sol` contract that lives in
the Optimism monorepo. Therefore the OP Contracts Manager will have the same interface as the
`Proxy.sol`, in addition to other methods defined in this specification.
### Interface

The privileged methods of the OP Contracts Manager will be held by the L1 ProxyAdmin owner, as
specified by the [standard configuration].
Version 1.0.0 of the OP Contracts Manager deploys the `op-contracts/v1.6.0` contracts release,
maurelian marked this conversation as resolved.
Show resolved Hide resolved
and is deployed at `0x9BC0A1eD534BFb31a6Be69e5b767Cba332f14347`. In the future this will
maurelian marked this conversation as resolved.
Show resolved Hide resolved
be tracked in the `superchain-registry`.

### `deploy`
#### `deploy`

The `deploy` method is the only non-view method in the contract. It is used to
deploy the full set of L1 contracts required to setup a new OP Stack chain that
complies with the [standard configuration]. It has the following interface:
The `deploy` method is used to deploy the full set of L1 contracts required to setup a new OP Stack
chain that complies with the [standard configuration]. It has the following interface:

```solidity
struct Roles {
blmalone marked this conversation as resolved.
Show resolved Hide resolved
maurelian marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -85,58 +125,31 @@ The `l2ChainId` has the following restrictions:

- It must not be equal to 0.
- It must not be equal to the chain ID of the chain the OP Contracts Manager is
deployed on.
deployed on.
- It must not be equal to a chain ID that is already present in the
[ethereum-lists/chains] repository. This is not enforced onchain, but may matter
for future versions of OP Contracts Manager that handle upgrades.
[ethereum-lists/chains] repository. This is not enforced onchain, but may matter
for future versions of OP Contracts Manager that handle upgrades.

On success, the following event is emitted:

```solidity
event Deployed(uint256 indexed l2ChainId, SystemConfig indexed systemConfig);
event Deployed(uint256 indexed outputVersion, uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput);
```

This method reverts on failure. This occurs when:

- The input `l2ChainId` does not comply with the restrictions above.
- The resulting configuration is not compliant with the [standard configuration].

### Getter Methods

The following interface defines the available getter methods:

```solidity
/// @notice The logic address and initializer selector for an implementation contract.
struct Implementation {
address logic; // Address containing the deployed logic contract.
bytes4 initializer; // Function selector for the initializer.
}

/// @notice Returns the latest approved release of the OP Stack contracts.
/// @notice Release strings follow semver and are named with the
/// format `op-contracts/vX.Y.Z`.
function latestRelease() external view returns (string memory);

/// @notice Maps a release version to a contract name to its implementation data.
function implementation(
string memory release,
string memory contractName
) external view returns (Implementation memory);

/// @notice Maps an L2 Chain ID to the SystemConfig address for that chain.
/// @notice All contracts for a chain can be found from its SystemConfig.
function systemConfig(uint256 chainId) external view returns (SystemConfig);
```

## Implementation
### Implementation

### Batch Inbox Address
#### Batch Inbox Address

The chain's [Batch Inbox] address is computed at deploy time using the recommend approach defined
in the [standard configuration]. This improves UX by removing an input, and ensures uniqueness of
the batch inbox addresses.

### Contract Deployments
#### Contract Deployments
blmalone marked this conversation as resolved.
Show resolved Hide resolved
blmalone marked this conversation as resolved.
Show resolved Hide resolved

All contracts deployed by the OP Contracts Manager are deployed with CREATE2, with a
maurelian marked this conversation as resolved.
Show resolved Hide resolved
salt equal to either:
Expand All @@ -153,27 +166,119 @@ This provides the following benefits:

- Contract addresses for a chain can be derived as a function of chain ID without any RPC calls.
- Chain ID uniqueness is enforced for free, as a deploy using the same chain ID
will result in attempting to deploy to the same address, which is prohibited by
the EVM.
will result in attempting to deploy to the same address, which is prohibited by
the EVM.
- This property is contingent on the proxy and `AddressManager` code not
changing when OP Contracts Manager is upgraded. Both of these are not planned to
change.
changing when OP Contracts Manager is upgraded. Both of these are not planned to
change.
- The OP Contracts Manager is not responsible for enforcing chain ID uniqueness, so it is acceptable
if this property is not preserved in future versions of the OP Contracts Manager.
if this property is not preserved in future versions of the OP Contracts Manager.

## Upgrading

### Interface

#### `upgrade`

The `upgrade` method is used by the Upgrade Controller to upgrade the full set of L1 contracts for
all chains that it controls.

It has the following interface:

```solidity
function upgrade(ISystemConfig[] _systemConfigs, NewChainConfig[] _newConfigs) public;
maurelian marked this conversation as resolved.
Show resolved Hide resolved
```

For each chain successfully upgraded, the following event is emitted:

```solidity
event Upgraded(uint256 indexed l2ChainId, SystemConfig indexed systemConfig);
maurelian marked this conversation as resolved.
Show resolved Hide resolved
```

This method reverts if the upgrade is not successful for any of the chains.

### Implementation

The high level logic of the upgrade method is as follows:

1. The Upgrade Controller Safe will `DELEGATECALL` to the `OPCM.upgrade()` method.
2. For each `_systemConfig`, the list of addresses in the chain is retrieved.
3. For each address, a two step upgrade is used where:
1. the first upgrade is to an `InitializerResetter` which resets the `initialized` value.
1. the implementation is updated to the final address and `upgrade()` is called on that address.

This approach requires that all contracts have an `upgrade()` function which sets the `initialized`
value to `true`. The `upgrade` function body should be empty unless it is used to set a new state
variable added to that contract since the last upgrade.

#### `NewChainConfig` struct
blmalone marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the definition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the following content below:


By way of example, if an upgrade is adding a new variable address foo to the SystemConfig contract, for
an upgrade named Example, the struct could have the following definition:

struct NewChainConfigForExampleUpgrade {
  address systemConfigFoo;
}

We could create a structure in this document wherein each upgrade is expected to document the specific struct required, but I think this is sufficient instruction for an implementer, and I'm hesitant to add that overhead to each upgrade.

Copy link
Contributor Author

Choose a reason for hiding this comment

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


This struct is used to pass the new chain configuration to the `upgrade` method, and so it will
vary for each release of the OP Contracts Manager, based on what (if any) new parameters are added.

In practice, this struct is likely to be have a unique name for each release of the OP Contracts
Manager.

#### Requirements on the OP Chain contracts

In general, all contracts used in an OP Chain SHOULD be proxied with a single shared implementation.
This means that all values which are not constant across OP Chains SHOULD be held in storage rather
than the bytecode of the implementation.

Any contracts which do not meet this requirement will need to be deployed by the `upgrade()`
blmalone marked this conversation as resolved.
Show resolved Hide resolved
function, increasing the cost and reducing the number of OP Chains which can be atomically upgraded.

## Adding game types

Because different OP Chains within a Superchain may use different dispute game types, and are
expected to move from a permissioned to permissionless game over time, an `addGameType` method is
provided to enable adding a new game type to multiple games at once.

### Interface

#### `addGameType`

The `addGameType` method is used to orchestrate the actions required to add a new game type to one
or more chains.

```solidity
struct NewGameConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should define the game types and the definitions per game type. This spec is not clear enough to enable a second implementation or a reviewer to know that an impl is correct

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having just learned that the Proofs contracts refactor has been deprioritized, I will need to rewrite this section. Will let you know when that's done.

Copy link
Contributor Author

@maurelian maurelian Dec 11, 2024

Choose a reason for hiding this comment

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

This commit should address your comment:
7703c51

// fields will vary depending on the game type
}

function addGameType(ISystemConfig[] _systemConfigs, NewGameConfig[] _newGames) public;
```

### Implementation

The high level logic of the `addGameType` method is as follows (for each chain):

1. Deploys the new game Creator contract for that game type.
2. Calls `setImplementation()` on the `DisputeGameFactory`
3. Calls `setAnchorState()` on the `AnchorStateRegistry`

Note that in [Standard Configuration chains](../protocol/superchain-configuration.md):

- `DisputeGameFactory.setImplementation()` is authorized to the Upgrade Controller
- `AnchorStateRegistry.setAnchorState()` is authorized to the Guardian

Since the Guardian is its own Safe controlled by the Security Council, and the Upgrade Controller is
a 2 of 2 jointly controlled by the Security Council and Optimism Foundation, this is not impossible
to implement, but will be ugly and require new tooling and processes to do correctly.
maurelian marked this conversation as resolved.
Show resolved Hide resolved

## Security Considerations

### Chain ID Source of Truth
#### Chain ID Source of Truth

One of the implicit restrictions on chain ID is that `deploy` can only be called
once per chain ID, because contract addresses are a function of chain ID. However,
future versions of OP Contracts Manager may:

- Change the Proxy code used, which would allow a duplicate chain ID to be deployed
if there is only the implicit check.
if there is only the implicit check.
- Manage upgrades, which will require "registering" existing pre-OP Contracts Manager
chains in the OP Contracts Manager. Registration will be a privileged action, and the [superchain registry] will be
used as the source of truth for registrations.
chains in the OP Contracts Manager. Registration will be a privileged action, and the [superchain registry] will be
used as the source of truth for registrations.

This means, for example, if deploying a chain with a chain ID of 10—which is OP
Mainnet's chain ID—deployment will execute successfully, but the entry in OP
Expand All @@ -182,15 +287,15 @@ uniqueness is not enforced by the OP Contracts Manager, and it is strongly
recommended to only use chain IDs that are not already present in the
[ethereum-lists/chains] repository.

### Chain ID Frontrunning
#### Chain ID Frontrunning

Contract addresses for a chain are a function of chain ID, which implies you
can counterfactually compute and use those chain addresses before the chain is
deployed. However, this property should not be relied upon—new chain deployments
are permissionless, so you cannot guarantee usage of a given chain ID, as deploy
transactions can be frontrun.

### Chain ID Value
#### Chain ID Value

While not specific to OP Contracts Manager, when choosing a chain ID is important
to consider that not all chain IDs are well supported by tools. For example,
Expand All @@ -201,16 +306,27 @@ OP Contracts Manager does not consider factors such as these. The EVM supports
256-bit chain IDs, so OP Contracts Manager sticks with the full 256-bit range to
maximize compatibility.

### Proxy Admin Owner
#### Proxy Admin Owner

The proxy admin owner is a very powerful role, as it allows upgrading protocol
contracts. When choosing the initial proxy admin owner, a Safe is recommended
to ensure admin privileges are sufficiently secured.

### Upgradeability (ABI Changes)
#### Safely using `DELEGATECALL`

Because a Safe will `DELEGATECALL` to the `upgrade()` and `addGameType()` methods, it is
critical that no storage writes occur. This should be enforced in multiple ways, including:

- By static analysis of the `upgrade()` and `addGameType()` methods during the development process.
- By simulating and verifying the state changes which occur in the Upgrade Controller Safe prior to execution.

### Atomicity of upgrades

Although atomicity of a superchain upgrade is not essential for many types of upgrade, it will
at times be necessary. It is certainly always desirable for operational reasons.

This contract is upgradeable, and breaking changes are expected, as upgrades
are required to update the contracts release that is deployed. This is because
the required inputs to the `deploy` method may change as new contract releases
are supported. Therefore, if calling this contract from another contract, be
sure to account for future breaking changes to the ABI.
maurelian marked this conversation as resolved.
Show resolved Hide resolved
For this reason, efficiency should be kept in mind when designing the upgrade path. When the size of
the superchain reaches a size that nears the block gas limit, upgrades may need to be broken up into
stages, so that components which must be upgrade atomically can be. For example, all
`OptimismPortal` contracts may need to be upgraded in one transaction, followed by another
transaction which upgrades all `L1CrossDomainMessenger` contracts.