Skip to content

OPContractsManagerMigrator: chain B's SystemConfig.delayedWETH not repointed during shared-DGF migration #20502

@ajsutton

Description

@ajsutton

Claude: This issue was opened by Claude on behalf of @ajsutton.

Summary

When OPContractsManagerMigrator.migrate(MigrateInput) merges N pre-interop chains into a shared DisputeGameFactory and AnchorStateRegistry, the new shared super games are wired to chainSystemConfigs[0].delayedWETH() only. Chain B (and any later chain in chainSystemConfigs[]) keeps its SystemConfig.delayedWETH pointing at its old per-chain DelayedWETH, which is no longer attached to any active super game.

This creates a long-lived divergence:

  • The shared super games' bonds flow through chain A's DelayedWETH.
  • Chain B's SystemConfig.delayedWETH() still returns chain B's old DelayedWETH.
  • Future OPCMv2.upgrade() runs against chain B will read chain B's old DelayedWETH from SystemConfig, which is not the contract the shared games use — exactly the kind of drift the in-source comment at OPContractsManagerMigrator.sol:176-181 warns against.

Reproduction

packages/contracts-bedrock/src/L1/opcm/OPContractsManagerMigrator.sol:181:

// Reuse the existing DelayedWETH from chainSystemConfigs[0] rather than deploying a
// new one. The migrated chains share a SystemConfig, and by extension share its
// DelayedWETH. ...
IDelayedWETH delayedWETH = IDelayedWETH(payable(_input.chainSystemConfigs[0].delayedWETH()));

The comment assumes "migrated chains share a SystemConfig," which is not true for two independent permissionless chains being merged for the first time. Each chain has its own SystemConfig with its own delayedWETH storage slot (see SystemConfig.sol).

migrate() and _migratePortal() never call SystemConfig.setAddresses or any equivalent on chain B to repoint its delayedWETH to chain A's. The only SystemConfig write _migratePortal performs is setFeature(ETH_LOCKBOX, true) at line 304.

Impact

  1. Future per-chain upgrades against chain B read a stale delayedWETH. Any OPCM flow that loads DelayedWETH from SystemConfig (per the in-source warning) gets the wrong contract on chain B.
  2. Operational confusion. Operators tracking the shared games' bond pool will use chain A's DelayedWETH. Anyone tracking chain B's SystemConfig.delayedWETH() for the same purpose gets a contract that receives no new bonds.
  3. Drain-window bonds claim correctly through the orphaned contract. Chain B's still-in-flight pre-migration games' bonds were posted into the old DelayedWETH via CWIA-immutable references, so they continue to claim out of it. The divergence does not break the migration itself; it persists afterward as a maintenance hazard.

Discovered while authoring the operator runbook for two-chain merges into a shared dispute game.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions