Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,17 @@ The portal-derived lookups return the chain's current `DisputeGameFactory`, `Anc

`migrate` reads each chain's `DelayedWETH` directly from `SystemConfig.delayedWETH()` at execution time, so there is nothing to capture for it here.

**Init bonds.** The migration registers two super game types on the new shared factory: `SUPER_PERMISSIONED_CANNON` (game type `5`) and `SUPER_CANNON_KONA` (game type `9`). Use `0.08 ether` (`80000000000000000` wei) as the `initBond` for both.
**Init bonds.** The migration registers two super game types on the new shared factory: `SUPER_PERMISSIONED` (game type `5`) and `SUPER_CANNON_KONA` (game type `9`). Use `0.08 ether` (`80000000000000000` wei) as the `initBond` for both.

**Release artifacts and infrastructure.**

| Item | Source |
| --- | --- |
| `OPCM` address for the migrate-feature container | The same OPCM the chains ran for the prerequisite `OPCMv2.upgrade()`. The container has `OPTIMISM_PORTAL_INTEROP` set in `devFeatureBitmap`, which is what enables both `Features.INTEROP` and `Features.ETH_LOCKBOX` on each `SystemConfig`. |
| `SUPER_CANNON_KONA` absolute prestate hash | `validation/standard/standard-prestates.toml` in `superchain-registry`. Use the entry with `type="interop"` for the OPCM release version. |
| `SUPER_PERMISSIONED_CANNON` absolute prestate hash | Same file, same `type="interop"` entry. The two super game types share the kona-interop prestate. |
| Prestate artifact URL | The `cannon-kona-prestates-url` server, with the kona-interop prestate uploaded ahead of the migration. |
| Shared `op-supernode` RPC URL | Your infrastructure team. The instance must derive **exactly `chainA` and `chainB`** (and no other chains). The shared `op-proposer`, `op-challenger`, and `op-dispute-mon` for the merged factory all point at this same instance. The per-chain single-chain supernodes already in use stay attached to their per-chain drain instances and are not replaced. |
| Privileged `proposer` for `SUPER_PERMISSIONED_CANNON` | The address that may post proposals to the permissioned super game. `op-proposer` is configured to sign with this key. |
| Privileged `challenger` for `SUPER_PERMISSIONED_CANNON` | The address that may counter proposals on the permissioned super game. Typically a multisig — countering a permissioned proposal is a manual intervention step that is not needed in normal operation. **Not** the key `op-challenger` signs with: `op-challenger` runs with a separate, unprivileged wallet and can still resolve permissioned games permissionlessly. |
| Privileged `proposer` for `SUPER_PERMISSIONED` | The address that may post proposals to the permissioned super game. `op-proposer` is configured to sign with this key. |

### Verify the Preconditions

Expand Down Expand Up @@ -130,7 +128,7 @@ Run every check below. Each one corresponds to an invariant the migrator enforce
# Expect: 9 for both.
```

* **Confirm the shared supernode derives exactly the two merging chains.** Verify with your infrastructure team that the instance behind `$SUPERNODE_RPC` has `chainA` and `chainB` in its dependency set and **no** other chains. A supernode that also tracks an unrelated chain (or an interop cluster that includes one) computes super roots over that broader set, and those roots will not match what the shared `SUPER_CANNON_KONA` and `SUPER_PERMISSIONED_CANNON` games verify. The per-chain drain instances continue to use their existing single-chain supernodes; do not repoint them at the shared one.
* **Confirm the shared supernode derives exactly the two merging chains.** Verify with your infrastructure team that the instance behind `$SUPERNODE_RPC` has `chainA` and `chainB` in its dependency set and **no** other chains. A supernode that also tracks an unrelated chain (or an interop cluster that includes one) computes super roots over that broader set, and those roots will not match what the shared `SUPER_CANNON_KONA` and `SUPER_PERMISSIONED` games verify. The per-chain drain instances continue to use their existing single-chain supernodes; do not repoint them at the shared one.
* **Confirm the shared supernode is in sync.** It must report a finalized L2 head within both chains' expected finality windows:

```bash
Expand All @@ -139,11 +137,10 @@ Run every check below. Each one corresponds to an invariant the migrator enforce

If the supernode is materially behind either chain, postpone the migration.

* **Confirm the prestate server serves both super prestates.**
* **Confirm the prestate server serves the `SUPER_CANNON_KONA` prestate.**

```bash
curl -fI "$PRESTATE_URL/$SUPER_CANNON_KONA_PRESTATE.bin.gz"
curl -fI "$PRESTATE_URL/$SUPER_PERMISSIONED_PRESTATE.bin.gz"
```

* **Note the chain B `DelayedWETH` divergence.** `migrate` reuses chain A's `DelayedWETH` for the new shared super games and does not touch chain B's. After migration, chain B's `SystemConfig.delayedWETH()` still references chain B's old `DelayedWETH`, which is no longer attached to any super game. Future `OPCMv2.upgrade()` calls against chain B that read `SystemConfig.delayedWETH()` will therefore reference a different contract than the shared games use. Track this in your operational runbook so future upgrades do not surprise you. The bonds posted by chain B's still-in-flight pre-migration games continue to claim out of chain B's old `DelayedWETH`, so the divergence does not block the drain described in [Drain Pre-Migration Games and Reclaim Bonds](#drain-pre-migration-games-and-reclaim-bonds).
Expand Down Expand Up @@ -245,7 +242,7 @@ startingRespectedGameType = 9
# Both super game types must be registered. The migration validator requires it.
# The template assembles the on-chain DisputeGameConfig.gameArgs bytes from these
# fields: abi.encode(absolutePrestate) for game type 9, and
# abi.encode(absolutePrestate, proposer, challenger) for game type 5.
# abi.encode(proposer) for game type 5.

[[disputeGameConfigs]]
gameType = 9 # SUPER_CANNON_KONA
Expand All @@ -254,12 +251,10 @@ initBond = 80000000000000000 # match the value gathered in the input
absolutePrestate = "0x<super-cannon-kona prestate>"

[[disputeGameConfigs]]
gameType = 5 # SUPER_PERMISSIONED_CANNON
gameType = 5 # SUPER_PERMISSIONED
enabled = true
initBond = 80000000000000000
absolutePrestate = "0x<super-permissioned-cannon prestate>"
proposer = "0x<privileged proposer>"
challenger = "0x<privileged challenger>"

expectedValidationErrors = "" # fill in after the dry run if any structurally expected codes appear
```
Expand All @@ -277,7 +272,7 @@ The validator behind `OPContractsManagerMigrationValidator` returns a comma-sepa

| Code | Meaning |
| --- | --- |
| `MIG-DGF-10` | `SUPER_PERMISSIONED_CANNON` is not registered on the shared factory. |
| `MIG-DGF-10` | `SUPER_PERMISSIONED` is not registered on the shared factory. |
| `MIG-DGF-20` | `SUPER_CANNON_KONA` is not registered on the shared factory. |
| `MIG-DGF-30` | `CANNON` is still registered on the shared factory. |
| `MIG-DGF-40` | `PERMISSIONED_CANNON` is still registered on the shared factory. |
Expand All @@ -291,13 +286,13 @@ The validator behind `OPContractsManagerMigrationValidator` returns a comma-sepa
| `MIG-CHAIN-{i}-20` | Chain `i`'s per-chain factory still has `CANNON` registered. |
| `MIG-CHAIN-{i}-30` | Chain `i`'s per-chain factory still has `PERMISSIONED_CANNON` registered. |
| `MIG-CHAIN-{i}-40` | Chain `i`'s per-chain factory still has `CANNON_KONA` registered. |
| `MIG-CHAIN-{i}-60` | Chain `i`'s per-chain factory still has `SUPER_PERMISSIONED_CANNON` registered. |
| `MIG-CHAIN-{i}-60` | Chain `i`'s per-chain factory still has `SUPER_PERMISSIONED` registered. |
| `MIG-CHAIN-{i}-70` | Chain `i`'s per-chain factory still has `SUPER_CANNON_KONA` registered. |
| `MIG-CHAIN-{i}-80` | Shared lockbox does not list chain `i`'s portal as authorized. |
| `MIG-CHAIN-{i}-90` | Chain `i`'s portal `ethLockbox` does not equal the shared lockbox. |
| `MIG-CHAIN-{i}-100` | Chain `i`'s `SystemConfig` does not have `Features.INTEROP` enabled. |
| `MIG-CHAIN-{i}-110` | Chain `i`'s `SystemConfig` does not have `Features.ETH_LOCKBOX` enabled. |
| `MIG-SPDG-*` or `MIG-SCKDG-*` | Super game drill-down. The first prefix covers `SUPER_PERMISSIONED_CANNON` and the second covers `SUPER_CANNON_KONA`. Subcodes include `-GARGS-10` and `-GARGS-30` for game-args shape and `weth` mismatches, plus `-130` and `-140` for proposer or challenger mismatches on the permissioned type. The migration validator also delegates to the standard validator for each super game, so every standard code (MIPS, prestate, depths, clocks, `AnchorStateRegistry` pointer) appears with the same prefix. |
| `MIG-SPDG-*` or `MIG-SCKDG-*` | Super game drill-down. The first prefix covers `SUPER_PERMISSIONED` and the second covers `SUPER_CANNON_KONA`. `MIG-SPDG-*` subcodes cover the simplified permissioned game's args, proposer, and `AnchorStateRegistry`; `MIG-SCKDG-*` covers the fault-proof game checks. |

## Execute the Migration

Expand Down Expand Up @@ -366,7 +361,7 @@ cast call $SHARED_ASR 'retirementTimestamp()(uint64)' --rpc-url $L1_RPC
# Expect: equal to the migration block's timestamp

# Game implementations registered on the shared factory
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED_CANNON
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 9 --rpc-url $L1_RPC # SUPER_CANNON_KONA
# Expect both: non-zero

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: Runbook for upgrading an OP Stack chain from output-root dispute ga

<Warning>This guide depends on the interop feature, which is still in development. Do not follow it on production chains.</Warning>

This runbook walks you through upgrading a single OP Stack chain from output-root dispute games (`PERMISSIONED_CANNON`, `CANNON` or `CANNON_KONA`) to super-root dispute games (`SUPER_PERMISSIONED_CANNON` or `SUPER_CANNON_KONA`). It runs as a single `opcm.upgrade` call against the chain's existing per-chain `AnchorStateRegistry` and `DisputeGameFactory`. The chain's permission model is preserved: a permissioned chain stays permissioned with `SUPER_PERMISSIONED_CANNON` only; a permissionless chain runs both super game types with `SUPER_CANNON_KONA` as the respected one. Differences between the two are called out inline.
This runbook walks you through upgrading a single OP Stack chain from output-root dispute games (`PERMISSIONED_CANNON`, `CANNON` or `CANNON_KONA`) to super-root dispute games (`SUPER_PERMISSIONED` or `SUPER_CANNON_KONA`). It runs as a single `opcm.upgrade` call against the chain's existing per-chain `AnchorStateRegistry` and `DisputeGameFactory`. The chain's permission model is preserved: a permissioned chain stays permissioned with `SUPER_PERMISSIONED` only; a permissionless chain runs both super game types with `SUPER_CANNON_KONA` as the respected one. Differences between the two are called out inline.

By the end you will have a chain proposing super-root claims via `op-proposer` against an `op-supernode`, defending them via `op-challenger`, monitored by `op-dispute-mon`, and rejecting creation of new pre-migration games at the factory. Every pre-migration dispute game already in flight continues to resolve, and proven withdrawals are not invalidated.

Expand Down Expand Up @@ -66,7 +66,7 @@ Read the values you need from `chain.json`:

**Chain type.** The upgrade preserves the chain's existing permission model. `chain.json` reports the current respected game type at `.faultProofs.respectedGameType`:

* `1` (`PERMISSIONED_CANNON`) — chain is **permissioned**. The new respected game type is `5` (`SUPER_PERMISSIONED_CANNON`).
* `1` (`PERMISSIONED_CANNON`) — chain is **permissioned**. The new respected game type is `5` (`SUPER_PERMISSIONED`).
* `0` (`CANNON`) or `8` (`CANNON_KONA`) — chain is **permissionless**. The new respected game type is `9` (`SUPER_CANNON_KONA`).

**Init bond.** The `OPCMUpgradeV800` template applies a single `initBond` value (in wei) to every enabled super game type. `0.08 ether` (`80000000000000000` wei) is the standard value used on existing chains.
Expand All @@ -85,7 +85,7 @@ Read the values you need from `chain.json`:
Before you change any configuration:

* **Confirm the template injects `overrides.cfg.startingAnchorRoot`.** Open `superchain-ops/src/template/OPCMUpgradeV800.sol` and check `_buildExtraInstructions`. As of writing it only injects `overrides.cfg.startingRespectedGameType` and `PermittedProxyDeployment`. Without an `overrides.cfg.startingAnchorRoot` override, OPCM falls back to the existing on-chain `startingAnchorRoot`, which on a pre-upgrade chain is an output-root-shaped value with a block-number `l2SequenceNumber` — not a valid super-root anchor. The template must be extended to read a starting super-root anchor from TOML and add it as a third extra instruction. Fix the template before continuing.
* **Confirm the `op-supernode` tracks only this chain.** `op-proposer`, `op-challenger`, and `op-dispute-mon` must point at a supernode instance whose dependency set contains only this chain's L2 chain ID. A supernode that also tracks other chains computes super roots across all of them, and those roots will not match what this chain's `SUPER_CANNON_KONA` / `SUPER_PERMISSIONED_CANNON` games verify. If the same physical operator also runs supernodes for other chains (or an interop cluster), stand up a dedicated single-chain supernode instance for this upgrade.
* **Confirm the `op-supernode` tracks only this chain.** `op-proposer`, `op-challenger`, and `op-dispute-mon` must point at a supernode instance whose dependency set contains only this chain's L2 chain ID. A supernode that also tracks other chains computes super roots across all of them, and those roots will not match what this chain's `SUPER_CANNON_KONA` / `SUPER_PERMISSIONED` games verify. If the same physical operator also runs supernodes for other chains (or an interop cluster), stand up a dedicated single-chain supernode instance for this upgrade.
* **Verify the `op-supernode` is healthy.** Confirm it reports a finalized L2 head within the chain's expected finality window:

```bash
Expand Down Expand Up @@ -164,8 +164,8 @@ templateName = "OPCMUpgradeV800"

[[opcmUpgrades]]
chainId = <chain id>
# Kona-interop super prestate, used by both SUPER_PERMISSIONED_CANNON and
# SUPER_CANNON_KONA. The `-interop` variant supports super roots and works whether
# Kona-interop super prestate, used by SUPER_CANNON_KONA. The `-interop` variant
# supports super roots and works whether
# or not interop is enabled or scheduled on the chain.
cannonKonaPrestate = "0x<kona-interop super prestate>"
expectedValidationErrors = "" # fill in after the dry run
Expand Down Expand Up @@ -243,7 +243,7 @@ cast call <DGF> 'gameImpls(uint32)(address)' 8 --rpc-url $L1_RPC # CANNON_KONA
### Permissionless Chain Checks

```bash
cast call <DGF> 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED_CANNON
cast call <DGF> 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED
cast call <DGF> 'gameImpls(uint32)(address)' 9 --rpc-url $L1_RPC # SUPER_CANNON_KONA
# Expect both: non-zero

Expand All @@ -255,7 +255,7 @@ cast call <DGF> 'initBonds(uint32)(uint256)' 9 --rpc-url $L1_RPC
### Permissioned Chain Checks

```bash
cast call <DGF> 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED_CANNON
cast call <DGF> 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED
# Expect: non-zero

cast call <DGF> 'initBonds(uint32)(uint256)' 5 --rpc-url $L1_RPC
Expand Down
2 changes: 1 addition & 1 deletion op-deployer/pkg/deployer/integration_test/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat
{
Enabled: false,
InitBond: big.NewInt(0),
GameType: embedded.GameTypeSuperPermCannon,
GameType: embedded.GameTypeSuperPermissioned,
},
{
Enabled: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func TestManageAddGameTypeV2_Integration(t *testing.T) {
{
Enabled: false,
InitBond: big.NewInt(0),
GameType: embedded.GameTypeSuperPermCannon,
GameType: embedded.GameTypeSuperPermissioned,
},
{
Enabled: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func buildV2OPCMUpgradeConfig(t *testing.T, prank, opcmAddr, systemConfigProxy c
{
Enabled: false,
InitBond: big.NewInt(0),
GameType: embedded.GameTypeSuperPermCannon,
GameType: embedded.GameTypeSuperPermissioned,
},
{
Enabled: false,
Expand Down
4 changes: 2 additions & 2 deletions op-deployer/pkg/deployer/upgrade/embedded/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type GameType uint32
const (
GameTypeCannon GameType = 0
GameTypePermissionedCannon GameType = 1
GameTypeSuperPermCannon GameType = 5
GameTypeSuperPermissioned GameType = 5
GameTypeCannonKona GameType = 8
GameTypeSuperCannonKona GameType = 9
GameTypeZKDisputeGame GameType = 10
Expand Down Expand Up @@ -158,7 +158,7 @@ func (u *UpgradeOPChainInput) EncodedUpgradeInputV2() ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("failed to encode permissioned game config: %w", err)
}
case GameTypeSuperPermCannon:
case GameTypeSuperPermissioned:
if gameConfig.SuperPermissionedDisputeGameConfig == nil {
return nil, fmt.Errorf("superPermissionedDisputeGameConfig is required for game type %d", gameConfig.GameType)
}
Expand Down
12 changes: 6 additions & 6 deletions op-deployer/pkg/deployer/upgrade/embedded/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,14 @@ func TestEncodedUpgradeInputV2_GameTypeConfigValidation(t *testing.T) {
shouldPass: false,
},
{
name: "SUPER_PERMISSIONED_CANNON requires SuperPermissionedDisputeGameConfig",
name: "SUPER_PERMISSIONED requires SuperPermissionedDisputeGameConfig",
gameConfig: DisputeGameConfig{
Enabled: true,
InitBond: big.NewInt(1000),
GameType: GameTypeSuperPermCannon,
GameType: GameTypeSuperPermissioned,
// Missing SuperPermissionedDisputeGameConfig
},
errorContains: fmt.Sprintf("superPermissionedDisputeGameConfig is required for game type %d", GameTypeSuperPermCannon),
errorContains: fmt.Sprintf("superPermissionedDisputeGameConfig is required for game type %d", GameTypeSuperPermissioned),
shouldPass: false,
},
{
Expand Down Expand Up @@ -349,11 +349,11 @@ func TestEncodedUpgradeInputV2_GameTypeConfigValidation(t *testing.T) {
shouldPass: true,
},
{
name: "SUPER_PERMISSIONED_CANNON with correct SuperPermissionedDisputeGameConfig",
name: "SUPER_PERMISSIONED with correct SuperPermissionedDisputeGameConfig",
gameConfig: DisputeGameConfig{
Enabled: true,
InitBond: big.NewInt(1000),
GameType: GameTypeSuperPermCannon,
GameType: GameTypeSuperPermissioned,
SuperPermissionedDisputeGameConfig: &SuperPermissionedDisputeGameConfig{
Proposer: common.HexToAddress("0x1111111111111111111111111111111111111111"),
},
Expand Down Expand Up @@ -629,7 +629,7 @@ func TestEncodedUpgradeInputV2_GameArgsEncoding(t *testing.T) {
{
Enabled: true,
InitBond: big.NewInt(1000),
GameType: GameTypeSuperPermCannon,
GameType: GameTypeSuperPermissioned,
SuperPermissionedDisputeGameConfig: &SuperPermissionedDisputeGameConfig{
Proposer: proposer,
},
Expand Down
4 changes: 2 additions & 2 deletions op-devstack/sysgo/add_game_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func addGameTypesForRuntime(
dummyCannonPrestate := common.HexToHash(sharedchallenger.DummyPermissionedPrestate)

// OPCMv2 requires all 6 game configs in order:
// CANNON, PERMISSIONED_CANNON, CANNON_KONA, SUPER_PERMISSIONED_CANNON, SUPER_CANNON_KONA, ZK_DISPUTE_GAME.
// CANNON, PERMISSIONED_CANNON, CANNON_KONA, SUPER_PERMISSIONED, SUPER_CANNON_KONA, ZK_DISPUTE_GAME.
// The CANNON (legacy) game type is permanently disabled, but its config slot must remain present
// and in order for the OPCMv2 upgrade.
configs := []embedded.DisputeGameConfig{
Expand Down Expand Up @@ -182,7 +182,7 @@ func addGameTypesForRuntime(
AbsolutePrestate: cannonKonaPrestate,
},
},
{Enabled: false, InitBond: new(big.Int), GameType: embedded.GameTypeSuperPermCannon},
{Enabled: false, InitBond: new(big.Int), GameType: embedded.GameTypeSuperPermissioned},
{Enabled: false, InitBond: new(big.Int), GameType: embedded.GameTypeSuperCannonKona},
{
Enabled: enabled[gameTypes.ZKDisputeGameType],
Expand Down
Loading
Loading