From f009180565491d0b76104a6f53345af5bb4197da Mon Sep 17 00:00:00 2001 From: Thanh Trinh Date: Thu, 3 Sep 2026 09:53:24 -0500 Subject: [PATCH 1/4] feat(L1): fork-gate AggregateVerifier proposal intervals for Denim Denim shortens the L2 block time from 2s to 200ms, which multiplies the proposal intervals by 10 (600 -> 6000 blocks, 30 -> 300 intermediate) for the same 20-minute range. Carry both value sets on one implementation and select between them per game so no contract swap is needed at the fork. Intervals are selected on the game's *starting* block relative to the Denim activation block, which is re-derived from the existing ProtocolVersions schedule. Selecting on the start block keeps the game chain contiguous and produces exactly one straddling game. Both interval pairs must yield the same intermediate root count, which the constructor enforces, so the CWIA extraData layout and INITIALIZE_CALLDATA_SIZE are unchanged across the fork. Co-Authored-By: Claude --- deploy-config/local.json | 2 + interfaces/L1/proofs/IAggregateVerifier.sol | 3 + scripts/deploy/DeployConfig.s.sol | 4 + scripts/deploy/SystemDeploy.s.sol | 27 +++- scripts/multiproof/DeployDevBase.s.sol | 6 +- scripts/multiproof/DeployDevNoNitro.s.sol | 17 +- scripts/multiproof/DeployDevWithNitro.s.sol | 17 +- snapshots/abi/AggregateVerifier.json | 99 +++++++++++- snapshots/semver-lock.json | 4 +- src/L1/proofs/AggregateVerifier.sol | 137 ++++++++++++---- test/L1/OptimismPortal2.t.sol | 8 +- test/L1/proofs/AggregateVerifier.t.sol | 171 ++++++++++++++++++-- test/L1/proofs/BaseTest.t.sol | 10 +- test/L1/proofs/DisputeGameFactory.t.sol | 10 +- test/deploy/SystemDeploy.t.sol | 5 + test/deploy/SystemDeployAssertions.sol | 8 + 16 files changed, 455 insertions(+), 73 deletions(-) diff --git a/deploy-config/local.json b/deploy-config/local.json index eec8c2b1f..1b540650c 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -19,6 +19,8 @@ "l2OutputOracleStartingTimestamp": 1, "multiproofBlockInterval": 100, "multiproofConfigHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "multiproofDenimBlockInterval": 1000, + "multiproofDenimIntermediateBlockInterval": 100, "multiproofGameType": 621, "multiproofGenesisBlockNumber": 0, "multiproofIntermediateBlockInterval": 10, diff --git a/interfaces/L1/proofs/IAggregateVerifier.sol b/interfaces/L1/proofs/IAggregateVerifier.sol index 9e3039953..3cfdee597 100644 --- a/interfaces/L1/proofs/IAggregateVerifier.sol +++ b/interfaces/L1/proofs/IAggregateVerifier.sol @@ -31,6 +31,9 @@ interface IAggregateVerifier is IDisputeGame { function L2_BLOCK_TIME() external view returns (uint64); function BLOCK_INTERVAL() external view returns (uint256); function INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); + function DENIM_BLOCK_INTERVAL() external view returns (uint256); + function DENIM_INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); + function intervalsForStartingBlock(uint256 startingBlock) external view returns (uint256, uint256); function startingOutputRoot() external view returns (Proposal memory); function bondRecipient() external view returns (address); diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index 975749864..e8ef1ae2f 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -52,6 +52,8 @@ contract DeployConfig is Script { uint256 public l2OutputOracleStartingBlockNumber; uint256 public l2OutputOracleStartingTimestamp; uint256 public multiproofBlockInterval; + uint256 public multiproofDenimBlockInterval; + uint256 public multiproofDenimIntermediateBlockInterval; uint256 public multiproofGameType; uint256 public multiproofGenesisBlockNumber; uint256 public multiproofIntermediateBlockInterval; @@ -116,6 +118,8 @@ contract DeployConfig is Script { l2GenesisBlockNumber = _json.readUintOr("$.l2GenesisBlockNumber", 0); l2GenesisTimestamp = _json.readUintOr("$.l2GenesisTimestamp", 0); multiproofBlockInterval = _json.readUintOr("$.multiproofBlockInterval", 100); + multiproofDenimBlockInterval = _json.readUintOr("$.multiproofDenimBlockInterval", 1000); + multiproofDenimIntermediateBlockInterval = _json.readUintOr("$.multiproofDenimIntermediateBlockInterval", 100); multiproofGameType = _json.readUintOr("$.multiproofGameType", 621); multiproofGenesisBlockNumber = _json.readUintOr("$.multiproofGenesisBlockNumber", 0); multiproofIntermediateBlockInterval = _json.readUintOr("$.multiproofIntermediateBlockInterval", 10); diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index b196fc42f..32c6d5f3f 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -81,6 +81,8 @@ contract SystemDeploy is Script { AggregateVerifier.ScheduleConfig scheduleConfig; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; + uint256 multiproofDenimBlockInterval; + uint256 multiproofDenimIntermediateBlockInterval; ISP1Verifier sp1Verifier; address teeProposer; address teeChallenger; @@ -130,6 +132,8 @@ contract SystemDeploy is Script { AggregateVerifier.ScheduleConfig scheduleConfig; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; + uint256 multiproofDenimBlockInterval; + uint256 multiproofDenimIntermediateBlockInterval; } struct MultiproofOutput { @@ -269,6 +273,8 @@ contract SystemDeploy is Script { scheduleConfig: _configuredScheduleConfig(), multiproofBlockInterval: cfg.multiproofBlockInterval(), multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(), + multiproofDenimBlockInterval: cfg.multiproofDenimBlockInterval(), + multiproofDenimIntermediateBlockInterval: cfg.multiproofDenimIntermediateBlockInterval(), sp1Verifier: ISP1Verifier(cfg.sp1Verifier()), teeProposer: cfg.teeProposer(), teeChallenger: cfg.teeChallenger(), @@ -1056,7 +1062,9 @@ contract SystemDeploy is Script { l2ChainId: _opChainInput.l2ChainId, scheduleConfig: scheduleConfig, multiproofBlockInterval: _input.multiproofBlockInterval, - multiproofIntermediateBlockInterval: _input.multiproofIntermediateBlockInterval + multiproofIntermediateBlockInterval: _input.multiproofIntermediateBlockInterval, + multiproofDenimBlockInterval: _input.multiproofDenimBlockInterval, + multiproofDenimIntermediateBlockInterval: _input.multiproofDenimIntermediateBlockInterval }) ); @@ -1084,8 +1092,12 @@ contract SystemDeploy is Script { AggregateVerifier.ZkHashes(_input.zkRangeHash, _input.zkAggregationHash), _input.multiproofConfigHash, _input.l2ChainId, - _input.multiproofBlockInterval, - _input.multiproofIntermediateBlockInterval, + AggregateVerifier.IntervalConfig({ + blockInterval: _input.multiproofBlockInterval, + intermediateBlockInterval: _input.multiproofIntermediateBlockInterval, + denimBlockInterval: _input.multiproofDenimBlockInterval, + denimIntermediateBlockInterval: _input.multiproofDenimIntermediateBlockInterval + }), _input.scheduleConfig ) ) @@ -1136,6 +1148,15 @@ contract SystemDeploy is Script { _input.multiproofBlockInterval % _input.multiproofIntermediateBlockInterval == 0, "SystemDeploy: invalid multiproof block intervals" ); + require(_input.multiproofDenimBlockInterval != 0, "SystemDeploy: multiproof Denim block interval not set"); + require( + _input.multiproofDenimIntermediateBlockInterval != 0, + "SystemDeploy: multiproof Denim intermediate interval not set" + ); + require( + _input.multiproofDenimBlockInterval % _input.multiproofDenimIntermediateBlockInterval == 0, + "SystemDeploy: invalid multiproof Denim block intervals" + ); require(_input.teeProposer != address(0), "SystemDeploy: teeProposer not set"); require(_input.teeChallenger != address(0), "SystemDeploy: teeChallenger not set"); } diff --git a/scripts/multiproof/DeployDevBase.s.sol b/scripts/multiproof/DeployDevBase.s.sol index 5e2f89208..7b097f3c3 100644 --- a/scripts/multiproof/DeployDevBase.s.sol +++ b/scripts/multiproof/DeployDevBase.s.sol @@ -128,8 +128,7 @@ abstract contract DeployDevBase is Script { zkHashes, cfg.multiproofConfigHash(), cfg.l2ChainId(), - _blockInterval(), - _intermediateBlockInterval(), + _intervalConfig(), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersionsProxy)), genesisBlockNumber: cfg.l2GenesisBlockNumber(), @@ -159,8 +158,7 @@ abstract contract DeployDevBase is Script { console.log("Deployment saved to:", outPath); } - function _blockInterval() internal pure virtual returns (uint256); - function _intermediateBlockInterval() internal pure virtual returns (uint256); + function _intervalConfig() internal pure virtual returns (AggregateVerifier.IntervalConfig memory); function _initBond() internal pure virtual returns (uint256); function _outputSuffix() internal pure virtual returns (string memory); function _deployTEERegistryImpl() internal virtual returns (address); diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 3eb209552..557a34194 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -8,6 +8,8 @@ import { INitroValidator } from "interfaces/L1/proofs/tee/INitroValidator.sol"; import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { MockNitroValidator } from "test/mocks/MockNitroValidator.sol"; +import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; + import { DeployDevBase } from "./DeployDevBase.s.sol"; /// @title DeployDevNoNitro @@ -16,14 +18,17 @@ import { DeployDevBase } from "./DeployDevBase.s.sol"; contract DeployDevNoNitro is DeployDevBase { uint256 public constant BLOCK_INTERVAL = 100; uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 public constant DENIM_BLOCK_INTERVAL = 1000; + uint256 public constant DENIM_INTERMEDIATE_BLOCK_INTERVAL = 100; uint256 public constant INIT_BOND = 0.001 ether; - function _blockInterval() internal pure override returns (uint256) { - return BLOCK_INTERVAL; - } - - function _intermediateBlockInterval() internal pure override returns (uint256) { - return INTERMEDIATE_BLOCK_INTERVAL; + function _intervalConfig() internal pure override returns (AggregateVerifier.IntervalConfig memory) { + return AggregateVerifier.IntervalConfig({ + blockInterval: BLOCK_INTERVAL, + intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + }); } function _initBond() internal pure override returns (uint256) { diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 1ef4fe01f..a16619b2d 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -7,6 +7,8 @@ import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.so import { INitroValidator } from "interfaces/L1/proofs/tee/INitroValidator.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; +import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; + import { DeployDevBase } from "./DeployDevBase.s.sol"; /// @title DeployDevWithNitro @@ -19,16 +21,19 @@ import { DeployDevBase } from "./DeployDevBase.s.sol"; contract DeployDevWithNitro is DeployDevBase { uint256 public constant BLOCK_INTERVAL = 600; uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30; + uint256 public constant DENIM_BLOCK_INTERVAL = 6000; + uint256 public constant DENIM_INTERMEDIATE_BLOCK_INTERVAL = 300; uint256 public constant INIT_BOND = 0.00001 ether; address public nitroValidatorAddr; - function _blockInterval() internal pure override returns (uint256) { - return BLOCK_INTERVAL; - } - - function _intermediateBlockInterval() internal pure override returns (uint256) { - return INTERMEDIATE_BLOCK_INTERVAL; + function _intervalConfig() internal pure override returns (AggregateVerifier.IntervalConfig memory) { + return AggregateVerifier.IntervalConfig({ + blockInterval: BLOCK_INTERVAL, + intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + }); } function _initBond() internal pure override returns (uint256) { diff --git a/snapshots/abi/AggregateVerifier.json b/snapshots/abi/AggregateVerifier.json index ee3b801a1..2697e7dab 100644 --- a/snapshots/abi/AggregateVerifier.json +++ b/snapshots/abi/AggregateVerifier.json @@ -59,14 +59,31 @@ "type": "uint256" }, { - "internalType": "uint256", - "name": "blockInterval", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "intermediateBlockInterval", - "type": "uint256" + "components": [ + { + "internalType": "uint256", + "name": "blockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "intermediateBlockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denimBlockInterval", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denimIntermediateBlockInterval", + "type": "uint256" + } + ], + "internalType": "struct AggregateVerifier.IntervalConfig", + "name": "intervalConfig", + "type": "tuple" }, { "components": [ @@ -151,6 +168,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "DENIM_BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DENIM_INTERMEDIATE_BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "DISPUTE_GAME_FACTORY", @@ -646,6 +689,30 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "startingBlock", + "type": "uint256" + } + ], + "name": "intervalsForStartingBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "l1Head", @@ -1204,6 +1271,22 @@ "name": "L2TimestampOverflow", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "preDenimCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denimCount", + "type": "uint256" + } + ], + "name": "MismatchedIntermediateRootCount", + "type": "error" + }, { "inputs": [ { diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 3d8078e45..37e3ecfc3 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -28,8 +28,8 @@ "sourceCodeHash": "0x780ff372493ba9010bc0d13100ac896f2bf75730a9b17d4bb63aaf694dc3c634" }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0xc14998e269a6ad7e5c2b5d5beee0eabb68a68c3758e666a76822905c4f16575f", - "sourceCodeHash": "0x04921678f197a1dbe20b04696822c02c22e528112900616aeeb78fd9649b28ef" + "initCodeHash": "0x9177562fa3918aad9ed85ce6b6f72e684551d52bb3b9f882ddbc36cd47b296a8", + "sourceCodeHash": "0x1c7bc9b357142d2e2484928df91332427b8c528c12b40d5d241117c906901ffc" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", diff --git a/src/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index 7ef9d4c28..fc8e17a02 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -53,6 +53,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { uint64 blockTime; } + /// @notice Proposal block intervals for each side of the Denim activation. + /// @dev Both pairs must yield the same intermediate root count, so the CWIA `extraData` layout + /// and `INITIALIZE_CALLDATA_SIZE` are identical on both sides of the fork. + struct IntervalConfig { + uint256 blockInterval; + uint256 intermediateBlockInterval; + uint256 denimBlockInterval; + uint256 denimIntermediateBlockInterval; + } + //////////////////////////////////////////////////////////////// // Constants // //////////////////////////////////////////////////////////////// @@ -123,14 +133,22 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The legacy number of seconds between consecutive L2 blocks. uint64 public immutable L2_BLOCK_TIME; - /// @notice The block interval between each proposal. + /// @notice The block interval between each proposal, for games starting before Denim. /// @dev The parent's block number + BLOCK_INTERVAL = this proposal's block number. uint256 public immutable BLOCK_INTERVAL; - /// @notice The block interval for intermediate proposals. + /// @notice The block interval for intermediate proposals, for games starting before Denim. /// @dev BLOCK_INTERVAL must be divisible by INTERMEDIATE_BLOCK_INTERVAL. uint256 public immutable INTERMEDIATE_BLOCK_INTERVAL; + /// @notice The block interval between each proposal, for games starting at or after Denim. + uint256 public immutable DENIM_BLOCK_INTERVAL; + + /// @notice The block interval for intermediate proposals, for games starting at or after Denim. + /// @dev DENIM_BLOCK_INTERVAL must be divisible by DENIM_INTERMEDIATE_BLOCK_INTERVAL, and their + /// ratio must equal the pre-Denim one. + uint256 public immutable DENIM_INTERMEDIATE_BLOCK_INTERVAL; + /// @notice The size of the initialize call data. uint256 internal immutable INITIALIZE_CALLDATA_SIZE; @@ -230,6 +248,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice When the block interval or intermediate block interval is invalid. error InvalidBlockInterval(uint256 blockInterval, uint256 intermediateBlockInterval); + /// @notice When the pre-Denim and Denim intervals do not yield the same intermediate root count. + error MismatchedIntermediateRootCount(uint256 preDenimCount, uint256 denimCount); + /// @notice When the block number is unexpected. error UnexpectedBlockNumber(uint256 expectedBlockNumber, uint256 actualBlockNumber); @@ -302,8 +323,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @param zkHashes The hashes of the ZK range and aggregate programs. /// @param configHash The hash of the rollup configuration. /// @param l2ChainId The chain ID of the L2 network. - /// @param blockInterval The block interval. - /// @param intermediateBlockInterval The intermediate block interval. + /// @param intervalConfig The pre-Denim and Denim proposal block intervals. /// @param scheduleConfig Upgrade registry and deterministic L2 timestamp configuration. constructor( GameType gameType_, @@ -315,14 +335,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { ZkHashes memory zkHashes, bytes32 configHash, uint256 l2ChainId, - uint256 blockInterval, - uint256 intermediateBlockInterval, + IntervalConfig memory intervalConfig, ScheduleConfig memory scheduleConfig ) { - // Block interval and intermediate block interval must be positive and divisible. - if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) { - revert InvalidBlockInterval(blockInterval, intermediateBlockInterval); - } + _validateIntervals(intervalConfig); if (scheduleConfig.blockTime == 0) revert InvalidL2BlockTime(); // Set up initial game state. @@ -340,8 +356,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { L2_GENESIS_BLOCK_NUMBER = scheduleConfig.genesisBlockNumber; L2_GENESIS_TIMESTAMP = scheduleConfig.genesisTimestamp; L2_BLOCK_TIME = scheduleConfig.blockTime; - BLOCK_INTERVAL = blockInterval; - INTERMEDIATE_BLOCK_INTERVAL = intermediateBlockInterval; + BLOCK_INTERVAL = intervalConfig.blockInterval; + INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.intermediateBlockInterval; + DENIM_BLOCK_INTERVAL = intervalConfig.denimBlockInterval; + DENIM_INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.denimIntermediateBlockInterval; PROTOCOL_VERSIONS = scheduleConfig.protocolVersions; INITIALIZE_CALLDATA_SIZE = 0x8E + 0x20 * intermediateOutputRootsCount(); @@ -409,9 +427,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { startingOutputRoot = ANCHOR_STATE_REGISTRY.getStartingAnchorRoot(); } - // The block number must be BLOCK_INTERVAL blocks after the starting block number. - if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL) { - revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + BLOCK_INTERVAL, l2SequenceNumber()); + // The block number must be one block interval after the starting block number. The interval + // is selected on the starting block so the game chain stays contiguous across Denim. + (uint256 blockInterval,) = _intervals(startingOutputRoot.l2SequenceNumber); + if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + blockInterval) { + revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + blockInterval, l2SequenceNumber()); } // Set the game as initialized. @@ -767,8 +787,20 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return expectedResolution.raw() <= block.timestamp; } + /// @notice Returns the proposal intervals that govern a game starting at `startingBlock`. + /// @dev Offchain proposers and challengers must resolve intervals per game through this getter + /// rather than reading `BLOCK_INTERVAL` / `INTERMEDIATE_BLOCK_INTERVAL`, which describe + /// only games starting before Denim. + /// @param startingBlock The starting L2 block number of the game. + /// @return The block interval and the intermediate block interval governing that game. + function intervalsForStartingBlock(uint256 startingBlock) public view returns (uint256, uint256) { + return _intervals(startingBlock); + } + /// @notice The number of intermediate output roots. /// @dev At least one as the proposal's root claim is considered an intermediate root. + /// The constructor requires both interval pairs to yield the same count, so this is constant + /// across the Denim activation and the CWIA `extraData` layout never changes. function intermediateOutputRootsCount() public view returns (uint256) { return (BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL); } @@ -1101,39 +1133,86 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); + (, uint256 intermediateBlockInterval) = _intervals(startingOutputRoot.l2SequenceNumber); uint64 startingL2SequenceNumber = - uint64(startingOutputRoot.l2SequenceNumber + intermediateRootIndex * INTERMEDIATE_BLOCK_INTERVAL); - uint64 endingL2SequenceNumber = startingL2SequenceNumber + uint64(INTERMEDIATE_BLOCK_INTERVAL); + uint64(startingOutputRoot.l2SequenceNumber + intermediateRootIndex * intermediateBlockInterval); + uint64 endingL2SequenceNumber = startingL2SequenceNumber + uint64(intermediateBlockInterval); return (startingRoot, startingL2SequenceNumber, endingL2SequenceNumber); } /// @notice Semantic version. - /// @custom:semver 0.1.0 + /// @custom:semver 0.2.0 function version() public pure virtual returns (string memory) { - return "0.1.0"; + return "0.2.0"; } /// @notice Derives an L2 block timestamp using the legacy cadence before Denim and whole-second groups after it. function _l2Timestamp(uint256 claimBlock) private view returns (uint64) { + uint256 denimBlock = _denimActivationBlock(); + if (claimBlock < denimBlock) return _legacyL2Timestamp(claimBlock); + + uint256 claimTimestamp = + uint256(_legacyL2Timestamp(denimBlock)) + (claimBlock - denimBlock) / DENIM_BLOCKS_PER_SECOND; + if (claimTimestamp > type(uint64).max) revert L2TimestampOverflow(claimBlock); + return uint64(claimTimestamp); + } + + /// @notice Reverts unless both interval pairs are positive, divisible, and yield the same + /// intermediate root count. + function _validateIntervals(IntervalConfig memory intervalConfig) private pure { + uint256 blockInterval = intervalConfig.blockInterval; + uint256 intermediateBlockInterval = intervalConfig.intermediateBlockInterval; + uint256 denimBlockInterval = intervalConfig.denimBlockInterval; + uint256 denimIntermediateBlockInterval = intervalConfig.denimIntermediateBlockInterval; + + if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) { + revert InvalidBlockInterval(blockInterval, intermediateBlockInterval); + } + if ( + denimBlockInterval == 0 || denimIntermediateBlockInterval == 0 + || denimBlockInterval % denimIntermediateBlockInterval != 0 + ) { + revert InvalidBlockInterval(denimBlockInterval, denimIntermediateBlockInterval); + } + + // The intermediate root count fixes the CWIA `extraData` layout, which is frozen for the + // lifetime of this implementation. Both interval pairs must therefore agree on it. + uint256 preDenimCount = blockInterval / intermediateBlockInterval; + uint256 denimCount = denimBlockInterval / denimIntermediateBlockInterval; + if (preDenimCount != denimCount) revert MismatchedIntermediateRootCount(preDenimCount, denimCount); + } + + /// @notice Returns the first L2 block number governed by Denim, or `type(uint256).max` when Denim + /// is not scheduled. + /// @dev Reading the live schedule is safe despite it being mutable. `initializeWithInitData` + /// rejects a game whose ending L2 timestamp has not yet been reached on L1, and + /// `ProtocolVersions` requires any new activation to be at least `MIN_NOTICE` in the future + /// and freezes one that has passed. A Denim timestamp that can still move is therefore + /// always later than the ending timestamp of every initialized game, so no game can have + /// its intervals reselected after creation. + function _denimActivationBlock() private view returns (uint256) { uint64[] memory schedule = PROTOCOL_VERSIONS.getSchedule(); - if (schedule.length <= DENIM_UPGRADE_INDEX) return _legacyL2Timestamp(claimBlock); + if (schedule.length <= DENIM_UPGRADE_INDEX) return type(uint256).max; uint64 denimActivationTimestamp = schedule[DENIM_UPGRADE_INDEX]; - if (denimActivationTimestamp == 0) return _legacyL2Timestamp(claimBlock); + if (denimActivationTimestamp == 0) return type(uint256).max; uint256 blocksUntilDenim; if (denimActivationTimestamp > L2_GENESIS_TIMESTAMP) { blocksUntilDenim = FixedPointMathLib.divUp(denimActivationTimestamp - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME); } + return L2_GENESIS_BLOCK_NUMBER + blocksUntilDenim; + } - uint256 blocksSinceGenesis = claimBlock - L2_GENESIS_BLOCK_NUMBER; - if (blocksSinceGenesis < blocksUntilDenim) return _legacyL2Timestamp(claimBlock); - - uint256 denimBlock = L2_GENESIS_BLOCK_NUMBER + blocksUntilDenim; - uint256 claimTimestamp = - uint256(_legacyL2Timestamp(denimBlock)) + (claimBlock - denimBlock) / DENIM_BLOCKS_PER_SECOND; - if (claimTimestamp > type(uint64).max) revert L2TimestampOverflow(claimBlock); - return uint64(claimTimestamp); + /// @notice Selects the proposal intervals governing a game, from its starting block number. + /// @dev Selection is on the starting block rather than the ending block so the game chain stays + /// contiguous across the activation: every game satisfies + /// `end == parent.end + blockInterval` for the interval its own start selects. Exactly one + /// game straddles the activation and is proven under the pre-Denim interval, which is + /// correct because provers apply fork rules per block by timestamp. + function _intervals(uint256 startingBlock) private view returns (uint256, uint256) { + if (startingBlock < _denimActivationBlock()) return (BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + return (DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); } /// @notice Derives an L2 block timestamp using the pre-Denim block cadence. diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index afa597ae1..801e99daf 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -89,8 +89,12 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), bytes32(uint256(4)), deploy.cfg().l2ChainId(), - 100, - 10, + AggregateVerifier.IntervalConfig({ + blockInterval: 100, + intermediateBlockInterval: 10, + denimBlockInterval: 1000, + denimIntermediateBlockInterval: 100 + }), AggregateVerifier.ScheduleConfig({ protocolVersions: protocolVersions, genesisBlockNumber: 0, genesisTimestamp: 1, blockTime: 2 }) diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index eba05dc11..a83151e0f 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -155,6 +155,85 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.scheduleId(), protocolVersions.scheduleId(DENIM_UPGRADE_INDEX - 1)); } + /// @notice Intervals are selected on the game's starting block relative to the Denim activation + /// block, so the chain of games stays contiguous across the fork. + function test_intervalsForStartingBlock_selectsOnDenimActivationBlock_succeeds() public { + // divUp(86500 - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME) == 50. + _importDenimSchedule(L2_GENESIS_TIMESTAMP + 100); + uint256 denimActivationBlock = 50; + + _assertIntervals(denimActivationBlock - 1, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(denimActivationBlock, DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(denimActivationBlock + 1, DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + } + + function test_intervalsForStartingBlock_denimUnscheduled_succeeds() public view { + _assertIntervals(0, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(type(uint64).max, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + } + + /// @notice A game starting at or after the Denim activation block must span DENIM_BLOCK_INTERVAL. + function test_initialize_denimIntervals_succeeds() public { + // The activation timestamp equals genesis, so every block including the anchor is Denim. + _importDenimSchedule(L2_GENESIS_TIMESTAMP); + _assertIntervals(0, DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + + vm.expectRevert( + abi.encodeWithSelector( + AggregateVerifier.UnexpectedBlockNumber.selector, DENIM_BLOCK_INTERVAL, BLOCK_INTERVAL + ) + ); + _createGameEndingAt(BLOCK_INTERVAL); + + AggregateVerifier game = _createGameEndingAt(DENIM_BLOCK_INTERVAL); + assertEq(game.l2SequenceNumber(), DENIM_BLOCK_INTERVAL); + } + + /// @notice The one game whose range contains the activation block starts before it, so it is + /// proven under the pre-Denim interval. + function test_initialize_straddlingGame_usesPreDenimInterval_succeeds() public { + // Activation block 50 falls inside the first game's [0, 100) range. + _importDenimSchedule(L2_GENESIS_TIMESTAMP + 100); + _assertIntervals(0, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + + vm.expectRevert( + abi.encodeWithSelector( + AggregateVerifier.UnexpectedBlockNumber.selector, BLOCK_INTERVAL, DENIM_BLOCK_INTERVAL + ) + ); + _createGameEndingAt(DENIM_BLOCK_INTERVAL); + + AggregateVerifier game = _createGameEndingAt(BLOCK_INTERVAL); + assertEq(game.l2SequenceNumber(), BLOCK_INTERVAL); + } + + function test_constructor_mismatchedIntermediateRootCount_reverts() public { + // 100 / 10 == 10 intermediate roots, but 1000 / 200 == 5. + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MismatchedIntermediateRootCount.selector, 10, 5)); + _deployAggregateVerifier( + AggregateVerifier.IntervalConfig({ + blockInterval: BLOCK_INTERVAL, + intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: 200 + }), + _defaultScheduleConfig() + ); + } + + function test_constructor_invalidDenimBlockIntervals_reverts() public { + vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 1000, 0)); + _deployAggregateVerifier( + AggregateVerifier.IntervalConfig({ + blockInterval: BLOCK_INTERVAL, + intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: 0 + }), + _defaultScheduleConfig() + ); + } + /// @notice A claim whose L2 timestamp L1 has not yet reached cannot open a game, so a game can /// never pin an activation that the owner is still able to clear or delay. function test_initialize_l2TimestampInFuture_reverts() public { @@ -524,8 +603,9 @@ contract AggregateVerifierTest is BaseTest { function _setSingleBlockAggregateVerifier(uint64 genesisTimestamp) private { AggregateVerifier implementation = _deployAggregateVerifier( - 1, - 1, + AggregateVerifier.IntervalConfig({ + blockInterval: 1, intermediateBlockInterval: 1, denimBlockInterval: 1, denimIntermediateBlockInterval: 1 + }), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER, @@ -676,8 +756,12 @@ contract AggregateVerifierTest is BaseTest { returns (AggregateVerifier) { return _deployAggregateVerifier( - blockInterval, - intermediateBlockInterval, + AggregateVerifier.IntervalConfig({ + blockInterval: blockInterval, + intermediateBlockInterval: intermediateBlockInterval, + denimBlockInterval: blockInterval, + denimIntermediateBlockInterval: intermediateBlockInterval + }), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER, @@ -691,12 +775,82 @@ contract AggregateVerifierTest is BaseTest { private returns (AggregateVerifier) { - return _deployAggregateVerifier(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL, scheduleConfig); + return _deployAggregateVerifier(_defaultIntervalConfig(), scheduleConfig); + } + + /// @dev Registers a schedule whose only meaningful entry is the Denim activation, and rebinds + /// the implementation to it. + function _importDenimSchedule(uint64 denimActivationTimestamp) private { + uint64[] memory schedule = new uint64[](DENIM_UPGRADE_INDEX + 1); + for (uint256 i; i < DENIM_UPGRADE_INDEX; i++) { + schedule[i] = L2_GENESIS_TIMESTAMP; + } + schedule[DENIM_UPGRADE_INDEX] = denimActivationTimestamp; + _importProtocolVersionsSchedule(schedule); + aggregateVerifierImpl = AggregateVerifier(address(factory.gameImpls(GameTypes.AGGREGATE_VERIFIER))); + } + + function _assertIntervals( + uint256 startingBlock, + uint256 expectedBlockInterval, + uint256 expectedIntermediateBlockInterval + ) + private + view + { + (uint256 blockInterval, uint256 intermediateBlockInterval) = + aggregateVerifierImpl.intervalsForStartingBlock(startingBlock); + assertEq(blockInterval, expectedBlockInterval); + assertEq(intermediateBlockInterval, expectedIntermediateBlockInterval); + } + + /// @dev Creates a game off the anchor root. Intermediate root values are unchecked by the mock + /// verifiers, so only their count has to match the implementation. + function _createGameEndingAt(uint256 endingBlock) private returns (AggregateVerifier) { + Claim rootClaim = Claim.wrap(keccak256(abi.encode(endingBlock))); + uint256 count = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; + bytes32[] memory intermediateRoots = new bytes32[](count); + for (uint256 i; i < count - 1; i++) { + intermediateRoots[i] = keccak256(abi.encode(endingBlock, i)); + } + intermediateRoots[count - 1] = rootClaim.raw(); + + bytes memory extraData = + abi.encodePacked(endingBlock, address(anchorStateRegistry), abi.encodePacked(intermediateRoots)); + bytes memory proof = _generateProof(abi.encode(endingBlock), AggregateVerifier.ProofType.TEE); + + _warpToL2Timestamp(endingBlock); + vm.deal(TEE_PROVER, INIT_BOND); + vm.prank(TEE_PROVER); + return AggregateVerifier( + address( + factory.createWithInitData{ value: INIT_BOND }( + GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData, proof + ) + ) + ); + } + + function _defaultScheduleConfig() private view returns (AggregateVerifier.ScheduleConfig memory) { + return AggregateVerifier.ScheduleConfig({ + protocolVersions: IProtocolVersions(address(protocolVersions)), + genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER, + genesisTimestamp: L2_GENESIS_TIMESTAMP, + blockTime: L2_BLOCK_TIME + }); + } + + function _defaultIntervalConfig() private pure returns (AggregateVerifier.IntervalConfig memory) { + return AggregateVerifier.IntervalConfig({ + blockInterval: BLOCK_INTERVAL, + intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + }); } function _deployAggregateVerifier( - uint256 blockInterval, - uint256 intermediateBlockInterval, + AggregateVerifier.IntervalConfig memory intervalConfig, AggregateVerifier.ScheduleConfig memory scheduleConfig ) private @@ -712,8 +866,7 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, - blockInterval, - intermediateBlockInterval, + intervalConfig, scheduleConfig ); } diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 123af8425..afb18b533 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -33,6 +33,8 @@ contract BaseTest is Test { // AggregateVerifier expects evenly spaced intermediate roots. uint256 internal constant BLOCK_INTERVAL = 100; uint256 internal constant INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 internal constant DENIM_BLOCK_INTERVAL = 1000; + uint256 internal constant DENIM_INTERMEDIATE_BLOCK_INTERVAL = 100; uint256 private constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; uint256 internal constant INIT_BOND = 1 ether; @@ -136,8 +138,12 @@ contract BaseTest is Test { AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, - BLOCK_INTERVAL, - INTERMEDIATE_BLOCK_INTERVAL, + AggregateVerifier.IntervalConfig({ + blockInterval: BLOCK_INTERVAL, + intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + }), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER, diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index 367ecd2a7..e776a80a4 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -47,6 +47,8 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { uint256 internal constant L2_CHAIN_ID = 111; uint256 internal constant AGGREGATE_BLOCK_INTERVAL = 100; uint256 internal constant AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 internal constant AGGREGATE_DENIM_BLOCK_INTERVAL = 1000; + uint256 internal constant AGGREGATE_DENIM_INTERMEDIATE_BLOCK_INTERVAL = 100; uint32 internal constant MAX_GAME_TYPE = 8; address internal constant NON_OWNER = address(0xBEEF); @@ -229,8 +231,12 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), bytes32(uint256(4)), L2_CHAIN_ID, - AGGREGATE_BLOCK_INTERVAL, - AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL, + AggregateVerifier.IntervalConfig({ + blockInterval: AGGREGATE_BLOCK_INTERVAL, + intermediateBlockInterval: AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL, + denimBlockInterval: AGGREGATE_DENIM_BLOCK_INTERVAL, + denimIntermediateBlockInterval: AGGREGATE_DENIM_INTERMEDIATE_BLOCK_INTERVAL + }), AggregateVerifier.ScheduleConfig({ protocolVersions: protocolVersions, genesisBlockNumber: 0, genesisTimestamp: 0, blockTime: 2 }) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index 832813952..edd70d7e6 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -462,6 +462,8 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { }), multiproofBlockInterval: 100, multiproofIntermediateBlockInterval: 10, + multiproofDenimBlockInterval: 1000, + multiproofDenimIntermediateBlockInterval: 100, sp1Verifier: ISP1Verifier(address(sp1Verifier)), teeProposer: proposer, teeChallenger: challenger, @@ -573,6 +575,9 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { l2BlockTime: _input.implementationsInput.scheduleConfig.blockTime, multiproofBlockInterval: _input.implementationsInput.multiproofBlockInterval, multiproofIntermediateBlockInterval: _input.implementationsInput.multiproofIntermediateBlockInterval, + multiproofDenimBlockInterval: _input.implementationsInput.multiproofDenimBlockInterval, + multiproofDenimIntermediateBlockInterval: _input.implementationsInput + .multiproofDenimIntermediateBlockInterval, withdrawalDelaySeconds: _input.implementationsInput.withdrawalDelaySeconds }); } diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index 2d8d3130b..4dbf4a85e 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -46,6 +46,8 @@ abstract contract SystemDeployAssertions is Test { uint64 l2BlockTime; uint256 multiproofBlockInterval; uint256 multiproofIntermediateBlockInterval; + uint256 multiproofDenimBlockInterval; + uint256 multiproofDenimIntermediateBlockInterval; uint256 withdrawalDelaySeconds; } @@ -261,6 +263,12 @@ abstract contract SystemDeployAssertions is Test { assertEq( _aggregateVerifier.INTERMEDIATE_BLOCK_INTERVAL(), _expected.multiproofIntermediateBlockInterval, "AV-150" ); + assertEq(_aggregateVerifier.DENIM_BLOCK_INTERVAL(), _expected.multiproofDenimBlockInterval, "AV-160"); + assertEq( + _aggregateVerifier.DENIM_INTERMEDIATE_BLOCK_INTERVAL(), + _expected.multiproofDenimIntermediateBlockInterval, + "AV-170" + ); } function _assertDelayedWETH( From b53b9431ff65dd828c443eaa127c950b85f87f5c Mon Sep 17 00:00:00 2001 From: Thanh Trinh Date: Thu, 3 Sep 2026 15:36:03 -0500 Subject: [PATCH 2/4] fix(L1): cover challenge under Denim intervals, resolve seeding intervals live Review follow-ups on the fork-gated proposal intervals. - Test `challenge` under both sides of the activation. `_intervals` drives two fork-sensitive call sites and only the `initializeWithInitData` one was covered. The other feeds the journal the prover signs, so a stale interval there makes a valid challenge unconstructable instead of reverting. - Merge `_intervals` into `intervalsForStartingBlock` and resolve `_denimActivationBlock()` once in `initializeWithInitData`, threading it into both the interval selection and `_l2Timestamp` instead of reading `PROTOCOL_VERSIONS.getSchedule()` twice. - Document the deliberate start-block vs ending-block asymmetry between the interval selection and the `scheduleId` pin, and extend the `_denimActivationBlock` safety argument to cover the owner moving the activation earlier, not just delaying it. - Drop the `SystemDeploy` Denim interval requires. The constructor already reverts on zero, non-divisible, and mismatched-ratio pairs; restating a subset read as full validation while skipping the ratio check. - `SeedGames.s.sol` reads the block interval and intermediate root count off the deployed implementation rather than hardcoding 600/30, which would have seeded unopenable games on a devnet with Denim active. `generate-roots.sh` cannot see the chain, so its intervals are env-overridable. - `OptimismPortal2.t.sol` moves off `BLOCK_INTERVAL()`. Generated with Claude Code Co-Authored-By: Claude --- scripts/deploy/SystemDeploy.s.sol | 9 ---- scripts/multiproof/README.md | 4 +- scripts/multiproof/SeedGames.s.sol | 45 +++++++++++++------- scripts/multiproof/generate-roots.sh | 10 +++-- snapshots/semver-lock.json | 4 +- src/L1/proofs/AggregateVerifier.sol | 59 +++++++++++++++++--------- test/L1/OptimismPortal2.t.sol | 3 +- test/L1/proofs/AggregateVerifier.t.sol | 53 +++++++++++++++++++++++ 8 files changed, 135 insertions(+), 52 deletions(-) diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index 32c6d5f3f..f29ed9112 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -1148,15 +1148,6 @@ contract SystemDeploy is Script { _input.multiproofBlockInterval % _input.multiproofIntermediateBlockInterval == 0, "SystemDeploy: invalid multiproof block intervals" ); - require(_input.multiproofDenimBlockInterval != 0, "SystemDeploy: multiproof Denim block interval not set"); - require( - _input.multiproofDenimIntermediateBlockInterval != 0, - "SystemDeploy: multiproof Denim intermediate interval not set" - ); - require( - _input.multiproofDenimBlockInterval % _input.multiproofDenimIntermediateBlockInterval == 0, - "SystemDeploy: invalid multiproof Denim block intervals" - ); require(_input.teeProposer != address(0), "SystemDeploy: teeProposer not set"); require(_input.teeChallenger != address(0), "SystemDeploy: teeChallenger not set"); } diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index 786b7bc78..b18fd8130 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -172,7 +172,9 @@ Games are created using `ProofType.ZK` with the `MockVerifier` (deployed by both ### Step 1: Set the anchor state -Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers `BLOCK_INTERVAL` (600) L2 blocks, so for 500 games you need 300,000 blocks of headroom. +Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers one `BLOCK_INTERVAL` of L2 blocks — 600 pre-Denim, 6,000 post-Denim — so for 500 pre-Denim games you need 300,000 blocks of headroom. + +`SeedGames.s.sol` reads the interval off the deployed `AggregateVerifier` and picks the side that matches the anchor block, so nothing needs changing here for Denim. `generate-roots.sh` cannot see the chain, so pass `BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300` when seeding a devnet on which Denim is already active; seeding will abort on a mismatched roots file rather than create bad games. ```bash # Calculate an anchor block 300,000 blocks behind the L2 tip diff --git a/scripts/multiproof/SeedGames.s.sol b/scripts/multiproof/SeedGames.s.sol index e04121dbd..2b768169f 100644 --- a/scripts/multiproof/SeedGames.s.sol +++ b/scripts/multiproof/SeedGames.s.sol @@ -26,10 +26,6 @@ import { MockAnchorStateRegistry } from "./mocks/MockAnchorStateRegistry.sol"; /// All transactions must confirm within the 256-block blockhash window of the /// L1 origin captured at simulation time. For large counts, use --slow. contract SeedGames is Script { - /// @notice Must match the AggregateVerifier deployment constants from DeployDevWithNitro/NoNitro. - uint256 public constant BLOCK_INTERVAL = 600; - uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30; - uint256 public constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; uint32 public constant GAME_TYPE_ID = 621; uint256 public constant PROGRESS_LOG_INTERVAL = 100; @@ -38,6 +34,8 @@ contract SeedGames is Script { GameType gameType; uint256 initBond; uint256 anchorBlock; + uint256 blockInterval; + uint256 intermediateRootsCount; bytes32[] roots; bytes proof; } @@ -56,6 +54,8 @@ contract SeedGames is Script { console.log("Roots file:", rootsPath); console.log("Game count:", gameCount); console.log("Game type:", uint256(GAME_TYPE_ID)); + console.log("Block interval:", ctx.blockInterval); + console.log("Intermediate roots per game:", ctx.intermediateRootsCount); console.log("Init bond per game:", ctx.initBond); console.log("Anchor block:", ctx.anchorBlock); console.log("Total ETH required:", ctx.initBond * gameCount); @@ -64,8 +64,8 @@ contract SeedGames is Script { (address firstGame, address lastGame) = _createGames(ctx, asrAddr); vm.stopBroadcast(); - uint256 l2Start = ctx.anchorBlock + BLOCK_INTERVAL; - uint256 l2End = ctx.anchorBlock + BLOCK_INTERVAL * gameCount; + uint256 l2Start = ctx.anchorBlock + ctx.blockInterval; + uint256 l2End = ctx.anchorBlock + ctx.blockInterval * gameCount; console.log(""); console.log("=== Seeding Complete ==="); @@ -93,10 +93,20 @@ contract SeedGames is Script { ctx.initBond = ctx.factory.initBonds(ctx.gameType); (, ctx.anchorBlock) = MockAnchorStateRegistry(asrAddr).getAnchorRoot(); + // Read the proposal geometry off the deployment rather than restating it. AggregateVerifier + // carries both sides of the Denim activation and picks per game, so a hardcoded pair here + // would silently seed unopenable games on a devnet with Denim scheduled. + // ponytail: resolved once from the anchor, so a chain seeded straight across the activation + // would be wrong for its later games. Seed before scheduling Denim, or seed each side + // separately; per-game resolution only matters if a devnet ever needs a straddling chain. + AggregateVerifier gameImpl = AggregateVerifier(address(ctx.factory.gameImpls(ctx.gameType))); + (ctx.blockInterval,) = gameImpl.intervalsForStartingBlock(ctx.anchorBlock); + ctx.intermediateRootsCount = gameImpl.intermediateOutputRootsCount(); + string memory rootsJson = vm.readFile(rootsPath); ctx.roots = abi.decode(vm.parseJson(rootsJson, ".roots"), (bytes32[])); - uint256 expectedRoots = gameCount * INTERMEDIATE_ROOTS_COUNT; + uint256 expectedRoots = gameCount * ctx.intermediateRootsCount; require( ctx.roots.length == expectedRoots, string.concat( @@ -104,7 +114,9 @@ contract SeedGames is Script { vm.toString(ctx.roots.length), ", expected ", vm.toString(expectedRoots), - ". Re-run generate-roots.sh with matching game count." + ". Re-run generate-roots.sh with matching game count and BLOCK_INTERVAL=", + vm.toString(ctx.blockInterval), + "." ) ); @@ -115,7 +127,7 @@ contract SeedGames is Script { } function _createGames(SeedCtx memory ctx, address asrAddr) internal returns (address firstGame, address lastGame) { - uint256 count = ctx.roots.length / INTERMEDIATE_ROOTS_COUNT; + uint256 count = ctx.roots.length / ctx.intermediateRootsCount; address parentAddr = asrAddr; for (uint256 i = 0; i < count; i++) { @@ -133,11 +145,12 @@ contract SeedGames is Script { } function _createSingleGame(SeedCtx memory ctx, uint256 index, address parentAddr) internal returns (address) { - uint256 l2Block = ctx.anchorBlock + BLOCK_INTERVAL * (index + 1); - uint256 rootsOffset = index * INTERMEDIATE_ROOTS_COUNT; - bytes32 rootClaimHash = ctx.roots[rootsOffset + INTERMEDIATE_ROOTS_COUNT - 1]; + uint256 l2Block = ctx.anchorBlock + ctx.blockInterval * (index + 1); + uint256 rootsOffset = index * ctx.intermediateRootsCount; + bytes32 rootClaimHash = ctx.roots[rootsOffset + ctx.intermediateRootsCount - 1]; - bytes memory extraData = abi.encodePacked(l2Block, parentAddr, _sliceRoots(ctx.roots, rootsOffset)); + bytes memory extraData = + abi.encodePacked(l2Block, parentAddr, _sliceRoots(ctx.roots, rootsOffset, ctx.intermediateRootsCount)); IDisputeGame created = ctx.factory.createWithInitData{ value: ctx.initBond }( ctx.gameType, Claim.wrap(rootClaimHash), extraData, ctx.proof @@ -145,9 +158,9 @@ contract SeedGames is Script { return address(created); } - function _sliceRoots(bytes32[] memory all, uint256 offset) internal pure returns (bytes memory) { - bytes32[] memory slice = new bytes32[](INTERMEDIATE_ROOTS_COUNT); - for (uint256 j = 0; j < INTERMEDIATE_ROOTS_COUNT; j++) { + function _sliceRoots(bytes32[] memory all, uint256 offset, uint256 count) internal pure returns (bytes memory) { + bytes32[] memory slice = new bytes32[](count); + for (uint256 j = 0; j < count; j++) { slice[j] = all[offset + j]; } return abi.encodePacked(slice); diff --git a/scripts/multiproof/generate-roots.sh b/scripts/multiproof/generate-roots.sh index 50b43f811..57d8ac39a 100755 --- a/scripts/multiproof/generate-roots.sh +++ b/scripts/multiproof/generate-roots.sh @@ -32,9 +32,12 @@ GAME_COUNT="${3:-500}" PARALLELISM="${4:-20}" OUTPUT_FILE="${5:-roots.json}" -# Must match AggregateVerifier / SeedGames constants -BLOCK_INTERVAL=600 -INTERMEDIATE_BLOCK_INTERVAL=30 +# Must match the intervals the deployed AggregateVerifier selects for these games. The contract +# carries both sides of the Denim activation, so override these when seeding a devnet on which Denim +# is already active: BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300 ./generate-roots.sh ... +# SeedGames.s.sol reads the live values off the deployment and will refuse a mismatched roots file. +BLOCK_INTERVAL="${BLOCK_INTERVAL:-600}" +INTERMEDIATE_BLOCK_INTERVAL="${INTERMEDIATE_BLOCK_INTERVAL:-30}" ROOTS_PER_GAME=$((BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL)) TOTAL_ROOTS=$((GAME_COUNT * ROOTS_PER_GAME)) @@ -43,6 +46,7 @@ LAST_BLOCK=$((ANCHOR_BLOCK + GAME_COUNT * BLOCK_INTERVAL)) echo "=== Generating Output Roots ===" echo "Anchor block: $ANCHOR_BLOCK" echo "Game count: $GAME_COUNT" +echo "Block interval: $BLOCK_INTERVAL" echo "Roots per game: $ROOTS_PER_GAME" echo "Total roots: $TOTAL_ROOTS" echo "L2 block range: [$((ANCHOR_BLOCK + INTERMEDIATE_BLOCK_INTERVAL)), $LAST_BLOCK]" diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 37e3ecfc3..403ed15b0 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -28,8 +28,8 @@ "sourceCodeHash": "0x780ff372493ba9010bc0d13100ac896f2bf75730a9b17d4bb63aaf694dc3c634" }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x9177562fa3918aad9ed85ce6b6f72e684551d52bb3b9f882ddbc36cd47b296a8", - "sourceCodeHash": "0x1c7bc9b357142d2e2484928df91332427b8c528c12b40d5d241117c906901ffc" + "initCodeHash": "0x071fb3f725812d4b4b7dea93348cfb57a3868efa3d64f2e403780b348c08a49c", + "sourceCodeHash": "0x6d6d782ecaa4d2dc1b2feaf0ad06f76036a74f9e6d0599f36708d5d01a2a1ee0" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", diff --git a/src/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index fc8e17a02..bd318df54 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -427,9 +427,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { startingOutputRoot = ANCHOR_STATE_REGISTRY.getStartingAnchorRoot(); } + // Resolved once and threaded through both fork-sensitive decisions below, which would + // otherwise re-read the schedule from `PROTOCOL_VERSIONS` a second time. + uint256 denimBlock = _denimActivationBlock(); + // The block number must be one block interval after the starting block number. The interval // is selected on the starting block so the game chain stays contiguous across Denim. - (uint256 blockInterval,) = _intervals(startingOutputRoot.l2SequenceNumber); + (uint256 blockInterval,) = _intervalsAt(startingOutputRoot.l2SequenceNumber, denimBlock); if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + blockInterval) { revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + blockInterval, l2SequenceNumber()); } @@ -439,12 +443,17 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // Pinning the upgrades active at the ending L2 block makes the schedule independent of both // the proof's L1 head and the L1 block in which this game is created. + // + // Note that this anchors on the *ending* block while the interval selection above anchors on + // the *starting* block. That is deliberate, and it is what makes the straddling game work: it + // spans the pre-Denim interval its start selects, while pinning the post-Denim schedule its + // end falls under, so the prover knows Denim is active for the blocks past the boundary. uint256 claimBlock = l2SequenceNumber(); if (claimBlock < L2_GENESIS_BLOCK_NUMBER) { revert L2BlockBeforeGenesis(claimBlock, L2_GENESIS_BLOCK_NUMBER); } - uint64 claimTimestamp = _l2Timestamp(claimBlock); + uint64 claimTimestamp = _l2Timestamp(claimBlock, denimBlock); // `ProtocolVersions` freezes mutations to an activation `FREEZE_WINDOW` before it takes // effect. Requiring the claim timestamp to have been reached on L1 additionally ensures the @@ -791,10 +800,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @dev Offchain proposers and challengers must resolve intervals per game through this getter /// rather than reading `BLOCK_INTERVAL` / `INTERMEDIATE_BLOCK_INTERVAL`, which describe /// only games starting before Denim. + /// @dev Selection is on the starting block rather than the ending block so the game chain stays + /// contiguous across the activation: every game satisfies + /// `end == parent.end + blockInterval` for the interval its own start selects. Exactly one + /// game straddles the activation and is proven under the pre-Denim interval, which is + /// correct because provers apply fork rules per block by timestamp. /// @param startingBlock The starting L2 block number of the game. /// @return The block interval and the intermediate block interval governing that game. function intervalsForStartingBlock(uint256 startingBlock) public view returns (uint256, uint256) { - return _intervals(startingBlock); + return _intervalsAt(startingBlock, _denimActivationBlock()); } /// @notice The number of intermediate output roots. @@ -1133,7 +1147,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); - (, uint256 intermediateBlockInterval) = _intervals(startingOutputRoot.l2SequenceNumber); + (, uint256 intermediateBlockInterval) = intervalsForStartingBlock(startingOutputRoot.l2SequenceNumber); uint64 startingL2SequenceNumber = uint64(startingOutputRoot.l2SequenceNumber + intermediateRootIndex * intermediateBlockInterval); uint64 endingL2SequenceNumber = startingL2SequenceNumber + uint64(intermediateBlockInterval); @@ -1147,8 +1161,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { } /// @notice Derives an L2 block timestamp using the legacy cadence before Denim and whole-second groups after it. - function _l2Timestamp(uint256 claimBlock) private view returns (uint64) { - uint256 denimBlock = _denimActivationBlock(); + /// @param claimBlock The L2 block number to derive a timestamp for. + /// @param denimBlock The Denim activation block, as returned by `_denimActivationBlock()`. + function _l2Timestamp(uint256 claimBlock, uint256 denimBlock) private view returns (uint64) { if (claimBlock < denimBlock) return _legacyL2Timestamp(claimBlock); uint256 claimTimestamp = @@ -1184,12 +1199,18 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice Returns the first L2 block number governed by Denim, or `type(uint256).max` when Denim /// is not scheduled. - /// @dev Reading the live schedule is safe despite it being mutable. `initializeWithInitData` - /// rejects a game whose ending L2 timestamp has not yet been reached on L1, and - /// `ProtocolVersions` requires any new activation to be at least `MIN_NOTICE` in the future - /// and freezes one that has passed. A Denim timestamp that can still move is therefore - /// always later than the ending timestamp of every initialized game, so no game can have - /// its intervals reselected after creation. + /// @dev Reading the live schedule is safe despite it being mutable, in both directions: + /// + /// - A game that selected the Denim intervals has a starting block at or past the + /// activation, so the activation is in the past. `ProtocolVersions._assertNotFrozen` + /// rejects every mutation of a passed activation, from `setTimestamp` and + /// `delayTimestamp` alike, so that game's selection can never be revoked. + /// - A game that selected the pre-Denim intervals cannot be pulled across the boundary + /// either, including by the owner moving the activation *earlier* rather than later. + /// `initializeWithInitData` rejects a game whose ending L2 timestamp L1 has not yet + /// reached, so every initialized game satisfies `startingTimestamp < endingTimestamp <= + /// block.timestamp`, while any new activation must clear `block.timestamp + MIN_NOTICE`. + /// The activation therefore always lands after the game's starting block. function _denimActivationBlock() private view returns (uint256) { uint64[] memory schedule = PROTOCOL_VERSIONS.getSchedule(); if (schedule.length <= DENIM_UPGRADE_INDEX) return type(uint256).max; @@ -1204,14 +1225,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return L2_GENESIS_BLOCK_NUMBER + blocksUntilDenim; } - /// @notice Selects the proposal intervals governing a game, from its starting block number. - /// @dev Selection is on the starting block rather than the ending block so the game chain stays - /// contiguous across the activation: every game satisfies - /// `end == parent.end + blockInterval` for the interval its own start selects. Exactly one - /// game straddles the activation and is proven under the pre-Denim interval, which is - /// correct because provers apply fork rules per block by timestamp. - function _intervals(uint256 startingBlock) private view returns (uint256, uint256) { - if (startingBlock < _denimActivationBlock()) return (BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + /// @notice Selects the proposal intervals governing a game, from its starting block number and an + /// already-resolved Denim activation block. + /// @dev Takes `denimBlock` rather than resolving it so a caller making more than one + /// fork-sensitive decision pays for `PROTOCOL_VERSIONS.getSchedule()` once. + function _intervalsAt(uint256 startingBlock, uint256 denimBlock) private view returns (uint256, uint256) { + if (startingBlock < denimBlock) return (BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); return (DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); } diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index 801e99daf..6894b3552 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -103,7 +103,8 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { disputeGameFactory.setInitBond(respectedGameType, 0); Proposal memory startingRoot = anchorStateRegistry.getStartingAnchorRoot(); - _proposedBlockNumber = startingRoot.l2SequenceNumber + gameImpl.BLOCK_INTERVAL(); + (uint256 blockInterval,) = gameImpl.intervalsForStartingBlock(startingRoot.l2SequenceNumber); + _proposedBlockNumber = startingRoot.l2SequenceNumber + blockInterval; depositor = makeAddr("depositor"); diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index a83151e0f..a005fe1f7 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -207,6 +207,23 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.l2SequenceNumber(), BLOCK_INTERVAL); } + /// @notice `challenge` derives the intermediate sub-range from the interval the game's starting + /// block selects. This is the second fork-sensitive call site, and unlike the block + /// number check its output goes into the journal the prover signs, so a stale interval + /// here makes a valid challenge unconstructable rather than reverting loudly. + function test_challenge_denimIntermediateInterval_succeeds() public { + _importDenimSchedule(L2_GENESIS_TIMESTAMP); + _assertChallengeIntermediateRange(DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + } + + /// @notice The straddling game's sub-ranges stay pre-Denim sized, matching the interval its own + /// starting block selects. + function test_challenge_straddlingGameUsesPreDenimIntermediateInterval_succeeds() public { + // Activation block 50 falls inside the first game's [0, 100) range. + _importDenimSchedule(L2_GENESIS_TIMESTAMP + 100); + _assertChallengeIntermediateRange(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + } + function test_constructor_mismatchedIntermediateRootCount_reverts() public { // 100 / 10 == 10 intermediate roots, but 1000 / 200 == 5. vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MismatchedIntermediateRootCount.selector, 10, 5)); @@ -804,10 +821,46 @@ contract AggregateVerifierTest is BaseTest { assertEq(intermediateBlockInterval, expectedIntermediateBlockInterval); } + /// @dev Challenges intermediate root 0 of a game ending at `endingBlock` and asserts the range + /// handed to the ZK verifier is `[0, expectedIntermediateBlockInterval)`. The mock verifier + /// accepts anything, so the journal is checked by matching the call rather than by reverting. + function _assertChallengeIntermediateRange(uint256 endingBlock, uint256 expectedIntermediateBlockInterval) private { + AggregateVerifier game = _createGameEndingAt(endingBlock); + (Hash startingRoot,) = game.startingOutputRoot(); + + bytes32 counterRoot = keccak256("counter"); + bytes memory zkProof = abi.encodePacked(uint8(AggregateVerifier.ProofType.ZK), bytes1(0)); + + bytes32 expectedJournal = keccak256( + abi.encodePacked( + ZK_PROVER, + game.l1Head().raw(), + startingRoot.raw(), + uint64(0), + counterRoot, + uint64(expectedIntermediateBlockInterval), + abi.encodePacked(counterRoot), + CONFIG_HASH, + ZK_RANGE_HASH, + game.scheduleId() + ) + ); + + // `zkProof[1:]` is the single trailing zero byte. + vm.expectCall( + address(zkVerifier), abi.encodeCall(IVerifier.verify, (hex"00", ZK_AGGREGATE_HASH, expectedJournal)) + ); + vm.prank(ZK_PROVER); + game.challenge(zkProof, 0, counterRoot); + } + /// @dev Creates a game off the anchor root. Intermediate root values are unchecked by the mock /// verifiers, so only their count has to match the implementation. function _createGameEndingAt(uint256 endingBlock) private returns (AggregateVerifier) { Claim rootClaim = Claim.wrap(keccak256(abi.encode(endingBlock))); + // Both interval pairs are constructor-checked to yield the same count, so the pre-Denim ratio + // is the count on either side. Reading it off the implementation instead would put a + // staticcall between a caller's `vm.expectRevert` and the call it is meant to apply to. uint256 count = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; bytes32[] memory intermediateRoots = new bytes32[](count); for (uint256 i; i < count - 1; i++) { From 4ac058ad99599573e02b6845004aae629edc2e84 Mon Sep 17 00:00:00 2001 From: Thanh Trinh Date: Thu, 3 Sep 2026 16:17:52 -0500 Subject: [PATCH 3/4] refactor(L1): name the interval pairs by block cadence, not by fork The two proposal interval pairs differ because they are calibrated for different L2 block cadences: 600 blocks at 2s and 6,000 at 200ms are both a 20-minute range. Name them for that, rather than for the fork that happens to introduce the second one. This also fixes an asymmetry. The pre-Denim pair was unprefixed and the post-Denim pair prefixed, implying "default plus special case", when the fast pair is the permanent steady state and the slow one is the legacy. BLOCK_INTERVAL -> SLOW_BLOCK_INTERVAL INTERMEDIATE_BLOCK_INTERVAL -> SLOW_INTERMEDIATE_BLOCK_INTERVAL DENIM_BLOCK_INTERVAL -> FAST_BLOCK_INTERVAL DENIM_INTERMEDIATE_BLOCK_INTERVAL -> FAST_INTERMEDIATE_BLOCK_INTERVAL DENIM_BLOCKS_PER_SECOND -> FAST_BLOCKS_PER_SECOND DENIM_UPGRADE_INDEX -> FAST_BLOCK_UPGRADE_INDEX _denimActivationBlock() -> _firstFastBlock() _legacyL2Timestamp() -> _slowL2Timestamp() `intervalsForStartingBlock` and `L2_BLOCK_TIME` are unchanged. IntervalConfig fields and the multiproof* deploy-config keys move with the immutables. `BLOCK_INTERVAL()` and `INTERMEDIATE_BLOCK_INTERVAL()` therefore leave the 0.2.0 ABI. That is deliberate: a consumer calling them post-Denim gets a plausible-but-wrong number today, and a missing method is a better failure mode than a wrong answer. It forces the migration to `intervalsForStartingBlock()` that the offchain follow-ups already require. In-flight 0.1.0 clones keep exposing the old names. Denim is now named in exactly one place, on FAST_BLOCK_UPGRADE_INDEX, which is the only spot the contract is pinned to a specific hardfork. A later cadence change is a new index and a new interval pair, not new machinery. Generated with Claude Code Co-Authored-By: Claude --- deploy-config/local.json | 8 +- interfaces/L1/proofs/IAggregateVerifier.sol | 8 +- scripts/deploy/DeployConfig.s.sol | 16 +- scripts/deploy/SystemDeploy.s.sol | 47 ++--- scripts/multiproof/DeployDevNoNitro.s.sol | 16 +- scripts/multiproof/DeployDevWithNitro.s.sol | 16 +- scripts/multiproof/README.md | 4 +- scripts/multiproof/SeedGames.s.sol | 18 +- scripts/multiproof/generate-roots.sh | 2 +- snapshots/abi/AggregateVerifier.json | 88 ++++---- snapshots/semver-lock.json | 4 +- src/L1/proofs/AggregateVerifier.sol | 177 ++++++++-------- test/L1/OptimismPortal2.t.sol | 12 +- test/L1/proofs/AggregateVerifier.t.sol | 211 ++++++++++---------- test/L1/proofs/AnchorStateRegistry.t.sol | 2 +- test/L1/proofs/BaseTest.t.sol | 23 ++- test/L1/proofs/Challenge.t.sol | 4 +- test/L1/proofs/DisputeGameFactory.t.sol | 30 +-- test/L1/proofs/Nullify.t.sol | 4 +- test/deploy/SystemDeploy.t.sol | 19 +- test/deploy/SystemDeployAssertions.sol | 20 +- 21 files changed, 373 insertions(+), 356 deletions(-) diff --git a/deploy-config/local.json b/deploy-config/local.json index 1b540650c..c38098f96 100644 --- a/deploy-config/local.json +++ b/deploy-config/local.json @@ -17,13 +17,13 @@ "l2GenesisBlockGasLimit": "0x17D7840", "l2OutputOracleStartingBlockNumber": 1, "l2OutputOracleStartingTimestamp": 1, - "multiproofBlockInterval": 100, + "multiproofSlowBlockInterval": 100, "multiproofConfigHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "multiproofDenimBlockInterval": 1000, - "multiproofDenimIntermediateBlockInterval": 100, + "multiproofFastBlockInterval": 1000, + "multiproofFastIntermediateBlockInterval": 100, "multiproofGameType": 621, "multiproofGenesisBlockNumber": 0, - "multiproofIntermediateBlockInterval": 10, + "multiproofSlowIntermediateBlockInterval": 10, "multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001", "nitroValidator": "0x0000000000000000000000000000000000000000", "operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", diff --git a/interfaces/L1/proofs/IAggregateVerifier.sol b/interfaces/L1/proofs/IAggregateVerifier.sol index 3cfdee597..1bb74be3a 100644 --- a/interfaces/L1/proofs/IAggregateVerifier.sol +++ b/interfaces/L1/proofs/IAggregateVerifier.sol @@ -29,10 +29,10 @@ interface IAggregateVerifier is IDisputeGame { function L2_GENESIS_BLOCK_NUMBER() external view returns (uint256); function L2_GENESIS_TIMESTAMP() external view returns (uint64); function L2_BLOCK_TIME() external view returns (uint64); - function BLOCK_INTERVAL() external view returns (uint256); - function INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); - function DENIM_BLOCK_INTERVAL() external view returns (uint256); - function DENIM_INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); + function SLOW_BLOCK_INTERVAL() external view returns (uint256); + function SLOW_INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); + function FAST_BLOCK_INTERVAL() external view returns (uint256); + function FAST_INTERMEDIATE_BLOCK_INTERVAL() external view returns (uint256); function intervalsForStartingBlock(uint256 startingBlock) external view returns (uint256, uint256); function startingOutputRoot() external view returns (Proposal memory); diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index e8ef1ae2f..42e1bbcc8 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -51,12 +51,12 @@ contract DeployConfig is Script { uint256 public l2GenesisTimestamp; uint256 public l2OutputOracleStartingBlockNumber; uint256 public l2OutputOracleStartingTimestamp; - uint256 public multiproofBlockInterval; - uint256 public multiproofDenimBlockInterval; - uint256 public multiproofDenimIntermediateBlockInterval; + uint256 public multiproofSlowBlockInterval; + uint256 public multiproofFastBlockInterval; + uint256 public multiproofFastIntermediateBlockInterval; uint256 public multiproofGameType; uint256 public multiproofGenesisBlockNumber; - uint256 public multiproofIntermediateBlockInterval; + uint256 public multiproofSlowIntermediateBlockInterval; uint256 public operatorFeeVaultMinimumWithdrawalAmount; uint256 public operatorFeeVaultWithdrawalNetwork; uint256 public proofMaturityDelaySeconds; @@ -117,12 +117,12 @@ contract DeployConfig is Script { l2OutputOracleStartingTimestamp = _json.readUint("$.l2OutputOracleStartingTimestamp"); l2GenesisBlockNumber = _json.readUintOr("$.l2GenesisBlockNumber", 0); l2GenesisTimestamp = _json.readUintOr("$.l2GenesisTimestamp", 0); - multiproofBlockInterval = _json.readUintOr("$.multiproofBlockInterval", 100); - multiproofDenimBlockInterval = _json.readUintOr("$.multiproofDenimBlockInterval", 1000); - multiproofDenimIntermediateBlockInterval = _json.readUintOr("$.multiproofDenimIntermediateBlockInterval", 100); + multiproofSlowBlockInterval = _json.readUintOr("$.multiproofSlowBlockInterval", 100); + multiproofFastBlockInterval = _json.readUintOr("$.multiproofFastBlockInterval", 1000); + multiproofFastIntermediateBlockInterval = _json.readUintOr("$.multiproofFastIntermediateBlockInterval", 100); multiproofGameType = _json.readUintOr("$.multiproofGameType", 621); multiproofGenesisBlockNumber = _json.readUintOr("$.multiproofGenesisBlockNumber", 0); - multiproofIntermediateBlockInterval = _json.readUintOr("$.multiproofIntermediateBlockInterval", 10); + multiproofSlowIntermediateBlockInterval = _json.readUintOr("$.multiproofSlowIntermediateBlockInterval", 10); operatorFeeVaultMinimumWithdrawalAmount = _json.readUint("$.operatorFeeVaultMinimumWithdrawalAmount"); operatorFeeVaultWithdrawalNetwork = _json.readUint("$.operatorFeeVaultWithdrawalNetwork"); proofMaturityDelaySeconds = _json.readUintOr("$.proofMaturityDelaySeconds", 0); diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index f29ed9112..0923c411c 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -79,10 +79,10 @@ contract SystemDeploy is Script { uint256 multiproofGameType; address nitroValidator; AggregateVerifier.ScheduleConfig scheduleConfig; - uint256 multiproofBlockInterval; - uint256 multiproofIntermediateBlockInterval; - uint256 multiproofDenimBlockInterval; - uint256 multiproofDenimIntermediateBlockInterval; + uint256 multiproofSlowBlockInterval; + uint256 multiproofSlowIntermediateBlockInterval; + uint256 multiproofFastBlockInterval; + uint256 multiproofFastIntermediateBlockInterval; ISP1Verifier sp1Verifier; address teeProposer; address teeChallenger; @@ -130,10 +130,10 @@ contract SystemDeploy is Script { bytes32 multiproofConfigHash; uint256 l2ChainId; AggregateVerifier.ScheduleConfig scheduleConfig; - uint256 multiproofBlockInterval; - uint256 multiproofIntermediateBlockInterval; - uint256 multiproofDenimBlockInterval; - uint256 multiproofDenimIntermediateBlockInterval; + uint256 multiproofSlowBlockInterval; + uint256 multiproofSlowIntermediateBlockInterval; + uint256 multiproofFastBlockInterval; + uint256 multiproofFastIntermediateBlockInterval; } struct MultiproofOutput { @@ -271,10 +271,10 @@ contract SystemDeploy is Script { multiproofGameType: cfg.multiproofGameType(), nitroValidator: cfg.nitroValidator(), scheduleConfig: _configuredScheduleConfig(), - multiproofBlockInterval: cfg.multiproofBlockInterval(), - multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(), - multiproofDenimBlockInterval: cfg.multiproofDenimBlockInterval(), - multiproofDenimIntermediateBlockInterval: cfg.multiproofDenimIntermediateBlockInterval(), + multiproofSlowBlockInterval: cfg.multiproofSlowBlockInterval(), + multiproofSlowIntermediateBlockInterval: cfg.multiproofSlowIntermediateBlockInterval(), + multiproofFastBlockInterval: cfg.multiproofFastBlockInterval(), + multiproofFastIntermediateBlockInterval: cfg.multiproofFastIntermediateBlockInterval(), sp1Verifier: ISP1Verifier(cfg.sp1Verifier()), teeProposer: cfg.teeProposer(), teeChallenger: cfg.teeChallenger(), @@ -1061,10 +1061,10 @@ contract SystemDeploy is Script { multiproofConfigHash: _input.multiproofConfigHash, l2ChainId: _opChainInput.l2ChainId, scheduleConfig: scheduleConfig, - multiproofBlockInterval: _input.multiproofBlockInterval, - multiproofIntermediateBlockInterval: _input.multiproofIntermediateBlockInterval, - multiproofDenimBlockInterval: _input.multiproofDenimBlockInterval, - multiproofDenimIntermediateBlockInterval: _input.multiproofDenimIntermediateBlockInterval + multiproofSlowBlockInterval: _input.multiproofSlowBlockInterval, + multiproofSlowIntermediateBlockInterval: _input.multiproofSlowIntermediateBlockInterval, + multiproofFastBlockInterval: _input.multiproofFastBlockInterval, + multiproofFastIntermediateBlockInterval: _input.multiproofFastIntermediateBlockInterval }) ); @@ -1093,10 +1093,10 @@ contract SystemDeploy is Script { _input.multiproofConfigHash, _input.l2ChainId, AggregateVerifier.IntervalConfig({ - blockInterval: _input.multiproofBlockInterval, - intermediateBlockInterval: _input.multiproofIntermediateBlockInterval, - denimBlockInterval: _input.multiproofDenimBlockInterval, - denimIntermediateBlockInterval: _input.multiproofDenimIntermediateBlockInterval + slowBlockInterval: _input.multiproofSlowBlockInterval, + slowIntermediateBlockInterval: _input.multiproofSlowIntermediateBlockInterval, + fastBlockInterval: _input.multiproofFastBlockInterval, + fastIntermediateBlockInterval: _input.multiproofFastIntermediateBlockInterval }), _input.scheduleConfig ) @@ -1140,12 +1140,13 @@ contract SystemDeploy is Script { require(address(_input.sp1Verifier) != address(0), "SystemDeploy: sp1Verifier not set"); DeployUtils.assertValidContractAddress(_input.nitroValidator); DeployUtils.assertValidContractAddress(address(_input.sp1Verifier)); - require(_input.multiproofBlockInterval != 0, "SystemDeploy: multiproof block interval not set"); + require(_input.multiproofSlowBlockInterval != 0, "SystemDeploy: multiproof block interval not set"); require( - _input.multiproofIntermediateBlockInterval != 0, "SystemDeploy: multiproof intermediate interval not set" + _input.multiproofSlowIntermediateBlockInterval != 0, + "SystemDeploy: multiproof intermediate interval not set" ); require( - _input.multiproofBlockInterval % _input.multiproofIntermediateBlockInterval == 0, + _input.multiproofSlowBlockInterval % _input.multiproofSlowIntermediateBlockInterval == 0, "SystemDeploy: invalid multiproof block intervals" ); require(_input.teeProposer != address(0), "SystemDeploy: teeProposer not set"); diff --git a/scripts/multiproof/DeployDevNoNitro.s.sol b/scripts/multiproof/DeployDevNoNitro.s.sol index 557a34194..a84acf448 100644 --- a/scripts/multiproof/DeployDevNoNitro.s.sol +++ b/scripts/multiproof/DeployDevNoNitro.s.sol @@ -16,18 +16,18 @@ import { DeployDevBase } from "./DeployDevBase.s.sol"; /// @notice Development deployment using DevTEEProverRegistry, which bypasses AWS Nitro attestation /// validation. See scripts/multiproof/README.md for usage. Not for production. contract DeployDevNoNitro is DeployDevBase { - uint256 public constant BLOCK_INTERVAL = 100; - uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; - uint256 public constant DENIM_BLOCK_INTERVAL = 1000; - uint256 public constant DENIM_INTERMEDIATE_BLOCK_INTERVAL = 100; + uint256 public constant SLOW_BLOCK_INTERVAL = 100; + uint256 public constant SLOW_INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 public constant FAST_BLOCK_INTERVAL = 1000; + uint256 public constant FAST_INTERMEDIATE_BLOCK_INTERVAL = 100; uint256 public constant INIT_BOND = 0.001 ether; function _intervalConfig() internal pure override returns (AggregateVerifier.IntervalConfig memory) { return AggregateVerifier.IntervalConfig({ - blockInterval: BLOCK_INTERVAL, - intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + slowBlockInterval: SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: FAST_INTERMEDIATE_BLOCK_INTERVAL }); } diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index a16619b2d..3562dc0bf 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -19,20 +19,20 @@ import { DeployDevBase } from "./DeployDevBase.s.sol"; /// then set `nitroValidator` in the deploy config. AWS Nitro attestations are only valid /// for 60 minutes, and the certificate chain must be cached before registerSigner() is called. contract DeployDevWithNitro is DeployDevBase { - uint256 public constant BLOCK_INTERVAL = 600; - uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 30; - uint256 public constant DENIM_BLOCK_INTERVAL = 6000; - uint256 public constant DENIM_INTERMEDIATE_BLOCK_INTERVAL = 300; + uint256 public constant SLOW_BLOCK_INTERVAL = 600; + uint256 public constant SLOW_INTERMEDIATE_BLOCK_INTERVAL = 30; + uint256 public constant FAST_BLOCK_INTERVAL = 6000; + uint256 public constant FAST_INTERMEDIATE_BLOCK_INTERVAL = 300; uint256 public constant INIT_BOND = 0.00001 ether; address public nitroValidatorAddr; function _intervalConfig() internal pure override returns (AggregateVerifier.IntervalConfig memory) { return AggregateVerifier.IntervalConfig({ - blockInterval: BLOCK_INTERVAL, - intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + slowBlockInterval: SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: FAST_INTERMEDIATE_BLOCK_INTERVAL }); } diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index b18fd8130..b20a93056 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -172,9 +172,9 @@ Games are created using `ProofType.ZK` with the `MockVerifier` (deployed by both ### Step 1: Set the anchor state -Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers one `BLOCK_INTERVAL` of L2 blocks — 600 pre-Denim, 6,000 post-Denim — so for 500 pre-Denim games you need 300,000 blocks of headroom. +Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers one block interval of L2 blocks — 600 on slow (pre-Denim) blocks, 6,000 on fast (post-Denim) ones — so for 500 slow-block games you need 300,000 blocks of headroom. -`SeedGames.s.sol` reads the interval off the deployed `AggregateVerifier` and picks the side that matches the anchor block, so nothing needs changing here for Denim. `generate-roots.sh` cannot see the chain, so pass `BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300` when seeding a devnet on which Denim is already active; seeding will abort on a mismatched roots file rather than create bad games. +`SeedGames.s.sol` reads the interval off the deployed `AggregateVerifier` and picks the side that matches the anchor block, so nothing needs changing here for Denim. The contract exposes the two pairs as `SLOW_BLOCK_INTERVAL` / `SLOW_INTERMEDIATE_BLOCK_INTERVAL` and `FAST_BLOCK_INTERVAL` / `FAST_INTERMEDIATE_BLOCK_INTERVAL`. `generate-roots.sh` cannot see the chain, so pass `BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300` when seeding a devnet on which Denim is already active; seeding will abort on a mismatched roots file rather than create bad games. ```bash # Calculate an anchor block 300,000 blocks behind the L2 tip diff --git a/scripts/multiproof/SeedGames.s.sol b/scripts/multiproof/SeedGames.s.sol index 2b768169f..a705cd984 100644 --- a/scripts/multiproof/SeedGames.s.sol +++ b/scripts/multiproof/SeedGames.s.sol @@ -34,7 +34,7 @@ contract SeedGames is Script { GameType gameType; uint256 initBond; uint256 anchorBlock; - uint256 blockInterval; + uint256 slowBlockInterval; uint256 intermediateRootsCount; bytes32[] roots; bytes proof; @@ -54,7 +54,7 @@ contract SeedGames is Script { console.log("Roots file:", rootsPath); console.log("Game count:", gameCount); console.log("Game type:", uint256(GAME_TYPE_ID)); - console.log("Block interval:", ctx.blockInterval); + console.log("Block interval:", ctx.slowBlockInterval); console.log("Intermediate roots per game:", ctx.intermediateRootsCount); console.log("Init bond per game:", ctx.initBond); console.log("Anchor block:", ctx.anchorBlock); @@ -64,8 +64,8 @@ contract SeedGames is Script { (address firstGame, address lastGame) = _createGames(ctx, asrAddr); vm.stopBroadcast(); - uint256 l2Start = ctx.anchorBlock + ctx.blockInterval; - uint256 l2End = ctx.anchorBlock + ctx.blockInterval * gameCount; + uint256 l2Start = ctx.anchorBlock + ctx.slowBlockInterval; + uint256 l2End = ctx.anchorBlock + ctx.slowBlockInterval * gameCount; console.log(""); console.log("=== Seeding Complete ==="); @@ -94,13 +94,13 @@ contract SeedGames is Script { (, ctx.anchorBlock) = MockAnchorStateRegistry(asrAddr).getAnchorRoot(); // Read the proposal geometry off the deployment rather than restating it. AggregateVerifier - // carries both sides of the Denim activation and picks per game, so a hardcoded pair here + // carries both the slow- and fast-block pairs and picks per game, so a hardcoded pair here // would silently seed unopenable games on a devnet with Denim scheduled. // ponytail: resolved once from the anchor, so a chain seeded straight across the activation // would be wrong for its later games. Seed before scheduling Denim, or seed each side // separately; per-game resolution only matters if a devnet ever needs a straddling chain. AggregateVerifier gameImpl = AggregateVerifier(address(ctx.factory.gameImpls(ctx.gameType))); - (ctx.blockInterval,) = gameImpl.intervalsForStartingBlock(ctx.anchorBlock); + (ctx.slowBlockInterval,) = gameImpl.intervalsForStartingBlock(ctx.anchorBlock); ctx.intermediateRootsCount = gameImpl.intermediateOutputRootsCount(); string memory rootsJson = vm.readFile(rootsPath); @@ -114,8 +114,8 @@ contract SeedGames is Script { vm.toString(ctx.roots.length), ", expected ", vm.toString(expectedRoots), - ". Re-run generate-roots.sh with matching game count and BLOCK_INTERVAL=", - vm.toString(ctx.blockInterval), + ". Re-run generate-roots.sh with matching game count and SLOW_BLOCK_INTERVAL=", + vm.toString(ctx.slowBlockInterval), "." ) ); @@ -145,7 +145,7 @@ contract SeedGames is Script { } function _createSingleGame(SeedCtx memory ctx, uint256 index, address parentAddr) internal returns (address) { - uint256 l2Block = ctx.anchorBlock + ctx.blockInterval * (index + 1); + uint256 l2Block = ctx.anchorBlock + ctx.slowBlockInterval * (index + 1); uint256 rootsOffset = index * ctx.intermediateRootsCount; bytes32 rootClaimHash = ctx.roots[rootsOffset + ctx.intermediateRootsCount - 1]; diff --git a/scripts/multiproof/generate-roots.sh b/scripts/multiproof/generate-roots.sh index 57d8ac39a..cc8b9f64d 100755 --- a/scripts/multiproof/generate-roots.sh +++ b/scripts/multiproof/generate-roots.sh @@ -33,7 +33,7 @@ PARALLELISM="${4:-20}" OUTPUT_FILE="${5:-roots.json}" # Must match the intervals the deployed AggregateVerifier selects for these games. The contract -# carries both sides of the Denim activation, so override these when seeding a devnet on which Denim +# carries both the slow- and fast-block pairs, so override these when seeding a devnet on which Denim # is already active: BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300 ./generate-roots.sh ... # SeedGames.s.sol reads the live values off the deployment and will refuse a mismatched roots file. BLOCK_INTERVAL="${BLOCK_INTERVAL:-600}" diff --git a/snapshots/abi/AggregateVerifier.json b/snapshots/abi/AggregateVerifier.json index 2697e7dab..618a657f6 100644 --- a/snapshots/abi/AggregateVerifier.json +++ b/snapshots/abi/AggregateVerifier.json @@ -62,22 +62,22 @@ "components": [ { "internalType": "uint256", - "name": "blockInterval", + "name": "slowBlockInterval", "type": "uint256" }, { "internalType": "uint256", - "name": "intermediateBlockInterval", + "name": "slowIntermediateBlockInterval", "type": "uint256" }, { "internalType": "uint256", - "name": "denimBlockInterval", + "name": "fastBlockInterval", "type": "uint256" }, { "internalType": "uint256", - "name": "denimIntermediateBlockInterval", + "name": "fastIntermediateBlockInterval", "type": "uint256" } ], @@ -129,19 +129,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "BLOCK_INTERVAL", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "CONFIG_HASH", @@ -170,25 +157,12 @@ }, { "inputs": [], - "name": "DENIM_BLOCK_INTERVAL", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DENIM_INTERMEDIATE_BLOCK_INTERVAL", + "name": "DISPUTE_GAME_FACTORY", "outputs": [ { - "internalType": "uint256", + "internalType": "contract IDisputeGameFactory", "name": "", - "type": "uint256" + "type": "address" } ], "stateMutability": "view", @@ -196,10 +170,10 @@ }, { "inputs": [], - "name": "DISPUTE_GAME_FACTORY", + "name": "EIP2935_CONTRACT", "outputs": [ { - "internalType": "contract IDisputeGameFactory", + "internalType": "address", "name": "", "type": "address" } @@ -209,12 +183,12 @@ }, { "inputs": [], - "name": "EIP2935_CONTRACT", + "name": "EIP2935_WINDOW", "outputs": [ { - "internalType": "address", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" } ], "stateMutability": "view", @@ -222,7 +196,7 @@ }, { "inputs": [], - "name": "EIP2935_WINDOW", + "name": "FAST_BLOCK_INTERVAL", "outputs": [ { "internalType": "uint256", @@ -248,7 +222,7 @@ }, { "inputs": [], - "name": "INTERMEDIATE_BLOCK_INTERVAL", + "name": "FAST_INTERMEDIATE_BLOCK_INTERVAL", "outputs": [ { "internalType": "uint256", @@ -337,6 +311,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "SLOW_BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "SLOW_FINALIZATION_DELAY", @@ -350,6 +337,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "SLOW_INTERMEDIATE_BLOCK_INTERVAL", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "TEE_IMAGE_HASH", @@ -1138,12 +1138,12 @@ "inputs": [ { "internalType": "uint256", - "name": "blockInterval", + "name": "slowBlockInterval", "type": "uint256" }, { "internalType": "uint256", - "name": "intermediateBlockInterval", + "name": "slowIntermediateBlockInterval", "type": "uint256" } ], @@ -1275,12 +1275,12 @@ "inputs": [ { "internalType": "uint256", - "name": "preDenimCount", + "name": "slowCount", "type": "uint256" }, { "internalType": "uint256", - "name": "denimCount", + "name": "fastCount", "type": "uint256" } ], diff --git a/snapshots/semver-lock.json b/snapshots/semver-lock.json index 403ed15b0..8f10fe974 100644 --- a/snapshots/semver-lock.json +++ b/snapshots/semver-lock.json @@ -28,8 +28,8 @@ "sourceCodeHash": "0x780ff372493ba9010bc0d13100ac896f2bf75730a9b17d4bb63aaf694dc3c634" }, "src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": { - "initCodeHash": "0x071fb3f725812d4b4b7dea93348cfb57a3868efa3d64f2e403780b348c08a49c", - "sourceCodeHash": "0x6d6d782ecaa4d2dc1b2feaf0ad06f76036a74f9e6d0599f36708d5d01a2a1ee0" + "initCodeHash": "0x981207fb011a16372054d383ee5a90cf65774aaa67d9a3fad9a8f9b80e1c25b5", + "sourceCodeHash": "0x0e54893d50a31bf65e72290de1fbde37b30e949f5419176a8b359cd3aa52271c" }, "src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1", diff --git a/src/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index bd318df54..8932bcb94 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -53,14 +53,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { uint64 blockTime; } - /// @notice Proposal block intervals for each side of the Denim activation. + /// @notice Proposal block intervals for each side of the block-speedup activation. /// @dev Both pairs must yield the same intermediate root count, so the CWIA `extraData` layout /// and `INITIALIZE_CALLDATA_SIZE` are identical on both sides of the fork. struct IntervalConfig { - uint256 blockInterval; - uint256 intermediateBlockInterval; - uint256 denimBlockInterval; - uint256 denimIntermediateBlockInterval; + uint256 slowBlockInterval; + uint256 slowIntermediateBlockInterval; + uint256 fastBlockInterval; + uint256 fastIntermediateBlockInterval; } //////////////////////////////////////////////////////////////// @@ -86,11 +86,15 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The minimum number of proofs required to resolve the game. uint256 public constant PROOF_THRESHOLD = 1; - /// @notice The ProtocolVersions upgrade index for Denim. - uint256 private constant DENIM_UPGRADE_INDEX = 13; + /// @notice The ProtocolVersions upgrade index at which L2 blocks switch to the fast cadence. + /// @dev This is the one place the contract is tied to a specific hardfork: index 13 is Denim, + /// which drops the L2 block time from 2s to 200ms. Everything downstream is expressed as + /// slow-vs-fast blocks, so a later cadence change is a new index and new interval pair + /// rather than new machinery. + uint256 private constant FAST_BLOCK_UPGRADE_INDEX = 13; - /// @notice The number of whole Denim blocks produced per second. - uint256 private constant DENIM_BLOCKS_PER_SECOND = 5; + /// @notice The number of whole fast-cadence L2 blocks produced per second. + uint256 private constant FAST_BLOCKS_PER_SECOND = 5; //////////////////////////////////////////////////////////////// // Immutables // //////////////////////////////////////////////////////////////// @@ -133,21 +137,21 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice The legacy number of seconds between consecutive L2 blocks. uint64 public immutable L2_BLOCK_TIME; - /// @notice The block interval between each proposal, for games starting before Denim. - /// @dev The parent's block number + BLOCK_INTERVAL = this proposal's block number. - uint256 public immutable BLOCK_INTERVAL; + /// @notice The block interval between each proposal, for games starting on slow blocks. + /// @dev The parent's block number + SLOW_BLOCK_INTERVAL = this proposal's block number. + uint256 public immutable SLOW_BLOCK_INTERVAL; - /// @notice The block interval for intermediate proposals, for games starting before Denim. - /// @dev BLOCK_INTERVAL must be divisible by INTERMEDIATE_BLOCK_INTERVAL. - uint256 public immutable INTERMEDIATE_BLOCK_INTERVAL; + /// @notice The block interval for intermediate proposals, for games starting on slow blocks. + /// @dev SLOW_BLOCK_INTERVAL must be divisible by SLOW_INTERMEDIATE_BLOCK_INTERVAL. + uint256 public immutable SLOW_INTERMEDIATE_BLOCK_INTERVAL; - /// @notice The block interval between each proposal, for games starting at or after Denim. - uint256 public immutable DENIM_BLOCK_INTERVAL; + /// @notice The block interval between each proposal, for games starting on fast blocks. + uint256 public immutable FAST_BLOCK_INTERVAL; - /// @notice The block interval for intermediate proposals, for games starting at or after Denim. - /// @dev DENIM_BLOCK_INTERVAL must be divisible by DENIM_INTERMEDIATE_BLOCK_INTERVAL, and their - /// ratio must equal the pre-Denim one. - uint256 public immutable DENIM_INTERMEDIATE_BLOCK_INTERVAL; + /// @notice The block interval for intermediate proposals, for games starting on fast blocks. + /// @dev FAST_BLOCK_INTERVAL must be divisible by FAST_INTERMEDIATE_BLOCK_INTERVAL, and their + /// ratio must equal the slow-block one. + uint256 public immutable FAST_INTERMEDIATE_BLOCK_INTERVAL; /// @notice The size of the initialize call data. uint256 internal immutable INITIALIZE_CALLDATA_SIZE; @@ -246,10 +250,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // Errors // //////////////////////////////////////////////////////////////// /// @notice When the block interval or intermediate block interval is invalid. - error InvalidBlockInterval(uint256 blockInterval, uint256 intermediateBlockInterval); + error InvalidBlockInterval(uint256 slowBlockInterval, uint256 slowIntermediateBlockInterval); - /// @notice When the pre-Denim and Denim intervals do not yield the same intermediate root count. - error MismatchedIntermediateRootCount(uint256 preDenimCount, uint256 denimCount); + /// @notice When the slow- and fast-block intervals do not yield the same intermediate root count. + error MismatchedIntermediateRootCount(uint256 slowCount, uint256 fastCount); /// @notice When the block number is unexpected. error UnexpectedBlockNumber(uint256 expectedBlockNumber, uint256 actualBlockNumber); @@ -323,7 +327,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @param zkHashes The hashes of the ZK range and aggregate programs. /// @param configHash The hash of the rollup configuration. /// @param l2ChainId The chain ID of the L2 network. - /// @param intervalConfig The pre-Denim and Denim proposal block intervals. + /// @param intervalConfig The slow- and fast-block proposal intervals. /// @param scheduleConfig Upgrade registry and deterministic L2 timestamp configuration. constructor( GameType gameType_, @@ -356,10 +360,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { L2_GENESIS_BLOCK_NUMBER = scheduleConfig.genesisBlockNumber; L2_GENESIS_TIMESTAMP = scheduleConfig.genesisTimestamp; L2_BLOCK_TIME = scheduleConfig.blockTime; - BLOCK_INTERVAL = intervalConfig.blockInterval; - INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.intermediateBlockInterval; - DENIM_BLOCK_INTERVAL = intervalConfig.denimBlockInterval; - DENIM_INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.denimIntermediateBlockInterval; + SLOW_BLOCK_INTERVAL = intervalConfig.slowBlockInterval; + SLOW_INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.slowIntermediateBlockInterval; + FAST_BLOCK_INTERVAL = intervalConfig.fastBlockInterval; + FAST_INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.fastIntermediateBlockInterval; PROTOCOL_VERSIONS = scheduleConfig.protocolVersions; INITIALIZE_CALLDATA_SIZE = 0x8E + 0x20 * intermediateOutputRootsCount(); @@ -387,8 +391,8 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // - 0x20 l1 head (CWIA data offset: 0x34) // - 0x20 extraData (l2BlockNumber) (CWIA data offset: 0x54) // - 0x14 extraData (parent game address) (CWIA data offset: 0x74) - // - 0x20 x (BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL) extraData (intermediate roots) (CWIA data offset: - // 0x88) + // - 0x20 x (SLOW_BLOCK_INTERVAL / SLOW_INTERMEDIATE_BLOCK_INTERVAL) extraData (intermediate roots) (CWIA data + // offset: 0x88) // - 0x02 CWIA bytes // - 0x20 proof length location @@ -429,13 +433,13 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // Resolved once and threaded through both fork-sensitive decisions below, which would // otherwise re-read the schedule from `PROTOCOL_VERSIONS` a second time. - uint256 denimBlock = _denimActivationBlock(); + uint256 firstFastBlock = _firstFastBlock(); // The block number must be one block interval after the starting block number. The interval - // is selected on the starting block so the game chain stays contiguous across Denim. - (uint256 blockInterval,) = _intervalsAt(startingOutputRoot.l2SequenceNumber, denimBlock); - if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + blockInterval) { - revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + blockInterval, l2SequenceNumber()); + // is selected on the starting block so the game chain stays contiguous across the speedup. + (uint256 slowBlockInterval,) = _intervalsAt(startingOutputRoot.l2SequenceNumber, firstFastBlock); + if (l2SequenceNumber() != startingOutputRoot.l2SequenceNumber + slowBlockInterval) { + revert UnexpectedBlockNumber(startingOutputRoot.l2SequenceNumber + slowBlockInterval, l2SequenceNumber()); } // Set the game as initialized. @@ -446,14 +450,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { // // Note that this anchors on the *ending* block while the interval selection above anchors on // the *starting* block. That is deliberate, and it is what makes the straddling game work: it - // spans the pre-Denim interval its start selects, while pinning the post-Denim schedule its - // end falls under, so the prover knows Denim is active for the blocks past the boundary. + // spans the slow-block interval its start selects, while pinning the post-speedup schedule + // its end falls under, so the prover knows the fast cadence is active past the boundary. uint256 claimBlock = l2SequenceNumber(); if (claimBlock < L2_GENESIS_BLOCK_NUMBER) { revert L2BlockBeforeGenesis(claimBlock, L2_GENESIS_BLOCK_NUMBER); } - uint64 claimTimestamp = _l2Timestamp(claimBlock, denimBlock); + uint64 claimTimestamp = _l2Timestamp(claimBlock, firstFastBlock); // `ProtocolVersions` freezes mutations to an activation `FREEZE_WINDOW` before it takes // effect. Requiring the claim timestamp to have been reached on L1 additionally ensures the @@ -798,25 +802,25 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice Returns the proposal intervals that govern a game starting at `startingBlock`. /// @dev Offchain proposers and challengers must resolve intervals per game through this getter - /// rather than reading `BLOCK_INTERVAL` / `INTERMEDIATE_BLOCK_INTERVAL`, which describe - /// only games starting before Denim. + /// rather than reading `SLOW_BLOCK_INTERVAL` / `SLOW_INTERMEDIATE_BLOCK_INTERVAL`, which describe + /// only games starting on slow blocks. /// @dev Selection is on the starting block rather than the ending block so the game chain stays /// contiguous across the activation: every game satisfies - /// `end == parent.end + blockInterval` for the interval its own start selects. Exactly one - /// game straddles the activation and is proven under the pre-Denim interval, which is + /// `end == parent.end + slowBlockInterval` for the interval its own start selects. Exactly one + /// game straddles the activation and is proven under the slow-block interval, which is /// correct because provers apply fork rules per block by timestamp. /// @param startingBlock The starting L2 block number of the game. /// @return The block interval and the intermediate block interval governing that game. function intervalsForStartingBlock(uint256 startingBlock) public view returns (uint256, uint256) { - return _intervalsAt(startingBlock, _denimActivationBlock()); + return _intervalsAt(startingBlock, _firstFastBlock()); } /// @notice The number of intermediate output roots. /// @dev At least one as the proposal's root claim is considered an intermediate root. /// The constructor requires both interval pairs to yield the same count, so this is constant - /// across the Denim activation and the CWIA `extraData` layout never changes. + /// across the speedup activation and the CWIA `extraData` layout never changes. function intermediateOutputRootsCount() public view returns (uint256) { - return (BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL); + return (SLOW_BLOCK_INTERVAL / SLOW_INTERMEDIATE_BLOCK_INTERVAL); } /// @notice The intermediate output roots of the game. @@ -1147,10 +1151,10 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { bytes32 startingRoot = intermediateRootIndex == 0 ? startingOutputRoot.root.raw() : intermediateOutputRoot(intermediateRootIndex - 1); - (, uint256 intermediateBlockInterval) = intervalsForStartingBlock(startingOutputRoot.l2SequenceNumber); + (, uint256 slowIntermediateBlockInterval) = intervalsForStartingBlock(startingOutputRoot.l2SequenceNumber); uint64 startingL2SequenceNumber = - uint64(startingOutputRoot.l2SequenceNumber + intermediateRootIndex * intermediateBlockInterval); - uint64 endingL2SequenceNumber = startingL2SequenceNumber + uint64(intermediateBlockInterval); + uint64(startingOutputRoot.l2SequenceNumber + intermediateRootIndex * slowIntermediateBlockInterval); + uint64 endingL2SequenceNumber = startingL2SequenceNumber + uint64(slowIntermediateBlockInterval); return (startingRoot, startingL2SequenceNumber, endingL2SequenceNumber); } @@ -1160,14 +1164,14 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { return "0.2.0"; } - /// @notice Derives an L2 block timestamp using the legacy cadence before Denim and whole-second groups after it. + /// @notice Derives an L2 block timestamp: the slow cadence before the speedup, whole-second groups after it. /// @param claimBlock The L2 block number to derive a timestamp for. - /// @param denimBlock The Denim activation block, as returned by `_denimActivationBlock()`. - function _l2Timestamp(uint256 claimBlock, uint256 denimBlock) private view returns (uint64) { - if (claimBlock < denimBlock) return _legacyL2Timestamp(claimBlock); + /// @param firstFastBlock The speedup activation block, as returned by `_firstFastBlock()`. + function _l2Timestamp(uint256 claimBlock, uint256 firstFastBlock) private view returns (uint64) { + if (claimBlock < firstFastBlock) return _slowL2Timestamp(claimBlock); uint256 claimTimestamp = - uint256(_legacyL2Timestamp(denimBlock)) + (claimBlock - denimBlock) / DENIM_BLOCKS_PER_SECOND; + uint256(_slowL2Timestamp(firstFastBlock)) + (claimBlock - firstFastBlock) / FAST_BLOCKS_PER_SECOND; if (claimTimestamp > type(uint64).max) revert L2TimestampOverflow(claimBlock); return uint64(claimTimestamp); } @@ -1175,67 +1179,70 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { /// @notice Reverts unless both interval pairs are positive, divisible, and yield the same /// intermediate root count. function _validateIntervals(IntervalConfig memory intervalConfig) private pure { - uint256 blockInterval = intervalConfig.blockInterval; - uint256 intermediateBlockInterval = intervalConfig.intermediateBlockInterval; - uint256 denimBlockInterval = intervalConfig.denimBlockInterval; - uint256 denimIntermediateBlockInterval = intervalConfig.denimIntermediateBlockInterval; + uint256 slowBlockInterval = intervalConfig.slowBlockInterval; + uint256 slowIntermediateBlockInterval = intervalConfig.slowIntermediateBlockInterval; + uint256 fastBlockInterval = intervalConfig.fastBlockInterval; + uint256 fastIntermediateBlockInterval = intervalConfig.fastIntermediateBlockInterval; - if (blockInterval == 0 || intermediateBlockInterval == 0 || blockInterval % intermediateBlockInterval != 0) { - revert InvalidBlockInterval(blockInterval, intermediateBlockInterval); + if ( + slowBlockInterval == 0 || slowIntermediateBlockInterval == 0 + || slowBlockInterval % slowIntermediateBlockInterval != 0 + ) { + revert InvalidBlockInterval(slowBlockInterval, slowIntermediateBlockInterval); } if ( - denimBlockInterval == 0 || denimIntermediateBlockInterval == 0 - || denimBlockInterval % denimIntermediateBlockInterval != 0 + fastBlockInterval == 0 || fastIntermediateBlockInterval == 0 + || fastBlockInterval % fastIntermediateBlockInterval != 0 ) { - revert InvalidBlockInterval(denimBlockInterval, denimIntermediateBlockInterval); + revert InvalidBlockInterval(fastBlockInterval, fastIntermediateBlockInterval); } // The intermediate root count fixes the CWIA `extraData` layout, which is frozen for the // lifetime of this implementation. Both interval pairs must therefore agree on it. - uint256 preDenimCount = blockInterval / intermediateBlockInterval; - uint256 denimCount = denimBlockInterval / denimIntermediateBlockInterval; - if (preDenimCount != denimCount) revert MismatchedIntermediateRootCount(preDenimCount, denimCount); + uint256 slowCount = slowBlockInterval / slowIntermediateBlockInterval; + uint256 fastCount = fastBlockInterval / fastIntermediateBlockInterval; + if (slowCount != fastCount) revert MismatchedIntermediateRootCount(slowCount, fastCount); } - /// @notice Returns the first L2 block number governed by Denim, or `type(uint256).max` when Denim - /// is not scheduled. + /// @notice Returns the first L2 block number produced at the fast cadence, or `type(uint256).max` + /// when the speedup is not scheduled. /// @dev Reading the live schedule is safe despite it being mutable, in both directions: /// - /// - A game that selected the Denim intervals has a starting block at or past the + /// - A game that selected the fast-block intervals has a starting block at or past the /// activation, so the activation is in the past. `ProtocolVersions._assertNotFrozen` /// rejects every mutation of a passed activation, from `setTimestamp` and /// `delayTimestamp` alike, so that game's selection can never be revoked. - /// - A game that selected the pre-Denim intervals cannot be pulled across the boundary + /// - A game that selected the slow-block intervals cannot be pulled across the boundary /// either, including by the owner moving the activation *earlier* rather than later. /// `initializeWithInitData` rejects a game whose ending L2 timestamp L1 has not yet /// reached, so every initialized game satisfies `startingTimestamp < endingTimestamp <= /// block.timestamp`, while any new activation must clear `block.timestamp + MIN_NOTICE`. /// The activation therefore always lands after the game's starting block. - function _denimActivationBlock() private view returns (uint256) { + function _firstFastBlock() private view returns (uint256) { uint64[] memory schedule = PROTOCOL_VERSIONS.getSchedule(); - if (schedule.length <= DENIM_UPGRADE_INDEX) return type(uint256).max; + if (schedule.length <= FAST_BLOCK_UPGRADE_INDEX) return type(uint256).max; - uint64 denimActivationTimestamp = schedule[DENIM_UPGRADE_INDEX]; - if (denimActivationTimestamp == 0) return type(uint256).max; + uint64 fastActivationTimestamp = schedule[FAST_BLOCK_UPGRADE_INDEX]; + if (fastActivationTimestamp == 0) return type(uint256).max; - uint256 blocksUntilDenim; - if (denimActivationTimestamp > L2_GENESIS_TIMESTAMP) { - blocksUntilDenim = FixedPointMathLib.divUp(denimActivationTimestamp - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME); + uint256 blocksUntilFast; + if (fastActivationTimestamp > L2_GENESIS_TIMESTAMP) { + blocksUntilFast = FixedPointMathLib.divUp(fastActivationTimestamp - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME); } - return L2_GENESIS_BLOCK_NUMBER + blocksUntilDenim; + return L2_GENESIS_BLOCK_NUMBER + blocksUntilFast; } /// @notice Selects the proposal intervals governing a game, from its starting block number and an - /// already-resolved Denim activation block. - /// @dev Takes `denimBlock` rather than resolving it so a caller making more than one + /// already-resolved speedup activation block. + /// @dev Takes `firstFastBlock` rather than resolving it so a caller making more than one /// fork-sensitive decision pays for `PROTOCOL_VERSIONS.getSchedule()` once. - function _intervalsAt(uint256 startingBlock, uint256 denimBlock) private view returns (uint256, uint256) { - if (startingBlock < denimBlock) return (BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); - return (DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + function _intervalsAt(uint256 startingBlock, uint256 firstFastBlock) private view returns (uint256, uint256) { + if (startingBlock < firstFastBlock) return (SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL); + return (FAST_BLOCK_INTERVAL, FAST_INTERMEDIATE_BLOCK_INTERVAL); } - /// @notice Derives an L2 block timestamp using the pre-Denim block cadence. - function _legacyL2Timestamp(uint256 claimBlock) private view returns (uint64) { + /// @notice Derives an L2 block timestamp using the slow block cadence. + function _slowL2Timestamp(uint256 claimBlock) private view returns (uint64) { uint256 blocksSinceGenesis = claimBlock - L2_GENESIS_BLOCK_NUMBER; uint256 maxBlocks = (type(uint64).max - L2_GENESIS_TIMESTAMP) / L2_BLOCK_TIME; if (blocksSinceGenesis > maxBlocks) revert L2TimestampOverflow(claimBlock); diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index 6894b3552..ff44eb887 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -90,10 +90,10 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { bytes32(uint256(4)), deploy.cfg().l2ChainId(), AggregateVerifier.IntervalConfig({ - blockInterval: 100, - intermediateBlockInterval: 10, - denimBlockInterval: 1000, - denimIntermediateBlockInterval: 100 + slowBlockInterval: 100, + slowIntermediateBlockInterval: 10, + fastBlockInterval: 1000, + fastIntermediateBlockInterval: 100 }), AggregateVerifier.ScheduleConfig({ protocolVersions: protocolVersions, genesisBlockNumber: 0, genesisTimestamp: 1, blockTime: 2 @@ -103,8 +103,8 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { disputeGameFactory.setInitBond(respectedGameType, 0); Proposal memory startingRoot = anchorStateRegistry.getStartingAnchorRoot(); - (uint256 blockInterval,) = gameImpl.intervalsForStartingBlock(startingRoot.l2SequenceNumber); - _proposedBlockNumber = startingRoot.l2SequenceNumber + blockInterval; + (uint256 slowBlockInterval,) = gameImpl.intervalsForStartingBlock(startingRoot.l2SequenceNumber); + _proposedBlockNumber = startingRoot.l2SequenceNumber + slowBlockInterval; depositor = makeAddr("depositor"); diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index a005fe1f7..d3ed4946b 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -20,8 +20,8 @@ import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { using LibClone for address; - uint256 private constant DENIM_UPGRADE_INDEX = 13; - uint256 private constant DENIM_BLOCKS_PER_SECOND = 5; + uint256 private constant FAST_BLOCK_UPGRADE_INDEX = 13; + uint256 private constant FAST_BLOCKS_PER_SECOND = 5; AggregateVerifier private aggregateVerifierImpl; @@ -43,8 +43,8 @@ contract AggregateVerifierTest is BaseTest { /// @notice Initialization pins the upgrades active at the claimed L2 block, independently of /// the L1 game-creation timestamp, and later schedule changes cannot alter the pin. function test_initialize_pinsScheduleId_succeeds() public { - uint64 firstGameTimestamp = _l2Timestamp(BLOCK_INTERVAL); - uint64 secondActivationTimestamp = _l2Timestamp(BLOCK_INTERVAL + BLOCK_INTERVAL / 2); + uint64 firstGameTimestamp = _l2Timestamp(SLOW_BLOCK_INTERVAL); + uint64 secondActivationTimestamp = _l2Timestamp(SLOW_BLOCK_INTERVAL + SLOW_BLOCK_INTERVAL / 2); // The first upgrade is active at the first game's L2 timestamp; the second is not. uint64[] memory schedule = new uint64[](2); @@ -80,7 +80,7 @@ contract AggregateVerifierTest is BaseTest { /// @notice Consecutive games on opposite sides of an L2 activation boundary pin different /// schedule commitments. function test_initialize_scheduleChangesAtL2ActivationBoundary_succeeds() public { - uint64 activationTimestamp = _l2Timestamp(BLOCK_INTERVAL + BLOCK_INTERVAL / 2); + uint64 activationTimestamp = _l2Timestamp(SLOW_BLOCK_INTERVAL + SLOW_BLOCK_INTERVAL / 2); uint64[] memory schedule = new uint64[](1); schedule[0] = activationTimestamp; _importProtocolVersionsSchedule(schedule); @@ -106,36 +106,36 @@ contract AggregateVerifierTest is BaseTest { assertEq(secondGame.scheduleId(), protocolVersions.activatedScheduleId(_l2Timestamp(currentL2BlockNumber))); } - function test_initialize_denimBlockTimestamps_succeeds() public { - uint64 denimActivationTimestamp = L2_GENESIS_TIMESTAMP + 1; - uint64 denimBlockTimestamp = L2_GENESIS_TIMESTAMP + L2_BLOCK_TIME; - uint64[] memory schedule = new uint64[](DENIM_UPGRADE_INDEX + 2); - for (uint256 i; i < DENIM_UPGRADE_INDEX; i++) { + function test_initialize_fastBlockTimestamps_succeeds() public { + uint64 fastActivationTimestamp = L2_GENESIS_TIMESTAMP + 1; + uint64 firstFastBlockTimestamp = L2_GENESIS_TIMESTAMP + L2_BLOCK_TIME; + uint64[] memory schedule = new uint64[](FAST_BLOCK_UPGRADE_INDEX + 2); + for (uint256 i; i < FAST_BLOCK_UPGRADE_INDEX; i++) { schedule[i] = L2_GENESIS_TIMESTAMP; } - schedule[DENIM_UPGRADE_INDEX] = denimActivationTimestamp; - schedule[DENIM_UPGRADE_INDEX + 1] = denimBlockTimestamp + 1; + schedule[FAST_BLOCK_UPGRADE_INDEX] = fastActivationTimestamp; + schedule[FAST_BLOCK_UPGRADE_INDEX + 1] = firstFastBlockTimestamp + 1; _importProtocolVersionsSchedule(schedule); _setSingleBlockAggregateVerifier(L2_GENESIS_TIMESTAMP); - bytes32 denimScheduleId = protocolVersions.scheduleId(DENIM_UPGRADE_INDEX); - bytes32 postDenimScheduleId = protocolVersions.scheduleId(DENIM_UPGRADE_INDEX + 1); + bytes32 speedupScheduleId = protocolVersions.scheduleId(FAST_BLOCK_UPGRADE_INDEX); + bytes32 postSpeedupScheduleId = protocolVersions.scheduleId(FAST_BLOCK_UPGRADE_INDEX + 1); address parent = address(anchorStateRegistry); - for (uint256 l2BlockNumber = 1; l2BlockNumber <= DENIM_BLOCKS_PER_SECOND + 1; l2BlockNumber++) { - vm.warp(denimBlockTimestamp + (l2BlockNumber - 1) / DENIM_BLOCKS_PER_SECOND); + for (uint256 l2BlockNumber = 1; l2BlockNumber <= FAST_BLOCKS_PER_SECOND + 1; l2BlockNumber++) { + vm.warp(firstFastBlockTimestamp + (l2BlockNumber - 1) / FAST_BLOCKS_PER_SECOND); AggregateVerifier game = _createSingleBlockGame(l2BlockNumber, parent); assertEq( - game.scheduleId(), l2BlockNumber <= DENIM_BLOCKS_PER_SECOND ? denimScheduleId : postDenimScheduleId + game.scheduleId(), l2BlockNumber <= FAST_BLOCKS_PER_SECOND ? speedupScheduleId : postSpeedupScheduleId ); parent = address(game); } } - function test_initialize_unscheduledDenimUsesLegacyTimestamp_succeeds() public { - uint64[] memory schedule = new uint64[](DENIM_UPGRADE_INDEX + 1); - for (uint256 i; i < DENIM_UPGRADE_INDEX; i++) { + function test_initialize_unscheduledSpeedupUsesSlowTimestamp_succeeds() public { + uint64[] memory schedule = new uint64[](FAST_BLOCK_UPGRADE_INDEX + 1); + for (uint256 i; i < FAST_BLOCK_UPGRADE_INDEX; i++) { schedule[i] = L2_GENESIS_TIMESTAMP; } _importProtocolVersionsSchedule(schedule); @@ -152,76 +152,76 @@ contract AggregateVerifierTest is BaseTest { vm.warp(legacyBlockTimestamp); AggregateVerifier game = _createSingleBlockGame(1, address(anchorStateRegistry)); - assertEq(game.scheduleId(), protocolVersions.scheduleId(DENIM_UPGRADE_INDEX - 1)); + assertEq(game.scheduleId(), protocolVersions.scheduleId(FAST_BLOCK_UPGRADE_INDEX - 1)); } - /// @notice Intervals are selected on the game's starting block relative to the Denim activation - /// block, so the chain of games stays contiguous across the fork. - function test_intervalsForStartingBlock_selectsOnDenimActivationBlock_succeeds() public { + /// @notice Intervals are selected on the game's starting block relative to the first fast block, + /// so the chain of games stays contiguous across the fork. + function test_intervalsForStartingBlock_selectsOnFirstFastBlock_succeeds() public { // divUp(86500 - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME) == 50. - _importDenimSchedule(L2_GENESIS_TIMESTAMP + 100); - uint256 denimActivationBlock = 50; + _importSpeedupSchedule(L2_GENESIS_TIMESTAMP + 100); + uint256 firstFastBlock = 50; - _assertIntervals(denimActivationBlock - 1, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); - _assertIntervals(denimActivationBlock, DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); - _assertIntervals(denimActivationBlock + 1, DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(firstFastBlock - 1, SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(firstFastBlock, FAST_BLOCK_INTERVAL, FAST_INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(firstFastBlock + 1, FAST_BLOCK_INTERVAL, FAST_INTERMEDIATE_BLOCK_INTERVAL); } - function test_intervalsForStartingBlock_denimUnscheduled_succeeds() public view { - _assertIntervals(0, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); - _assertIntervals(type(uint64).max, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + function test_intervalsForStartingBlock_speedupUnscheduled_succeeds() public view { + _assertIntervals(0, SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL); + _assertIntervals(type(uint64).max, SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL); } - /// @notice A game starting at or after the Denim activation block must span DENIM_BLOCK_INTERVAL. - function test_initialize_denimIntervals_succeeds() public { - // The activation timestamp equals genesis, so every block including the anchor is Denim. - _importDenimSchedule(L2_GENESIS_TIMESTAMP); - _assertIntervals(0, DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + /// @notice A game starting at or after the first fast block must span FAST_BLOCK_INTERVAL. + function test_initialize_fastIntervals_succeeds() public { + // The activation timestamp equals genesis, so every block including the anchor is fast. + _importSpeedupSchedule(L2_GENESIS_TIMESTAMP); + _assertIntervals(0, FAST_BLOCK_INTERVAL, FAST_INTERMEDIATE_BLOCK_INTERVAL); vm.expectRevert( abi.encodeWithSelector( - AggregateVerifier.UnexpectedBlockNumber.selector, DENIM_BLOCK_INTERVAL, BLOCK_INTERVAL + AggregateVerifier.UnexpectedBlockNumber.selector, FAST_BLOCK_INTERVAL, SLOW_BLOCK_INTERVAL ) ); - _createGameEndingAt(BLOCK_INTERVAL); + _createGameEndingAt(SLOW_BLOCK_INTERVAL); - AggregateVerifier game = _createGameEndingAt(DENIM_BLOCK_INTERVAL); - assertEq(game.l2SequenceNumber(), DENIM_BLOCK_INTERVAL); + AggregateVerifier game = _createGameEndingAt(FAST_BLOCK_INTERVAL); + assertEq(game.l2SequenceNumber(), FAST_BLOCK_INTERVAL); } /// @notice The one game whose range contains the activation block starts before it, so it is - /// proven under the pre-Denim interval. - function test_initialize_straddlingGame_usesPreDenimInterval_succeeds() public { + /// proven under the slow-block interval. + function test_initialize_straddlingGame_usesSlowInterval_succeeds() public { // Activation block 50 falls inside the first game's [0, 100) range. - _importDenimSchedule(L2_GENESIS_TIMESTAMP + 100); - _assertIntervals(0, BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + _importSpeedupSchedule(L2_GENESIS_TIMESTAMP + 100); + _assertIntervals(0, SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL); vm.expectRevert( abi.encodeWithSelector( - AggregateVerifier.UnexpectedBlockNumber.selector, BLOCK_INTERVAL, DENIM_BLOCK_INTERVAL + AggregateVerifier.UnexpectedBlockNumber.selector, SLOW_BLOCK_INTERVAL, FAST_BLOCK_INTERVAL ) ); - _createGameEndingAt(DENIM_BLOCK_INTERVAL); + _createGameEndingAt(FAST_BLOCK_INTERVAL); - AggregateVerifier game = _createGameEndingAt(BLOCK_INTERVAL); - assertEq(game.l2SequenceNumber(), BLOCK_INTERVAL); + AggregateVerifier game = _createGameEndingAt(SLOW_BLOCK_INTERVAL); + assertEq(game.l2SequenceNumber(), SLOW_BLOCK_INTERVAL); } /// @notice `challenge` derives the intermediate sub-range from the interval the game's starting /// block selects. This is the second fork-sensitive call site, and unlike the block /// number check its output goes into the journal the prover signs, so a stale interval /// here makes a valid challenge unconstructable rather than reverting loudly. - function test_challenge_denimIntermediateInterval_succeeds() public { - _importDenimSchedule(L2_GENESIS_TIMESTAMP); - _assertChallengeIntermediateRange(DENIM_BLOCK_INTERVAL, DENIM_INTERMEDIATE_BLOCK_INTERVAL); + function test_challenge_fastIntermediateInterval_succeeds() public { + _importSpeedupSchedule(L2_GENESIS_TIMESTAMP); + _assertChallengeIntermediateRange(FAST_BLOCK_INTERVAL, FAST_INTERMEDIATE_BLOCK_INTERVAL); } - /// @notice The straddling game's sub-ranges stay pre-Denim sized, matching the interval its own + /// @notice The straddling game's sub-ranges stay slow-block sized, matching the interval its own /// starting block selects. - function test_challenge_straddlingGameUsesPreDenimIntermediateInterval_succeeds() public { + function test_challenge_straddlingGameUsesSlowIntermediateInterval_succeeds() public { // Activation block 50 falls inside the first game's [0, 100) range. - _importDenimSchedule(L2_GENESIS_TIMESTAMP + 100); - _assertChallengeIntermediateRange(BLOCK_INTERVAL, INTERMEDIATE_BLOCK_INTERVAL); + _importSpeedupSchedule(L2_GENESIS_TIMESTAMP + 100); + _assertChallengeIntermediateRange(SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL); } function test_constructor_mismatchedIntermediateRootCount_reverts() public { @@ -229,23 +229,23 @@ contract AggregateVerifierTest is BaseTest { vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MismatchedIntermediateRootCount.selector, 10, 5)); _deployAggregateVerifier( AggregateVerifier.IntervalConfig({ - blockInterval: BLOCK_INTERVAL, - intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: 200 + slowBlockInterval: SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: 200 }), _defaultScheduleConfig() ); } - function test_constructor_invalidDenimBlockIntervals_reverts() public { + function test_constructor_invalidFastBlockIntervals_reverts() public { vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 1000, 0)); _deployAggregateVerifier( AggregateVerifier.IntervalConfig({ - blockInterval: BLOCK_INTERVAL, - intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: 0 + slowBlockInterval: SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: 0 }), _defaultScheduleConfig() ); @@ -256,7 +256,7 @@ contract AggregateVerifierTest is BaseTest { function test_initialize_l2TimestampInFuture_reverts() public { // Scheduling the upgrade at the first game's deterministic L2 timestamp and leaving the L1 // clock short of it is the window the finding exploits. - uint64 activationTimestamp = _l2Timestamp(BLOCK_INTERVAL); + uint64 activationTimestamp = _l2Timestamp(SLOW_BLOCK_INTERVAL); uint64[] memory schedule = new uint64[](1); schedule[0] = activationTimestamp; _importProtocolVersionsSchedule(schedule); @@ -305,7 +305,7 @@ contract AggregateVerifierTest is BaseTest { function test_initialize_l2BlockBeforeGenesis_reverts() public { AggregateVerifier.ScheduleConfig memory scheduleConfig = AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), - genesisBlockNumber: BLOCK_INTERVAL + 1, + genesisBlockNumber: SLOW_BLOCK_INTERVAL + 1, genesisTimestamp: 0, blockTime: L2_BLOCK_TIME }); @@ -315,7 +315,7 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = _advanceL2BlockAndClaim(); vm.expectRevert( abi.encodeWithSelector( - AggregateVerifier.L2BlockBeforeGenesis.selector, currentL2BlockNumber, BLOCK_INTERVAL + 1 + AggregateVerifier.L2BlockBeforeGenesis.selector, currentL2BlockNumber, SLOW_BLOCK_INTERVAL + 1 ) ); _createAggregateVerifierGame( @@ -348,23 +348,23 @@ contract AggregateVerifierTest is BaseTest { ); } - function test_initialize_denimTimestampOverflow_reverts() public { + function test_initialize_fastTimestampOverflow_reverts() public { uint64 genesisTimestamp = type(uint64).max - L2_BLOCK_TIME; - uint64[] memory schedule = new uint64[](DENIM_UPGRADE_INDEX + 1); - for (uint256 i; i < DENIM_UPGRADE_INDEX; i++) { + uint64[] memory schedule = new uint64[](FAST_BLOCK_UPGRADE_INDEX + 1); + for (uint256 i; i < FAST_BLOCK_UPGRADE_INDEX; i++) { schedule[i] = genesisTimestamp; } - schedule[DENIM_UPGRADE_INDEX] = genesisTimestamp + 1; + schedule[FAST_BLOCK_UPGRADE_INDEX] = genesisTimestamp + 1; _importProtocolVersionsSchedule(schedule); _setSingleBlockAggregateVerifier(genesisTimestamp); vm.warp(uint256(type(uint64).max) + 3); address parent = address(anchorStateRegistry); - for (uint256 l2BlockNumber = 1; l2BlockNumber <= DENIM_BLOCKS_PER_SECOND; l2BlockNumber++) { + for (uint256 l2BlockNumber = 1; l2BlockNumber <= FAST_BLOCKS_PER_SECOND; l2BlockNumber++) { parent = address(_createSingleBlockGame(l2BlockNumber, parent)); } - uint256 overflowingBlock = DENIM_BLOCKS_PER_SECOND + 1; + uint256 overflowingBlock = FAST_BLOCKS_PER_SECOND + 1; vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L2TimestampOverflow.selector, overflowingBlock)); _createSingleBlockGame(overflowingBlock, parent); } @@ -489,7 +489,7 @@ contract AggregateVerifierTest is BaseTest { /// @dev Parent is a real `AggregateVerifier` clone initialized like a factory game, but deployed without /// `_finalizeGameCreation`, so the factory UUID mapping has no entry. function testInitializeFailsIfParentGameNotFactoryRegistered() public { - currentL2BlockNumber += BLOCK_INTERVAL; + currentL2BlockNumber += SLOW_BLOCK_INTERVAL; Claim parentRootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "parent"))); AggregateVerifier unregisteredParent = _deployAggregateVerifierCloneWithoutFactoryRegistration( @@ -500,7 +500,7 @@ contract AggregateVerifierTest is BaseTest { _generateProof("parent-tee", AggregateVerifier.ProofType.TEE) ); - currentL2BlockNumber += BLOCK_INTERVAL; + currentL2BlockNumber += SLOW_BLOCK_INTERVAL; Claim childRootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "child"))); vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); @@ -592,13 +592,13 @@ contract AggregateVerifierTest is BaseTest { } function testDeployWithInvalidBlockIntervals() public { - _expectDeployWithInvalidBlockIntervalsReverts(0, INTERMEDIATE_BLOCK_INTERVAL); - _expectDeployWithInvalidBlockIntervalsReverts(BLOCK_INTERVAL, 0); + _expectDeployWithInvalidBlockIntervalsReverts(0, SLOW_INTERMEDIATE_BLOCK_INTERVAL); + _expectDeployWithInvalidBlockIntervalsReverts(SLOW_BLOCK_INTERVAL, 0); _expectDeployWithInvalidBlockIntervalsReverts(3, 2); } function _advanceL2BlockAndClaim() private returns (Claim rootClaim) { - currentL2BlockNumber += BLOCK_INTERVAL; + currentL2BlockNumber += SLOW_BLOCK_INTERVAL; return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); } @@ -621,7 +621,10 @@ contract AggregateVerifierTest is BaseTest { function _setSingleBlockAggregateVerifier(uint64 genesisTimestamp) private { AggregateVerifier implementation = _deployAggregateVerifier( AggregateVerifier.IntervalConfig({ - blockInterval: 1, intermediateBlockInterval: 1, denimBlockInterval: 1, denimIntermediateBlockInterval: 1 + slowBlockInterval: 1, + slowIntermediateBlockInterval: 1, + fastBlockInterval: 1, + fastIntermediateBlockInterval: 1 }), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), @@ -727,17 +730,17 @@ contract AggregateVerifierTest is BaseTest { } function _expectDeployWithInvalidBlockIntervalsReverts( - uint256 blockInterval, - uint256 intermediateBlockInterval + uint256 slowBlockInterval, + uint256 slowIntermediateBlockInterval ) private { vm.expectRevert( abi.encodeWithSelector( - AggregateVerifier.InvalidBlockInterval.selector, blockInterval, intermediateBlockInterval + AggregateVerifier.InvalidBlockInterval.selector, slowBlockInterval, slowIntermediateBlockInterval ) ); - _deployAggregateVerifierWithIntervals(blockInterval, intermediateBlockInterval); + _deployAggregateVerifierWithIntervals(slowBlockInterval, slowIntermediateBlockInterval); } /// @notice Clones the implementation like the factory, but skips `_finalizeGameCreation`. @@ -766,18 +769,18 @@ contract AggregateVerifierTest is BaseTest { } function _deployAggregateVerifierWithIntervals( - uint256 blockInterval, - uint256 intermediateBlockInterval + uint256 slowBlockInterval, + uint256 slowIntermediateBlockInterval ) private returns (AggregateVerifier) { return _deployAggregateVerifier( AggregateVerifier.IntervalConfig({ - blockInterval: blockInterval, - intermediateBlockInterval: intermediateBlockInterval, - denimBlockInterval: blockInterval, - denimIntermediateBlockInterval: intermediateBlockInterval + slowBlockInterval: slowBlockInterval, + slowIntermediateBlockInterval: slowIntermediateBlockInterval, + fastBlockInterval: slowBlockInterval, + fastIntermediateBlockInterval: slowIntermediateBlockInterval }), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), @@ -795,14 +798,14 @@ contract AggregateVerifierTest is BaseTest { return _deployAggregateVerifier(_defaultIntervalConfig(), scheduleConfig); } - /// @dev Registers a schedule whose only meaningful entry is the Denim activation, and rebinds + /// @dev Registers a schedule whose only meaningful entry is the speedup activation, and rebinds /// the implementation to it. - function _importDenimSchedule(uint64 denimActivationTimestamp) private { - uint64[] memory schedule = new uint64[](DENIM_UPGRADE_INDEX + 1); - for (uint256 i; i < DENIM_UPGRADE_INDEX; i++) { + function _importSpeedupSchedule(uint64 fastActivationTimestamp) private { + uint64[] memory schedule = new uint64[](FAST_BLOCK_UPGRADE_INDEX + 1); + for (uint256 i; i < FAST_BLOCK_UPGRADE_INDEX; i++) { schedule[i] = L2_GENESIS_TIMESTAMP; } - schedule[DENIM_UPGRADE_INDEX] = denimActivationTimestamp; + schedule[FAST_BLOCK_UPGRADE_INDEX] = fastActivationTimestamp; _importProtocolVersionsSchedule(schedule); aggregateVerifierImpl = AggregateVerifier(address(factory.gameImpls(GameTypes.AGGREGATE_VERIFIER))); } @@ -815,10 +818,10 @@ contract AggregateVerifierTest is BaseTest { private view { - (uint256 blockInterval, uint256 intermediateBlockInterval) = + (uint256 slowBlockInterval, uint256 slowIntermediateBlockInterval) = aggregateVerifierImpl.intervalsForStartingBlock(startingBlock); - assertEq(blockInterval, expectedBlockInterval); - assertEq(intermediateBlockInterval, expectedIntermediateBlockInterval); + assertEq(slowBlockInterval, expectedBlockInterval); + assertEq(slowIntermediateBlockInterval, expectedIntermediateBlockInterval); } /// @dev Challenges intermediate root 0 of a game ending at `endingBlock` and asserts the range @@ -858,10 +861,10 @@ contract AggregateVerifierTest is BaseTest { /// verifiers, so only their count has to match the implementation. function _createGameEndingAt(uint256 endingBlock) private returns (AggregateVerifier) { Claim rootClaim = Claim.wrap(keccak256(abi.encode(endingBlock))); - // Both interval pairs are constructor-checked to yield the same count, so the pre-Denim ratio + // Both interval pairs are constructor-checked to yield the same count, so the slow-block ratio // is the count on either side. Reading it off the implementation instead would put a // staticcall between a caller's `vm.expectRevert` and the call it is meant to apply to. - uint256 count = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; + uint256 count = SLOW_BLOCK_INTERVAL / SLOW_INTERMEDIATE_BLOCK_INTERVAL; bytes32[] memory intermediateRoots = new bytes32[](count); for (uint256 i; i < count - 1; i++) { intermediateRoots[i] = keccak256(abi.encode(endingBlock, i)); @@ -895,10 +898,10 @@ contract AggregateVerifierTest is BaseTest { function _defaultIntervalConfig() private pure returns (AggregateVerifier.IntervalConfig memory) { return AggregateVerifier.IntervalConfig({ - blockInterval: BLOCK_INTERVAL, - intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + slowBlockInterval: SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: FAST_INTERMEDIATE_BLOCK_INTERVAL }); } diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index 0f794f967..b97f63c1c 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -50,7 +50,7 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { // Get the actual anchor roots (, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - validL2BlockNumber = l2BlockNumber + BLOCK_INTERVAL; + validL2BlockNumber = l2BlockNumber + SLOW_BLOCK_INTERVAL; Claim rootClaim = Claim.wrap(keccak256(abi.encode(validL2BlockNumber))); bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); gameProxy = IDisputeGame( diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index afb18b533..1f75627da 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -31,11 +31,11 @@ contract BaseTest is Test { uint64 internal constant L2_BLOCK_TIME = 2; // AggregateVerifier expects evenly spaced intermediate roots. - uint256 internal constant BLOCK_INTERVAL = 100; - uint256 internal constant INTERMEDIATE_BLOCK_INTERVAL = 10; - uint256 internal constant DENIM_BLOCK_INTERVAL = 1000; - uint256 internal constant DENIM_INTERMEDIATE_BLOCK_INTERVAL = 100; - uint256 private constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; + uint256 internal constant SLOW_BLOCK_INTERVAL = 100; + uint256 internal constant SLOW_INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 internal constant FAST_BLOCK_INTERVAL = 1000; + uint256 internal constant FAST_INTERMEDIATE_BLOCK_INTERVAL = 100; + uint256 private constant INTERMEDIATE_ROOTS_COUNT = SLOW_BLOCK_INTERVAL / SLOW_INTERMEDIATE_BLOCK_INTERVAL; uint256 internal constant INIT_BOND = 1 ether; uint256 internal constant DELAYED_WETH_DELAY = 1 days; @@ -139,10 +139,10 @@ contract BaseTest is Test { CONFIG_HASH, L2_CHAIN_ID, AggregateVerifier.IntervalConfig({ - blockInterval: BLOCK_INTERVAL, - intermediateBlockInterval: INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: DENIM_INTERMEDIATE_BLOCK_INTERVAL + slowBlockInterval: SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: FAST_INTERMEDIATE_BLOCK_INTERVAL }), AggregateVerifier.ScheduleConfig({ protocolVersions: IProtocolVersions(address(protocolVersions)), @@ -226,9 +226,10 @@ contract BaseTest is Test { function _generateIntermediateRoots(uint256 l2BlockNumber, Claim rootClaim) internal pure returns (bytes memory) { bytes32[] memory intermediateRoots = new bytes32[](INTERMEDIATE_ROOTS_COUNT); - uint256 startingL2BlockNumber = l2BlockNumber - BLOCK_INTERVAL; + uint256 startingL2BlockNumber = l2BlockNumber - SLOW_BLOCK_INTERVAL; for (uint256 i = 1; i < INTERMEDIATE_ROOTS_COUNT; i++) { - intermediateRoots[i - 1] = keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)); + intermediateRoots[i - 1] = + keccak256(abi.encode(startingL2BlockNumber + SLOW_INTERMEDIATE_BLOCK_INTERVAL * i)); } intermediateRoots[INTERMEDIATE_ROOTS_COUNT - 1] = rootClaim.raw(); diff --git a/test/L1/proofs/Challenge.t.sol b/test/L1/proofs/Challenge.t.sol index 0aca7e4d2..3e425684c 100644 --- a/test/L1/proofs/Challenge.t.sol +++ b/test/L1/proofs/Challenge.t.sol @@ -12,7 +12,7 @@ import { Verifier } from "src/L1/proofs/Verifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract ChallengeTest is BaseTest { - uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; + uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = SLOW_BLOCK_INTERVAL / SLOW_INTERMEDIATE_BLOCK_INTERVAL - 1; function testChallengeTEEProofWithZKProof() public { AggregateVerifier game = @@ -158,7 +158,7 @@ contract ChallengeTest is BaseTest { private returns (AggregateVerifier) { - currentL2BlockNumber += BLOCK_INTERVAL; + currentL2BlockNumber += SLOW_BLOCK_INTERVAL; Claim rootClaim = _claim(claimSalt); bytes memory proof = _generateProof(proofSalt, proofType); return _createAggregateVerifierGame(prover, rootClaim, currentL2BlockNumber, parent, proof); diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index e776a80a4..a09931e65 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -45,10 +45,10 @@ contract DisputeGameFactory_FakeClone_Harness { abstract contract DisputeGameFactory_TestInit is CommonTest { uint256 internal constant DEFAULT_INIT_BOND = 0.08 ether; uint256 internal constant L2_CHAIN_ID = 111; - uint256 internal constant AGGREGATE_BLOCK_INTERVAL = 100; - uint256 internal constant AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL = 10; - uint256 internal constant AGGREGATE_DENIM_BLOCK_INTERVAL = 1000; - uint256 internal constant AGGREGATE_DENIM_INTERMEDIATE_BLOCK_INTERVAL = 100; + uint256 internal constant AGGREGATE_SLOW_BLOCK_INTERVAL = 100; + uint256 internal constant AGGREGATE_SLOW_INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 internal constant AGGREGATE_FAST_BLOCK_INTERVAL = 1000; + uint256 internal constant AGGREGATE_FAST_INTERMEDIATE_BLOCK_INTERVAL = 100; uint32 internal constant MAX_GAME_TYPE = 8; address internal constant NON_OWNER = address(0xBEEF); @@ -232,10 +232,10 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { bytes32(uint256(4)), L2_CHAIN_ID, AggregateVerifier.IntervalConfig({ - blockInterval: AGGREGATE_BLOCK_INTERVAL, - intermediateBlockInterval: AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL, - denimBlockInterval: AGGREGATE_DENIM_BLOCK_INTERVAL, - denimIntermediateBlockInterval: AGGREGATE_DENIM_INTERMEDIATE_BLOCK_INTERVAL + slowBlockInterval: AGGREGATE_SLOW_BLOCK_INTERVAL, + slowIntermediateBlockInterval: AGGREGATE_SLOW_INTERMEDIATE_BLOCK_INTERVAL, + fastBlockInterval: AGGREGATE_FAST_BLOCK_INTERVAL, + fastIntermediateBlockInterval: AGGREGATE_FAST_INTERMEDIATE_BLOCK_INTERVAL }), AggregateVerifier.ScheduleConfig({ protocolVersions: protocolVersions, genesisBlockNumber: 0, genesisTimestamp: 0, blockTime: 2 @@ -250,12 +250,14 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { for (uint256 i = 1; i < intermediateRootsCount; i++) { intermediateRoots = abi.encodePacked( intermediateRoots, - keccak256(abi.encode(startingRoot.l2SequenceNumber + AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL * i)) + keccak256(abi.encode(startingRoot.l2SequenceNumber + AGGREGATE_SLOW_INTERMEDIATE_BLOCK_INTERVAL * i)) ); } intermediateRoots = abi.encodePacked(intermediateRoots, rootClaim.raw()); bytes memory extraData = abi.encodePacked( - startingRoot.l2SequenceNumber + AGGREGATE_BLOCK_INTERVAL, address(anchorStateRegistry), intermediateRoots + startingRoot.l2SequenceNumber + AGGREGATE_SLOW_BLOCK_INTERVAL, + address(anchorStateRegistry), + intermediateRoots ); bytes memory proof = abi.encodePacked( uint8(AggregateVerifier.ProofType.TEE), blockhash(block.number - 1), block.number - 1, bytes32(0) @@ -266,7 +268,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { // The claimed L2 block's deterministic timestamp (block number x blockTime) must have // already passed on L1 for the game to be creatable. - vm.warp((startingRoot.l2SequenceNumber + AGGREGATE_BLOCK_INTERVAL) * 2); + vm.warp((startingRoot.l2SequenceNumber + AGGREGATE_SLOW_BLOCK_INTERVAL) * 2); uint256 gameCountBefore = disputeGameFactory.gameCount(); IDisputeGame proxy = disputeGameFactory.createWithInitData{ value: bondAmount }( @@ -281,13 +283,13 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { assertEq(gameV2.extraData(), extraData); assertEq(gameV2.L2_CHAIN_ID(), L2_CHAIN_ID); assertEq(address(gameV2.gameCreator()), address(this)); - assertEq(gameV2.l2SequenceNumber(), startingRoot.l2SequenceNumber + AGGREGATE_BLOCK_INTERVAL); + assertEq(gameV2.l2SequenceNumber(), startingRoot.l2SequenceNumber + AGGREGATE_SLOW_BLOCK_INTERVAL); assertEq(gameV2.parentAddress(), address(anchorStateRegistry)); assertEq(address(gameV2.DELAYED_WETH()), address(delayedWeth)); assertEq(address(gameV2.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(GameType.unwrap(gameV2.gameType()), GameType.unwrap(GameTypes.AGGREGATE_VERIFIER)); - assertEq(gameV2.BLOCK_INTERVAL(), AGGREGATE_BLOCK_INTERVAL); - assertEq(gameV2.INTERMEDIATE_BLOCK_INTERVAL(), AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL); + assertEq(gameV2.SLOW_BLOCK_INTERVAL(), AGGREGATE_SLOW_BLOCK_INTERVAL); + assertEq(gameV2.SLOW_INTERMEDIATE_BLOCK_INTERVAL(), AGGREGATE_SLOW_INTERMEDIATE_BLOCK_INTERVAL); } } diff --git a/test/L1/proofs/Nullify.t.sol b/test/L1/proofs/Nullify.t.sol index 841bc8cca..f9303dfcf 100644 --- a/test/L1/proofs/Nullify.t.sol +++ b/test/L1/proofs/Nullify.t.sol @@ -10,7 +10,7 @@ import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract NullifyTest is BaseTest { - uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; + uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = SLOW_BLOCK_INTERVAL / SLOW_INTERMEDIATE_BLOCK_INTERVAL - 1; uint256 private constant NO_PROOF_CREDIT_CLAIM_DELAY = 14 days; function testNullifyWithTEEProof() public { @@ -142,7 +142,7 @@ contract NullifyTest is BaseTest { private returns (AggregateVerifier) { - currentL2BlockNumber += BLOCK_INTERVAL; + currentL2BlockNumber += SLOW_BLOCK_INTERVAL; return _createAggregateVerifierGame( prover, _claim(claimSalt), currentL2BlockNumber, parent, _generateProof(proofSalt, proofType) ); diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index edd70d7e6..7f39e14b0 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -460,10 +460,10 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { genesisTimestamp: 1, blockTime: 2 }), - multiproofBlockInterval: 100, - multiproofIntermediateBlockInterval: 10, - multiproofDenimBlockInterval: 1000, - multiproofDenimIntermediateBlockInterval: 100, + multiproofSlowBlockInterval: 100, + multiproofSlowIntermediateBlockInterval: 10, + multiproofFastBlockInterval: 1000, + multiproofFastIntermediateBlockInterval: 100, sp1Verifier: ISP1Verifier(address(sp1Verifier)), teeProposer: proposer, teeChallenger: challenger, @@ -573,11 +573,12 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { l2GenesisBlockNumber: _input.implementationsInput.scheduleConfig.genesisBlockNumber, l2GenesisTimestamp: _input.implementationsInput.scheduleConfig.genesisTimestamp, l2BlockTime: _input.implementationsInput.scheduleConfig.blockTime, - multiproofBlockInterval: _input.implementationsInput.multiproofBlockInterval, - multiproofIntermediateBlockInterval: _input.implementationsInput.multiproofIntermediateBlockInterval, - multiproofDenimBlockInterval: _input.implementationsInput.multiproofDenimBlockInterval, - multiproofDenimIntermediateBlockInterval: _input.implementationsInput - .multiproofDenimIntermediateBlockInterval, + multiproofSlowBlockInterval: _input.implementationsInput.multiproofSlowBlockInterval, + multiproofSlowIntermediateBlockInterval: _input.implementationsInput + .multiproofSlowIntermediateBlockInterval, + multiproofFastBlockInterval: _input.implementationsInput.multiproofFastBlockInterval, + multiproofFastIntermediateBlockInterval: _input.implementationsInput + .multiproofFastIntermediateBlockInterval, withdrawalDelaySeconds: _input.implementationsInput.withdrawalDelaySeconds }); } diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index 4dbf4a85e..749ed5dcb 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -44,10 +44,10 @@ abstract contract SystemDeployAssertions is Test { uint256 l2GenesisBlockNumber; uint64 l2GenesisTimestamp; uint64 l2BlockTime; - uint256 multiproofBlockInterval; - uint256 multiproofIntermediateBlockInterval; - uint256 multiproofDenimBlockInterval; - uint256 multiproofDenimIntermediateBlockInterval; + uint256 multiproofSlowBlockInterval; + uint256 multiproofSlowIntermediateBlockInterval; + uint256 multiproofFastBlockInterval; + uint256 multiproofFastIntermediateBlockInterval; uint256 withdrawalDelaySeconds; } @@ -259,14 +259,16 @@ abstract contract SystemDeployAssertions is Test { assertEq(_aggregateVerifier.L2_GENESIS_BLOCK_NUMBER(), _expected.l2GenesisBlockNumber, "AV-132"); assertEq(_aggregateVerifier.L2_GENESIS_TIMESTAMP(), _expected.l2GenesisTimestamp, "AV-134"); assertEq(_aggregateVerifier.L2_BLOCK_TIME(), _expected.l2BlockTime, "AV-136"); - assertEq(_aggregateVerifier.BLOCK_INTERVAL(), _expected.multiproofBlockInterval, "AV-140"); + assertEq(_aggregateVerifier.SLOW_BLOCK_INTERVAL(), _expected.multiproofSlowBlockInterval, "AV-140"); assertEq( - _aggregateVerifier.INTERMEDIATE_BLOCK_INTERVAL(), _expected.multiproofIntermediateBlockInterval, "AV-150" + _aggregateVerifier.SLOW_INTERMEDIATE_BLOCK_INTERVAL(), + _expected.multiproofSlowIntermediateBlockInterval, + "AV-150" ); - assertEq(_aggregateVerifier.DENIM_BLOCK_INTERVAL(), _expected.multiproofDenimBlockInterval, "AV-160"); + assertEq(_aggregateVerifier.FAST_BLOCK_INTERVAL(), _expected.multiproofFastBlockInterval, "AV-160"); assertEq( - _aggregateVerifier.DENIM_INTERMEDIATE_BLOCK_INTERVAL(), - _expected.multiproofDenimIntermediateBlockInterval, + _aggregateVerifier.FAST_INTERMEDIATE_BLOCK_INTERVAL(), + _expected.multiproofFastIntermediateBlockInterval, "AV-170" ); } From fa0584d44f682529c5033304413c30827d96abed Mon Sep 17 00:00:00 2001 From: Thanh Trinh Date: Sun, 6 Sep 2026 16:36:57 -0500 Subject: [PATCH 4/4] fix(L1): target Cobalt for the block cadence speedup Index 12 is Cobalt, the next unscheduled entry on the Base mainnet `ProtocolVersions` schedule; index 13 was Denim, which the schedule does not reach yet, so `_firstFastBlock` could only ever return `type(uint256).max`. Also folds the early-genesis branch of `_firstFastBlock` into an early return, per review. Co-Authored-By: Claude --- scripts/multiproof/README.md | 4 ++-- scripts/multiproof/SeedGames.s.sol | 4 ++-- scripts/multiproof/generate-roots.sh | 2 +- src/L1/proofs/AggregateVerifier.sol | 13 +++++++------ test/L1/proofs/AggregateVerifier.t.sol | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/multiproof/README.md b/scripts/multiproof/README.md index b20a93056..be8f1e6af 100644 --- a/scripts/multiproof/README.md +++ b/scripts/multiproof/README.md @@ -172,9 +172,9 @@ Games are created using `ProofType.ZK` with the `MockVerifier` (deployed by both ### Step 1: Set the anchor state -Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers one block interval of L2 blocks — 600 on slow (pre-Denim) blocks, 6,000 on fast (post-Denim) ones — so for 500 slow-block games you need 300,000 blocks of headroom. +Pick an anchor block far enough behind the L2 tip to cover all the games you want to create. Each game covers one block interval of L2 blocks — 600 on slow (pre-Cobalt) blocks, 6,000 on fast (post-Cobalt) ones — so for 500 slow-block games you need 300,000 blocks of headroom. -`SeedGames.s.sol` reads the interval off the deployed `AggregateVerifier` and picks the side that matches the anchor block, so nothing needs changing here for Denim. The contract exposes the two pairs as `SLOW_BLOCK_INTERVAL` / `SLOW_INTERMEDIATE_BLOCK_INTERVAL` and `FAST_BLOCK_INTERVAL` / `FAST_INTERMEDIATE_BLOCK_INTERVAL`. `generate-roots.sh` cannot see the chain, so pass `BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300` when seeding a devnet on which Denim is already active; seeding will abort on a mismatched roots file rather than create bad games. +`SeedGames.s.sol` reads the interval off the deployed `AggregateVerifier` and picks the side that matches the anchor block, so nothing needs changing here for Cobalt. The contract exposes the two pairs as `SLOW_BLOCK_INTERVAL` / `SLOW_INTERMEDIATE_BLOCK_INTERVAL` and `FAST_BLOCK_INTERVAL` / `FAST_INTERMEDIATE_BLOCK_INTERVAL`. `generate-roots.sh` cannot see the chain, so pass `BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300` when seeding a devnet on which Cobalt is already active; seeding will abort on a mismatched roots file rather than create bad games. ```bash # Calculate an anchor block 300,000 blocks behind the L2 tip diff --git a/scripts/multiproof/SeedGames.s.sol b/scripts/multiproof/SeedGames.s.sol index a705cd984..9081e4626 100644 --- a/scripts/multiproof/SeedGames.s.sol +++ b/scripts/multiproof/SeedGames.s.sol @@ -95,9 +95,9 @@ contract SeedGames is Script { // Read the proposal geometry off the deployment rather than restating it. AggregateVerifier // carries both the slow- and fast-block pairs and picks per game, so a hardcoded pair here - // would silently seed unopenable games on a devnet with Denim scheduled. + // would silently seed unopenable games on a devnet with Cobalt scheduled. // ponytail: resolved once from the anchor, so a chain seeded straight across the activation - // would be wrong for its later games. Seed before scheduling Denim, or seed each side + // would be wrong for its later games. Seed before scheduling Cobalt, or seed each side // separately; per-game resolution only matters if a devnet ever needs a straddling chain. AggregateVerifier gameImpl = AggregateVerifier(address(ctx.factory.gameImpls(ctx.gameType))); (ctx.slowBlockInterval,) = gameImpl.intervalsForStartingBlock(ctx.anchorBlock); diff --git a/scripts/multiproof/generate-roots.sh b/scripts/multiproof/generate-roots.sh index cc8b9f64d..f34145fd6 100755 --- a/scripts/multiproof/generate-roots.sh +++ b/scripts/multiproof/generate-roots.sh @@ -33,7 +33,7 @@ PARALLELISM="${4:-20}" OUTPUT_FILE="${5:-roots.json}" # Must match the intervals the deployed AggregateVerifier selects for these games. The contract -# carries both the slow- and fast-block pairs, so override these when seeding a devnet on which Denim +# carries both the slow- and fast-block pairs, so override these when seeding a devnet on which Cobalt # is already active: BLOCK_INTERVAL=6000 INTERMEDIATE_BLOCK_INTERVAL=300 ./generate-roots.sh ... # SeedGames.s.sol reads the live values off the deployment and will refuse a mismatched roots file. BLOCK_INTERVAL="${BLOCK_INTERVAL:-600}" diff --git a/src/L1/proofs/AggregateVerifier.sol b/src/L1/proofs/AggregateVerifier.sol index 8932bcb94..44b9ca218 100644 --- a/src/L1/proofs/AggregateVerifier.sol +++ b/src/L1/proofs/AggregateVerifier.sol @@ -87,11 +87,11 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { uint256 public constant PROOF_THRESHOLD = 1; /// @notice The ProtocolVersions upgrade index at which L2 blocks switch to the fast cadence. - /// @dev This is the one place the contract is tied to a specific hardfork: index 13 is Denim, + /// @dev This is the one place the contract is tied to a specific hardfork: index 12 is Cobalt, /// which drops the L2 block time from 2s to 200ms. Everything downstream is expressed as /// slow-vs-fast blocks, so a later cadence change is a new index and new interval pair /// rather than new machinery. - uint256 private constant FAST_BLOCK_UPGRADE_INDEX = 13; + uint256 private constant FAST_BLOCK_UPGRADE_INDEX = 12; /// @notice The number of whole fast-cadence L2 blocks produced per second. uint256 private constant FAST_BLOCKS_PER_SECOND = 5; @@ -1225,11 +1225,12 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver { uint64 fastActivationTimestamp = schedule[FAST_BLOCK_UPGRADE_INDEX]; if (fastActivationTimestamp == 0) return type(uint256).max; - uint256 blocksUntilFast; - if (fastActivationTimestamp > L2_GENESIS_TIMESTAMP) { - blocksUntilFast = FixedPointMathLib.divUp(fastActivationTimestamp - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME); + if (fastActivationTimestamp <= L2_GENESIS_TIMESTAMP) { + return L2_GENESIS_BLOCK_NUMBER; } - return L2_GENESIS_BLOCK_NUMBER + blocksUntilFast; + + return L2_GENESIS_BLOCK_NUMBER + + FixedPointMathLib.divUp(fastActivationTimestamp - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME); } /// @notice Selects the proposal intervals governing a game, from its starting block number and an diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index d3ed4946b..2a7f76c76 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -20,7 +20,7 @@ import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { using LibClone for address; - uint256 private constant FAST_BLOCK_UPGRADE_INDEX = 13; + uint256 private constant FAST_BLOCK_UPGRADE_INDEX = 12; uint256 private constant FAST_BLOCKS_PER_SECOND = 5; AggregateVerifier private aggregateVerifierImpl;