diff --git a/src/BLSSignatureChecker.sol b/src/BLSSignatureChecker.sol index 5392289c..32d83793 100644 --- a/src/BLSSignatureChecker.sol +++ b/src/BLSSignatureChecker.sol @@ -19,6 +19,7 @@ contract BLSSignatureChecker is IBLSSignatureChecker { using BN254 for BN254.G1Point; // CONSTANTS & IMMUTABLES + bytes internal constant ALIGNED_QUORUM_NUMBER = hex"00"; // gas cost of multiplying 2 pairings uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 120_000; @@ -79,7 +80,6 @@ contract BLSSignatureChecker is IBLSSignatureChecker { * @dev NOTE: Be careful to ensure `msgHash` is collision-resistant! This method does not hash * `msgHash` in any way, so if an attacker is able to pass in an arbitrary value, they may be able * to tamper with signature verification. - * @param quorumNumbers is the bytes array of quorum numbers that are being signed for * @param referenceBlockNumber is the block number at which the stake information is being verified * @param params is the struct containing information on nonsigners, stakes, quorum apks, and the aggregate signature * @return quorumStakeTotals is the struct containing the total and signed stake for each quorum @@ -87,20 +87,14 @@ contract BLSSignatureChecker is IBLSSignatureChecker { */ function checkSignatures( bytes32 msgHash, - bytes calldata quorumNumbers, uint32 referenceBlockNumber, NonSignerStakesAndSignature memory params ) public view returns (QuorumStakeTotals memory, bytes32) { require( - quorumNumbers.length != 0, - "BLSSignatureChecker.checkSignatures: empty quorum input" - ); - - require( - (quorumNumbers.length == params.quorumApks.length) && - (quorumNumbers.length == params.quorumApkIndices.length) && - (quorumNumbers.length == params.totalStakeIndices.length) && - (quorumNumbers.length == params.nonSignerStakeIndices.length), + (ALIGNED_QUORUM_NUMBER.length == params.quorumApks.length) && + (ALIGNED_QUORUM_NUMBER.length == params.quorumApkIndices.length) && + (ALIGNED_QUORUM_NUMBER.length == params.totalStakeIndices.length) && + (ALIGNED_QUORUM_NUMBER.length == params.nonSignerStakeIndices.length), "BLSSignatureChecker.checkSignatures: input quorum length mismatch" ); @@ -127,8 +121,8 @@ contract BLSSignatureChecker is IBLSSignatureChecker { // at the referenceBlockNumber, and derive the stake held by signers by subtracting out // stakes held by nonsigners. QuorumStakeTotals memory stakeTotals; - stakeTotals.totalStakeForQuorum = new uint96[](quorumNumbers.length); - stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length); + stakeTotals.totalStakeForQuorum = new uint96[](ALIGNED_QUORUM_NUMBER.length); + stakeTotals.signedStakeForQuorum = new uint96[](ALIGNED_QUORUM_NUMBER.length); NonSignerInfo memory nonSigners; nonSigners.quorumBitmaps = new uint256[]( @@ -140,7 +134,7 @@ contract BLSSignatureChecker is IBLSSignatureChecker { // Get a bitmap of the quorums signing the message, and validate that // quorumNumbers contains only unique, valid quorum numbers uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap( - quorumNumbers, + ALIGNED_QUORUM_NUMBER, registryCoordinator.quorumCount() ); @@ -197,13 +191,13 @@ contract BLSSignatureChecker is IBLSSignatureChecker { ? delegation.minWithdrawalDelayBlocks() : 0; - for (uint256 i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < ALIGNED_QUORUM_NUMBER.length; i++) { // If we're disallowing stale stake updates, check that each quorum's last update block // is within withdrawalDelayBlocks if (_staleStakesForbidden) { require( registryCoordinator.quorumUpdateBlockNumber( - uint8(quorumNumbers[i]) + uint8(ALIGNED_QUORUM_NUMBER[i]) ) + withdrawalDelayBlocks > referenceBlockNumber, @@ -216,7 +210,7 @@ contract BLSSignatureChecker is IBLSSignatureChecker { require( bytes24(params.quorumApks[i].hashG1Point()) == blsApkRegistry.getApkHashAtBlockNumberAndIndex({ - quorumNumber: uint8(quorumNumbers[i]), + quorumNumber: uint8(ALIGNED_QUORUM_NUMBER[i]), blockNumber: referenceBlockNumber, index: params.quorumApkIndices[i] }), @@ -227,7 +221,7 @@ contract BLSSignatureChecker is IBLSSignatureChecker { // Get the total and starting signed stake for the quorum at referenceBlockNumber stakeTotals.totalStakeForQuorum[i] = stakeRegistry .getTotalStakeAtBlockNumberFromIndex({ - quorumNumber: uint8(quorumNumbers[i]), + quorumNumber: uint8(ALIGNED_QUORUM_NUMBER[i]), blockNumber: referenceBlockNumber, index: params.totalStakeIndices[i] }); @@ -244,12 +238,12 @@ contract BLSSignatureChecker is IBLSSignatureChecker { if ( BitmapUtils.isSet( nonSigners.quorumBitmaps[j], - uint8(quorumNumbers[i]) + uint8(ALIGNED_QUORUM_NUMBER[i]) ) ) { stakeTotals.signedStakeForQuorum[i] -= stakeRegistry .getStakeAtBlockNumberAndIndex({ - quorumNumber: uint8(quorumNumbers[i]), + quorumNumber: uint8(ALIGNED_QUORUM_NUMBER[i]), blockNumber: referenceBlockNumber, operatorId: nonSigners.pubkeyHashes[j], index: params.nonSignerStakeIndices[i][ diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 4006d9f4..00b4ddea 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -21,6 +21,8 @@ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.so import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; import {RegistryCoordinatorStorage} from "./RegistryCoordinatorStorage.sol"; +import {Whitelist} from "./Whitelist.sol"; + /** * @title A `RegistryCoordinator` that has three registries: * 1) a `StakeRegistry` that keeps track of operators' stakes @@ -36,13 +38,14 @@ contract RegistryCoordinator is OwnableUpgradeable, RegistryCoordinatorStorage, ISocketUpdater, - ISignatureUtils + ISignatureUtils, + Whitelist { using BitmapUtils for *; using BN254 for BN254.G1Point; modifier onlyEjector { - require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector"); + require(msg.sender == ejector, "RC!ejector"); _; } @@ -51,7 +54,7 @@ contract RegistryCoordinator is modifier quorumExists(uint8 quorumNumber) { require( quorumNumber < quorumCount, - "RegistryCoordinator.quorumExists: quorum does not exist" + "RC:q!E" ); _; } @@ -91,7 +94,7 @@ contract RegistryCoordinator is ) external initializer { require( _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length, - "RegistryCoordinator.initialize: input length mismatch" + "IE" ); // Initialize roles @@ -130,7 +133,7 @@ contract RegistryCoordinator is string calldata socket, IBLSApkRegistry.PubkeyRegistrationParams calldata params, SignatureWithSaltAndExpiry memory operatorSignature - ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { + ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) onlyWhitelisted { /** * If the operator has NEVER registered a pubkey before, use `params` to register * their pubkey in blsApkRegistry @@ -152,14 +155,10 @@ contract RegistryCoordinator is // For each quorum, validate that the new operator count does not exceed the maximum // (If it does, an operator needs to be replaced -- see `registerOperatorWithChurn`) - for (uint256 i = 0; i < quorumNumbers.length; i++) { - uint8 quorumNumber = uint8(quorumNumbers[i]); - - require( - numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount, - "RegistryCoordinator.registerOperator: operator count exceeds maximum" - ); - } + require( + numOperatorsPerQuorum[0] <= _quorumParams[0].maxOperatorCount, + "qMaxOp" + ); } /** @@ -181,8 +180,8 @@ contract RegistryCoordinator is OperatorKickParam[] calldata operatorKickParams, SignatureWithSaltAndExpiry memory churnApproverSignature, SignatureWithSaltAndExpiry memory operatorSignature - ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { - require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch"); + ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) onlyWhitelisted { + require(operatorKickParams.length == quorumNumbers.length, "qLen"); /** * If the operator has NEVER registered a pubkey before, use `params` to register @@ -213,26 +212,25 @@ contract RegistryCoordinator is // Check that each quorum's operator count is below the configured maximum. If the max // is exceeded, use `operatorKickParams` to deregister an existing operator to make space - for (uint256 i = 0; i < quorumNumbers.length; i++) { - OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[i])]; - - /** - * If the new operator count for any quorum exceeds the maximum, validate - * that churn can be performed, then deregister the specified operator - */ - if (results.numOperatorsPerQuorum[i] > operatorSetParams.maxOperatorCount) { - _validateChurn({ - quorumNumber: uint8(quorumNumbers[i]), - totalQuorumStake: results.totalStakes[i], - newOperator: msg.sender, - newOperatorStake: results.operatorStakes[i], - kickParams: operatorKickParams[i], - setParams: operatorSetParams - }); - - _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]); - } + OperatorSetParam memory operatorSetParams = _quorumParams[uint8(quorumNumbers[0])]; + + /** + * If the new operator count for any quorum exceeds the maximum, validate + * that churn can be performed, then deregister the specified operator + */ + if (results.numOperatorsPerQuorum[0] > operatorSetParams.maxOperatorCount) { + _validateChurn({ + quorumNumber: uint8(quorumNumbers[0]), + totalQuorumStake: results.totalStakes[0], + newOperator: msg.sender, + newOperatorStake: results.operatorStakes[0], + kickParams: operatorKickParams[0], + setParams: operatorSetParams + }); + + _deregisterOperator(operatorKickParams[0].operator, quorumNumbers[0:1]); } + } /** @@ -292,7 +290,7 @@ contract RegistryCoordinator is uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); require( operatorsPerQuorum.length == quorumNumbers.length, - "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch" + "rcQLen" ); // For each quorum, update ALL registered operators @@ -303,7 +301,7 @@ contract RegistryCoordinator is address[] calldata currQuorumOperators = operatorsPerQuorum[i]; require( currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber), - "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total" + "rcQOpLen" ); address prevOperatorAddress = address(0); @@ -322,12 +320,12 @@ contract RegistryCoordinator is // Check that the operator is registered require( BitmapUtils.isSet(currentBitmap, quorumNumber), - "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum" + "rcOp!Reg" ); // Prevent duplicate operators require( operator > prevOperatorAddress, - "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order" + "rcDupOp" ); } @@ -347,7 +345,7 @@ contract RegistryCoordinator is * @param socket is the new socket of the operator */ function updateSocket(string memory socket) external { - require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegistryCoordinator.updateSocket: operator is not registered"); + require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "o!Reg"); emit OperatorSocketUpdate(_operatorInfo[msg.sender].operatorId, socket); } @@ -476,12 +474,12 @@ contract RegistryCoordinator is */ uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); uint192 currentBitmap = _currentOperatorBitmap(operatorId); - require(!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0"); - require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); + require(!quorumsToAdd.isEmpty(), "qEmp"); + require(quorumsToAdd.noBitsInCommon(currentBitmap), "!Bit"); uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd)); // Check that the operator can reregister if ejected - require(lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp, "RegistryCoordinator._registerOperator: operator cannot reregister yet"); + require(lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp, "!opEj"); /** * Update operator's bitmap, socket, and status. Only update operatorInfo if needed: @@ -564,18 +562,18 @@ contract RegistryCoordinator is ) internal view { address operatorToKick = kickParams.operator; bytes32 idToKick = _operatorInfo[operatorToKick].operatorId; - require(newOperator != operatorToKick, "RegistryCoordinator._validateChurn: cannot churn self"); - require(kickParams.quorumNumber == quorumNumber, "RegistryCoordinator._validateChurn: quorumNumber not the same as signed"); + require(newOperator != operatorToKick, "ch0"); + require(kickParams.quorumNumber == quorumNumber, "ch1"); // Get the target operator's stake and check that it is below the kick thresholds uint96 operatorToKickStake = stakeRegistry.getCurrentStake(idToKick, quorumNumber); require( newOperatorStake > _individualKickThreshold(operatorToKickStake, setParams), - "RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn" + "ch2" ); require( operatorToKickStake < _totalKickThreshold(totalQuorumStake, setParams), - "RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake" + "ch3" ); } @@ -591,7 +589,7 @@ contract RegistryCoordinator is // Fetch the operator's info and ensure they are registered OperatorInfo storage operatorInfo = _operatorInfo[operator]; bytes32 operatorId = operatorInfo.operatorId; - require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered"); + require(operatorInfo.status == OperatorStatus.REGISTERED, "o!Reg"); /** * Get bitmap of quorums to deregister from and operator's current bitmap. Validate that: @@ -602,8 +600,8 @@ contract RegistryCoordinator is */ uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); uint192 currentBitmap = _currentOperatorBitmap(operatorId); - require(!quorumsToRemove.isEmpty(), "RegistryCoordinator._deregisterOperator: bitmap cannot be 0"); - require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums"); + require(!quorumsToRemove.isEmpty(), "qEmp"); + require(quorumsToRemove.isSubsetOf(currentBitmap), "!Sub"); uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove)); // Update operator's bitmap and status @@ -675,8 +673,8 @@ contract RegistryCoordinator is SignatureWithSaltAndExpiry memory churnApproverSignature ) internal { // make sure the salt hasn't been used already - require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used"); - require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired"); + require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "chSalt"); + require(churnApproverSignature.expiry >= block.timestamp, "chExp"); // set salt used to true isChurnApproverSaltUsed[churnApproverSignature.salt] = true; @@ -704,7 +702,7 @@ contract RegistryCoordinator is ) internal { // Increment the total quorum count. Fails if we're already at the max uint8 prevQuorumCount = quorumCount; - require(prevQuorumCount < MAX_QUORUM_COUNT, "RegistryCoordinator.createQuorum: max quorums reached"); + require(prevQuorumCount < MAX_QUORUM_COUNT, "qMax"); quorumCount = prevQuorumCount + 1; // The previous count is the new quorum's number @@ -786,7 +784,7 @@ contract RegistryCoordinator is } revert( - "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number" + "O!Reg" ); } @@ -870,11 +868,11 @@ contract RegistryCoordinator is */ require( blockNumber >= quorumBitmapUpdate.updateBlockNumber, - "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" + "bn<" ); require( quorumBitmapUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber, - "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" + "bn<2" ); return quorumBitmapUpdate.quorumBitmap; diff --git a/src/Whitelist.sol b/src/Whitelist.sol new file mode 100644 index 00000000..6bbaa20a --- /dev/null +++ b/src/Whitelist.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.12; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; + +/* + * Note: The OwnableUpgradeable contract is used to ensure that only the owner of the contract + * can add or remove addresses from the whitelist. It must be OwnableUpgradeable instead of Ownable + * because RegistryCoordinator is OwnableUpgradeable, and it caused conflicts on onlyOwner modifier. + */ +contract Whitelist is OwnableUpgradeable { + event AddedToWhiteList(address indexed added); + event RemovedFromWhiteList(address indexed removed); + + mapping(address => bool) whitelist; + + modifier onlyWhitelisted() { + require(isWhitelisted(msg.sender), "not whitelisted"); + _; + } + + function add(address _address) public onlyOwner { + whitelist[_address] = true; + emit AddedToWhiteList(_address); + } + + function add_multiple(address[] memory _addresses) public onlyOwner { + for (uint256 i = 0; i < _addresses.length; i++) { + whitelist[_addresses[i]] = true; + emit AddedToWhiteList(_addresses[i]); + } + } + + function remove(address _address) public onlyOwner { + whitelist[_address] = false; + emit RemovedFromWhiteList(_address); + } + + function remove_multiple(address[] memory _addresses) public onlyOwner { + for (uint256 i = 0; i < _addresses.length; i++) { + whitelist[_addresses[i]] = false; + emit RemovedFromWhiteList(_addresses[i]); + } + } + + function isWhitelisted(address _address) public view returns (bool) { + return whitelist[_address]; + } +} diff --git a/src/interfaces/IBLSSignatureChecker.sol b/src/interfaces/IBLSSignatureChecker.sol index 844c7217..6e75f930 100644 --- a/src/interfaces/IBLSSignatureChecker.sol +++ b/src/interfaces/IBLSSignatureChecker.sol @@ -69,8 +69,7 @@ interface IBLSSignatureChecker { */ function checkSignatures( bytes32 msgHash, - bytes calldata quorumNumbers, - uint32 referenceBlockNumber, + uint32 referenceBlockNumber, NonSignerStakesAndSignature memory nonSignerStakesAndSignature ) external