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
- 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.
- 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.
- 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.
Claude: This issue was opened by Claude on behalf of @ajsutton.
Summary
When
OPContractsManagerMigrator.migrate(MigrateInput)merges N pre-interop chains into a sharedDisputeGameFactoryandAnchorStateRegistry, the new shared super games are wired tochainSystemConfigs[0].delayedWETH()only. Chain B (and any later chain inchainSystemConfigs[]) keeps itsSystemConfig.delayedWETHpointing at its old per-chainDelayedWETH, which is no longer attached to any active super game.This creates a long-lived divergence:
DelayedWETH.SystemConfig.delayedWETH()still returns chain B's oldDelayedWETH.OPCMv2.upgrade()runs against chain B will read chain B's oldDelayedWETHfromSystemConfig, which is not the contract the shared games use — exactly the kind of drift the in-source comment atOPContractsManagerMigrator.sol:176-181warns against.Reproduction
packages/contracts-bedrock/src/L1/opcm/OPContractsManagerMigrator.sol:181: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
SystemConfigwith its owndelayedWETHstorage slot (seeSystemConfig.sol).migrate()and_migratePortal()never callSystemConfig.setAddressesor any equivalent on chain B to repoint itsdelayedWETHto chain A's. The onlySystemConfigwrite_migratePortalperforms issetFeature(ETH_LOCKBOX, true)at line 304.Impact
delayedWETH. Any OPCM flow that loadsDelayedWETHfromSystemConfig(per the in-source warning) gets the wrong contract on chain B.DelayedWETH. Anyone tracking chain B'sSystemConfig.delayedWETH()for the same purpose gets a contract that receives no new bonds.DelayedWETHvia 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.