From 56cd5f0e273fb0e9b6b401224c6c9a447d928d84 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 29 Jul 2026 13:21:30 +1000 Subject: [PATCH 1/5] contracts-bedrock: Bump OPCM major version to 8.0.0 for U20 U20 is a new sequential upgrade, and the OPCM versioning rules require a major bump for each one. Without this the release would be tagged against an OPCM still advertising the U19 major. The rule is documented in book/src/policies/versioning.md and restated on version() itself. --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- .../contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 22aa825f14d..d64a9446198 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -36,8 +36,8 @@ "sourceCodeHash": "0xceeedf69dcb6c05b88fd1569c53606dec02195b4d393a6491876b6d2f0c2ff1f" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0x5cf001b0bc8758c996db25ebdfe181516bfeb0697d066bbde2e907e76aaefd03", - "sourceCodeHash": "0xa2b3f54c8350ce4408760a45f631acad4458e466ab7c0bb1a2b7ff4dadf63511" + "initCodeHash": "0xa3800f08c6e8a0ddcff71d00615e0fe420bbe430b65a96300aa9b8215391d10a", + "sourceCodeHash": "0x6b92e36efc42190262d169540b7a9f032230426b78b8bdbde1e4d539579c68cc" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x18650c65fa6f23fefc6f0c5fff605f12be99fce0bfbcbc0644403138e4c8bb12", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index ef43a37560c..9b071dc2083 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -158,9 +158,9 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { /// - Major bump: New required sequential upgrade /// - Minor bump: Replacement OPCM for same upgrade /// - Patch bump: Development changes (expected for normal dev work) - /// @custom:semver 7.2.5 + /// @custom:semver 8.0.0 function version() public pure returns (string memory) { - return "7.2.5"; + return "8.0.0"; } /// @param _standardValidator The standard validator for this OPCM release. From 0786c7a26f1cb642cef58d04d5075196fcb28c53 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 29 Jul 2026 14:39:30 +1000 Subject: [PATCH 2/5] contracts-bedrock: Keep OPCM upgrade allowances live for U20 The upgrade allowances in _isPermittedInstruction are scoped to a single upgrade by requiring version() < NEXT release. Bumping OPCM to 8.0.0 made that guard false, so the DelayedWETH permitted proxy deployment and the super-root migration startingAnchorRoot override were both rejected, failing every fork upgrade test, the ZK super-root migration tests and the op-deployer chain upgrade. Both allowances are still needed by U20, so move the guard to 9.0.0. The 7.0.0 bump in #18631 did the same. The upgrade sequence check keeps its 8.0.0 threshold: mainnet and sepolia are both on OPCM 7.1.17, so 8.0.0 is a permitted next major. --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- .../contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index d64a9446198..c1d12292982 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -36,8 +36,8 @@ "sourceCodeHash": "0xceeedf69dcb6c05b88fd1569c53606dec02195b4d393a6491876b6d2f0c2ff1f" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0xa3800f08c6e8a0ddcff71d00615e0fe420bbe430b65a96300aa9b8215391d10a", - "sourceCodeHash": "0x6b92e36efc42190262d169540b7a9f032230426b78b8bdbde1e4d539579c68cc" + "initCodeHash": "0x12076df21fef6629398de33d4da3f18cdfeacb873c450f03aad2b507933598a9", + "sourceCodeHash": "0x358a28cb02767bb8e7ec87edba3b328f2ea6c0faabb078c8e768701f3b257edd" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x18650c65fa6f23fefc6f0c5fff605f12be99fce0bfbcbc0644403138e4c8bb12", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index 9b071dc2083..a81a4fa3235 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -359,7 +359,7 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { // developers start working on the next release this will automatically become false so // even if the code is somehow forgotten it will not actually apply to the deployment. Make // sure to REMOVE the allowance once the upgrade is complete. - if (SemverComp.lt(_version(), "8.0.0")) { + if (SemverComp.lt(_version(), "9.0.0")) { // Unified DelayedWETH is being deployed for the first time. // TODO:(#18382): Remove this allowance after unified DelayedWETH is deployed. if (_isMatchingInstruction(_instruction, Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, "DelayedWETH")) { From 974bcc862bc4ec27e086e9671096a7e73de74143 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Thu, 30 Jul 2026 09:00:57 +1000 Subject: [PATCH 3/5] contracts-bedrock: Remove OPCM DelayedWETH proxy deployment allowance The unified DelayedWETH shipped with the 7.x OPCM, so every chain that can pass the upgrade sequence check already has a DelayedWETH proxy on its SystemConfig and the upgrade never needs to deploy one. Verified on chain: op-mainnet, op-sepolia, ink-mainnet and unichain-mainnet all report SystemConfig 3.14.2, lastUsedOPCMVersion 7.1.17 and a non-zero delayedWETH(). Closes the TODO from #18382. Drops the instruction from every caller: the fork upgrade setup, the OPCMv2 upgrade tests, op-deployer's upgrade and add-game-type tests and the devstack super-root paths. The duplicate-key test now asserts that duplicate PermittedProxyDeployment keys still skip the duplicate check and fall through to the permission check. superchain-ops' OPCMUpgradeV800 template still injects the instruction and will revert against OPCM 8.0.0; noted in the super-roots upgrade tutorial preconditions. --- .../upgrade-chain-to-super-roots.mdx | 2 +- .../deployer/integration_test/apply_test.go | 7 +---- .../cli/manage_add_game_type_v2_test.go | 1 - op-devstack/sysgo/add_game_type.go | 1 - op-devstack/sysgo/superroot_via_upgrade.go | 1 - .../snapshots/semver-lock.json | 4 +-- .../src/L1/opcm/OPContractsManagerV2.sol | 6 ----- .../test/L1/opcm/OPContractsManagerV2.t.sol | 26 +++++++++++-------- .../test/setup/ForkL1Live.s.sol | 20 ++++---------- 9 files changed, 24 insertions(+), 44 deletions(-) diff --git a/docs/public-docs/chain-operators/tutorials/upgrade-chain-to-super-roots.mdx b/docs/public-docs/chain-operators/tutorials/upgrade-chain-to-super-roots.mdx index d3f9b3a9ab2..22aea05866b 100644 --- a/docs/public-docs/chain-operators/tutorials/upgrade-chain-to-super-roots.mdx +++ b/docs/public-docs/chain-operators/tutorials/upgrade-chain-to-super-roots.mdx @@ -84,7 +84,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 template's extra instructions.** Open `superchain-ops/src/template/OPCMUpgradeV800.sol` and check `_buildExtraInstructions`. As of writing it injects `overrides.cfg.startingRespectedGameType` and `PermittedProxyDeployment`. Both need attention before continuing. `PermittedProxyDeployment` must be dropped: OPCM `8.0.0` permits no proxy deployments during an upgrade, because the unified `DelayedWETH` shipped with the 7.x OPCM, so the instruction now reverts with `OPContractsManagerV2_InvalidUpgradeInstruction`. An `overrides.cfg.startingAnchorRoot` override must be added: without it, 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 an extra instruction. Fix the template before continuing. * **Confirm the RPC endpoint returns a single-chain super root.** Either the chain's op-node RPC endpoint or an op-supernode `//` endpoint can be used. Do not use a multi-chain op-supernode root endpoint; it computes roots across its full dependency set. * **Verify the single-chain RPC endpoint is healthy.** For an op-node or namespaced op-supernode endpoint, confirm it reports a recent finalized L2 head and can produce the corresponding super root: diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index 835169fa428..ea0cdd824c4 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -1078,12 +1078,7 @@ func runEndToEndBootstrapAndApplyUpgradeTest(t *testing.T, afactsFS foundry.Stat GameType: embedded.GameTypeZKDisputeGame, }, }, - ExtraInstructions: []embedded.ExtraInstruction{ - { - Key: "PermittedProxyDeployment", - Data: []byte("DelayedWETH"), - }, - }, + ExtraInstructions: []embedded.ExtraInstruction{}, }, } diff --git a/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go b/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go index af44c8c5d49..1a4ad9f1fa3 100644 --- a/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go +++ b/op-deployer/pkg/deployer/integration_test/cli/manage_add_game_type_v2_test.go @@ -168,7 +168,6 @@ func TestManageAddGameTypeV2_Integration(t *testing.T) { ExtraInstructions: []embedded.ExtraInstruction{ {Key: "overrides.cfg.startingAnchorRoot", Data: startingAnchorRoot}, {Key: "overrides.cfg.startingRespectedGameType", Data: startingRespectedGameType}, - {Key: "PermittedProxyDeployment", Data: []byte("DelayedWETH")}, }, }, } diff --git a/op-devstack/sysgo/add_game_type.go b/op-devstack/sysgo/add_game_type.go index c2dea631ba7..365d4ba3f77 100644 --- a/op-devstack/sysgo/add_game_type.go +++ b/op-devstack/sysgo/add_game_type.go @@ -144,7 +144,6 @@ func addGameTypesForRuntime( Key: "overrides.cfg.startingRespectedGameType", Data: encodeStartingRespectedGameType(t, superPermissionedGameType), }, - {Key: "PermittedProxyDeployment", Data: []byte("DelayedWETH")}, } // Download the contracts artifacts once; reused for the mock verifier deploy and the upgrade. diff --git a/op-devstack/sysgo/superroot_via_upgrade.go b/op-devstack/sysgo/superroot_via_upgrade.go index af108e12637..31de5fa9e1d 100644 --- a/op-devstack/sysgo/superroot_via_upgrade.go +++ b/op-devstack/sysgo/superroot_via_upgrade.go @@ -67,7 +67,6 @@ func upgradeToSuperRoots( ExtraInstructions: []embedded.ExtraInstruction{ {Key: "overrides.cfg.startingAnchorRoot", Data: anchorRootData}, {Key: "overrides.cfg.startingRespectedGameType", Data: respectedGameTypeData}, - {Key: "PermittedProxyDeployment", Data: []byte("DelayedWETH")}, }, }, }) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index c1d12292982..2e20e7a36ee 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -36,8 +36,8 @@ "sourceCodeHash": "0xceeedf69dcb6c05b88fd1569c53606dec02195b4d393a6491876b6d2f0c2ff1f" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0x12076df21fef6629398de33d4da3f18cdfeacb873c450f03aad2b507933598a9", - "sourceCodeHash": "0x358a28cb02767bb8e7ec87edba3b328f2ea6c0faabb078c8e768701f3b257edd" + "initCodeHash": "0x72252577edb0d71a246672c9d646271e5bbc30caf9f17485ec54c0100c303c99", + "sourceCodeHash": "0x7f474955ce6b11d17fb989da1266fc03b45ddefa8b556300b3929289aee4263a" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x18650c65fa6f23fefc6f0c5fff605f12be99fce0bfbcbc0644403138e4c8bb12", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index a81a4fa3235..eb56af8a68e 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -360,12 +360,6 @@ contract OPContractsManagerV2 is ISemver, OPContractsManagerUtilsCaller { // even if the code is somehow forgotten it will not actually apply to the deployment. Make // sure to REMOVE the allowance once the upgrade is complete. if (SemverComp.lt(_version(), "9.0.0")) { - // Unified DelayedWETH is being deployed for the first time. - // TODO:(#18382): Remove this allowance after unified DelayedWETH is deployed. - if (_isMatchingInstruction(_instruction, Constants.PERMITTED_PROXY_DEPLOYMENT_KEY, "DelayedWETH")) { - return true; - } - // Super root games migration requires overriding anchor root. if (isDevFeatureEnabled(DevFeatures.SUPER_ROOT_GAMES_MIGRATION)) { if (_isMatchingInstructionByKey(_instruction, "overrides.cfg.startingAnchorRoot")) return true; diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index 130801d1a16..b585f68190a 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -330,14 +330,6 @@ contract OPContractsManagerV2_Upgrade_TestInit is OPContractsManagerV2_TestInit ) }) ); - - _pushPermittedProxyDeploymentInstruction(); - } - - function _pushPermittedProxyDeploymentInstruction() internal { - v2UpgradeInput.extraInstructions.push( - IOPContractsManagerUtils.ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("DelayedWETH") }) - ); } /// @notice Helper function that runs an OPCM V2 upgrade, asserts that the upgrade was successful, @@ -929,8 +921,9 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI ); } - /// @notice Tests that duplicate PermittedProxyDeployment instruction keys are allowed. - function test_upgrade_duplicatePermittedProxyDeploymentKeys_succeeds() public { + /// @notice Tests that duplicate PermittedProxyDeployment instruction keys are exempt from the + /// duplicate instruction check, so validation carries on to the permission check. + function test_upgrade_duplicatePermittedProxyDeploymentKeys_reverts() public { delete v2UpgradeInput.extraInstructions; v2UpgradeInput.extraInstructions.push( IOPContractsManagerUtils.ExtraInstruction({ @@ -944,7 +937,18 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI data: bytes("DelayedWETH") }) ); - runCurrentUpgradeV2(chainPAO); + + // This upgrade permits no proxy deployments, so the instructions are rejected by the + // permission check. Without the duplicate exemption the revert would instead be + // OPContractsManagerV2_DuplicateUpgradeInstruction. + // nosemgrep: sol-style-use-abi-encodecall + runCurrentUpgradeV2( + chainPAO, + abi.encodeWithSelector( + IOPContractsManagerV2.OPContractsManagerV2_InvalidUpgradeInstruction.selector, + Constants.PERMITTED_PROXY_DEPLOYMENT_KEY + ) + ); } /// @notice INVARIANT: Upgrades must always work when the system is paused. diff --git a/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol b/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol index d3cff3e52e7..7611a900fcf 100644 --- a/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkL1Live.s.sol @@ -346,19 +346,15 @@ contract ForkL1Live is Deployer, StdAssertions, FeatureFlags { gameArgs: hex"" }); - // Migration needs 3 extra instructions: DelayedWETH proxy + anchor root + game type overrides. - extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](3); + // Migration needs 2 extra instructions: anchor root + game type overrides. + extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](2); extraInstructions[0] = IOPContractsManagerUtils.ExtraInstruction({ - key: "PermittedProxyDeployment", - data: bytes("DelayedWETH") - }); - extraInstructions[1] = IOPContractsManagerUtils.ExtraInstruction({ key: "overrides.cfg.startingAnchorRoot", data: abi.encode( Proposal({ root: Hash.wrap(keccak256("migrationAnchorRoot")), l2SequenceNumber: currentAnchorSeqNum + 1 }) ) }); - extraInstructions[2] = IOPContractsManagerUtils.ExtraInstruction({ + extraInstructions[1] = IOPContractsManagerUtils.ExtraInstruction({ key: "overrides.cfg.startingRespectedGameType", data: abi.encode(targetGameType) }); @@ -419,14 +415,8 @@ contract ForkL1Live is Deployer, StdAssertions, FeatureFlags { gameArgs: hex"" }); - // Permit the upgrade to (re)deploy the DelayedWETH proxy if it is missing on the forked - // chain. The standard path deploys no other proxies (unlike the super-root migration path - // above), so this is the only PermittedProxyDeployment instruction it needs. - extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](1); - extraInstructions[0] = IOPContractsManagerUtils.ExtraInstruction({ - key: "PermittedProxyDeployment", - data: bytes("DelayedWETH") - }); + // The standard upgrade path deploys no proxies, so it needs no extra instructions. + extraInstructions = new IOPContractsManagerUtils.ExtraInstruction[](0); } vm.prank(_delegateCaller, true); From 7c0614eb5aa4af7fd94d48a5d85f81a8785ef633 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 31 Jul 2026 07:18:18 +1000 Subject: [PATCH 4/5] contracts-bedrock: Regenerate semver-lock for OPCM 8.0.0 Refreshes the OPContractsManagerV2 hashes after the rebase onto develop. --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 2e20e7a36ee..c931f8972a7 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -36,8 +36,8 @@ "sourceCodeHash": "0xceeedf69dcb6c05b88fd1569c53606dec02195b4d393a6491876b6d2f0c2ff1f" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0x72252577edb0d71a246672c9d646271e5bbc30caf9f17485ec54c0100c303c99", - "sourceCodeHash": "0x7f474955ce6b11d17fb989da1266fc03b45ddefa8b556300b3929289aee4263a" + "initCodeHash": "0x4554a189b4481e32892cc2c276f41e407ba015442568f6779389e649f6d7a4e0", + "sourceCodeHash": "0x7c10505fe79bddcd92e6ff5ee2df648a68b743d608fbb34c2fb5ceef5b271dda" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x18650c65fa6f23fefc6f0c5fff605f12be99fce0bfbcbc0644403138e4c8bb12", From e3953d1abe4619ef49f753912af22e0f859cc1c2 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 31 Jul 2026 07:18:23 +1000 Subject: [PATCH 5/5] contracts-bedrock: Refresh OPContractsManagerUtils ABI snapshot systemConfigFor was added in ethereum-optimism/optimism#22034 but the ABI snapshot was not regenerated. No CI job diffs the ABI snapshots, so the drift went unnoticed on develop. --- .../abi/OPContractsManagerUtils.json | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json index a05a5009bc7..edbe40d081f 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUtils.json @@ -576,6 +576,30 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract ISystemConfig", + "name": "_default", + "type": "address" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + } + ], + "name": "systemConfigFor", + "outputs": [ + { + "internalType": "contract ISystemConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ {