diff --git a/deploy-config/local.json b/deploy-config/local.json index c38098f96..4d80f4919 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -31,6 +31,7 @@ "operatorFeeVaultWithdrawalNetwork": 0, "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "proofMaturityDelaySeconds": 604800, + "protocolVersionsInitialMinimumVersion": "0x1000000000000000000000000", "proxyAdminOwner": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "respectedGameType": 621, "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index 42e1bbcc8..6097a0e94 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -136,6 +136,7 @@ contract DeployConfig is Script { function _readProtocolVersionsInitialMinimumVersion(string memory _json) internal { uint256 minimumVersion = _json.readUintOr("$.protocolVersionsInitialMinimumVersion", 0); + require(minimumVersion != 0, "DeployConfig: initial minimum protocol version must be non-zero"); require(minimumVersion <= type(uint128).max, "DeployConfig: initial minimum protocol version exceeds uint128"); protocolVersionsInitialMinimumVersion = minimumVersion; } diff --git a/scripts/libraries/DeployUtils.sol b/scripts/libraries/DeployUtils.sol index 66eaa6a85..3eb15e803 100644 --- a/scripts/libraries/DeployUtils.sol +++ b/scripts/libraries/DeployUtils.sol @@ -106,7 +106,7 @@ library DeployUtils { internal view { - if (_minimumProtocolVersion > type(uint128).max) { + if (_minimumProtocolVersion == 0 || _minimumProtocolVersion > type(uint128).max) { revert IProtocolVersions.ProtocolVersions_InvalidProtocolVersion(); } @@ -117,9 +117,6 @@ library DeployUtils { uint64 previousTimestamp; for (uint256 id = 0; id < _schedule.length; id++) { uint64 timestamp = _schedule[id]; - if (timestamp != 0 && _minimumProtocolVersion == 0) { - revert IProtocolVersions.ProtocolVersions_InvalidProtocolVersion(); - } if (timestamp > currentTimestamp && timestamp < minimumFutureTimestamp) { revert IProtocolVersions.ProtocolVersions_InsufficientNotice(timestamp); } diff --git a/scripts/libraries/Types.sol b/scripts/libraries/Types.sol index 1f6f398b2..4f5d43e00 100644 --- a/scripts/libraries/Types.sol +++ b/scripts/libraries/Types.sol @@ -36,8 +36,7 @@ library Types { /// upgrade in the node's fork order, zero for unscheduled ones. Seeds /// `ProtocolVersions` at initialization, which is the only way to enter /// activations that are already in the past. - /// @custom:field initialMinimumProtocolVersion Packed semver required by non-zero timestamps in the initial - /// schedule. + /// @custom:field initialMinimumProtocolVersion Non-zero packed semver required by nodes. struct DeployInput { Roles roles; uint32 basefeeScalar; diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index be8f1e6af..35dec295b 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -56,7 +56,7 @@ Other relevant fields: | `multiproofGenesisOutputRoot` | Initial anchor output root | | `multiproofGenesisBlockNumber` | Initial anchor L2 block number | | `protocolVersionsInitialSchedule` | Hardfork activation timestamps in the node's fork order, `0` for unscheduled forks. Omit for a chain with no history; past activations cannot be added after deployment | -| `protocolVersionsInitialMinimumVersion` | Packed semver required by every non-zero initial schedule timestamp. Must be non-zero when the initial schedule contains an activation | +| `protocolVersionsInitialMinimumVersion` | Non-zero packed semver required by nodes | ### Step 2: Deploy contracts diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 3e3c23654..cf5bd40ef 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -16,8 +16,8 @@ "sourceCodeHash": "0x811596e7486cab9ceeeb61405b9ae510d93fcbbdfa11949201a367506c53194a" }, "src/L1/ProtocolVersions.sol:ProtocolVersions": { - "initCodeHash": "0x54c2a142728d60511fd15a7d6bb962b0f33d833ee727de16189fd18b6246b492", - "sourceCodeHash": "0x53c8ad6dc42e61b43b3ef2a91e154a02e719c595c8d1481b4b738faa7ebdf13d" + "initCodeHash": "0xbca4c0d69dcb7a196a9ebfcc8cb77162fa090377c5dda99831d61c6c68ba4352", + "sourceCodeHash": "0x411f2590b43bf6fe9d70f393c8e0349192a1aa895ec0fbe0d7695831d3b6cb94" }, "src/L1/SuperchainConfig.sol:SuperchainConfig": { "initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858", diff --git a/src/L1/ProtocolVersions.sol b/src/L1/ProtocolVersions.sol index 78b7cf450..607401ef9 100644 --- a/src/L1/ProtocolVersions.sol +++ b/src/L1/ProtocolVersions.sol @@ -128,16 +128,15 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable /// already has a hardfork history can be represented faithfully at deployment, while it is /// still impossible for any proof game to have pinned a commitment from this registry. /// Future activations must provide MIN_NOTICE, matching every post-initialization write path. - /// @dev Any non-zero imported timestamp requires a non-zero packed protocol version so nodes can - /// validate the schedule immediately after deployment. + /// @dev A non-zero packed protocol version is required so nodes can validate the registry + /// immediately after deployment. /// /// @param _incidentResponder Initial incidentResponder allowed to delay activations, or address(0) to leave unset. /// @param _initialSchedule Activation timestamps for already-known upgrades, ordered by ascending /// upgrade id, using 0 for an upgrade that is registered but unscheduled. /// Future timestamps must be at least MIN_NOTICE from block.timestamp. /// Pass an empty array for a chain with no upgrade history. - /// @param _initialMinimumProtocolVersion Packed semver required by an imported activation, or 0 when - /// the initial schedule has no non-zero timestamps. + /// @param _initialMinimumProtocolVersion Packed semver required by nodes. Must be non-zero and fit in 128 bits. function initialize( address _incidentResponder, uint64[] calldata _initialSchedule, @@ -148,7 +147,7 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable { // Initialization transactions must come from the ProxyAdmin or its owner. _assertOnlyProxyAdminOrProxyAdminOwner(); - if (_initialMinimumProtocolVersion > type(uint128).max) { + if (_initialMinimumProtocolVersion == 0 || _initialMinimumProtocolVersion > type(uint128).max) { revert ProtocolVersions_InvalidProtocolVersion(); } @@ -159,9 +158,6 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable for (uint256 id = 0; id < _initialSchedule.length; id++) { uint64 timestamp = _initialSchedule[id]; - if (timestamp != 0 && _initialMinimumProtocolVersion == 0) { - revert ProtocolVersions_InvalidProtocolVersion(); - } if (timestamp > uint64(block.timestamp) && timestamp < uint64(block.timestamp) + MIN_NOTICE) { revert ProtocolVersions_InsufficientNotice(timestamp); } @@ -177,9 +173,7 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable // pass. With an empty import this just re-emits the seed as the current commitment. _refreshScheduleId(0); - if (_initialMinimumProtocolVersion != 0) { - _writeMinimumProtocolVersion(_initialMinimumProtocolVersion); - } + _writeMinimumProtocolVersion(_initialMinimumProtocolVersion); incidentResponder = _incidentResponder; emit IncidentResponderUpdated(address(0), _incidentResponder); @@ -232,8 +226,9 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable /// @param protocolVersion Packed semver uint256 (must be non-zero and fit in 128 bits). function setMinimumProtocolVersion(uint256 protocolVersion) external { _assertOnlyProxyAdminOwner(); - if (protocolVersion == 0) revert ProtocolVersions_InvalidProtocolVersion(); - if (protocolVersion > type(uint128).max) revert ProtocolVersions_InvalidProtocolVersion(); + if (protocolVersion == 0 || protocolVersion > type(uint128).max) { + revert ProtocolVersions_InvalidProtocolVersion(); + } _writeMinimumProtocolVersion(protocolVersion); } diff --git a/test/L1/ProtocolVersions.t.sol b/test/L1/ProtocolVersions.t.sol index 19b1ddd86..21200cec3 100644 --- a/test/L1/ProtocolVersions.t.sol +++ b/test/L1/ProtocolVersions.t.sol @@ -82,7 +82,7 @@ contract ProtocolVersions_Initialize_Test is ProtocolVersions_TestInit { vm.expectEmit(true, true, false, false, address(uninitialized)); emit IncidentResponderUpdated(address(0), _incidentResponder); vm.prank(EIP1967Helper.getAdmin(address(uninitialized))); - uninitialized.initialize(_incidentResponder, new uint64[](0), 0); + uninitialized.initialize(_incidentResponder, new uint64[](0), 1); assertEq(uninitialized.incidentResponder(), _incidentResponder); } @@ -92,7 +92,7 @@ contract ProtocolVersions_Initialize_Test is ProtocolVersions_TestInit { IProtocolVersions uninitialized = _deployUninitializedProxy(); vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); vm.prank(_nonOwner); - uninitialized.initialize(_incidentResponder, new uint64[](0), 0); + uninitialized.initialize(_incidentResponder, new uint64[](0), 1); } /// @notice Tests that the initializer imports a preexisting schedule, building the same hash @@ -170,27 +170,12 @@ contract ProtocolVersions_Initialize_Test is ProtocolVersions_TestInit { imported.initialize(address(0), schedule, 42); } - /// @notice Tests that an imported activation cannot omit the minimum protocol version required by nodes. - function test_initialize_importWithoutMinimumProtocolVersion_reverts() external { - uint64[] memory schedule = new uint64[](1); - schedule[0] = 1; - + /// @notice Tests that the initial minimum protocol version is required even with an empty schedule. + function test_initialize_zeroMinimumProtocolVersion_reverts() external { IProtocolVersions imported = _deployUninitializedProxy(); vm.expectRevert(IProtocolVersions.ProtocolVersions_InvalidProtocolVersion.selector); vm.prank(EIP1967Helper.getAdmin(address(imported))); - imported.initialize(address(0), schedule, 0); - } - - /// @notice Tests that zero-only imports may leave the minimum protocol version unset. - function test_initialize_zeroOnlyScheduleWithoutMinimumProtocolVersion_succeeds() external { - uint64[] memory schedule = new uint64[](1); - - IProtocolVersions imported = _deployUninitializedProxy(); - vm.prank(EIP1967Helper.getAdmin(address(imported))); - imported.initialize(address(0), schedule, 0); - - assertEq(imported.getSchedule().length, 1); - assertEq(imported.minimumProtocolVersion(), 0); + imported.initialize(address(0), new uint64[](0), 0); } /// @notice Tests that the initial minimum protocol version must fit in the node's 128-bit packed semver layout. @@ -205,14 +190,14 @@ contract ProtocolVersions_Initialize_Test is ProtocolVersions_TestInit { function test_initialize_alreadyInitialized_reverts() external { vm.expectRevert("Initializable: contract is already initialized"); vm.prank(EIP1967Helper.getAdmin(address(protocolVersions))); - protocolVersions.initialize(address(0), new uint64[](0), 0); + protocolVersions.initialize(address(0), new uint64[](0), 1); } /// @notice Tests that the implementation itself cannot be initialized (initializers disabled). function test_initialize_implementationDisabled_reverts() external { IProtocolVersions impl = IProtocolVersions(EIP1967Helper.getImplementation(address(protocolVersions))); vm.expectRevert("Initializable: contract is already initialized"); - impl.initialize(address(0), new uint64[](0), 0); + impl.initialize(address(0), new uint64[](0), 1); } } diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 1f75627da..b0066d574 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -114,7 +114,7 @@ contract BaseTest is Test { ); factory.initialize(address(this)); delayedWETH.initialize(systemConfig); - protocolVersions.initialize(address(0), new uint64[](0), 0); + protocolVersions.initialize(address(0), new uint64[](0), 1); } /// @dev Rebuilds the schedule registry around a preset schedule and rebinds the verifier to it. diff --git a/test/deploy/DeployConfig.t.sol b/test/deploy/DeployConfig.t.sol index fdbdd4201..3cf73da57 100644 --- a/test/deploy/DeployConfig.t.sol +++ b/test/deploy/DeployConfig.t.sol @@ -63,10 +63,9 @@ contract DeployConfig_Test is Test { assertEq(cfg.protocolVersionsInitialMinimumVersion(), 42); } - function test_readMinimumVersion_omitted_defaultsToZero_succeeds() public { + function test_readMinimumVersion_omitted_reverts() public { + vm.expectRevert("DeployConfig: initial minimum protocol version must be non-zero"); cfg.readMinimumVersion('{"l1ChainId":1}'); - - assertEq(cfg.protocolVersionsInitialMinimumVersion(), 0); } function test_readMinimumVersion_aboveUint128_reverts() public { @@ -74,12 +73,13 @@ contract DeployConfig_Test is Test { cfg.readMinimumVersion('{"protocolVersionsInitialMinimumVersion":340282366920938463463374607431768211456}'); } - /// @notice The shipped configs describe chains without a recorded history, so they must keep the initial registry - /// state empty. - function test_read_localConfig_leavesProtocolVersionsStateEmpty_succeeds() public { + /// @notice The shipped configs describe chains without a recorded history, so the imported schedule stays empty. + /// The minimum protocol version is required even without recorded upgrade history. + function test_read_localConfig_leavesScheduleEmptyWithMinimumVersionSet_succeeds() public { cfg.read("deploy-config/local.json"); assertEq(cfg.protocolVersionsInitialSchedule().length, 0); - assertEq(cfg.protocolVersionsInitialMinimumVersion(), 0); + assertGt(cfg.protocolVersionsInitialMinimumVersion(), 0); + assertLe(cfg.protocolVersionsInitialMinimumVersion(), type(uint128).max); } } diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index 7f39e14b0..02d3e0f7f 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -187,12 +187,9 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { } /// @notice Pins ProtocolVersions input validation before superchain deployment can broadcast. - function test_deploy_initialScheduleWithoutMinimumProtocolVersion_reverts() public { - uint64[] memory schedule = new uint64[](1); - schedule[0] = 1; - + function test_deploy_zeroInitialMinimumProtocolVersion_reverts() public { SystemDeploy.DeployInput memory input = _defaultDeployInput(); - input.opChainInput.initialUpgradeSchedule = schedule; + input.opChainInput.initialMinimumProtocolVersion = 0; input.superchainInput.superchainProxyAdminOwner = address(0); vm.expectRevert(IProtocolVersions.ProtocolVersions_InvalidProtocolVersion.selector); @@ -485,7 +482,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { saltMixer: "system-deploy-test", gasLimit: 60_000_000, initialUpgradeSchedule: new uint64[](0), - initialMinimumProtocolVersion: 0 + initialMinimumProtocolVersion: 1 }); }