diff --git a/packages/protocol/.gas-snapshot b/packages/protocol/.gas-snapshot index 8262ae21a95..d2ed28e618e 100644 --- a/packages/protocol/.gas-snapshot +++ b/packages/protocol/.gas-snapshot @@ -70,9 +70,9 @@ InboxTest_ProposeAndProve:test_inbox_reprove_the_same_batch_with_same_transition InboxTest_ProposeAndProve:test_inbox_ring_buffer_will_be_reused() (gas: 14705853) InboxTest_ProposeAndProve:test_proposeBatch_reverts_for_invalid_proposer_and_operator() (gas: 165337) InboxTest_StopBatch:test_inbox_num_batches_verified() (gas: 8162653) -PreconfRouterTest:test_preconfRouter_proposeBatch() (gas: 374389) -PreconfRouterTest:test_preconfRouter_proposeBatch_notOperator() (gas: 325555) -PreconfRouterTest:test_preconfRouter_proposeBatch_proposerNotSender() (gas: 373120) +PreconfRouterTest:test_preconfRouter_proposeBatch() (gas: 375140) +PreconfRouterTest:test_preconfRouter_proposeBatch_notOperator() (gas: 326307) +PreconfRouterTest:test_preconfRouter_proposeBatch_proposerNotSender() (gas: 373872) SendMessageToDelegateOwner:testAddress1() (gas: 2391) TestAutomataDcapV3Attestation:testAttestation() (gas: 4848498) TestAutomataDcapV3Attestation:testParsedCustomQuoteBinAttestation() (gas: 4553350) @@ -107,16 +107,16 @@ TestMinimalOwner:test_minimalOwner_InitialOwner() (gas: 13083) TestMinimalOwner:test_minimalOwner_TransferOwnership() (gas: 21879) TestMinimalOwner:test_minimalOwner_TransferOwnershipToSameAddress() (gas: 13832) TestMinimalOwner:test_minimalOwner_TransferOwnershipToZeroAddress() (gas: 13493) -TestPreconfWhitelist:test_whitelistNoDelay_consolidationPreservesOrder() (gas: 252745) -TestPreconfWhitelist:test_whitelistNoDelay_consolidationWillNotChangeCurrentEpochOperator() (gas: 299356) -TestPreconfWhitelist:test_whitelist_addBackRemovedOperator() (gas: 106230) -TestPreconfWhitelist:test_whitelist_addOrRemoveTheSameOperatorTwiceWillRevert() (gas: 83296) -TestPreconfWhitelist:test_whitelist_consolidate_whenEmpty_not_revert() (gas: 19756) -TestPreconfWhitelist:test_whitelist_delay2epoch_addThenRemoveOneOperator() (gas: 257049) -TestPreconfWhitelist:test_whitelist_delay2epoch_addThenRemoveTwoOperators() (gas: 305557) -TestPreconfWhitelist:test_whitelist_noDelay_addThenRemoveOneOperator() (gas: 154524) -TestPreconfWhitelist:test_whitelist_removeNonExistingOperatorWillRevert() (gas: 25210) -TestPreconfWhitelist:test_whitelist_selfRemoval() (gas: 174701) +TestPreconfWhitelist:test_whitelistNoDelay_consolidationPreservesOrder() (gas: 251985) +TestPreconfWhitelist:test_whitelistNoDelay_consolidationWillNotChangeCurrentEpochOperator() (gas: 291066) +TestPreconfWhitelist:test_whitelist_addBackRemovedOperator() (gas: 105759) +TestPreconfWhitelist:test_whitelist_addOrRemoveTheSameOperatorTwiceWillRevert() (gas: 82692) +TestPreconfWhitelist:test_whitelist_consolidate_whenEmpty_not_revert() (gas: 19712) +TestPreconfWhitelist:test_whitelist_delay2epoch_addThenRemoveOneOperator() (gas: 245550) +TestPreconfWhitelist:test_whitelist_delay2epoch_addThenRemoveTwoOperators() (gas: 294371) +TestPreconfWhitelist:test_whitelist_noDelay_addThenRemoveOneOperator() (gas: 158506) +TestPreconfWhitelist:test_whitelist_removeNonExistingOperatorWillRevert() (gas: 23065) +TestPreconfWhitelist:test_whitelist_selfRemoval() (gas: 139154) TestTaikoTreasuryVault:testForwardCallFailsOnError() (gas: 79082) TestTaikoTreasuryVault:testForwardCallToSelfShouldRevert() (gas: 26006) TestTaikoTreasuryVault:testNonOwnerCannotForwardCall() (gas: 28382) diff --git a/packages/protocol/contracts/layer1/preconf/iface/IPreconfWhitelist.sol b/packages/protocol/contracts/layer1/preconf/iface/IPreconfWhitelist.sol index d96dd460566..760e23619ff 100644 --- a/packages/protocol/contracts/layer1/preconf/iface/IPreconfWhitelist.sol +++ b/packages/protocol/contracts/layer1/preconf/iface/IPreconfWhitelist.sol @@ -17,6 +17,7 @@ interface IPreconfWhitelist { error InvalidOperatorIndex(); error InvalidOperatorCount(); error InvalidOperatorAddress(); + error SelectorEpochOffsetTooSmall(); error OperatorAlreadyExists(); error OperatorAlreadyRemoved(); error OperatorNotAvailableYet(); diff --git a/packages/protocol/contracts/layer1/preconf/impl/PreconfWhitelist.sol b/packages/protocol/contracts/layer1/preconf/impl/PreconfWhitelist.sol index 8cd323e052d..b3f286a5523 100644 --- a/packages/protocol/contracts/layer1/preconf/impl/PreconfWhitelist.sol +++ b/packages/protocol/contracts/layer1/preconf/impl/PreconfWhitelist.sol @@ -17,29 +17,38 @@ contract PreconfWhitelist is EssentialContract, IPreconfWhitelist { } event Consolidated(uint8 previousCount, uint8 newCount, bool havingPerfectOperators); - event OperatorChangeDelaySet(uint8 delay); + uint256 public immutable selectorEpochOffset; // in epochs + uint256 public immutable operatorChangeDelay; // in epochs + + // Slot 1 mapping(address operator => OperatorInfo info) public operators; + + // Slot 2 mapping(uint256 index => address operator) public operatorMapping; + + // Slot 3 uint8 public operatorCount; - uint8 public operatorChangeDelay; // in epochs // all operators in operatorMapping are active and none of them is to be deactivated. bool public havingPerfectOperators; uint256[47] private __gap; - constructor() EssentialContract(address(0)) { } - - function init(address _owner, uint8 _operatorChangeDelay) external initializer { - __Essential_init(_owner); + constructor( + uint256 _operatorChangeDelay, + uint256 _selectorEpochOffset + ) + EssentialContract(address(0)) + { + require(_selectorEpochOffset > 1, SelectorEpochOffsetTooSmall()); + selectorEpochOffset = _selectorEpochOffset; operatorChangeDelay = _operatorChangeDelay; - havingPerfectOperators = true; } - function setOperatorChangeDelay(uint8 _operatorChangeDelay) external onlyOwner { - operatorChangeDelay = _operatorChangeDelay; - emit OperatorChangeDelaySet(_operatorChangeDelay); + function init(address _owner) external initializer { + __Essential_init(_owner); + havingPerfectOperators = true; } /// @inheritdoc IPreconfWhitelist @@ -161,7 +170,7 @@ contract PreconfWhitelist is EssentialContract, IPreconfWhitelist { ); } - function _addOperator(address _operator, uint8 _operatorChangeDelay) internal { + function _addOperator(address _operator, uint256 _operatorChangeDelay) internal { require(_operator != address(0), InvalidOperatorAddress()); require(operators[_operator].activeSince == 0, OperatorAlreadyExists()); @@ -184,7 +193,7 @@ contract PreconfWhitelist is EssentialContract, IPreconfWhitelist { emit OperatorAdded(_operator, activeSince); } - function _removeOperator(address _operator, uint8 _operatorChangeDelay) internal { + function _removeOperator(address _operator, uint256 _operatorChangeDelay) internal { require(_operator != address(0), InvalidOperatorAddress()); OperatorInfo memory info = operators[_operator]; require(info.inactiveSince == 0, OperatorAlreadyRemoved()); @@ -209,17 +218,18 @@ contract PreconfWhitelist is EssentialContract, IPreconfWhitelist { /// @dev The cost of this function is primarily linear with respect to operatorCount. function _getOperatorForEpoch(uint64 _epochTimestamp) internal view returns (address) { - if (_epochTimestamp < LibPreconfConstants.SECONDS_IN_EPOCH) { - return address(0); - } + uint256 timeShift = selectorEpochOffset * LibPreconfConstants.SECONDS_IN_EPOCH; + // We use the beacon root at or after ` _epochTimestamp - timeShift` as the random number to + // select an operator. + // selectorEpochOffset must be big enough to ensure a non-zero beacon root is + // available and immutable. - // Use the previous epoch's start timestamp as the random number, if it is not available - // (zero), return address(0) directly. - uint256 rand = uint256( - LibPreconfUtils.getBeaconBlockRoot( - _epochTimestamp - LibPreconfConstants.SECONDS_IN_SLOT - ) - ); + if (_epochTimestamp < timeShift) return address(0); + + uint256 rand; + unchecked { + rand = uint256(LibPreconfUtils.getBeaconBlockRoot(_epochTimestamp - timeShift)); + } if (rand == 0) return address(0); diff --git a/packages/protocol/layout/layer1-contracts.md b/packages/protocol/layout/layer1-contracts.md index b2b0d8eb4b2..52cdfaab7d3 100644 --- a/packages/protocol/layout/layer1-contracts.md +++ b/packages/protocol/layout/layer1-contracts.md @@ -1346,9 +1346,7 @@ | | operatorCount | uint8 | 253 | 0 | 1 | | -| operatorChangeDelay | uint8 | 253 | 1 | 1 | -| -| havingPerfectOperators | bool | 253 | 2 | 1 | +| havingPerfectOperators | bool | 253 | 1 | 1 | | | __gap | uint256[47] | 254 | 0 | 1504 | ╰-----------------------------+----------------------------------------------------------+------+--------+-------+---------------------------------------------------------------------╯ diff --git a/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol b/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol index b1dc0a11a52..704986dd149 100644 --- a/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol +++ b/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol @@ -528,8 +528,8 @@ contract DeployProtocolOnL1 is DeployCapability { { whitelist = deployProxy({ name: "preconf_whitelist", - impl: address(new PreconfWhitelist()), - data: abi.encodeCall(PreconfWhitelist.init, (owner, 2)), + impl: address(new PreconfWhitelist(2, 3)), + data: abi.encodeCall(PreconfWhitelist.init, (owner)), registerTo: rollupResolver }); diff --git a/packages/protocol/script/layer1/preconf/DeployPreconfContracts.s.sol b/packages/protocol/script/layer1/preconf/DeployPreconfContracts.s.sol index 11c227d0d68..ed15a31009d 100644 --- a/packages/protocol/script/layer1/preconf/DeployPreconfContracts.s.sol +++ b/packages/protocol/script/layer1/preconf/DeployPreconfContracts.s.sol @@ -28,8 +28,8 @@ contract DeployPreconfContracts is BaseScript { // Deploy PreconfWhitelist deploy( LibStrings.B_PRECONF_WHITELIST, - address(new PreconfWhitelist()), - abi.encodeCall(PreconfWhitelist.init, (contractOwner, 2)) + address(new PreconfWhitelist(2, 3)), + abi.encodeCall(PreconfWhitelist.init, (contractOwner)) ); // Deploy PreconfRouter diff --git a/packages/protocol/test/layer1/preconf/router/PreconfRouter.t.sol b/packages/protocol/test/layer1/preconf/router/PreconfRouter.t.sol index 12acf8e8c9e..32c5e35448a 100644 --- a/packages/protocol/test/layer1/preconf/router/PreconfRouter.t.sol +++ b/packages/protocol/test/layer1/preconf/router/PreconfRouter.t.sol @@ -15,9 +15,10 @@ contract PreconfRouterTest is PreconfRouterTestBase { // Setup mock beacon for operator selection vm.chainId(1); - uint256 epochOneStart = LibPreconfConstants.getGenesisTimestamp(block.chainid); + uint256 epoch1Start = LibPreconfConstants.getGenesisTimestamp(block.chainid); // Current epoch - uint256 epochTwoStart = epochOneStart + LibPreconfConstants.SECONDS_IN_EPOCH; + uint256 epoch4Start = + epoch1Start + whitelist.selectorEpochOffset() * LibPreconfConstants.SECONDS_IN_EPOCH; MockBeaconBlockRoot mockBeacon = new MockBeaconBlockRoot(); bytes32 mockRoot = bytes32(uint256(1)); // This will select Carol @@ -25,7 +26,7 @@ contract PreconfRouterTest is PreconfRouterTestBase { address beaconBlockRootContract = LibPreconfConstants.getBeaconBlockRootContract(); vm.etch(beaconBlockRootContract, address(mockBeacon).code); MockBeaconBlockRoot(payable(beaconBlockRootContract)).set( - epochOneStart + LibPreconfConstants.SECONDS_IN_SLOT, mockRoot + epoch1Start + LibPreconfConstants.SECONDS_IN_SLOT, mockRoot ); // Setup block params @@ -51,7 +52,7 @@ contract PreconfRouterTest is PreconfRouterTestBase { }); // Warp to arbitrary slot in epoch 2 - vm.warp(epochTwoStart + 2 * LibPreconfConstants.SECONDS_IN_SLOT); + vm.warp(epoch4Start + 2 * LibPreconfConstants.SECONDS_IN_SLOT); // Prank as Carol (selected operator) and propose blocks vm.prank(Carol); @@ -70,21 +71,22 @@ contract PreconfRouterTest is PreconfRouterTestBase { // Setup mock beacon for operator selection vm.chainId(1); - uint256 epochOneStart = LibPreconfConstants.getGenesisTimestamp(block.chainid); + uint256 epoch1Start = LibPreconfConstants.getGenesisTimestamp(block.chainid); MockBeaconBlockRoot mockBeacon = new MockBeaconBlockRoot(); // Current epoch - uint256 epochTwoStart = epochOneStart + LibPreconfConstants.SECONDS_IN_EPOCH; + uint256 epoch4Start = + epoch1Start + whitelist.selectorEpochOffset() * LibPreconfConstants.SECONDS_IN_EPOCH; bytes32 mockRoot = bytes32(uint256(1)); // This will select Carol address beaconBlockRootContract = LibPreconfConstants.getBeaconBlockRootContract(); vm.etch(beaconBlockRootContract, address(mockBeacon).code); MockBeaconBlockRoot(payable(beaconBlockRootContract)).set( - epochOneStart + LibPreconfConstants.SECONDS_IN_SLOT, mockRoot + epoch1Start + LibPreconfConstants.SECONDS_IN_SLOT, mockRoot ); // Warp to arbitrary slot in epoch 2 - vm.warp(epochTwoStart + 2 * LibPreconfConstants.SECONDS_IN_SLOT); + vm.warp(epoch4Start + 2 * LibPreconfConstants.SECONDS_IN_SLOT); // Prank as David (not the selected operator) and propose blocks vm.prank(David); @@ -101,9 +103,10 @@ contract PreconfRouterTest is PreconfRouterTestBase { // Setup mock beacon for operator selection vm.chainId(1); - uint256 epochOneStart = LibPreconfConstants.getGenesisTimestamp(block.chainid); + uint256 epoch1Start = LibPreconfConstants.getGenesisTimestamp(block.chainid); // Current epoch - uint256 epochTwoStart = epochOneStart + LibPreconfConstants.SECONDS_IN_EPOCH; + uint256 epoch4Start = + epoch1Start + whitelist.selectorEpochOffset() * LibPreconfConstants.SECONDS_IN_EPOCH; MockBeaconBlockRoot mockBeacon = new MockBeaconBlockRoot(); bytes32 mockRoot = bytes32(uint256(1)); // This will select Carol @@ -111,7 +114,7 @@ contract PreconfRouterTest is PreconfRouterTestBase { address beaconBlockRootContract = LibPreconfConstants.getBeaconBlockRootContract(); vm.etch(beaconBlockRootContract, address(mockBeacon).code); MockBeaconBlockRoot(payable(beaconBlockRootContract)).set( - epochOneStart + LibPreconfConstants.SECONDS_IN_SLOT, mockRoot + epoch1Start + LibPreconfConstants.SECONDS_IN_SLOT, mockRoot ); // Setup block params @@ -137,7 +140,7 @@ contract PreconfRouterTest is PreconfRouterTestBase { }); // Warp to arbitrary slot in epoch 2 - vm.warp(epochTwoStart + 2 * LibPreconfConstants.SECONDS_IN_SLOT); + vm.warp(epoch4Start + 2 * LibPreconfConstants.SECONDS_IN_SLOT); // Prank as Carol (selected operator) and propose blocks vm.prank(Carol); diff --git a/packages/protocol/test/layer1/preconf/router/PreconfRouterTestBase.sol b/packages/protocol/test/layer1/preconf/router/PreconfRouterTestBase.sol index b2a0db0ef29..6d8359697d6 100644 --- a/packages/protocol/test/layer1/preconf/router/PreconfRouterTestBase.sol +++ b/packages/protocol/test/layer1/preconf/router/PreconfRouterTestBase.sol @@ -30,8 +30,8 @@ abstract contract PreconfRouterTestBase is Layer1Test { whitelist = PreconfWhitelist( deploy({ name: "preconf_whitelist", - impl: address(new PreconfWhitelist()), - data: abi.encodeCall(PreconfWhitelist.init, (whitelistOwner, 2)) + impl: address(new PreconfWhitelist(2, 3)), + data: abi.encodeCall(PreconfWhitelist.init, (whitelistOwner)) }) ); diff --git a/packages/protocol/test/layer1/preconf/whitelist/PeconfWhitelist.t.sol b/packages/protocol/test/layer1/preconf/whitelist/PeconfWhitelist.t.sol index 3b77475a15a..27774243b8c 100644 --- a/packages/protocol/test/layer1/preconf/whitelist/PeconfWhitelist.t.sol +++ b/packages/protocol/test/layer1/preconf/whitelist/PeconfWhitelist.t.sol @@ -16,16 +16,16 @@ contract TestPreconfWhitelist is Layer1Test { whitelist = PreconfWhitelist( deploy({ name: "preconf_whitelist", - impl: address(new PreconfWhitelist()), - data: abi.encodeCall(PreconfWhitelist.init, (whitelistOwner, 2)) + impl: address(new PreconfWhitelist(2, 3)), + data: abi.encodeCall(PreconfWhitelist.init, (whitelistOwner)) }) ); whitelistNoDelay = PreconfWhitelist( deploy({ name: "preconf_whitelist_nodelay", - impl: address(new PreconfWhitelist()), - data: abi.encodeCall(PreconfWhitelist.init, (whitelistOwner, 0)) + impl: address(new PreconfWhitelist(0, 3)), + data: abi.encodeCall(PreconfWhitelist.init, (whitelistOwner)) }) ); @@ -276,6 +276,9 @@ contract TestPreconfWhitelist is Layer1Test { assertEq(inactiveSince, 0); assertEq(index, 0); + assertEq(whitelistNoDelay.getOperatorForCurrentEpoch(), address(0)); + + vm.warp(whitelistNoDelay.selectorEpochOffset() * LibPreconfConstants.SECONDS_IN_EPOCH); assertEq(whitelistNoDelay.getOperatorForCurrentEpoch(), Bob); assertEq(whitelistNoDelay.getOperatorForNextEpoch(), Bob);