Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions interfaces/L1/proofs/IAggregateVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ 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 FAST_BLOCK_ACTIVATION_TIMESTAMP() external view returns (uint64);
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);
Expand Down
2 changes: 2 additions & 0 deletions scripts/deploy/DeployConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ contract DeployConfig is Script {
uint256 public l2OutputOracleStartingBlockNumber;
uint256 public l2OutputOracleStartingTimestamp;
uint256 public multiproofSlowBlockInterval;
uint256 public multiproofFastBlockActivationTimestamp;
uint256 public multiproofFastBlockInterval;
uint256 public multiproofFastIntermediateBlockInterval;
uint256 public multiproofGameType;
Expand Down Expand Up @@ -118,6 +119,7 @@ contract DeployConfig is Script {
l2GenesisBlockNumber = _json.readUintOr("$.l2GenesisBlockNumber", 0);
l2GenesisTimestamp = _json.readUintOr("$.l2GenesisTimestamp", 0);
multiproofSlowBlockInterval = _json.readUintOr("$.multiproofSlowBlockInterval", 100);
multiproofFastBlockActivationTimestamp = _json.readUintOr("$.multiproofFastBlockActivationTimestamp", 0);
multiproofFastBlockInterval = _json.readUintOr("$.multiproofFastBlockInterval", 1000);
multiproofFastIntermediateBlockInterval = _json.readUintOr("$.multiproofFastIntermediateBlockInterval", 100);
multiproofGameType = _json.readUintOr("$.multiproofGameType", 621);
Expand Down
7 changes: 6 additions & 1 deletion scripts/deploy/SystemDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,19 @@ contract SystemDeploy is Script {
function _configuredScheduleConfig() internal view returns (AggregateVerifier.ScheduleConfig memory config_) {
uint256 genesisTimestamp = cfg.l2GenesisTimestamp();
uint256 blockTime = cfg.l2BlockTime();
uint256 fastBlockActivationTimestamp = cfg.multiproofFastBlockActivationTimestamp();
require(genesisTimestamp <= type(uint64).max, "SystemDeploy: L2 genesis timestamp overflow");
require(blockTime <= type(uint64).max, "SystemDeploy: L2 block time overflow");
require(
fastBlockActivationTimestamp <= type(uint64).max, "SystemDeploy: fast block activation timestamp overflow"
);

config_ = AggregateVerifier.ScheduleConfig({
protocolVersions: IProtocolVersions(address(0)),
genesisBlockNumber: cfg.l2GenesisBlockNumber(),
genesisTimestamp: uint64(genesisTimestamp),
blockTime: uint64(blockTime)
blockTime: uint64(blockTime),
fastBlockActivationTimestamp: uint64(fastBlockActivationTimestamp)
});
}

Expand Down
3 changes: 2 additions & 1 deletion scripts/multiproof/DeployDevBase.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ abstract contract DeployDevBase is Script {
protocolVersions: IProtocolVersions(address(protocolVersionsProxy)),
genesisBlockNumber: cfg.l2GenesisBlockNumber(),
genesisTimestamp: uint64(cfg.l2GenesisTimestamp()),
blockTime: uint64(cfg.l2BlockTime())
blockTime: uint64(cfg.l2BlockTime()),
fastBlockActivationTimestamp: 0
})
)
);
Expand Down
4 changes: 2 additions & 2 deletions snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"sourceCodeHash": "0x780ff372493ba9010bc0d13100ac896f2bf75730a9b17d4bb63aaf694dc3c634"
},
"src/L1/proofs/AggregateVerifier.sol:AggregateVerifier": {
"initCodeHash": "0x0642919b493a739e0280ded0dab2123c0c0d569fb613203ad71f79eca25af8e4",
"sourceCodeHash": "0xa03d89648901890b4e2ab06a72578045f75d6dfa649dbac0ae9364fd20f0657d"
"initCodeHash": "0x7b12663e3d6891ad906be1ec99d5e962873344931faae6b79b2f8cc1e85b56fa",
"sourceCodeHash": "0x48a54be27a13e1037310c927fbc869cadf714ee22131bd004b1542fd6279b2d6"
},
"src/L1/proofs/AnchorStateRegistry.sol:AnchorStateRegistry": {
"initCodeHash": "0x6f3afd2d0ef97a82ca3111976322b99343a270e54cd4a405028f2f29c75f7fb1",
Expand Down
55 changes: 24 additions & 31 deletions src/L1/proofs/AggregateVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
uint256 genesisBlockNumber;
uint64 genesisTimestamp;
uint64 blockTime;
uint64 fastBlockActivationTimestamp;
}

/// @notice Proposal block intervals for each side of the block-speedup activation.
Expand Down Expand Up @@ -86,13 +87,6 @@ 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 at which L2 blocks switch to the fast cadence.
/// @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 = 12;

/// @notice The number of whole fast-cadence L2 blocks produced per second.
uint256 private constant FAST_BLOCKS_PER_SECOND = 5;
////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -137,6 +131,16 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
/// @notice The legacy number of seconds between consecutive L2 blocks.
uint64 public immutable L2_BLOCK_TIME;

/// @notice The L2 timestamp at which blocks switch to the fast cadence, or 0 when unscheduled.
/// @dev This is the one place the contract is tied to a specific hardfork: it is Cobalt's
/// activation, 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 deployment with a
/// new timestamp and interval pair rather than new machinery.
/// @dev 0 means the speedup is not scheduled, so every game uses the slow-block intervals.
/// This lets the implementation be deployed before the activation time is agreed, at the
/// cost of a redeploy once it is.
uint64 public immutable FAST_BLOCK_ACTIVATION_TIMESTAMP;

/// @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;
Expand Down Expand Up @@ -360,6 +364,7 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
L2_GENESIS_BLOCK_NUMBER = scheduleConfig.genesisBlockNumber;
L2_GENESIS_TIMESTAMP = scheduleConfig.genesisTimestamp;
L2_BLOCK_TIME = scheduleConfig.blockTime;
FAST_BLOCK_ACTIVATION_TIMESTAMP = scheduleConfig.fastBlockActivationTimestamp;
SLOW_BLOCK_INTERVAL = intervalConfig.slowBlockInterval;
SLOW_INTERMEDIATE_BLOCK_INTERVAL = intervalConfig.slowIntermediateBlockInterval;
FAST_BLOCK_INTERVAL = intervalConfig.fastBlockInterval;
Expand Down Expand Up @@ -431,8 +436,7 @@ 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.
// Resolved once and threaded through both fork-sensitive decisions below.
uint256 firstFastBlock = _firstFastBlock();

// The block number must be one block interval after the starting block number. The interval
Expand Down Expand Up @@ -1159,9 +1163,9 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {
}

/// @notice Semantic version.
/// @custom:semver 0.2.0
/// @custom:semver 0.3.0
function version() public pure virtual returns (string memory) {
return "0.2.0";
return "0.3.0";
}

/// @notice Derives an L2 block timestamp: the slow cadence before the speedup, whole-second groups after it.
Expand Down Expand Up @@ -1206,37 +1210,26 @@ contract AggregateVerifier is Clone, ReentrancyGuard, ISemver {

/// @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 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 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.
/// @dev The activation is an immutable, so every game this implementation ever backs selects
/// its intervals against the same boundary. There is no window in which one game reads a
/// different activation than another, and no mutation that can move a game across the
/// boundary after the fact. Changing the activation means a new implementation, which is
/// a new game type in the factory and therefore never retroactive.
function _firstFastBlock() private view returns (uint256) {
uint64[] memory schedule = PROTOCOL_VERSIONS.getSchedule();
if (schedule.length <= FAST_BLOCK_UPGRADE_INDEX) return type(uint256).max;

uint64 fastActivationTimestamp = schedule[FAST_BLOCK_UPGRADE_INDEX];
if (fastActivationTimestamp == 0) return type(uint256).max;
if (FAST_BLOCK_ACTIVATION_TIMESTAMP == 0) return type(uint256).max;

if (fastActivationTimestamp <= L2_GENESIS_TIMESTAMP) {
if (FAST_BLOCK_ACTIVATION_TIMESTAMP <= L2_GENESIS_TIMESTAMP) {
return L2_GENESIS_BLOCK_NUMBER;
}

return L2_GENESIS_BLOCK_NUMBER
+ FixedPointMathLib.divUp(fastActivationTimestamp - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME);
+ FixedPointMathLib.divUp(FAST_BLOCK_ACTIVATION_TIMESTAMP - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME);
}

/// @notice Selects the proposal intervals governing a game, from its starting block number and an
/// 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.
/// fork-sensitive decision derives the activation block once.
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);
Expand Down
6 changes: 5 additions & 1 deletion test/L1/OptimismPortal2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit {
fastIntermediateBlockInterval: 100
}),
AggregateVerifier.ScheduleConfig({
protocolVersions: protocolVersions, genesisBlockNumber: 0, genesisTimestamp: 1, blockTime: 2
protocolVersions: protocolVersions,
genesisBlockNumber: 0,
genesisTimestamp: 1,
blockTime: 2,
fastBlockActivationTimestamp: 0
})
);
disputeGameFactory.setImplementation(respectedGameType, IDisputeGame(address(gameImpl)));
Expand Down
46 changes: 40 additions & 6 deletions test/L1/proofs/AggregateVerifier.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ contract AggregateVerifierTest is BaseTest {
schedule[FAST_BLOCK_UPGRADE_INDEX + 1] = firstFastBlockTimestamp + 1;
_importProtocolVersionsSchedule(schedule);

fastBlockActivationTimestamp = fastActivationTimestamp;
_setSingleBlockAggregateVerifier(L2_GENESIS_TIMESTAMP);

bytes32 speedupScheduleId = protocolVersions.scheduleId(FAST_BLOCK_UPGRADE_INDEX);
Expand Down Expand Up @@ -167,6 +168,28 @@ contract AggregateVerifierTest is BaseTest {
_assertIntervals(firstFastBlock + 1, FAST_BLOCK_INTERVAL, FAST_INTERMEDIATE_BLOCK_INTERVAL);
}

/// @notice The activation is an implementation immutable, so the speedup takes effect with the
/// upgrade registry left empty at the speedup index. This is what lets the cadence switch
/// land on a chain whose `ProtocolVersions` schedule is never written for it.
function test_intervalsForStartingBlock_activationIndependentOfRegistry_succeeds() public {
// A registry that stops short of the speedup index: nothing here can select fast intervals.
uint64[] memory schedule = new uint64[](FAST_BLOCK_UPGRADE_INDEX);
for (uint256 i; i < FAST_BLOCK_UPGRADE_INDEX; i++) {
schedule[i] = L2_GENESIS_TIMESTAMP;
}
_importProtocolVersionsSchedule(schedule);
assertEq(protocolVersions.getSchedule().length, FAST_BLOCK_UPGRADE_INDEX);

// divUp(86500 - L2_GENESIS_TIMESTAMP, L2_BLOCK_TIME) == 50.
_setFastBlockActivation(L2_GENESIS_TIMESTAMP + 100);
aggregateVerifierImpl = AggregateVerifier(address(factory.gameImpls(GameTypes.AGGREGATE_VERIFIER)));
uint256 firstFastBlock = 50;

assertEq(aggregateVerifierImpl.FAST_BLOCK_ACTIVATION_TIMESTAMP(), L2_GENESIS_TIMESTAMP + 100);
_assertIntervals(firstFastBlock - 1, SLOW_BLOCK_INTERVAL, SLOW_INTERMEDIATE_BLOCK_INTERVAL);
_assertIntervals(firstFastBlock, FAST_BLOCK_INTERVAL, FAST_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);
Expand Down Expand Up @@ -295,7 +318,8 @@ contract AggregateVerifierTest is BaseTest {
protocolVersions: IProtocolVersions(address(protocolVersions)),
genesisBlockNumber: 0,
genesisTimestamp: 0,
blockTime: 0
blockTime: 0,
fastBlockActivationTimestamp: 0
});

vm.expectRevert(AggregateVerifier.InvalidL2BlockTime.selector);
Expand All @@ -307,7 +331,8 @@ contract AggregateVerifierTest is BaseTest {
protocolVersions: IProtocolVersions(address(protocolVersions)),
genesisBlockNumber: SLOW_BLOCK_INTERVAL + 1,
genesisTimestamp: 0,
blockTime: L2_BLOCK_TIME
blockTime: L2_BLOCK_TIME,
fastBlockActivationTimestamp: 0
});
AggregateVerifier implementation = _deployAggregateVerifierWithScheduleConfig(scheduleConfig);
factory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(implementation)));
Expand All @@ -332,7 +357,8 @@ contract AggregateVerifierTest is BaseTest {
protocolVersions: IProtocolVersions(address(protocolVersions)),
genesisBlockNumber: 0,
genesisTimestamp: type(uint64).max,
blockTime: L2_BLOCK_TIME
blockTime: L2_BLOCK_TIME,
fastBlockActivationTimestamp: 0
});
AggregateVerifier implementation = _deployAggregateVerifierWithScheduleConfig(scheduleConfig);
factory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(implementation)));
Expand All @@ -356,6 +382,7 @@ contract AggregateVerifierTest is BaseTest {
}
schedule[FAST_BLOCK_UPGRADE_INDEX] = genesisTimestamp + 1;
_importProtocolVersionsSchedule(schedule);
fastBlockActivationTimestamp = genesisTimestamp + 1;
_setSingleBlockAggregateVerifier(genesisTimestamp);

vm.warp(uint256(type(uint64).max) + 3);
Expand Down Expand Up @@ -630,7 +657,8 @@ contract AggregateVerifierTest is BaseTest {
protocolVersions: IProtocolVersions(address(protocolVersions)),
genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER,
genesisTimestamp: genesisTimestamp,
blockTime: L2_BLOCK_TIME
blockTime: L2_BLOCK_TIME,
fastBlockActivationTimestamp: fastBlockActivationTimestamp
})
);
factory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(implementation)));
Expand Down Expand Up @@ -786,7 +814,8 @@ contract AggregateVerifierTest is BaseTest {
protocolVersions: IProtocolVersions(address(protocolVersions)),
genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER,
genesisTimestamp: L2_GENESIS_TIMESTAMP,
blockTime: L2_BLOCK_TIME
blockTime: L2_BLOCK_TIME,
fastBlockActivationTimestamp: fastBlockActivationTimestamp
})
);
}
Expand All @@ -800,12 +829,16 @@ contract AggregateVerifierTest is BaseTest {

/// @dev Registers a schedule whose only meaningful entry is the speedup activation, and rebinds
/// the implementation to it.
/// @dev Bakes the speedup activation into a fresh implementation. The registry entry is written
/// alongside it only so `scheduleId` pinning still reflects a chain that reached the fork;
/// interval and timestamp selection read the immutable, not the registry.
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[FAST_BLOCK_UPGRADE_INDEX] = fastActivationTimestamp;
fastBlockActivationTimestamp = fastActivationTimestamp;
_importProtocolVersionsSchedule(schedule);
aggregateVerifierImpl = AggregateVerifier(address(factory.gameImpls(GameTypes.AGGREGATE_VERIFIER)));
}
Expand Down Expand Up @@ -892,7 +925,8 @@ contract AggregateVerifierTest is BaseTest {
protocolVersions: IProtocolVersions(address(protocolVersions)),
genesisBlockNumber: L2_GENESIS_BLOCK_NUMBER,
genesisTimestamp: L2_GENESIS_TIMESTAMP,
blockTime: L2_BLOCK_TIME
blockTime: L2_BLOCK_TIME,
fastBlockActivationTimestamp: fastBlockActivationTimestamp
});
}

Expand Down
Loading
Loading