From 8d6737d75bb3a1e224f1b141070e55d9c8d04d83 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 25 May 2023 14:56:47 -0700 Subject: [PATCH 001/110] add initial stake registry --- src/contracts/interfaces/IStakeRegistry.sol | 17 +- src/contracts/middleware/StakeRegistry.sol | 563 ++++++++++++++++++++ 2 files changed, 571 insertions(+), 9 deletions(-) create mode 100644 src/contracts/middleware/StakeRegistry.sol diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 04b40b998..9dc8ff24e 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -42,7 +42,8 @@ interface IStakeRegistry is IRegistry { /** * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. - * @dev Function will revert in the event that `index` is out-of-bounds. + * @param quorumNumber The quorum number to get the stake for. + * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. */ function getTotalStakeUpdateForQuorumFromIndex(uint8 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory); @@ -85,11 +86,10 @@ interface IStakeRegistry is IRegistry { /** * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. - * @param operator is the operator of interest + * @param operatorId is the id of the operator of interest * @param blockNumber is the block number of interest * @param quorumNumber is the quorum number which the operator had stake in - * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]`, where `operatorId` is looked up - * in `registryCoordinator.getOperatorId(operator)` + * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` @@ -100,7 +100,7 @@ interface IStakeRegistry is IRegistry { * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. */ function checkOperatorActiveAtBlockNumber( - address operator, + bytes32 operatorId, uint256 blockNumber, uint8 quorumNumber, uint256 stakeHistoryIndex @@ -108,11 +108,10 @@ interface IStakeRegistry is IRegistry { /** * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` for `quorumNumber` as proof. - * @param operator is the operator of interest + * @param operatorId is the id of the operator of interest * @param blockNumber is the block number of interest * @param quorumNumber is the quorum number which the operator had no stake in - * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]`, where `operatorId` is looked up - * in `registryCoordinator.getOperatorId(operator)` + * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` @@ -123,7 +122,7 @@ interface IStakeRegistry is IRegistry { * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. */ function checkOperatorInactiveAtBlockNumber( - address operator, + bytes32 operatorId, uint256 blockNumber, uint8 quorumNumber, uint256 stakeHistoryIndex diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol new file mode 100644 index 000000000..e95abba6f --- /dev/null +++ b/src/contracts/middleware/StakeRegistry.sol @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "../interfaces/IServiceManager.sol"; +import "../interfaces/IQuorumRegistry.sol"; +import "./VoteWeigherBase.sol"; + +/** + * @title An abstract Registry-type contract that is signature scheme agnostic. + * @author Layr Labs, Inc. + * @notice This contract is used for + * - registering new operators + * - committing to and finalizing de-registration as an operator + * - updating the stakes of the operator + * @dev This contract is missing key functions. See `BLSRegistry` or `ECDSARegistry` for examples that inherit from this contract. + */ +abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { + + // TODO: set these on initialization + /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as + /// evaluated by this contract's 'VoteWeigher' logic. + uint96[256] public minimumStakeForQuorum; + + /// @notice array of the history of the total stakes for each quorum -- marked as internal since getTotalStakeFromIndex is a getter for this + OperatorStakeUpdate[][256] internal totalStakeHistory; + + /// @notice mapping from operator's operatorId to the history of their stake updates + mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public operatorIdToStakeHistory; + + /// @notice mapping from operator's operatorId to the history of their index in the array of all operators + mapping(bytes32 => OperatorIndex[]) public operatorIdToIndexHistory; + + // EVENTS + /// @notice emitted whenever the stake of `operator` is updated + event StakeUpdate( + address operator, + uint8 quorumNumber, + uint96 stake, + uint32 updateBlockNumber, + uint32 prevUpdateBlockNumber + ); + + /** + * @notice Irrevocably sets the (immutable) `delegation` & `strategyManager` addresses, and `NUMBER_OF_QUORUMS` variable. + */ + constructor( + IStrategyManager _strategyManager, + IServiceManager _serviceManager + ) VoteWeigherBase(_strategyManager, _serviceManager) + // solhint-disable-next-line no-empty-blocks + { + } + + /** + * @notice Adds empty first entries to the dynamic arrays `totalStakeHistory` and `totalOperatorsHistory`, + * to record an initial condition of zero operators with zero total stake. + * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with + */ + function _initialize( + uint96[] memory _minimumStakeForQuorum, + StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers + ) internal virtual onlyInitializing { + // sanity check lengths + require(_minimumStakeForQuorum.length == _quorumStrategiesConsideredAndMultipliers.length, "Registry._initialize: minimumStakeForQuorum length mismatch"); + // push an empty OperatorStakeUpdate struct to the total stake history to record starting with zero stake + // TODO: Address this @ gpsanant + OperatorStakeUpdate memory _totalStakeUpdate; + for (uint quorumNumber = 0; quorumNumber < 256;) { + totalStakeHistory[quorumNumber].push(_totalStakeUpdate); + unchecked { + ++quorumNumber; + } + } + + // add the strategies considered and multipliers for each quorum + for (uint8 quorumNumber = 0; quorumNumber < _quorumStrategiesConsideredAndMultipliers.length;) { + minimumStakeForQuorum[quorumNumber] = _minimumStakeForQuorum[quorumNumber]; + _createQuorum(_quorumStrategiesConsideredAndMultipliers[quorumNumber]); + unchecked { + ++quorumNumber; + } + } + } + + /** + * @notice Returns the `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array. + * @param quorumNumber The quorum number to get the stake for. + * @param operatorId The id of the operator of interest. + * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. + * @dev Function will revert if `index` is out-of-bounds. + */ + function getStakeUpdateForQuorumFromOperatorIdAndIndex(uint8 quorumNumber, bytes32 operatorId, uint256 index) + external + view + returns (OperatorStakeUpdate memory) + { + return operatorIdToStakeHistory[operatorId][quorumNumber][index]; + } + + /** + * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. + * @param quorumNumber The quorum number to get the stake for. + * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. + */ + function getTotalStakeUpdateForQuorumFromIndex(uint256 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) { + return totalStakeHistory[quorumNumber][index]; + } + + /** + * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the + * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if it was the operator's + * stake at `blockNumber`. Reverts otherwise. + * @param quorumNumber The quorum number to get the stake for. + * @param operatorId The id of the operator of interest. + * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. + * @param blockNumber Block number to make sure the stake is from. + * @dev Function will revert if `index` is out-of-bounds. + */ + function getStakeForQuorumAtBlockNumberFromOperatorIdAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 operatorId, uint256 index) + external + view + returns (uint96) + { + OperatorStakeUpdate memory operatorStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][index]; + _validateOperatorStakeUpdateAtBlockNumber(operatorStakeUpdate, blockNumber); + return operatorStakeUpdate.stake; + } + + /** + * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the + * `totalStakeHistory[quorumNumber]` array if it was the stake at `blockNumber`. Reverts otherwise. + * @param quorumNumber The quorum number to get the stake for. + * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. + * @param blockNumber Block number to make sure the stake is from. + * @dev Function will revert if `index` is out-of-bounds. + */ + function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96) { + OperatorStakeUpdate memory totalStakeUpdate = totalStakeHistory[quorumNumber][index]; + _validateOperatorStakeUpdateAtBlockNumber(totalStakeUpdate, blockNumber); + return totalStakeUpdate.stake; + } + + /** + * @notice Returns the most recent stake weight for the `operatorId` for a certain quorum + * @dev Function returns an OperatorStakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history + */ + function getMostRecentStakeUpdateByOperatorId(bytes32 operatorId, uint8 quorumNumber) public view returns (OperatorStakeUpdate memory) { + uint256 historyLength = operatorIdToStakeHistory[operatorId][quorumNumber].length; + OperatorStakeUpdate memory operatorStakeUpdate; + if (historyLength == 0) { + return operatorStakeUpdate; + } else { + operatorStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][historyLength - 1]; + return operatorStakeUpdate; + } + } + + function getStakeHistoryLengthForQuorumNumber(bytes32 operatorId, uint8 quorumNumber) external view returns (uint256) { + return operatorIdToStakeHistory[operatorId][quorumNumber].length; + } + + /** + * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber` + * @dev Function returns weight of **0** in the event that the operator has no stake history + */ + function getCurrentOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) { + OperatorStakeUpdate memory operatorStakeUpdate = getMostRecentStakeUpdateByOperator(operatorId, quorumNumber); + return operatorStakeUpdate.stake; + } + + /// @notice Returns the stake weight from the latest entry in `totalStakeHistory` for quorum `quorumNumber`. + function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) { + // no chance of underflow / error in next line, since an empty entry is pushed in the constructor + return totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake; + } + + function getLengthOfOperatorIdStakeHistoryForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint256) { + return operatorIdToStakeHistory[operatorId][quorumNumber].length; + } + + function getLengthOfOperatorIdIndexHistory(bytes32 operatorId) external view returns (uint256) { + return operatorIdToIndexHistory[operatorId].length; + } + + function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) { + return totalStakeHistory[quorumNumber].length; + } + + function getLengthOfTotalOperatorsHistory() external view returns (uint256) { + return totalOperatorsHistory.length; + } + + /// @notice Returns task number from when `operator` has been registered. + function getFromTaskNumberForOperator(address operator) external view returns (uint32) { + return registry[operator].fromTaskNumber; + } + + /// @notice Returns the current number of operators of this service. + function numOperators() public view returns (uint32) { + return uint32(operatorList.length); + } + + /** + * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. + * @param operatorId is the id of the operator of interest + * @param blockNumber is the block number of interest + * @param quorumNumber is the quorum number which the operator had stake in + * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` + * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise + * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: + * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` + * 2) `operatorIdToStakeHistory[operatorId][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or + * is must be strictly greater than `blockNumber` + * 3) `operatorIdToStakeHistory[operatorId][quorumNumber][index].stake > 0` i.e. the operator had nonzero stake + * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a + * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. + */ + function checkOperatorActiveAtBlockNumber( + bytes32 operatorId, + uint256 blockNumber, + uint8 quorumNumber, + uint256 stakeHistoryIndex + ) external view returns (bool) + { + // pull the stake history entry specified by `stakeHistoryIndex` + OperatorStakeUpdate memory operatorStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][stakeHistoryIndex]; + return ( + // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber` + (operatorStakeUpdate.updateBlockNumber <= blockNumber) + && + // if there is a next update, then check that the next update occurred strictly after `blockNumber` + (operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber) + && + /// verify that the stake was non-zero at the time (note: here was use the assumption that the operator was 'inactive' + /// once their stake fell to zero) + operatorStakeUpdate.stake != 0 // this implicitly checks that the quorum bitmap was 1 for the quorum of interest + ); + } + + /** + * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` for `quorumNumber` as proof. + * @param operatorId is the id of the operator of interest + * @param blockNumber is the block number of interest + * @param quorumNumber is the quorum number which the operator had no stake in + * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` + * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise + * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: + * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` + * 2) `operatorIdToStakeHistory[operatorId][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or + * is must be strictly greater than `blockNumber` + * 3) `operatorIdToStakeHistory[operatorId][quorumNumber][index].stake == 0` i.e. the operator had zero stake + * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a + * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. + * @dev One precondition that must be checked is that the operator is a part of the given `quorumNumber` + */ + function checkOperatorInactiveAtBlockNumber( + bytes32 operatorId, + uint256 blockNumber, + uint8 quorumNumber, + uint256 stakeHistoryIndex + ) external view returns (bool) + { + // special case for `operatorIdToStakeHistory[operatorId]` having lenght zero -- in which case we know the operator was never registered + if (operatorIdToStakeHistory[operatorId][quorumNumber].length == 0) { + return true; + } + // pull the stake history entry specified by `stakeHistoryIndex` + OperatorStakeUpdate memory operatorStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][stakeHistoryIndex]; + return ( + // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber` + (operatorStakeUpdate.updateBlockNumber <= blockNumber) + && + // if there is a next update, then check that the next update occurred strictly after `blockNumber` + (operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber) + && + /// verify that the stake was zero at the time (note: here was use the assumption that the operator was 'inactive' + /// once their stake fell to zero) + operatorStakeUpdate.stake == 0 + ); + } + + // MUTATING FUNCTIONS + + /// @notice Adjusts the `minimumStakeFirstQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 1st quorum. + function setMinimumStakeForQuorum(uint8 quorumNumber, uint96 minimumStake) external onlyServiceManagerOwner { + minimumStakeForQuorum[quorumNumber] = minimumStake; + } + + function updateSocket(string calldata newSocket) external { + require( + registry[msg.sender].status == IQuorumRegistry.Status.ACTIVE, + "RegistryBase.updateSocket: Can only update socket if active on the service" + ); + emit SocketUpdate(msg.sender, newSocket); + } + + + // INTERNAL FUNCTIONS + /** + * @notice Called when the total number of operators has changed. + * Sets the `toBlockNumber` field on the last entry *so far* in thedynamic array `totalOperatorsHistory` to the current `block.number`, + * recording that the previous entry is *no longer the latest* and the block number at which the next was added. + * Pushes a new entry to `totalOperatorsHistory`, with `index` field set equal to the new amount of operators, recording the new number + * of total operators (and leaving the `toBlockNumber` field at zero, signaling that this is the latest entry in the array) + */ + function _updateTotalOperatorsHistory() internal { + // set the 'toBlockNumber' field on the last entry *so far* in 'totalOperatorsHistory' to the current block number + totalOperatorsHistory[totalOperatorsHistory.length - 1].toBlockNumber = uint32(block.number); + // push a new entry to 'totalOperatorsHistory', with 'index' field set equal to the new amount of operators + OperatorIndex memory _totalOperators; + _totalOperators.index = uint32(operatorList.length); + totalOperatorsHistory.push(_totalOperators); + } + + /** + * @notice Remove the operator from active status. Removes the operator with the given `operatorId` from the `index` in `operatorList`, + * updates operatorList and index histories, and performs other necessary updates for removing operator + */ + function _removeOperator(address operator, bytes32 operatorId, uint32 index) internal virtual { + // remove the operator's stake + _removeOperatorStake(operatorId); + + // store blockNumber at which operator index changed (stopped being applicable) + operatorIdToIndexHistory[operatorId][operatorIdToIndexHistory[operatorId].length - 1].toBlockNumber = + uint32(block.number); + + // remove the operator at `index` from the `operatorList` + address swappedOperator = _removeOperatorFromOperatorListAndIndex(index); + + // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges + uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); + // committing to not signing off on any more middleware tasks + registry[operator].status = IQuorumRegistry.Status.INACTIVE; + + // record a stake update unbonding the operator after `latestServeUntilBlock` + serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); + + // Emit `Deregistration` event + emit Deregistration(operator, swappedOperator, index); + } + + /** + * @notice Removes the stakes of the operator + */ + function _removeOperatorStake(bytes32 operatorId) internal { + // loop through the operator's quorum bitmap and remove the operator's stake for each quorum + uint256 quorumBitmap = operatorIdToQuorumBitmap[operatorId]; + for (uint quorumNumber = 0; quorumNumber < quorumCount;) { + if (quorumBitmap >> quorumNumber & 1 == 1) { + _removeOperatorStakeForQuorum(operatorId, uint8(quorumNumber)); + } + unchecked { + quorumNumber++; + } + } + + } + + /** + * @notice Removes the stakes of the operator with operatorId `operatorId` for the quorum with number `quorumNumber` + */ + function _removeOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) internal { + // gas saving by caching length here + uint256 operatorIdToStakeHistoryLengthMinusOne = operatorIdToStakeHistory[operatorId][quorumNumber].length - 1; + + // determine current stakes + OperatorStakeUpdate memory currentStakeUpdate = + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne]; + //set nextUpdateBlockNumber in current stakes + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = + uint32(block.number); + + /** + * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. + */ + operatorIdToStakeHistory[operatorId][quorumNumber].push( + OperatorStakeUpdate({ + // recording the current block number where the operator stake got updated + updateBlockNumber: uint32(block.number), + // mark as 0 since the next update has not yet occurred + nextUpdateBlockNumber: 0, + // setting the operator's stake to 0 + stake: 0 + }) + ); + + // subtract the amounts staked by the operator that is getting deregistered from the total stake + // copy latest totalStakes to memory + OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; + currentTotalStakeUpdate.stake -= currentStakeUpdate.stake; + // update storage of total stake + _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); + + emit StakeUpdate( + msg.sender, + // new stakes are zero + quorumNumber, + 0, + uint32(block.number), + currentStakeUpdate.updateBlockNumber + ); + } + + /// @notice Adds the Operator `operator` with the given `pubkeyHash` to the `operatorList` and performs necessary related updates. + function _addRegistrant( + address operator, + bytes32 pubkeyHash, + uint256 quorumBitmap + ) + internal virtual + { + + require( + slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, + "RegistryBase._addRegistrant: operator must be opted into slashing by the serviceManager" + ); + // store the Operator's info in mapping + registry[operator] = Operator({ + pubkeyHash: pubkeyHash, + status: IQuorumRegistry.Status.ACTIVE, + fromTaskNumber: serviceManager.taskNumber() + }); + + // store the operator's quorum bitmap + pubkeyHashToQuorumBitmap[pubkeyHash] = quorumBitmap; + + // add the operator to the list of operators + operatorList.push(operator); + + // calculate stakes for each quorum the operator is trying to join + _registerStake(operator, pubkeyHash, quorumBitmap); + + // record `operator`'s index in list of operators + OperatorIndex memory operatorIndex; + operatorIndex.index = uint32(operatorList.length - 1); + pubkeyHashToIndexHistory[pubkeyHash].push(operatorIndex); + + // Update totalOperatorsHistory array + _updateTotalOperatorsHistory(); + + // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet + serviceManager.recordFirstStakeUpdate(operator, 0); + } + + /** + * TODO: critique: "Currently only `_registrationStakeEvaluation` uses the `uint256 registrantType` input -- we should **EITHER** store this + * and keep using it in other places as well, **OR** stop using it altogether" + */ + /** + * @notice Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`. + * @dev This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere. + */ + function _registerStake(address operator, bytes32 pubkeyHash, uint256 quorumBitmap) + internal + { + // verify that the `operator` is not already registered + require( + registry[operator].status == IQuorumRegistry.Status.INACTIVE, + "RegistryBase._registerStake: Operator is already registered" + ); + + OperatorStakeUpdate memory _operatorStakeUpdate; + // add the `updateBlockNumber` info + _operatorStakeUpdate.updateBlockNumber = uint32(block.number); + OperatorStakeUpdate memory _newTotalStakeUpdate; + // add the `updateBlockNumber` info + _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); + // for each quorum, evaluate stake and add to total stake + for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { + // evaluate the stake for the operator + if(quorumBitmap >> quorumNumber & 1 == 1) { + _operatorStakeUpdate.stake = uint96(weightOfOperator(operator, quorumNumber)); + // check if minimum requirement has been met + require(_operatorStakeUpdate.stake >= minimumStakeForQuorum[quorumNumber], "RegistryBase._registerStake: Operator does not meet minimum stake requirement for quorum"); + // check special case that operator is re-registering (and thus already has some history) + if (pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length != 0) { + // correctly set the 'nextUpdateBlockNumber' field for the re-registering operator's oldest history entry + pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1].nextUpdateBlockNumber + = uint32(block.number); + } + // push the new stake for the operator to storage + pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push(_operatorStakeUpdate); + + // get the total stake for the quorum + _newTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; + // add operator stakes to total stake (in memory) + _newTotalStakeUpdate.stake = uint96(_newTotalStakeUpdate.stake + _operatorStakeUpdate.stake); + // update storage of total stake + _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); + + emit StakeUpdate( + operator, + quorumNumber, + _operatorStakeUpdate.stake, + uint32(block.number), + // no previous update block number -- use 0 instead + 0 // TODO: Decide whether this needs to be set in re-registration edge case + ); + } + unchecked { + ++quorumNumber; + } + } + } + + /** + * @notice Finds the updated stake for `operator`, stores it and records the update. + * @dev **DOES NOT UPDATE `totalStake` IN ANY WAY** -- `totalStake` updates must be done elsewhere. + */ + function _updateOperatorStake(address operator, bytes32 pubkeyHash, uint256 quorumBitmap, uint8 quorumNumber, uint32 prevUpdateBlockNumber) + internal + returns (OperatorStakeUpdate memory operatorStakeUpdate) + { + // if the operator is part of the quorum + if (quorumBitmap >> quorumNumber & 1 == 1) { + // determine new stakes + operatorStakeUpdate.updateBlockNumber = uint32(block.number); + operatorStakeUpdate.stake = weightOfOperator(operator, quorumNumber); + + // check if minimum requirements have been met + if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { + operatorStakeUpdate.stake = uint96(0); + } + + // set nextUpdateBlockNumber in prev stakes + pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1].nextUpdateBlockNumber = + uint32(block.number); + // push new stake to storage + pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push(operatorStakeUpdate); + + emit StakeUpdate( + operator, + quorumNumber, + operatorStakeUpdate.stake, + uint32(block.number), + prevUpdateBlockNumber + ); + } + } + + /// @notice Records that the `totalStake` is now equal to the input param @_totalStake + function _recordTotalStakeUpdate(uint8 quorumNumber, OperatorStakeUpdate memory _totalStake) internal { + _totalStake.updateBlockNumber = uint32(block.number); + totalStakeHistory[quorumNumber][totalStakeHistory.length - 1].nextUpdateBlockNumber = uint32(block.number); + totalStakeHistory[quorumNumber].push(_totalStake); + } + + /// @notice Validates that the `operatorStake` was accurate at the given `blockNumber` + function _validateOperatorStakeUpdateAtBlockNumber(OperatorStakeUpdate memory operatorStakeUpdate, uint32 blockNumber) internal pure { + require( + operatorStakeUpdate.updateBlockNumber <= blockNumber, + "RegistryBase._validateOperatorStakeAtBlockNumber: operatorStakeUpdate is from after blockNumber" + ); + require( + operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber, + "RegistryBase._validateOperatorStakeAtBlockNumber: there is a newer operatorStakeUpdate available before blockNumber" + ); + } + + // storage gap + uint256[50] private __GAP; +} \ No newline at end of file From 98c5e77705fe4b6771f3dc25988933980b4ea76f Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 25 May 2023 16:34:40 -0700 Subject: [PATCH 002/110] everything but quorumNumbers --- src/contracts/interfaces/IStakeRegistry.sol | 19 +- src/contracts/middleware/StakeRegistry.sol | 317 +++++++++----------- 2 files changed, 155 insertions(+), 181 deletions(-) diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 3432f9709..c1834bf2a 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -32,11 +32,12 @@ interface IStakeRegistry is IRegistry { /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. + * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. * @param quorumNumbers The quourm numbers the operator is deregistering from. * @dev access restricted to the RegistryCoordinator */ - function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external; + function deregisterOperator(address operator, bytes32 operatorId, uint8[] memory quorumNumbers) external; function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256); @@ -131,20 +132,12 @@ interface IStakeRegistry is IRegistry { uint256 stakeHistoryIndex ) external view returns (bool); - /** - * @notice Returns the most recent stake weight for the `operator` for quorum `quorumNumber` - * @dev Function returns weight of **0** in the event that the operator has no stake history - */ - function getCurrentOperatorStakeForQuorum(address operator, uint8 quorumNumber) external view returns (uint96); - - /// @notice Returns the stake weight from the latest entry in `totalStakeHistory` for quorum `quorumNumber`. - function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96); - /** * @notice Used for updating information on deposits of nodes. - * @param operatorIds are the ids of the nodes whose deposit information is getting updated + * @param operators are the addresses of the operators whose stake information is getting updated + * @param operatorIds are the ids of the operators whose stake information is getting updated + * @param quorumNumbers are the quorumNumbers for each operator in `operators` that they are a part of * @param prevElements are the elements before this middleware in the operator's linked list within the slasher - * @dev updates the stakes of the operators in storage and communicates the updates to the service manager that sends them to the slasher */ - function updateStakes(bytes32[] memory operatorIds, uint256[] memory prevElements) external; + function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint8[][] memory quorumNumbers, uint256[] memory prevElements) external; } \ No newline at end of file diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index e95abba6f..404c65fbf 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -3,7 +3,8 @@ pragma solidity =0.8.12; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IServiceManager.sol"; -import "../interfaces/IQuorumRegistry.sol"; +import "../interfaces/IStakeRegistry.sol"; +import "../interfaces/IRegistryCoordinator.sol"; import "./VoteWeigherBase.sol"; /** @@ -15,7 +16,9 @@ import "./VoteWeigherBase.sol"; * - updating the stakes of the operator * @dev This contract is missing key functions. See `BLSRegistry` or `ECDSARegistry` for examples that inherit from this contract. */ -abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { +contract StakeRegistry is VoteWeigherBase, IStakeRegistry { + + IRegistryCoordinator public registryCoordinator; // TODO: set these on initialization /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as @@ -28,13 +31,10 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { /// @notice mapping from operator's operatorId to the history of their stake updates mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public operatorIdToStakeHistory; - /// @notice mapping from operator's operatorId to the history of their index in the array of all operators - mapping(bytes32 => OperatorIndex[]) public operatorIdToIndexHistory; - // EVENTS /// @notice emitted whenever the stake of `operator` is updated event StakeUpdate( - address operator, + bytes32 operatorId, uint8 quorumNumber, uint96 stake, uint32 updateBlockNumber, @@ -103,7 +103,7 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { * @param quorumNumber The quorum number to get the stake for. * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. */ - function getTotalStakeUpdateForQuorumFromIndex(uint256 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) { + function getTotalStakeUpdateForQuorumFromIndex(uint8 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) { return totalStakeHistory[quorumNumber][index]; } @@ -165,7 +165,7 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { * @dev Function returns weight of **0** in the event that the operator has no stake history */ function getCurrentOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) { - OperatorStakeUpdate memory operatorStakeUpdate = getMostRecentStakeUpdateByOperator(operatorId, quorumNumber); + OperatorStakeUpdate memory operatorStakeUpdate = getMostRecentStakeUpdateByOperatorId(operatorId, quorumNumber); return operatorStakeUpdate.stake; } @@ -179,28 +179,10 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { return operatorIdToStakeHistory[operatorId][quorumNumber].length; } - function getLengthOfOperatorIdIndexHistory(bytes32 operatorId) external view returns (uint256) { - return operatorIdToIndexHistory[operatorId].length; - } - function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) { return totalStakeHistory[quorumNumber].length; } - function getLengthOfTotalOperatorsHistory() external view returns (uint256) { - return totalOperatorsHistory.length; - } - - /// @notice Returns task number from when `operator` has been registered. - function getFromTaskNumberForOperator(address operator) external view returns (uint32) { - return registry[operator].fromTaskNumber; - } - - /// @notice Returns the current number of operators of this service. - function numOperators() public view returns (uint32) { - return uint32(operatorList.length); - } - /** * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. * @param operatorId is the id of the operator of interest @@ -287,157 +269,90 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { minimumStakeForQuorum[quorumNumber] = minimumStake; } - function updateSocket(string calldata newSocket) external { - require( - registry[msg.sender].status == IQuorumRegistry.Status.ACTIVE, - "RegistryBase.updateSocket: Can only update socket if active on the service" - ); - emit SocketUpdate(msg.sender, newSocket); - } - - - // INTERNAL FUNCTIONS /** - * @notice Called when the total number of operators has changed. - * Sets the `toBlockNumber` field on the last entry *so far* in thedynamic array `totalOperatorsHistory` to the current `block.number`, - * recording that the previous entry is *no longer the latest* and the block number at which the next was added. - * Pushes a new entry to `totalOperatorsHistory`, with `index` field set equal to the new amount of operators, recording the new number - * of total operators (and leaving the `toBlockNumber` field at zero, signaling that this is the latest entry in the array) + * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`. + * @param operator The address of the operator to register. + * @param operatorId The id of the operator to register. + * @param quorumNumbers The quorum numbers the operator is registering for. + * @dev access restricted to the RegistryCoordinator */ - function _updateTotalOperatorsHistory() internal { - // set the 'toBlockNumber' field on the last entry *so far* in 'totalOperatorsHistory' to the current block number - totalOperatorsHistory[totalOperatorsHistory.length - 1].toBlockNumber = uint32(block.number); - // push a new entry to 'totalOperatorsHistory', with 'index' field set equal to the new amount of operators - OperatorIndex memory _totalOperators; - _totalOperators.index = uint32(operatorList.length); - totalOperatorsHistory.push(_totalOperators); + function registerOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) external virtual { + _registerOperator(operator, operatorId, quorumBitmap); } /** - * @notice Remove the operator from active status. Removes the operator with the given `operatorId` from the `index` in `operatorList`, - * updates operatorList and index histories, and performs other necessary updates for removing operator + * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. + * @param operator The address of the operator to deregister. + * @param operatorId The id of the operator to deregister. + * @param quorumNumbers The quourm numbers the operator is deregistering from. + * @dev access restricted to the RegistryCoordinator */ - function _removeOperator(address operator, bytes32 operatorId, uint32 index) internal virtual { - // remove the operator's stake - _removeOperatorStake(operatorId); - - // store blockNumber at which operator index changed (stopped being applicable) - operatorIdToIndexHistory[operatorId][operatorIdToIndexHistory[operatorId].length - 1].toBlockNumber = - uint32(block.number); - - // remove the operator at `index` from the `operatorList` - address swappedOperator = _removeOperatorFromOperatorListAndIndex(index); - - // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - // committing to not signing off on any more middleware tasks - registry[operator].status = IQuorumRegistry.Status.INACTIVE; - - // record a stake update unbonding the operator after `latestServeUntilBlock` - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); - - // Emit `Deregistration` event - emit Deregistration(operator, swappedOperator, index); + function deregisterOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) external virtual { + _deregisterOperator(operator, operatorId, quorumBitmap); } /** - * @notice Removes the stakes of the operator + * @notice Used for updating information on deposits of nodes. + * @param operators are the addresses of the operators whose stake information is getting updated + * @param operatorIds are the ids of the operators whose stake information is getting updated + * @param quorumBitmaps are the bitmaps of what quorums each operator in `operators` is part of + * @param prevElements are the elements before this middleware in the operator's linked list within the slasher */ - function _removeOperatorStake(bytes32 operatorId) internal { - // loop through the operator's quorum bitmap and remove the operator's stake for each quorum - uint256 quorumBitmap = operatorIdToQuorumBitmap[operatorId]; - for (uint quorumNumber = 0; quorumNumber < quorumCount;) { - if (quorumBitmap >> quorumNumber & 1 == 1) { - _removeOperatorStakeForQuorum(operatorId, uint8(quorumNumber)); + function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint256[] memory quorumBitmaps, uint256[] memory prevElements) external { + // for each quorum, loop through operators and see if they are apart of the quorum + // if they are, get their new weight and update their individual stake history and the + // quorum's total stake history accordingly + for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { + OperatorStakeUpdate memory totalStakeUpdate; + // for each operator + for(uint i = 0; i < operatorIds.length;) { + // if the operator is apart of the quorum + if (quorumBitmaps[i] >> quorumNumber & 1 == 1) { + // if the total stake has not been loaded yet, load it + if (totalStakeUpdate.updateBlockNumber == 0) { + totalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; + } + bytes32 operatorId = operatorIds[i]; + // get the operator's current stake + OperatorStakeUpdate memory prevStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistory[operatorId][quorumNumber].length - 1]; + // update the operator's stake based on current state + OperatorStakeUpdate memory newStakeUpdate = _updateOperatorStake(operators[i], operatorId, quorumBitmaps[i], quorumNumber, prevStakeUpdate.updateBlockNumber); + // calculate the new total stake for the quorum + totalStakeUpdate.stake = totalStakeUpdate.stake - prevStakeUpdate.stake + newStakeUpdate.stake; + } + unchecked { + ++i; + } + } + // if the total stake for this quorum was updated, record it in storage + if (totalStakeUpdate.updateBlockNumber != 0) { + // update the total stake history for the quorum + _recordTotalStakeUpdate(quorumNumber, totalStakeUpdate); } unchecked { - quorumNumber++; + ++quorumNumber; } } + // record stake updates in the EigenLayer Slasher + for (uint i = 0; i < operators.length;) { + serviceManager.recordStakeUpdate(operators[i], uint32(block.number), serviceManager.latestServeUntilBlock(), prevElements[i]); + unchecked { + ++i; + } + } } - /** - * @notice Removes the stakes of the operator with operatorId `operatorId` for the quorum with number `quorumNumber` - */ - function _removeOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) internal { - // gas saving by caching length here - uint256 operatorIdToStakeHistoryLengthMinusOne = operatorIdToStakeHistory[operatorId][quorumNumber].length - 1; - - // determine current stakes - OperatorStakeUpdate memory currentStakeUpdate = - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne]; - //set nextUpdateBlockNumber in current stakes - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = - uint32(block.number); - - /** - * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. - */ - operatorIdToStakeHistory[operatorId][quorumNumber].push( - OperatorStakeUpdate({ - // recording the current block number where the operator stake got updated - updateBlockNumber: uint32(block.number), - // mark as 0 since the next update has not yet occurred - nextUpdateBlockNumber: 0, - // setting the operator's stake to 0 - stake: 0 - }) - ); - - // subtract the amounts staked by the operator that is getting deregistered from the total stake - // copy latest totalStakes to memory - OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; - currentTotalStakeUpdate.stake -= currentStakeUpdate.stake; - // update storage of total stake - _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); - - emit StakeUpdate( - msg.sender, - // new stakes are zero - quorumNumber, - 0, - uint32(block.number), - currentStakeUpdate.updateBlockNumber - ); - } - - /// @notice Adds the Operator `operator` with the given `pubkeyHash` to the `operatorList` and performs necessary related updates. - function _addRegistrant( - address operator, - bytes32 pubkeyHash, - uint256 quorumBitmap - ) - internal virtual - { + // INTERNAL FUNCTIONS + function _registerOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) internal { require( slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, "RegistryBase._addRegistrant: operator must be opted into slashing by the serviceManager" ); - // store the Operator's info in mapping - registry[operator] = Operator({ - pubkeyHash: pubkeyHash, - status: IQuorumRegistry.Status.ACTIVE, - fromTaskNumber: serviceManager.taskNumber() - }); - - // store the operator's quorum bitmap - pubkeyHashToQuorumBitmap[pubkeyHash] = quorumBitmap; - - // add the operator to the list of operators - operatorList.push(operator); // calculate stakes for each quorum the operator is trying to join - _registerStake(operator, pubkeyHash, quorumBitmap); - - // record `operator`'s index in list of operators - OperatorIndex memory operatorIndex; - operatorIndex.index = uint32(operatorList.length - 1); - pubkeyHashToIndexHistory[pubkeyHash].push(operatorIndex); - - // Update totalOperatorsHistory array - _updateTotalOperatorsHistory(); + _registerStake(operator, operatorId, quorumBitmap); // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet serviceManager.recordFirstStakeUpdate(operator, 0); @@ -451,15 +366,9 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { * @notice Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`. * @dev This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere. */ - function _registerStake(address operator, bytes32 pubkeyHash, uint256 quorumBitmap) + function _registerStake(address operator, bytes32 operatorId, uint256 quorumBitmap) internal { - // verify that the `operator` is not already registered - require( - registry[operator].status == IQuorumRegistry.Status.INACTIVE, - "RegistryBase._registerStake: Operator is already registered" - ); - OperatorStakeUpdate memory _operatorStakeUpdate; // add the `updateBlockNumber` info _operatorStakeUpdate.updateBlockNumber = uint32(block.number); @@ -474,13 +383,13 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { // check if minimum requirement has been met require(_operatorStakeUpdate.stake >= minimumStakeForQuorum[quorumNumber], "RegistryBase._registerStake: Operator does not meet minimum stake requirement for quorum"); // check special case that operator is re-registering (and thus already has some history) - if (pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length != 0) { + if (operatorIdToStakeHistory[operatorId][quorumNumber].length != 0) { // correctly set the 'nextUpdateBlockNumber' field for the re-registering operator's oldest history entry - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1].nextUpdateBlockNumber + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistory[operatorId][quorumNumber].length - 1].nextUpdateBlockNumber = uint32(block.number); } // push the new stake for the operator to storage - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push(_operatorStakeUpdate); + operatorIdToStakeHistory[operatorId][quorumNumber].push(_operatorStakeUpdate); // get the total stake for the quorum _newTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; @@ -490,7 +399,7 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); emit StakeUpdate( - operator, + operatorId, quorumNumber, _operatorStakeUpdate.stake, uint32(block.number), @@ -504,11 +413,83 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { } } + function _deregisterOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) internal { + // remove the operator's stake + _removeOperatorStake(operatorId, quorumBitmap); + + // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges + uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); + + // record a stake update unbonding the operator after `latestServeUntilBlock` + serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); + } + + /** + * @notice Removes the stakes of the operator + */ + function _removeOperatorStake(bytes32 operatorId, uint256 quorumBitmap) internal { + // loop through the operator's quorum bitmap and remove the operator's stake for each quorum + for (uint quorumNumber = 0; quorumNumber < quorumCount;) { + if (quorumBitmap >> quorumNumber & 1 == 1) { + _removeOperatorStakeForQuorum(operatorId, uint8(quorumNumber)); + } + unchecked { + quorumNumber++; + } + } + + } + + /** + * @notice Removes the stakes of the operator with operatorId `operatorId` for the quorum with number `quorumNumber` + */ + function _removeOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) internal { + // gas saving by caching length here + uint256 operatorIdToStakeHistoryLengthMinusOne = operatorIdToStakeHistory[operatorId][quorumNumber].length - 1; + + // determine current stakes + OperatorStakeUpdate memory currentStakeUpdate = + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne]; + //set nextUpdateBlockNumber in current stakes + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = + uint32(block.number); + + /** + * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. + */ + operatorIdToStakeHistory[operatorId][quorumNumber].push( + OperatorStakeUpdate({ + // recording the current block number where the operator stake got updated + updateBlockNumber: uint32(block.number), + // mark as 0 since the next update has not yet occurred + nextUpdateBlockNumber: 0, + // setting the operator's stake to 0 + stake: 0 + }) + ); + + // subtract the amounts staked by the operator that is getting deregistered from the total stake + // copy latest totalStakes to memory + OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; + currentTotalStakeUpdate.stake -= currentStakeUpdate.stake; + // update storage of total stake + _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); + + emit StakeUpdate( + operatorId, + // new stakes are zero + quorumNumber, + 0, + uint32(block.number), + currentStakeUpdate.updateBlockNumber + ); + } + /** * @notice Finds the updated stake for `operator`, stores it and records the update. * @dev **DOES NOT UPDATE `totalStake` IN ANY WAY** -- `totalStake` updates must be done elsewhere. */ - function _updateOperatorStake(address operator, bytes32 pubkeyHash, uint256 quorumBitmap, uint8 quorumNumber, uint32 prevUpdateBlockNumber) + function _updateOperatorStake(address operator, bytes32 operatorId, uint256 quorumBitmap, uint8 quorumNumber, uint32 prevUpdateBlockNumber) internal returns (OperatorStakeUpdate memory operatorStakeUpdate) { @@ -524,13 +505,13 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { } // set nextUpdateBlockNumber in prev stakes - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1].nextUpdateBlockNumber = + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistory[operatorId][quorumNumber].length - 1].nextUpdateBlockNumber = uint32(block.number); // push new stake to storage - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push(operatorStakeUpdate); + operatorIdToStakeHistory[operatorId][quorumNumber].push(operatorStakeUpdate); emit StakeUpdate( - operator, + operatorId, quorumNumber, operatorStakeUpdate.stake, uint32(block.number), From 5161eabcf68b8a538291c84db29cc4ce36fb7d94 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 10:58:25 +0530 Subject: [PATCH 003/110] added indexregistry --- src/contracts/middleware/IndexRegistry.sol | 151 +++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/contracts/middleware/IndexRegistry.sol diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol new file mode 100644 index 000000000..4861c4157 --- /dev/null +++ b/src/contracts/middleware/IndexRegistry.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + + +import "../interfaces/IIndexRegistry.sol"; +import "../interfaces/IRegistryCoordinator.sol"; +import "../libraries/BN254.sol"; + + +contract IndexRegistry is IIndexRegistry { + + IRegistryCoordinator registryCoordinator; + + bytes32[] public globalOperatorList; + mapping(uint8 => bytes32[]) public quorumToOperatorList; + + mapping(bytes32 => mapping(uint8 => OperatorIndex[])) operatorIdToIndexHistory; + mapping(uint8 => OperatorIndex[]) totalOperatorsHistory; + + modifier onlyRegistryCoordinator() { + require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); + _; + } + + constructor( + IRegistryCoordinator _registryCoordinator + ){ + registryCoordinator = _registryCoordinator; + + } + function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external onlyRegistryCoordinator { + //add operator to operatorList + for (uint i = 0; i < quorumNumbers.length; i++) { + uint8 quorumNumber = quorumNumbers[i]; + quorumToOperatorList[quorumNumber].push(operatorId); + globalOperatorList.push(operatorId); + + _updateOperatorIdToIndexHistory(operatorId, quorumNumber); + _updateTotalOperatorHistory(quorumNumber); + } + } + + function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { + require(quorumNumbers.length == indexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); + + for (uint i = 0; i < quorumNumbers.length; i++) { + uint8 quorumNumber = quorumNumbers[i]; + + _removeOperatorFromQuorumToOperatorList(quorumNumber, quorumToOperatorListIndexes[i]); + _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); + _updateTotalOperatorHistory(quorumNumber); + } + } + + /** + * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. + * @param operatorId is the id of the operator for which the index is desired + * @param quorumNumber is the quorum number for which the operator index is desired + * @param blockNumber is the block number at which the index of the operator is desired + * @param index Used to specify the entry within the dynamic array `operatorIdToIndexHistory[operatorId]` to + * read data from + * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the + * array `operatorIdToIndexHistory[operatorId][quorumNumber]` to pull the info from. + */ + function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ + OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; + + require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + + if(index < operatorIdToIndexHistory[operatorId][quorumNumber].length - 1){ + OperatorIndex memory nextOperatorIndex = operatorIdToIndexHistory[operatorId][quorumNumber][index + 1]; + require(blockNumber < nextOperatorIndex.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number") + } + return operatorIndexToCheck.index; + } + + /** + * @notice Looks up the number of total operators for `quorumNumber` at the specified `blockNumber`. + * @param quorumNumber is the quorum number for which the total number of operators is desired + * @param blockNumber is the block number at which the total number of operators is desired + * @param index is the index of the entry in the dynamic array `totalOperatorsHistory[quorumNumber]` to read data from + */ + function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ + OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; + + require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + if(index < totalOperatorsHistory[quorumNumber].length - 1){ + OperatorIndex memory nextOperatorIndex = totalOperatorsHistory[quorumNumber][index + 1]; + require(blockNumber < nextOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number") + } + return operatorIndexToCheck.index; + } + + function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32){ + return uint32(quorumToOperatorList[quorumNumber].length); + } + + + /// @notice Returns the current number of operators of this service. + function totalOperators() external view returns (uint32){ + return numOperators; + } + + + function _updateTotalOperatorHistory(uint8 quorumNumber, uint256 numOperators) internal { + uint256 numOperators = quorumToOperatorList[quorumNumber].length; + + OperatorIndex memory totalOperatorUpdate = OperatorIndex({ + blockNumber: block.number, + index: uint32(numOperators) + }); + totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); + } + + function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber) internal { + if (operatorIdToIndexHistoryLength > 0) { + uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length; + operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = block.number; + } + OperatorIndex memory latestOperatorIndex; + latestOperatorIndex.index = quorumToOperatorList[quorumNumber].length - 1; + operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); + } + + + function _removeOperatorFromQuorumToOperatorList(uint8 quorumNumber, uint32 indexToRemove) internal { + + quorumToOperatorListLastIndex = quorumToOperatorList[quorumNumber].length - 1; + + if(indexToRemove != quorumToOperatorListLastIndex){ + bytes32 operatorIdToSwap = quorumToOperatorList[quorumNumber][quorumToOperatorListLastIndex]; + //update the swapped operator's + _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber); + + quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; + } + quorumToOperatorList[quorumNumber].pop(); + } + + function _removeOperatorFromGlobalOperatorList(uint32 indexToRemove) internal { + uint32 globalOperatorListLastIndex = globalOperatorList.length - 1; + + if(indexToRemove != globalOperatorListLastIndex){ + bytes32 operatorIdToSwap = globalOperatorList[globalOperatorListLastIndex]; + globalOperatorList[indexToRemove] = operatorIdToSwap; + } + globalOperatorList.pop(); + } + + +} \ No newline at end of file From 518ef4e8a89b0a463a3e157643248aa3ab815a7b Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:11:47 +0530 Subject: [PATCH 004/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 4861c4157..cd76aa2f6 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -10,10 +10,9 @@ import "../libraries/BN254.sol"; contract IndexRegistry is IIndexRegistry { IRegistryCoordinator registryCoordinator; - bytes32[] public globalOperatorList; - mapping(uint8 => bytes32[]) public quorumToOperatorList; + mapping(uint8 => bytes32[]) public quorumToOperatorList; mapping(bytes32 => mapping(uint8 => OperatorIndex[])) operatorIdToIndexHistory; mapping(uint8 => OperatorIndex[]) totalOperatorsHistory; @@ -42,12 +41,12 @@ contract IndexRegistry is IIndexRegistry { function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { require(quorumNumbers.length == indexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); + _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = quorumNumbers[i]; _removeOperatorFromQuorumToOperatorList(quorumNumber, quorumToOperatorListIndexes[i]); - _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); _updateTotalOperatorHistory(quorumNumber); } } From eac96b143440ed8094332c33770d31ff6afbf3d6 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:12:40 +0530 Subject: [PATCH 005/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index cd76aa2f6..258395a9f 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -29,11 +29,11 @@ contract IndexRegistry is IIndexRegistry { } function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external onlyRegistryCoordinator { //add operator to operatorList + globalOperatorList.push(operatorId); + for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = quorumNumbers[i]; quorumToOperatorList[quorumNumber].push(operatorId); - globalOperatorList.push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumber); _updateTotalOperatorHistory(quorumNumber); } @@ -45,7 +45,6 @@ contract IndexRegistry is IIndexRegistry { for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = quorumNumbers[i]; - _removeOperatorFromQuorumToOperatorList(quorumNumber, quorumToOperatorListIndexes[i]); _updateTotalOperatorHistory(quorumNumber); } From bfae8923abe0c7ccdc5e307e7e6e597f622061c8 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:13:48 +0530 Subject: [PATCH 006/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 258395a9f..a6a3cdac9 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -96,7 +96,7 @@ contract IndexRegistry is IIndexRegistry { /// @notice Returns the current number of operators of this service. function totalOperators() external view returns (uint32){ - return numOperators; + return uint32(globalOperatorList.length); } From dedc64248fe100a467d097a4fb7c20138f0ad853 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:16:58 +0530 Subject: [PATCH 007/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index a6a3cdac9..82ebc75e5 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -32,10 +32,9 @@ contract IndexRegistry is IIndexRegistry { globalOperatorList.push(operatorId); for (uint i = 0; i < quorumNumbers.length; i++) { - uint8 quorumNumber = quorumNumbers[i]; quorumToOperatorList[quorumNumber].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumber); - _updateTotalOperatorHistory(quorumNumber); + _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i]); + _updateTotalOperatorHistory(quorumNumbers[i]); } } @@ -44,9 +43,8 @@ contract IndexRegistry is IIndexRegistry { _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); for (uint i = 0; i < quorumNumbers.length; i++) { - uint8 quorumNumber = quorumNumbers[i]; - _removeOperatorFromQuorumToOperatorList(quorumNumber, quorumToOperatorListIndexes[i]); - _updateTotalOperatorHistory(quorumNumber); + _removeOperatorFromQuorumToOperatorList(quorumNumbers[i], quorumToOperatorListIndexes[i]); + _updateTotalOperatorHistory(quorumNumbers[i]); } } From 2f8df6482500be3922dd810f0996b4a76a804709 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:24:58 +0530 Subject: [PATCH 008/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 82ebc75e5..2b8b923d5 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -60,7 +60,6 @@ contract IndexRegistry is IIndexRegistry { */ function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; - require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); if(index < operatorIdToIndexHistory[operatorId][quorumNumber].length - 1){ @@ -142,6 +141,4 @@ contract IndexRegistry is IIndexRegistry { } globalOperatorList.pop(); } - - } \ No newline at end of file From 4d80a7e9424fd79e54c7f915f8408d13e385ceae Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:26:49 +0530 Subject: [PATCH 009/110] modified interface --- src/contracts/interfaces/IIndexRegistry.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 5d4e2e644..8bfee5ea0 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -31,10 +31,11 @@ interface IIndexRegistry is IRegistry { * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param indexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum + * @param quorumToOperatorListIndexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum + * @param globalOperatorListIndex is the index of the operator that is to be removed from the list; * @dev access restricted to the RegistryCoordinator */ - function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory indexes) external; + function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. From 9ae1af4190e56771075154f7d0ef91f4bd29d9f6 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:29:16 +0530 Subject: [PATCH 010/110] modified interface --- src/contracts/middleware/IndexRegistry.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 2b8b923d5..b16d7f6d1 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -61,11 +61,6 @@ contract IndexRegistry is IIndexRegistry { function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); - - if(index < operatorIdToIndexHistory[operatorId][quorumNumber].length - 1){ - OperatorIndex memory nextOperatorIndex = operatorIdToIndexHistory[operatorId][quorumNumber][index + 1]; - require(blockNumber < nextOperatorIndex.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number") - } return operatorIndexToCheck.index; } @@ -79,10 +74,6 @@ contract IndexRegistry is IIndexRegistry { OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); - if(index < totalOperatorsHistory[quorumNumber].length - 1){ - OperatorIndex memory nextOperatorIndex = totalOperatorsHistory[quorumNumber][index + 1]; - require(blockNumber < nextOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number") - } return operatorIndexToCheck.index; } From 3abc044d06a3c363305f5694d42095a7cc6dd485 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:29:59 +0530 Subject: [PATCH 011/110] fixed incorrect checl --- src/contracts/middleware/IndexRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index b16d7f6d1..489a9b7fe 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -60,7 +60,7 @@ contract IndexRegistry is IIndexRegistry { */ function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; - require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); return operatorIndexToCheck.index; } @@ -73,7 +73,7 @@ contract IndexRegistry is IIndexRegistry { function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; - require(blockNumber >= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); return operatorIndexToCheck.index; } From de579eccd82d2172c90dea3f1424c615e3e43c65 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:32:26 +0530 Subject: [PATCH 012/110] added additional lower bound check to getters --- src/contracts/middleware/IndexRegistry.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 489a9b7fe..582e1f84f 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -61,6 +61,10 @@ contract IndexRegistry is IIndexRegistry { function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + if(index != 0){ + OperatorIndex memory previousOperatorIndex = operatorIdToIndexHistory[operatorId][quorumNumber][index - 1]; + require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); + } return operatorIndexToCheck.index; } @@ -72,8 +76,11 @@ contract IndexRegistry is IIndexRegistry { */ function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; - require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + if expression(index != 0){ + OperatorIndex memory previousOperatorIndex = totalOperatorsHistory[quorumNumber][index - 1]; + require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); + } return operatorIndexToCheck.index; } From 6b760fb8253bc80597bec6e65d59d8d5b4cb0571 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:36:23 +0530 Subject: [PATCH 013/110] added additional lower bound check to getters --- src/contracts/interfaces/IIndexRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 8bfee5ea0..df15501a3 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -32,7 +32,7 @@ interface IIndexRegistry is IRegistry { * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for * @param quorumToOperatorListIndexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum - * @param globalOperatorListIndex is the index of the operator that is to be removed from the list; + * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator */ function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; From 235b8ecccd245689195e5c666e80b1b0287575af Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:42:46 +0530 Subject: [PATCH 014/110] fixed _updateTotalOperatorHistory --- src/contracts/middleware/IndexRegistry.sol | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 582e1f84f..61ca021c1 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -9,12 +9,12 @@ import "../libraries/BN254.sol"; contract IndexRegistry is IIndexRegistry { - IRegistryCoordinator registryCoordinator; + IRegistryCoordinator public registryCoordinator; bytes32[] public globalOperatorList; mapping(uint8 => bytes32[]) public quorumToOperatorList; - mapping(bytes32 => mapping(uint8 => OperatorIndex[])) operatorIdToIndexHistory; - mapping(uint8 => OperatorIndex[]) totalOperatorsHistory; + mapping(bytes32 => mapping(uint8 => OperatorIndex[])) public operatorIdToIndexHistory; + mapping(uint8 => OperatorIndex[]) public totalOperatorsHistory; modifier onlyRegistryCoordinator() { require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); @@ -25,7 +25,6 @@ contract IndexRegistry is IIndexRegistry { IRegistryCoordinator _registryCoordinator ){ registryCoordinator = _registryCoordinator; - } function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external onlyRegistryCoordinator { //add operator to operatorList @@ -77,7 +76,7 @@ contract IndexRegistry is IIndexRegistry { function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); - if expression(index != 0){ + if (index != 0){ OperatorIndex memory previousOperatorIndex = totalOperatorsHistory[quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); } @@ -95,20 +94,19 @@ contract IndexRegistry is IIndexRegistry { } - function _updateTotalOperatorHistory(uint8 quorumNumber, uint256 numOperators) internal { - uint256 numOperators = quorumToOperatorList[quorumNumber].length; + function _updateTotalOperatorHistory(uint8 quorumNumber) internal { + if (totalOperatorsHistory[quorumNumber].length > 0) { + totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].toBlockNumber = block.number; + } - OperatorIndex memory totalOperatorUpdate = OperatorIndex({ - blockNumber: block.number, - index: uint32(numOperators) - }); + OperatorIndex memory totalOperatorUpdate; + totalOperatorUpdate.index = quorumToOperatorList[quorumNumber].length - 1; totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber) internal { if (operatorIdToIndexHistoryLength > 0) { - uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length; - operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = block.number; + operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length; - 1].toBlockNumber = block.number; } OperatorIndex memory latestOperatorIndex; latestOperatorIndex.index = quorumToOperatorList[quorumNumber].length - 1; From ae519e7eb9e695fc224008372275259a1d81176e Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:46:38 +0530 Subject: [PATCH 015/110] fixed index update in _updateOperatorIdToIndexHistory --- src/contracts/middleware/IndexRegistry.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 61ca021c1..3feaae801 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -32,7 +32,7 @@ contract IndexRegistry is IIndexRegistry { for (uint i = 0; i < quorumNumbers.length; i++) { quorumToOperatorList[quorumNumber].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i]); + _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i], quorumToOperatorList[quorumNumber].length - 1); _updateTotalOperatorHistory(quorumNumbers[i]); } } @@ -104,12 +104,12 @@ contract IndexRegistry is IIndexRegistry { totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } - function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber) internal { + function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { if (operatorIdToIndexHistoryLength > 0) { operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length; - 1].toBlockNumber = block.number; } OperatorIndex memory latestOperatorIndex; - latestOperatorIndex.index = quorumToOperatorList[quorumNumber].length - 1; + latestOperatorIndex.index = index; operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); } @@ -121,7 +121,7 @@ contract IndexRegistry is IIndexRegistry { if(indexToRemove != quorumToOperatorListLastIndex){ bytes32 operatorIdToSwap = quorumToOperatorList[quorumNumber][quorumToOperatorListLastIndex]; //update the swapped operator's - _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber); + _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; } From eee09362511a08938f31e67f185c5868cac55fe8 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:55:15 +0530 Subject: [PATCH 016/110] added blockNumber update for deregistering operator --- src/contracts/middleware/IndexRegistry.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 3feaae801..90e7e35ba 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -116,7 +116,7 @@ contract IndexRegistry is IIndexRegistry { function _removeOperatorFromQuorumToOperatorList(uint8 quorumNumber, uint32 indexToRemove) internal { - quorumToOperatorListLastIndex = quorumToOperatorList[quorumNumber].length - 1; + uint32 quorumToOperatorListLastIndex = uint32(quorumToOperatorList[quorumNumber].length - 1); if(indexToRemove != quorumToOperatorListLastIndex){ bytes32 operatorIdToSwap = quorumToOperatorList[quorumNumber][quorumToOperatorListLastIndex]; @@ -125,6 +125,8 @@ contract IndexRegistry is IIndexRegistry { quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; } + //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = block.number; quorumToOperatorList[quorumNumber].pop(); } From 58ead13c4224024ca074a96cade5898f9a0dd5e8 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 11:57:45 +0530 Subject: [PATCH 017/110] added blockNumber update for deregistering operator --- src/contracts/middleware/IndexRegistry.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 90e7e35ba..0692db8bb 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -105,8 +105,11 @@ contract IndexRegistry is IIndexRegistry { } function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { + uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; + + //if there is a prior entry for the operator, set the previous entry's toBlocknumber if (operatorIdToIndexHistoryLength > 0) { - operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length; - 1].toBlockNumber = block.number; + operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = block.number; } OperatorIndex memory latestOperatorIndex; latestOperatorIndex.index = index; @@ -120,13 +123,14 @@ contract IndexRegistry is IIndexRegistry { if(indexToRemove != quorumToOperatorListLastIndex){ bytes32 operatorIdToSwap = quorumToOperatorList[quorumNumber][quorumToOperatorListLastIndex]; - //update the swapped operator's + //update the swapped operator's operatorIdToIndexHistory list with a new entry, as their index has now changed _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; } //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = block.number; + //removing the swapped operator from the list quorumToOperatorList[quorumNumber].pop(); } From bf3a7401ca7fa58392b71868f5da2de9631a33e6 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 12:04:48 +0530 Subject: [PATCH 018/110] added comments --- src/contracts/middleware/IndexRegistry.sol | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 0692db8bb..a000f6f7c 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -59,7 +59,11 @@ contract IndexRegistry is IIndexRegistry { */ function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; + + //blocknumber must be before the "index'th" entry's toBlockNumber require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + + //if there is an index update before the "index'th" update, the blocknumber must be after than the previous entry's toBlockNumber if(index != 0){ OperatorIndex memory previousOperatorIndex = operatorIdToIndexHistory[operatorId][quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); @@ -75,7 +79,11 @@ contract IndexRegistry is IIndexRegistry { */ function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; + + //blocknumber must be before the "index'th" entry's toBlockNumber require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); + + //if there is an index update before the "index'th" update, the blocknumber must be after than the previous entry's toBlockNumber if (index != 0){ OperatorIndex memory previousOperatorIndex = totalOperatorsHistory[quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); @@ -95,6 +103,8 @@ contract IndexRegistry is IIndexRegistry { function _updateTotalOperatorHistory(uint8 quorumNumber) internal { + + //if there is a prior entry, update its "toBlockNumber" if (totalOperatorsHistory[quorumNumber].length > 0) { totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].toBlockNumber = block.number; } @@ -104,6 +114,10 @@ contract IndexRegistry is IIndexRegistry { totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } + /// + /// @param operatorId operatorId of the operator to update + /// @param quorumNumber quorumNumber of the operator to update + /// @param index the latest index of that operator in quorumToOperatorList function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; @@ -116,16 +130,21 @@ contract IndexRegistry is IIndexRegistry { operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); } - + /// @notice when we remove an operator from quorumToOperatorList, we swap the last operator in + /// quorumToOperatorList[quorumNumber] to the position of the operator we are removing + /// @param quorumNumber quorum number of the operator to remove + /// @param indexToRemove index of the operator to remove function _removeOperatorFromQuorumToOperatorList(uint8 quorumNumber, uint32 indexToRemove) internal { - + uint32 quorumToOperatorListLastIndex = uint32(quorumToOperatorList[quorumNumber].length - 1); + // if the operator is not the last in the list, we must swap the last operator into their positon if(indexToRemove != quorumToOperatorListLastIndex){ bytes32 operatorIdToSwap = quorumToOperatorList[quorumNumber][quorumToOperatorListLastIndex]; //update the swapped operator's operatorIdToIndexHistory list with a new entry, as their index has now changed _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); + //set last operator in the list to removed operator's position in the array quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; } //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number From 52869129124db0b2d1796fdebc6e1beeaed07a21 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Fri, 26 May 2023 12:05:45 +0530 Subject: [PATCH 019/110] added comments --- src/contracts/middleware/IndexRegistry.sol | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index a000f6f7c..b287b93fe 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -26,6 +26,13 @@ contract IndexRegistry is IIndexRegistry { ){ registryCoordinator = _registryCoordinator; } + + /** + * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. + * @param operatorId is the id of the operator that is being registered + * @param quorumNumbers is the quorum numbers the operator is registered for + * @dev access restricted to the RegistryCoordinator + */ function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external onlyRegistryCoordinator { //add operator to operatorList globalOperatorList.push(operatorId); @@ -37,6 +44,14 @@ contract IndexRegistry is IIndexRegistry { } } + /** + * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. + * @param operatorId is the id of the operator that is being deregistered + * @param quorumNumbers is the quorum numbers the operator is deregistered for + * @param quorumToOperatorListIndexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum + * @param globalOperatorListIndex is the index of the operator that is to be removed from the list + * @dev access restricted to the RegistryCoordinator + */ function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { require(quorumNumbers.length == indexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); From 69f04fb4f173669c99a52216eaf96ab8a37821f1 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 26 May 2023 14:31:53 -0700 Subject: [PATCH 020/110] more work on stakeregistry --- src/contracts/interfaces/IStakeRegistry.sol | 25 +- src/contracts/middleware/StakeRegistry.sol | 248 +++++++++----------- 2 files changed, 136 insertions(+), 137 deletions(-) diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index c1834bf2a..57129aace 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -25,19 +25,30 @@ interface IStakeRegistry is IRegistry { * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to register. * @param operatorId The id of the operator to register. - * @param quorumNumbers The quorum numbers the operator is registering for. + * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already registered */ - function registerOperator(address operator, bytes32 operatorId, uint8[] memory quorumNumbers) external; + function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external; /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. - * @param quorumNumbers The quourm numbers the operator is deregistering from. + * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already deregistered + * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, uint8[] memory quorumNumbers) external; + function deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external; function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256); @@ -136,8 +147,10 @@ interface IStakeRegistry is IRegistry { * @notice Used for updating information on deposits of nodes. * @param operators are the addresses of the operators whose stake information is getting updated * @param operatorIds are the ids of the operators whose stake information is getting updated - * @param quorumNumbers are the quorumNumbers for each operator in `operators` that they are a part of + * @param quorumBitmaps are the bitmap of the quorums that each operator in `operators` is part of * @param prevElements are the elements before this middleware in the operator's linked list within the slasher + * @dev Precondition: + * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for */ - function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint8[][] memory quorumNumbers, uint256[] memory prevElements) external; + function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint256[] memory quorumBitmaps, uint256[] memory prevElements) external; } \ No newline at end of file diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 404c65fbf..7c6dcc2bc 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -36,9 +36,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { event StakeUpdate( bytes32 operatorId, uint8 quorumNumber, - uint96 stake, - uint32 updateBlockNumber, - uint32 prevUpdateBlockNumber + uint96 stake ); /** @@ -216,7 +214,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { && /// verify that the stake was non-zero at the time (note: here was use the assumption that the operator was 'inactive' /// once their stake fell to zero) - operatorStakeUpdate.stake != 0 // this implicitly checks that the quorum bitmap was 1 for the quorum of interest + operatorStakeUpdate.stake != 0 // this implicitly checks that the operator was a part of the quorum of interest ); } @@ -273,34 +271,47 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to register. * @param operatorId The id of the operator to register. - * @param quorumNumbers The quorum numbers the operator is registering for. + * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already registered */ - function registerOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) external virtual { - _registerOperator(operator, operatorId, quorumBitmap); + function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external virtual { + _registerOperator(operator, operatorId, quorumNumbers); } /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. - * @param quorumNumbers The quourm numbers the operator is deregistering from. + * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already deregistered + * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) external virtual { - _deregisterOperator(operator, operatorId, quorumBitmap); + function deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external virtual { + _deregisterOperator(operator, operatorId, quorumNumbers); } /** * @notice Used for updating information on deposits of nodes. * @param operators are the addresses of the operators whose stake information is getting updated * @param operatorIds are the ids of the operators whose stake information is getting updated - * @param quorumBitmaps are the bitmaps of what quorums each operator in `operators` is part of + * @param quorumBitmaps are the bitmap of the quorums that each operator in `operators` is part of * @param prevElements are the elements before this middleware in the operator's linked list within the slasher + * @dev Precondition: + * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for */ function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint256[] memory quorumBitmaps, uint256[] memory prevElements) external { // for each quorum, loop through operators and see if they are apart of the quorum - // if they are, get their new weight and update their individual stake history and the + // if they are, get their new weight and update their individual stake history and thes // quorum's total stake history accordingly for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { OperatorStakeUpdate memory totalStakeUpdate; @@ -313,12 +324,10 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { totalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; } bytes32 operatorId = operatorIds[i]; - // get the operator's current stake - OperatorStakeUpdate memory prevStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistory[operatorId][quorumNumber].length - 1]; // update the operator's stake based on current state - OperatorStakeUpdate memory newStakeUpdate = _updateOperatorStake(operators[i], operatorId, quorumBitmaps[i], quorumNumber, prevStakeUpdate.updateBlockNumber); + (uint96 stakeBeforeUpdate, uint96 stakeAfterUpdate) = _updateOperatorStake(operators[i], operatorId, quorumNumber); // calculate the new total stake for the quorum - totalStakeUpdate.stake = totalStakeUpdate.stake - prevStakeUpdate.stake + newStakeUpdate.stake; + totalStakeUpdate.stake = totalStakeUpdate.stake - stakeBeforeUpdate + stakeAfterUpdate; } unchecked { ++i; @@ -345,14 +354,14 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { // INTERNAL FUNCTIONS - function _registerOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) internal { + function _registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { require( slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, "RegistryBase._addRegistrant: operator must be opted into slashing by the serviceManager" ); // calculate stakes for each quorum the operator is trying to join - _registerStake(operator, operatorId, quorumBitmap); + _registerStake(operator, operatorId, quorumNumbers); // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet serviceManager.recordFirstStakeUpdate(operator, 0); @@ -366,56 +375,40 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { * @notice Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`. * @dev This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere. */ - function _registerStake(address operator, bytes32 operatorId, uint256 quorumBitmap) + function _registerStake(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { - OperatorStakeUpdate memory _operatorStakeUpdate; - // add the `updateBlockNumber` info - _operatorStakeUpdate.updateBlockNumber = uint32(block.number); + uint8 quorumNumbersLength = uint8(quorumNumbers.length); + // check the operator is registering for only valid quorums + require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerStake: greatest quorumNumber must be less than quorumCount"); OperatorStakeUpdate memory _newTotalStakeUpdate; // add the `updateBlockNumber` info _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); // for each quorum, evaluate stake and add to total stake - for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { + for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbersLength;) { + // get the next quourumNumber + uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); // evaluate the stake for the operator - if(quorumBitmap >> quorumNumber & 1 == 1) { - _operatorStakeUpdate.stake = uint96(weightOfOperator(operator, quorumNumber)); - // check if minimum requirement has been met - require(_operatorStakeUpdate.stake >= minimumStakeForQuorum[quorumNumber], "RegistryBase._registerStake: Operator does not meet minimum stake requirement for quorum"); - // check special case that operator is re-registering (and thus already has some history) - if (operatorIdToStakeHistory[operatorId][quorumNumber].length != 0) { - // correctly set the 'nextUpdateBlockNumber' field for the re-registering operator's oldest history entry - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistory[operatorId][quorumNumber].length - 1].nextUpdateBlockNumber - = uint32(block.number); - } - // push the new stake for the operator to storage - operatorIdToStakeHistory[operatorId][quorumNumber].push(_operatorStakeUpdate); - - // get the total stake for the quorum - _newTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; - // add operator stakes to total stake (in memory) - _newTotalStakeUpdate.stake = uint96(_newTotalStakeUpdate.stake + _operatorStakeUpdate.stake); - // update storage of total stake - _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); - - emit StakeUpdate( - operatorId, - quorumNumber, - _operatorStakeUpdate.stake, - uint32(block.number), - // no previous update block number -- use 0 instead - 0 // TODO: Decide whether this needs to be set in re-registration edge case - ); - } + // since we don't use the first output, this will use 1 extra sload when deregistered operator's register again + (, uint96 stake) = _updateOperatorStake(operator, operatorId, quorumNumber); + // @JEFF: This reverts pretty late, but i think that's fine. wdyt? + // check if minimum requirement has been met + require(stake >= minimumStakeForQuorum[quorumNumber], "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); + // get the total stake for the quorum + _newTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; + // add operator stakes to total stake (in memory) + _newTotalStakeUpdate.stake = _newTotalStakeUpdate.stake + stake; + // update storage of total stake + _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); unchecked { - ++quorumNumber; + ++quorumNumbersIndex; } } } - function _deregisterOperator(address operator, bytes32 operatorId, uint256 quorumBitmap) internal { + function _deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { // remove the operator's stake - _removeOperatorStake(operatorId, quorumBitmap); + _removeOperatorStake(operatorId, quorumNumbers); // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); @@ -427,97 +420,90 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { /** * @notice Removes the stakes of the operator */ - function _removeOperatorStake(bytes32 operatorId, uint256 quorumBitmap) internal { - // loop through the operator's quorum bitmap and remove the operator's stake for each quorum - for (uint quorumNumber = 0; quorumNumber < quorumCount;) { - if (quorumBitmap >> quorumNumber & 1 == 1) { - _removeOperatorStakeForQuorum(operatorId, uint8(quorumNumber)); - } - unchecked { - quorumNumber++; - } - } - - } + function _removeOperatorStake(bytes32 operatorId, bytes memory quorumNumbers) internal { + uint8 quorumNumbersLength = uint8(quorumNumbers.length); + // check the operator is deregistering from only valid quorums + require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerStake: greatest quorumNumber must be less than quorumCount"); + OperatorStakeUpdate memory _operatorStakeUpdate; + // add the `updateBlockNumber` info + _operatorStakeUpdate.updateBlockNumber = uint32(block.number); + OperatorStakeUpdate memory _newTotalStakeUpdate; + // add the `updateBlockNumber` info + _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); + // loop through the operator's quorums and remove the operator's stake for each quorum + for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbersLength;) { + uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); + // gas saving by caching length here + uint256 operatorIdToStakeHistoryLengthMinusOne = operatorIdToStakeHistory[operatorId][quorumNumber].length - 1; + + // determine current stakes + OperatorStakeUpdate memory currentStakeUpdate = + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne]; + //set nextUpdateBlockNumber in current stakes + operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = + uint32(block.number); - /** - * @notice Removes the stakes of the operator with operatorId `operatorId` for the quorum with number `quorumNumber` - */ - function _removeOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) internal { - // gas saving by caching length here - uint256 operatorIdToStakeHistoryLengthMinusOne = operatorIdToStakeHistory[operatorId][quorumNumber].length - 1; - - // determine current stakes - OperatorStakeUpdate memory currentStakeUpdate = - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne]; - //set nextUpdateBlockNumber in current stakes - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = - uint32(block.number); - - /** - * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. - */ - operatorIdToStakeHistory[operatorId][quorumNumber].push( - OperatorStakeUpdate({ - // recording the current block number where the operator stake got updated - updateBlockNumber: uint32(block.number), - // mark as 0 since the next update has not yet occurred - nextUpdateBlockNumber: 0, - // setting the operator's stake to 0 - stake: 0 - }) - ); + /** + * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. + */ + operatorIdToStakeHistory[operatorId][quorumNumber].push(_operatorStakeUpdate); - // subtract the amounts staked by the operator that is getting deregistered from the total stake - // copy latest totalStakes to memory - OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; - currentTotalStakeUpdate.stake -= currentStakeUpdate.stake; - // update storage of total stake - _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); + // subtract the amounts staked by the operator that is getting deregistered from the total stake + // copy latest totalStakes to memory + OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; + _newTotalStakeUpdate.stake -= currentStakeUpdate.stake; + // update storage of total stake + _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); - emit StakeUpdate( - operatorId, - // new stakes are zero - quorumNumber, - 0, - uint32(block.number), - currentStakeUpdate.updateBlockNumber - ); + emit StakeUpdate( + operatorId, + // new stakes are zero + quorumNumber, + 0 + ); + unchecked { + ++quorumNumbersIndex; + } + } } /** * @notice Finds the updated stake for `operator`, stores it and records the update. * @dev **DOES NOT UPDATE `totalStake` IN ANY WAY** -- `totalStake` updates must be done elsewhere. */ - function _updateOperatorStake(address operator, bytes32 operatorId, uint256 quorumBitmap, uint8 quorumNumber, uint32 prevUpdateBlockNumber) + function _updateOperatorStake(address operator, bytes32 operatorId, uint8 quorumNumber) internal - returns (OperatorStakeUpdate memory operatorStakeUpdate) + returns (uint96, uint96) { - // if the operator is part of the quorum - if (quorumBitmap >> quorumNumber & 1 == 1) { - // determine new stakes - operatorStakeUpdate.updateBlockNumber = uint32(block.number); - operatorStakeUpdate.stake = weightOfOperator(operator, quorumNumber); - - // check if minimum requirements have been met - if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { - operatorStakeUpdate.stake = uint96(0); - } + // determine new stakes + OperatorStakeUpdate memory operatorStakeUpdate; + operatorStakeUpdate.updateBlockNumber = uint32(block.number); + operatorStakeUpdate.stake = weightOfOperator(operator, quorumNumber); + // check if minimum requirements have been met + if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { + operatorStakeUpdate.stake = uint96(0); + } + // initialize stakeBeforeUpdate to 0 + uint96 stakeBeforeUpdate; + uint256 operatorStakeHistoryLength = operatorIdToStakeHistory[operatorId][quorumNumber].length; + if (operatorStakeHistoryLength != 0) { // set nextUpdateBlockNumber in prev stakes - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistory[operatorId][quorumNumber].length - 1].nextUpdateBlockNumber = + operatorIdToStakeHistory[operatorId][quorumNumber][operatorStakeHistoryLength - 1].nextUpdateBlockNumber = uint32(block.number); - // push new stake to storage - operatorIdToStakeHistory[operatorId][quorumNumber].push(operatorStakeUpdate); - - emit StakeUpdate( - operatorId, - quorumNumber, - operatorStakeUpdate.stake, - uint32(block.number), - prevUpdateBlockNumber - ); + // load stake before update into memory if it exists + stakeBeforeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][operatorStakeHistoryLength - 1].stake; } + // push new stake to storage + operatorIdToStakeHistory[operatorId][quorumNumber].push(operatorStakeUpdate); + + emit StakeUpdate( + operatorId, + quorumNumber, + operatorStakeUpdate.stake + ); + + return (stakeBeforeUpdate, operatorStakeUpdate.stake); } /// @notice Records that the `totalStake` is now equal to the input param @_totalStake From cef971f2021e210f527f3c05282668fdd88edadf Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 26 May 2023 14:41:13 -0700 Subject: [PATCH 021/110] further reuse of internal functions --- src/contracts/middleware/StakeRegistry.sol | 44 ++++++++++------------ 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 7c6dcc2bc..334882222 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -433,32 +433,19 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { // loop through the operator's quorums and remove the operator's stake for each quorum for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbersLength;) { uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); - // gas saving by caching length here - uint256 operatorIdToStakeHistoryLengthMinusOne = operatorIdToStakeHistory[operatorId][quorumNumber].length - 1; - - // determine current stakes - OperatorStakeUpdate memory currentStakeUpdate = - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne]; - //set nextUpdateBlockNumber in current stakes - operatorIdToStakeHistory[operatorId][quorumNumber][operatorIdToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = - uint32(block.number); - - /** - * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. - */ - operatorIdToStakeHistory[operatorId][quorumNumber].push(_operatorStakeUpdate); - + // update the operator's stake + uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, _operatorStakeUpdate); // subtract the amounts staked by the operator that is getting deregistered from the total stake // copy latest totalStakes to memory OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; - _newTotalStakeUpdate.stake -= currentStakeUpdate.stake; + _newTotalStakeUpdate.stake = currentTotalStakeUpdate.stake - stakeBeforeUpdate; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); emit StakeUpdate( operatorId, - // new stakes are zero quorumNumber, + // new stakes are zero 0 ); unchecked { @@ -484,6 +471,20 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { operatorStakeUpdate.stake = uint96(0); } + // initialize stakeBeforeUpdate to 0 + uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, operatorStakeUpdate); + + emit StakeUpdate( + operatorId, + quorumNumber, + operatorStakeUpdate.stake + ); + + return (stakeBeforeUpdate, operatorStakeUpdate.stake); + } + + /// @notice Records that `operatorId`'s current stake is now param @operatorStakeUpdate + function _recordOperatorStakeUpdate(bytes32 operatorId, uint8 quorumNumber, OperatorStakeUpdate memory operatorStakeUpdate) internal returns(uint96) { // initialize stakeBeforeUpdate to 0 uint96 stakeBeforeUpdate; uint256 operatorStakeHistoryLength = operatorIdToStakeHistory[operatorId][quorumNumber].length; @@ -496,14 +497,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { } // push new stake to storage operatorIdToStakeHistory[operatorId][quorumNumber].push(operatorStakeUpdate); - - emit StakeUpdate( - operatorId, - quorumNumber, - operatorStakeUpdate.stake - ); - - return (stakeBeforeUpdate, operatorStakeUpdate.stake); + return stakeBeforeUpdate; } /// @notice Records that the `totalStake` is now equal to the input param @_totalStake From 500b41f382c9e1d86f0f156bd2fe4d267f2f6bf6 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 26 May 2023 14:45:21 -0700 Subject: [PATCH 022/110] fix total stake update logic --- src/contracts/middleware/StakeRegistry.sol | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 334882222..c4ef1d140 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -392,12 +392,10 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { // since we don't use the first output, this will use 1 extra sload when deregistered operator's register again (, uint96 stake) = _updateOperatorStake(operator, operatorId, quorumNumber); // @JEFF: This reverts pretty late, but i think that's fine. wdyt? - // check if minimum requirement has been met - require(stake >= minimumStakeForQuorum[quorumNumber], "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); - // get the total stake for the quorum - _newTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; - // add operator stakes to total stake (in memory) - _newTotalStakeUpdate.stake = _newTotalStakeUpdate.stake + stake; + // check if minimum requirement has been met, will be 0 if not + require(stake != 0, "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); + // add operator stakes to total stake before update (in memory) + _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake + stake; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); unchecked { @@ -435,10 +433,9 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); // update the operator's stake uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, _operatorStakeUpdate); - // subtract the amounts staked by the operator that is getting deregistered from the total stake + // subtract the amounts staked by the operator that is getting deregistered from the total stake before deregistration // copy latest totalStakes to memory - OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; - _newTotalStakeUpdate.stake = currentTotalStakeUpdate.stake - stakeBeforeUpdate; + _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1].stake - stakeBeforeUpdate; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); From 1fb084c35454e8fb4a453ab6a2e529667bd36584 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 26 May 2023 17:00:50 -0700 Subject: [PATCH 023/110] fix voteweigher interface --- src/contracts/interfaces/IVoteWeigher.sol | 68 ++++++++++++++++++ src/contracts/middleware/StakeRegistry.sol | 2 +- src/contracts/middleware/VoteWeigherBase.sol | 2 +- .../middleware/VoteWeigherBaseStorage.sol | 9 --- src/test/harnesses/StakeRegistryHarness.sol | 28 ++++++++ src/test/mocks/OwnableMock.sol | 6 ++ src/test/unit/VoteWeigherUnit.t.sol | 69 +++++++++++++++++++ 7 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 src/test/harnesses/StakeRegistryHarness.sol create mode 100644 src/test/mocks/OwnableMock.sol create mode 100644 src/test/unit/VoteWeigherUnit.t.sol diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index ddd28332b..419159518 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -1,12 +1,35 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; +import "../interfaces/IStrategyManager.sol"; +import "../interfaces/IServiceManager.sol"; +import "../interfaces/ISlasher.sol"; +import "../interfaces/IDelegationManager.sol"; + /** * @title Interface for a `VoteWeigher`-type contract. * @author Layr Labs, Inc. * @notice Note that `NUMBER_OF_QUORUMS` is expected to remain constant, as suggested by its uppercase formatting. */ interface IVoteWeigher { + /** + * @notice In weighing a particular strategy, the amount of underlying asset for that strategy is + * multiplied by its multiplier, then divided by WEIGHTING_DIVISOR + */ + struct StrategyAndWeightingMultiplier { + IStrategy strategy; + uint96 multiplier; + } + + /// @notice Returns the strategy manager contract. + function strategyManager() external view returns (IStrategyManager); + /// @notice Returns the stake registry contract. + function slasher() external view returns (ISlasher); + /// @notice Returns the delegation manager contract. + function delegation() external view returns (IDelegationManager); + /// @notice Returns the service manager contract. + function serviceManager() external view returns (IServiceManager); + /** * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber. * @dev returns zero in the case that `quorumNumber` is greater than or equal to `NUMBER_OF_QUORUMS` @@ -21,4 +44,49 @@ interface IVoteWeigher { * @dev The sum of all entries, i.e. sum(quorumBips[0] through quorumBips[NUMBER_OF_QUORUMS - 1]) should *always* be 10,000! */ function quorumBips(uint8 quorumNumber) external view returns (uint256); + + /// @notice Create a new quorum and add the strategies and their associated weights to the quorum. + function createQuorum( + StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers + ) external; + + /// @notice Adds new strategies and the associated multipliers to the @param quorumNumber. + function addStrategiesConsideredAndMultipliers( + uint8 quorumNumber, + StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers + ) external; + + /** + * @notice This function is used for removing strategies and their associated weights from the + * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber. + * @dev higher indices should be *first* in the list of @param indicesToRemove, since otherwise + * the removal of lower index entries will cause a shift in the indices of the other strategiesToRemove + */ + function removeStrategiesConsideredAndMultipliers( + uint8 quorumNumber, + IStrategy[] calldata _strategiesToRemove, + uint256[] calldata indicesToRemove + ) external; + + /** + * @notice This function is used for modifying the weights of strategies that are already in the + * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber. + * @param strategyIndices is a correctness-check input -- the supplied values must match the indices of the + * strategiesToModifyWeightsOf in strategiesConsideredAndMultipliers[quorumNumber] + */ + function modifyStrategyWeights( + uint8 quorumNumber, + uint256[] calldata strategyIndices, + uint96[] calldata newMultipliers + ) external; + + /** + * @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. + * @dev Reverts if `quorumNumber` < `NUMBER_OF_QUORUMS`, i.e. the input is out of bounds. + */ + function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) external view returns (uint256); + + + + } diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index c4ef1d140..c2348b7ee 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -437,7 +437,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { // copy latest totalStakes to memory _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1].stake - stakeBeforeUpdate; // update storage of total stake - _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); + _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); emit StakeUpdate( operatorId, diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index a27a60464..ccced20c7 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -15,7 +15,7 @@ import "./VoteWeigherBaseStorage.sol"; * by the middleware for each of the quorum(s) * @dev */ -abstract contract VoteWeigherBase is VoteWeigherBaseStorage { +contract VoteWeigherBase is VoteWeigherBaseStorage { /// @notice emitted when a new quorum is created event QuorumCreated(uint8 indexed quorumNumber); /// @notice emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]` diff --git a/src/contracts/middleware/VoteWeigherBaseStorage.sol b/src/contracts/middleware/VoteWeigherBaseStorage.sol index 70f6b9fa3..f470e350c 100644 --- a/src/contracts/middleware/VoteWeigherBaseStorage.sol +++ b/src/contracts/middleware/VoteWeigherBaseStorage.sol @@ -15,15 +15,6 @@ import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; * @notice This storage contract is separate from the logic to simplify the upgrade process. */ abstract contract VoteWeigherBaseStorage is Initializable, IVoteWeigher { - /** - * @notice In weighing a particular strategy, the amount of underlying asset for that strategy is - * multiplied by its multiplier, then divided by WEIGHTING_DIVISOR - */ - struct StrategyAndWeightingMultiplier { - IStrategy strategy; - uint96 multiplier; - } - /// @notice Constant used as a divisor in calculating weights. uint256 internal constant WEIGHTING_DIVISOR = 1e18; /// @notice Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping. diff --git a/src/test/harnesses/StakeRegistryHarness.sol b/src/test/harnesses/StakeRegistryHarness.sol new file mode 100644 index 000000000..241ca0566 --- /dev/null +++ b/src/test/harnesses/StakeRegistryHarness.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "../../contracts/middleware/StakeRegistry.sol"; + +// wrapper around the StakeRegistry contract that exposes the internal functions for unit testing. +contract StakeRegistryHarness is StakeRegistry { + constructor( + IStrategyManager _strategyManager, + IServiceManager _serviceManager + ) StakeRegistry(_strategyManager, _serviceManager) {} + + function recordOperatorStakeUpdate(bytes32 operatorId, uint8 quorumNumber, OperatorStakeUpdate memory operatorStakeUpdate) external returns(uint96) { + return _recordOperatorStakeUpdate(operatorId, quorumNumber, operatorStakeUpdate); + } + + function updateOperatorStake(address operator, bytes32 operatorId, uint8 quorumNumber) external returns (uint96, uint96) { + return _updateOperatorStake(operator, operatorId, quorumNumber); + } + + function registerStake(address operator, bytes32 operatorId, bytes memory quorumNumbers) external { + _registerStake(operator, operatorId, quorumNumbers); + } + + function removeOperatorStake(bytes32 operatorId, bytes memory quorumNumbers) external { + _removeOperatorStake(operatorId, quorumNumbers); + } +} \ No newline at end of file diff --git a/src/test/mocks/OwnableMock.sol b/src/test/mocks/OwnableMock.sol new file mode 100644 index 000000000..52170e00e --- /dev/null +++ b/src/test/mocks/OwnableMock.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract OwnableMock is Ownable {} \ No newline at end of file diff --git a/src/test/unit/VoteWeigherUnit.t.sol b/src/test/unit/VoteWeigherUnit.t.sol new file mode 100644 index 000000000..a475b3281 --- /dev/null +++ b/src/test/unit/VoteWeigherUnit.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import "../../contracts/permissions/PauserRegistry.sol"; +import "../../contracts/interfaces/IStrategyManager.sol"; +import "../../contracts/interfaces/IServiceManager.sol"; +import "../../contracts/interfaces/IVoteWeigher.sol"; +import "../../contracts/middleware/VoteWeigherBase.sol"; + +import "../mocks/StrategyManagerMock.sol"; +import "../mocks/OwnableMock.sol"; + +import "forge-std/Test.sol"; + +contract VoteWeigherUnitTests is Test { + + Vm cheats = Vm(HEVM_ADDRESS); + + ProxyAdmin public proxyAdmin; + PauserRegistry public pauserRegistry; + IStrategyManager public strategyManager; + + address serviceManagerOwner; + IServiceManager public serviceManager; + + IVoteWeigher public voteWeigher; + + IVoteWeigher public voteWeigherImplementation; + + address public pauser = address(555); + address public unpauser = address(999); + + uint256 initialSupply = 1e36; + address initialOwner = address(this); + + function setUp() virtual public { + proxyAdmin = new ProxyAdmin(); + + pauserRegistry = new PauserRegistry(pauser, unpauser); + + strategyManager = new StrategyManagerMock(); + + // make the serviceManagerOwner the owner of the serviceManager contract + cheats.prank(serviceManagerOwner); + serviceManager = IServiceManager(address(new OwnableMock())); + + voteWeigherImplementation = new VoteWeigherBase(strategyManager, serviceManager); + + voteWeigher = IVoteWeigher(address(new TransparentUpgradeableProxy(address(voteWeigherImplementation), address(proxyAdmin), ""))); + } + + function testCorrectConstructionParameters() public { + assertEq(address(voteWeigherImplementation.strategyManager()), address(strategyManager)); + assertEq(address(voteWeigherImplementation.slasher()), address(strategyManager.slasher())); + assertEq(address(voteWeigherImplementation.delegation()), address(strategyManager.delegation())); + assertEq(address(voteWeigherImplementation.serviceManager()), address(serviceManager)); + } + + function testValidCreateQuorum() public { + // create a quorum from the serviceManagerOwner + cheats.prank(serviceManagerOwner); + voteWeigher.createQuorum() + } + +} \ No newline at end of file From ba56be4cdf038f1816d02a200faf8f9902eb2f63 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Sun, 28 May 2023 17:16:57 +0530 Subject: [PATCH 024/110] removed random inut --- src/contracts/middleware/IndexRegistry.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index b287b93fe..3878b06e4 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -10,10 +10,15 @@ import "../libraries/BN254.sol"; contract IndexRegistry is IIndexRegistry { IRegistryCoordinator public registryCoordinator; + + // list of all unique registered operators bytes32[] public globalOperatorList; + // mapping of quorumNumber => list of operators registered for that quorum mapping(uint8 => bytes32[]) public quorumToOperatorList; + // mapping of operatorId => quorumNumber => index history of that operator mapping(bytes32 => mapping(uint8 => OperatorIndex[])) public operatorIdToIndexHistory; + // mapping of quorumNumber => history of numbers of unique registered operators mapping(uint8 => OperatorIndex[]) public totalOperatorsHistory; modifier onlyRegistryCoordinator() { @@ -39,7 +44,7 @@ contract IndexRegistry is IIndexRegistry { for (uint i = 0; i < quorumNumbers.length; i++) { quorumToOperatorList[quorumNumber].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i], quorumToOperatorList[quorumNumber].length - 1); + _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i]); _updateTotalOperatorHistory(quorumNumbers[i]); } } @@ -133,7 +138,7 @@ contract IndexRegistry is IIndexRegistry { /// @param operatorId operatorId of the operator to update /// @param quorumNumber quorumNumber of the operator to update /// @param index the latest index of that operator in quorumToOperatorList - function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { + function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber) internal { uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; //if there is a prior entry for the operator, set the previous entry's toBlocknumber @@ -141,7 +146,7 @@ contract IndexRegistry is IIndexRegistry { operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = block.number; } OperatorIndex memory latestOperatorIndex; - latestOperatorIndex.index = index; + latestOperatorIndex.index = quorumToOperatorList[quorumNumber].length - 1; operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); } From b0ac3b110998de991079bb8bb4fd6d013c2e2e1c Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Sun, 28 May 2023 17:20:02 +0530 Subject: [PATCH 025/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 3878b06e4..34273e8c0 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -130,7 +130,8 @@ contract IndexRegistry is IIndexRegistry { } OperatorIndex memory totalOperatorUpdate; - totalOperatorUpdate.index = quorumToOperatorList[quorumNumber].length - 1; + // In the case of totalOperatorsHistory, the index parameter is the number of operators in the quorum + totalOperatorUpdate.index = quorumToOperatorList[quorumNumber].length; totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } From 8f8c912aca6173fceb7e7fbff1ef407dd3c03ab5 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Sun, 28 May 2023 17:53:00 +0530 Subject: [PATCH 026/110] fixed error --- src/contracts/middleware/IndexRegistry.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 34273e8c0..a96bcfcf3 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -44,7 +44,7 @@ contract IndexRegistry is IIndexRegistry { for (uint i = 0; i < quorumNumbers.length; i++) { quorumToOperatorList[quorumNumber].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i]); + _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i], quorumToOperatorList[quorumNumber].length - 1); _updateTotalOperatorHistory(quorumNumbers[i]); } } @@ -139,7 +139,7 @@ contract IndexRegistry is IIndexRegistry { /// @param operatorId operatorId of the operator to update /// @param quorumNumber quorumNumber of the operator to update /// @param index the latest index of that operator in quorumToOperatorList - function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber) internal { + function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; //if there is a prior entry for the operator, set the previous entry's toBlocknumber @@ -147,7 +147,7 @@ contract IndexRegistry is IIndexRegistry { operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = block.number; } OperatorIndex memory latestOperatorIndex; - latestOperatorIndex.index = quorumToOperatorList[quorumNumber].length - 1; + latestOperatorIndex.index = index; operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); } From 55295682f7e3d1e373cc750961bde594f16f060e Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Sun, 28 May 2023 18:01:59 +0530 Subject: [PATCH 027/110] added comment --- src/contracts/middleware/IndexRegistry.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index a96bcfcf3..c4d64bee3 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -174,6 +174,8 @@ contract IndexRegistry is IIndexRegistry { quorumToOperatorList[quorumNumber].pop(); } + /// @notice remove an operator from the globalOperatorList + /// @param indexToRemove index of the operator to remove function _removeOperatorFromGlobalOperatorList(uint32 indexToRemove) internal { uint32 globalOperatorListLastIndex = globalOperatorList.length - 1; From abfa22c9ef32ef6b63ca8c0fe1f1e6a820de3ce1 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Sun, 28 May 2023 21:37:49 +0530 Subject: [PATCH 028/110] added blackbox unit tests --- src/contracts/middleware/IndexRegistry.sol | 18 ++--- src/test/mocks/RegistryCoordinatorMock.sol | 29 ++++++++ src/test/unit/IndexRegistryUnit.t.sol | 83 ++++++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 src/test/mocks/RegistryCoordinatorMock.sol create mode 100644 src/test/unit/IndexRegistryUnit.t.sol diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index c4d64bee3..ce49b81b8 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -43,8 +43,8 @@ contract IndexRegistry is IIndexRegistry { globalOperatorList.push(operatorId); for (uint i = 0; i < quorumNumbers.length; i++) { - quorumToOperatorList[quorumNumber].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i], quorumToOperatorList[quorumNumber].length - 1); + quorumToOperatorList[quorumNumbers[i]].push(operatorId); + _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i], uint32(quorumToOperatorList[quorumNumbers[i]].length - 1)); _updateTotalOperatorHistory(quorumNumbers[i]); } } @@ -58,12 +58,14 @@ contract IndexRegistry is IIndexRegistry { * @dev access restricted to the RegistryCoordinator */ function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { - require(quorumNumbers.length == indexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); + require(quorumNumbers.length == quorumToOperatorListIndexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); for (uint i = 0; i < quorumNumbers.length; i++) { _removeOperatorFromQuorumToOperatorList(quorumNumbers[i], quorumToOperatorListIndexes[i]); _updateTotalOperatorHistory(quorumNumbers[i]); + //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number + operatorIdToIndexHistory[operatorId][quorumNumbers[i]][operatorIdToIndexHistory[operatorId][quorumNumbers[i]].length - 1].toBlockNumber = uint32(block.number); } } @@ -126,12 +128,12 @@ contract IndexRegistry is IIndexRegistry { //if there is a prior entry, update its "toBlockNumber" if (totalOperatorsHistory[quorumNumber].length > 0) { - totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].toBlockNumber = block.number; + totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].toBlockNumber = uint32(block.number); } OperatorIndex memory totalOperatorUpdate; // In the case of totalOperatorsHistory, the index parameter is the number of operators in the quorum - totalOperatorUpdate.index = quorumToOperatorList[quorumNumber].length; + totalOperatorUpdate.index = uint32(quorumToOperatorList[quorumNumber].length); totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } @@ -144,7 +146,7 @@ contract IndexRegistry is IIndexRegistry { //if there is a prior entry for the operator, set the previous entry's toBlocknumber if (operatorIdToIndexHistoryLength > 0) { - operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = block.number; + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = uint32(block.number); } OperatorIndex memory latestOperatorIndex; latestOperatorIndex.index = index; @@ -168,8 +170,6 @@ contract IndexRegistry is IIndexRegistry { //set last operator in the list to removed operator's position in the array quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; } - //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = block.number; //removing the swapped operator from the list quorumToOperatorList[quorumNumber].pop(); } @@ -177,7 +177,7 @@ contract IndexRegistry is IIndexRegistry { /// @notice remove an operator from the globalOperatorList /// @param indexToRemove index of the operator to remove function _removeOperatorFromGlobalOperatorList(uint32 indexToRemove) internal { - uint32 globalOperatorListLastIndex = globalOperatorList.length - 1; + uint32 globalOperatorListLastIndex = uint32(globalOperatorList.length - 1); if(indexToRemove != globalOperatorListLastIndex){ bytes32 operatorIdToSwap = globalOperatorList[globalOperatorListLastIndex]; diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol new file mode 100644 index 000000000..5db821c58 --- /dev/null +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + + +import "../../contracts/interfaces/IRegistryCoordinator.sol"; + + +contract RegistryCoordinatorMock is IRegistryCoordinator { + /// @notice Returns the bitmap of the quroums the operator is registered for. + function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} + + /// @notice Returns the stored id for the specified `operator`. + function getOperatorId(address operator) external view returns (bytes32){} + + /// @notice Returns task number from when `operator` has been registered. + function getFromTaskNumberForOperator(address operator) external view returns (uint32){} + + /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data + function registerOperator(uint8[] memory quorumNumbers, bytes calldata) external returns (bytes32){} + + /// @notice deregisters the sender with additional bytes for registry interaction data + function deregisterOperator(bytes calldata) external returns (bytes32){} + + /// @notice Returns the registry at the desired index + function registries(uint256) external view returns (address){} + + /// @notice Returns the number of registries + function numRegistries() external view returns (uint256){} +} \ No newline at end of file diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol new file mode 100644 index 000000000..53ce72a31 --- /dev/null +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -0,0 +1,83 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "../../contracts/interfaces/IIndexRegistry.sol"; +import "../../contracts/middleware/IndexRegistry.sol"; +import "../mocks/RegistryCoordinatorMock.sol"; + +import "forge-std/Test.sol"; + + +contract IndexRegistryUnitTests is Test { + Vm cheats = Vm(HEVM_ADDRESS); + + IndexRegistry indexRegistry; + RegistryCoordinatorMock registryCoordinatorMock; + + + function setUp() public { + // deploy the contract + registryCoordinatorMock = new RegistryCoordinatorMock(); + indexRegistry = new IndexRegistry(registryCoordinatorMock); + } + + + function testConstructor() public { + // check that the registry coordinator is set correctly + assertEq(address(indexRegistry.registryCoordinator()), address(registryCoordinatorMock), "IndexRegistry.constructor: registry coordinator not set correctly"); + } + + function testRegisterOperatorInIndexRegistry(bytes32 operatorId) public { + // register an operator + uint8[] memory quorumNumbers = new uint8[](1); + quorumNumbers[0] = 1; + + cheats.startPrank(address(registryCoordinatorMock)); + indexRegistry.registerOperator(operatorId, quorumNumbers); + cheats.stopPrank(); + + require(indexRegistry.globalOperatorList(0) == operatorId, "IndexRegistry.registerOperator: operator not registered correctly"); + require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: operator not registered correctly"); + require(indexRegistry.totalOperatorsForQuorum(1) == 1, "IndexRegistry.registerOperator: operator not registered correctly"); + (uint32 index, uint32 toBlockNumber) = indexRegistry.operatorIdToIndexHistory(operatorId, 1, 0); + require(index == 0, "IndexRegistry.registerOperator: operator not registered correctly"); + } + + function testRegisterOperatorFromNonRegisterCoordinator(address nonRegistryCoordinator) public { + cheats.assume(address(registryCoordinatorMock) != nonRegistryCoordinator); + // register an operator + uint8[] memory quorumNumbers = new uint8[](1); + + cheats.startPrank(nonRegistryCoordinator); + cheats.expectRevert(bytes("IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); + indexRegistry.registerOperator(bytes32(0), quorumNumbers); + cheats.stopPrank(); + } + + function testDeregisterOperatorInIndexRegistry(bytes32 operatorId1, bytes32 operatorId2) public { + uint8[] memory quorumNumbers = new uint8[](2); + quorumNumbers[0] = 1; + quorumNumbers[1] = 2; + _registerOperator(operatorId1, quorumNumbers); + _registerOperator(operatorId2, quorumNumbers); + + require(indexRegistry.totalOperators() == 2, "IndexRegistry.registerOperator: operator not registered correctly"); + require(indexRegistry.totalOperatorsForQuorum(1) == 2, "IndexRegistry.registerOperator: operator not registered correctly"); + require(indexRegistry.totalOperatorsForQuorum(2) == 2, "IndexRegistry.registerOperator: operator not registered correctly"); + + uint32[] memory quorumToOperatorListIndexes = new uint32[](2); + quorumToOperatorListIndexes[0] = 0; + quorumToOperatorListIndexes[1] = 0; + + // deregister the operatorId1, removing it from both quorum 1 and 2. + cheats.startPrank(address(registryCoordinatorMock)); + indexRegistry.deregisterOperator(operatorId1, quorumNumbers, quorumToOperatorListIndexes, 0); + cheats.stopPrank(); + } + + function _registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) public { + cheats.startPrank(address(registryCoordinatorMock)); + indexRegistry.registerOperator(operatorId, quorumNumbers); + cheats.stopPrank(); + } +} From 2dd2c72ca523ee352f177f412b6889039551d1e2 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath <60323455+Sidu28@users.noreply.github.com> Date: Sun, 28 May 2023 22:19:33 +0530 Subject: [PATCH 029/110] added blackbox unit tests --- src/test/unit/IndexRegistryUnit.t.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index 53ce72a31..9e0862fe3 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -73,6 +73,11 @@ contract IndexRegistryUnitTests is Test { cheats.startPrank(address(registryCoordinatorMock)); indexRegistry.deregisterOperator(operatorId1, quorumNumbers, quorumToOperatorListIndexes, 0); cheats.stopPrank(); + + require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: operator not registered correctly"); + require(indexRegistry.quorumToOperatorList(1, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); + require(indexRegistry.quorumToOperatorList(2, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); + require(indexRegistry.globalOperatorList(0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); } function _registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) public { From a0381d062e2d64bd8d45088b6f2281b3b99ed01d Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Tue, 30 May 2023 13:54:28 +0530 Subject: [PATCH 030/110] added tests --- src/test/unit/IndexRegistryUnit.t.sol | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index 9e0862fe3..f3715ddff 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -41,6 +41,7 @@ contract IndexRegistryUnitTests is Test { require(indexRegistry.totalOperatorsForQuorum(1) == 1, "IndexRegistry.registerOperator: operator not registered correctly"); (uint32 index, uint32 toBlockNumber) = indexRegistry.operatorIdToIndexHistory(operatorId, 1, 0); require(index == 0, "IndexRegistry.registerOperator: operator not registered correctly"); + require(toBlockNumber == 0, "block number should not be set"); } function testRegisterOperatorFromNonRegisterCoordinator(address nonRegistryCoordinator) public { @@ -54,6 +55,18 @@ contract IndexRegistryUnitTests is Test { cheats.stopPrank(); } + function testDeregisterOperatorFromNonRegisterCoordinator(address nonRegistryCoordinator) public { + cheats.assume(address(registryCoordinatorMock) != nonRegistryCoordinator); + // register an operator + uint8[] memory quorumNumbers = new uint8[](1); + uint32[] memory quorumToOperatorListIndexes = new uint32[](1); + + cheats.startPrank(nonRegistryCoordinator); + cheats.expectRevert(bytes("IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); + indexRegistry.deregisterOperator(bytes32(0), quorumNumbers, quorumToOperatorListIndexes, 0); + cheats.stopPrank(); + } + function testDeregisterOperatorInIndexRegistry(bytes32 operatorId1, bytes32 operatorId2) public { uint8[] memory quorumNumbers = new uint8[](2); quorumNumbers[0] = 1; @@ -69,6 +82,9 @@ contract IndexRegistryUnitTests is Test { quorumToOperatorListIndexes[0] = 0; quorumToOperatorListIndexes[1] = 0; + + cheats.roll(block.number + 1); + // deregister the operatorId1, removing it from both quorum 1 and 2. cheats.startPrank(address(registryCoordinatorMock)); indexRegistry.deregisterOperator(operatorId1, quorumNumbers, quorumToOperatorListIndexes, 0); @@ -78,6 +94,28 @@ contract IndexRegistryUnitTests is Test { require(indexRegistry.quorumToOperatorList(1, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); require(indexRegistry.quorumToOperatorList(2, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); require(indexRegistry.globalOperatorList(0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); + + (uint32 index1, uint32 toBlockNumber1) = indexRegistry.operatorIdToIndexHistory(operatorId1, 1, 0); + require(toBlockNumber1 == block.number, "toBlockNumber not set correctly"); + require(index1 == 0); + + (uint32 index2, uint32 toBlockNumber2) = indexRegistry.operatorIdToIndexHistory(operatorId2, 1, 1); + require(toBlockNumber2 == block.number, "toBlockNumber not set correctly"); + require(index2 == 1); + + } + + function testTotalOperatorUpdatesForOneQuorum(uint8 numOperators) public { + uint8[] memory quorumNumbers = new uint8[](1); + quorumNumbers[0] = 1; + + uint256 lengthBefore = 0; + for (uint256 i = 0; i < numOperators; i++) { + _registerOperator(bytes32(i), quorumNumbers); + require(indexRegistry.totalOperatorsForQuorum(1) - lengthBefore == 1, "incorrect update"); + require(indexRegistry.totalOperators() - lengthBefore == 1, "incorrect update"); + lengthBefore++; + } } function _registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) public { @@ -85,4 +123,10 @@ contract IndexRegistryUnitTests is Test { indexRegistry.registerOperator(operatorId, quorumNumbers); cheats.stopPrank(); } + + function _deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 index) public { + cheats.startPrank(address(registryCoordinatorMock)); + indexRegistry.deregisterOperator(operatorId, quorumNumbers, quorumToOperatorListIndexes, index); + cheats.stopPrank(); + } } From 0d5b0bcde3a7d1b73c2dbf8b65bd85e802c939d1 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 30 May 2023 10:33:18 -0700 Subject: [PATCH 031/110] almost all tests working for vote weigher --- src/contracts/interfaces/IVoteWeigher.sol | 4 +- src/contracts/middleware/VoteWeigherBase.sol | 46 +- src/test/harnesses/VoteWeigherBaseHarness.sol | 16 + src/test/unit/VoteWeigherBaseUnit.t.sol | 570 ++++++++++++++++++ src/test/unit/VoteWeigherUnit.t.sol | 69 --- 5 files changed, 615 insertions(+), 90 deletions(-) create mode 100644 src/test/harnesses/VoteWeigherBaseHarness.sol create mode 100644 src/test/unit/VoteWeigherBaseUnit.t.sol delete mode 100644 src/test/unit/VoteWeigherUnit.t.sol diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index 419159518..6fc1ebeee 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -45,6 +45,9 @@ interface IVoteWeigher { */ function quorumBips(uint8 quorumNumber) external view returns (uint256); + /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber` + function strategyAndWeightingMultiplierForQuorumByIndex(uint8 quorumNumber, uint256 index) external view returns (StrategyAndWeightingMultiplier memory); + /// @notice Create a new quorum and add the strategies and their associated weights to the quorum. function createQuorum( StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers @@ -64,7 +67,6 @@ interface IVoteWeigher { */ function removeStrategiesConsideredAndMultipliers( uint8 quorumNumber, - IStrategy[] calldata _strategiesToRemove, uint256[] calldata indicesToRemove ) external; diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index ccced20c7..878e21e1e 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -18,16 +18,16 @@ import "./VoteWeigherBaseStorage.sol"; contract VoteWeigherBase is VoteWeigherBaseStorage { /// @notice emitted when a new quorum is created event QuorumCreated(uint8 indexed quorumNumber); - /// @notice emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]` - event StrategyAddedToQuorum(uint256 indexed quorumNumber, IStrategy strategy); + /// @notice emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]` with the `multiplier` + event StrategyAddedToQuorum(uint8 indexed quorumNumber, IStrategy strategy, uint96 multiplier); /// @notice emitted when `strategy` has removed from the array at `strategiesConsideredAndMultipliers[quorumNumber]` - event StrategyRemovedFromQuorum(uint256 indexed quorumNumber, IStrategy strategy); + event StrategyRemovedFromQuorum(uint8 indexed quorumNumber, IStrategy strategy); /// @notice emitted when `strategy` has its `multiplier` updated in the array at `strategiesConsideredAndMultipliers[quorumNumber]` - event StrategyMultiplierUpdated(uint256 indexed quorumNumber, IStrategy strategy, uint256 multiplier); + event StrategyMultiplierUpdated(uint8 indexed quorumNumber, IStrategy strategy, uint256 multiplier); /// @notice when applied to a function, ensures that the function is only callable by the current `owner` of the `serviceManager` modifier onlyServiceManagerOwner() { - require(msg.sender == serviceManager.owner(), "onlyServiceManagerOwner"); + require(msg.sender == serviceManager.owner(), "VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); _; } @@ -39,6 +39,15 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { // solhint-disable-next-line no-empty-blocks {} + /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber` + function strategyAndWeightingMultiplierForQuorumByIndex(uint8 quorumNumber, uint256 index) + public + view + returns (StrategyAndWeightingMultiplier memory) + { + return strategiesConsideredAndMultipliers[quorumNumber][index]; + } + /** * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber. * @dev returns zero in the case that `quorumNumber` is greater than or equal to `quorumCount` @@ -100,25 +109,15 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { */ function removeStrategiesConsideredAndMultipliers( uint8 quorumNumber, - IStrategy[] calldata _strategiesToRemove, uint256[] calldata indicesToRemove ) external virtual onlyServiceManagerOwner { - uint256 numStrats = _strategiesToRemove.length; - // sanity check on input lengths - require(indicesToRemove.length == numStrats, "VoteWeigherBase.removeStrategiesConsideredAndWeights: input length mismatch"); - - for (uint256 i = 0; i < numStrats;) { - // check that the provided index is correct - require( - strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]].strategy == _strategiesToRemove[i], - "VoteWeigherBase.removeStrategiesConsideredAndWeights: index incorrect" - ); - + require(quorumNumber < quorumCount, "VoteWeigherBase.removeStrategiesConsideredAndMultipliers: quorumNumber is greater than or equal to quorumCount"); + for (uint256 i = 0; i < indicesToRemove.length;) { + emit StrategyRemovedFromQuorum(quorumNumber, strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]].strategy); // remove strategy and its associated multiplier strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]] = strategiesConsideredAndMultipliers[quorumNumber][strategiesConsideredAndMultipliers[quorumNumber] .length - 1]; strategiesConsideredAndMultipliers[quorumNumber].pop(); - emit StrategyRemovedFromQuorum(quorumNumber, _strategiesToRemove[i]); unchecked { ++i; @@ -137,6 +136,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { uint256[] calldata strategyIndices, uint96[] calldata newMultipliers ) external virtual onlyServiceManagerOwner { + require(quorumNumber < quorumCount, "VoteWeigherBase.modifyStrategyWeights: quorumNumber is greater than or equal to quorumCount"); uint256 numStrats = strategyIndices.length; // sanity check on input lengths require(newMultipliers.length == numStrats, @@ -171,6 +171,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers ) internal { uint8 quorumNumber = quorumCount; + require(quorumNumber < 255, "VoteWeigherBase._createQuorum: number of quorums cannot 256"); // increment quorumCount quorumCount = quorumNumber + 1; @@ -191,7 +192,8 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { uint8 quorumNumber, StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers ) internal { - require(quorumNumber <= quorumCount, "VoteWeigherBase._addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); + require(quorumNumber < quorumCount, "VoteWeigherBase._addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); + require(_newStrategiesConsideredAndMultipliers.length > 0, "VoteWeigherBase._addStrategiesConsideredAndMultipliers: no strategies provided"); uint256 numStratsToAdd = _newStrategiesConsideredAndMultipliers.length; uint256 numStratsExisting = strategiesConsideredAndMultipliers[quorumNumber].length; require( @@ -210,8 +212,12 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { ++j; } } + require( + _newStrategiesConsideredAndMultipliers[i].multiplier > 0, + "VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add strategy with zero weight" + ); strategiesConsideredAndMultipliers[quorumNumber].push(_newStrategiesConsideredAndMultipliers[i]); - emit StrategyAddedToQuorum(quorumNumber, _newStrategiesConsideredAndMultipliers[i].strategy); + emit StrategyAddedToQuorum(quorumNumber, _newStrategiesConsideredAndMultipliers[i].strategy, _newStrategiesConsideredAndMultipliers[i].multiplier); unchecked { ++i; } diff --git a/src/test/harnesses/VoteWeigherBaseHarness.sol b/src/test/harnesses/VoteWeigherBaseHarness.sol new file mode 100644 index 000000000..735b53579 --- /dev/null +++ b/src/test/harnesses/VoteWeigherBaseHarness.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "../../contracts/middleware/VoteWeigherBase.sol"; + +// wrapper around the StakeRegistry contract that exposes the internal functions for unit testing. +contract VoteWeigherBaseHarness is VoteWeigherBase { + constructor( + IStrategyManager _strategyManager, + IServiceManager _serviceManager + ) VoteWeigherBase(_strategyManager, _serviceManager) {} + + function getMaxWeighingFunctionLength() public pure returns (uint8) { + return MAX_WEIGHING_FUNCTION_LENGTH; + } +} \ No newline at end of file diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol new file mode 100644 index 000000000..7a3130202 --- /dev/null +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import "../../contracts/permissions/PauserRegistry.sol"; +import "../../contracts/interfaces/IStrategyManager.sol"; +import "../../contracts/interfaces/IServiceManager.sol"; +import "../../contracts/interfaces/IVoteWeigher.sol"; +import "../../contracts/middleware/VoteWeigherBase.sol"; + +import "../harnesses/VoteWeigherBaseHarness.sol"; + +import "../mocks/StrategyManagerMock.sol"; +import "../mocks/OwnableMock.sol"; + +import "forge-std/Test.sol"; + +contract VoteWeigherBaseUnitTests is Test { + + Vm cheats = Vm(HEVM_ADDRESS); + + ProxyAdmin public proxyAdmin; + PauserRegistry public pauserRegistry; + IStrategyManager public strategyManager; + + address serviceManagerOwner; + IServiceManager public serviceManager; + + VoteWeigherBaseHarness public voteWeigher; + + VoteWeigherBaseHarness public voteWeigherImplementation; + + address public pauser = address(555); + address public unpauser = address(999); + + uint256 initialSupply = 1e36; + address initialOwner = address(this); + + /// @notice emitted when a new quorum is created + event QuorumCreated(uint8 indexed quorumNumber); + /// @notice emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]` with the `multiplier` + event StrategyAddedToQuorum(uint8 indexed quorumNumber, IStrategy strategy, uint96 multiplier); + /// @notice emitted when `strategy` has removed from the array at `strategiesConsideredAndMultipliers[quorumNumber]` + event StrategyRemovedFromQuorum(uint8 indexed quorumNumber, IStrategy strategy); + + function setUp() virtual public { + proxyAdmin = new ProxyAdmin(); + + pauserRegistry = new PauserRegistry(pauser, unpauser); + + strategyManager = new StrategyManagerMock(); + + // make the serviceManagerOwner the owner of the serviceManager contract + cheats.prank(serviceManagerOwner); + serviceManager = IServiceManager(address(new OwnableMock())); + + voteWeigherImplementation = new VoteWeigherBaseHarness(strategyManager, serviceManager); + + voteWeigher = VoteWeigherBaseHarness(address(new TransparentUpgradeableProxy(address(voteWeigherImplementation), address(proxyAdmin), ""))); + } + + function testCorrectConstructionParameters() public { + assertEq(address(voteWeigherImplementation.strategyManager()), address(strategyManager)); + assertEq(address(voteWeigherImplementation.slasher()), address(strategyManager.slasher())); + assertEq(address(voteWeigherImplementation.delegation()), address(strategyManager.delegation())); + assertEq(address(voteWeigherImplementation.serviceManager()), address(serviceManager)); + } + + function testCreateQuorum_Valid(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + // create a quorum from the serviceManagerOwner + // get the quorum count before the quorum is created + uint8 quorumCountBefore = voteWeigher.quorumCount(); + cheats.prank(serviceManagerOwner); + // expect each strategy to be added to the quorum + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + cheats.expectEmit(true, true, true, true, address(voteWeigher)); + emit StrategyAddedToQuorum(quorumCountBefore, strategiesAndWeightingMultipliers[i].strategy, strategiesAndWeightingMultipliers[i].multiplier); + } + // created quorum will have quorum number of the count before it was created + cheats.expectEmit(true, true, true, true, address(voteWeigher)); + emit QuorumCreated(quorumCountBefore); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + assertEq(voteWeigher.quorumCount(), quorumCountBefore + 1); + // check that all of the weights are correct + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumCountBefore, i); + assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); + assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); + } + } + + function testCreateQuorum_FromNotServiceManagerOwner_Reverts( + address notServiceManagerOwner, + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + cheats.assume(notServiceManagerOwner != serviceManagerOwner); + cheats.prank(notServiceManagerOwner); + cheats.expectRevert("VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + + function testCreateQuorum_StrategiesAndWeightingMultipliers_LengthGreaterThanMaxAllowed_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); + _assumeNoZeroWeights(strategiesAndWeightingMultipliers); + + cheats.assume(strategiesAndWeightingMultipliers.length > voteWeigher.getMaxWeighingFunctionLength()); + cheats.prank(serviceManagerOwner); + cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: exceed MAX_WEIGHING_FUNCTION_LENGTH"); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + + function testCreateQuorum_StrategiesAndWeightingMultipliers_WithDuplicateStrategies_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length > 1); + _assumeNoZeroWeights(strategiesAndWeightingMultipliers); + + // make sure a duplicate strategy exists + bool duplicateExists; + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + for (uint256 j = i + 1; j < strategiesAndWeightingMultipliers.length; j++) { + if (strategiesAndWeightingMultipliers[i].strategy == strategiesAndWeightingMultipliers[j].strategy) { + duplicateExists = true; + break; + } + } + } + cheats.assume(duplicateExists); + + cheats.prank(serviceManagerOwner); + cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add same strategy 2x"); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + + function testCreateQuorum_EmptyStrategiesAndWeightingMultipliers_Reverts() public { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers; + cheats.prank(serviceManagerOwner); + cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: no strategies provided"); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + + function testCreateQuorum_StrategiesAndWeightingMultipliers_WithZeroWeight( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); + cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length > 0); + // make sure a zero weight exists + bool zeroWeightExists; + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + if (strategiesAndWeightingMultipliers[i].multiplier == 0) { + zeroWeightExists = true; + break; + } + } + cheats.assume(zeroWeightExists); + + cheats.prank(serviceManagerOwner); + cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add strategy with zero weight"); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + + function testCreateQuorum_MoreThan256Quorums_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + cheats.startPrank(serviceManagerOwner); + for (uint i = 0; i < 255; i++) { + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + cheats.expectRevert("VoteWeigherBase._createQuorum: number of quorums cannot 256"); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + } + + function testAddStrategiesConsideredAndMultipliers_Valid( + uint256 randomSplit, + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + // make sure there is at least 2 strategies + cheats.assume(strategiesAndWeightingMultipliers.length > 1); + // we need at least 1 strategy in each side of the split + randomSplit = randomSplit % (strategiesAndWeightingMultipliers.length - 1) + 1; + // create 2 arrays, 1 with the first randomSplit elements and 1 with the rest + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers1 = new IVoteWeigher.StrategyAndWeightingMultiplier[](randomSplit); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers2 = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - randomSplit); + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + if (i < randomSplit) { + strategiesAndWeightingMultipliers1[i] = strategiesAndWeightingMultipliers[i]; + } else { + strategiesAndWeightingMultipliers2[i - randomSplit] = strategiesAndWeightingMultipliers[i]; + } + } + uint8 quorumNumber = voteWeigher.quorumCount(); + // create quorum with the first randomSplit elements + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers1); + + // add the rest of the strategies + for (uint i = 0; i < strategiesAndWeightingMultipliers2.length; i++) { + cheats.expectEmit(true, true, true, true, address(voteWeigher)); + emit StrategyAddedToQuorum(quorumNumber, strategiesAndWeightingMultipliers2[i].strategy, strategiesAndWeightingMultipliers2[i].multiplier); + } + voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber, strategiesAndWeightingMultipliers2); + + // check that the quorum was created and strategies were added correctly + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); + assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); + assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); + } + } + + function testAddStrategiesConsideredAndMultipliers_NotFromServiceManagerOwner_Reverts( + address notServiceManagerOwner, + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + cheats.assume(notServiceManagerOwner != serviceManagerOwner); + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + // we need 2 or more strategies to create the quorums and then add one + cheats.assume(strategiesAndWeightingMultipliers.length > 1); + + // separate strategiesAndWeightingMultipliers into 2 arrays, 1 with the last element and 1 with the rest + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers1 = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - 1); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers2 = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length - 1; i++) { + strategiesAndWeightingMultipliers1[i] = strategiesAndWeightingMultipliers[i]; + } + strategiesAndWeightingMultipliers2[0] = strategiesAndWeightingMultipliers[strategiesAndWeightingMultipliers.length - 1]; + + // create quorum with all but the last element + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.prank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers1); + + // add the last element + cheats.prank(notServiceManagerOwner); + cheats.expectRevert("VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); + voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber, strategiesAndWeightingMultipliers2); + } + + function testAddStrategiesConsideredAndMultipliers_ForNonExistantQuorum_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + // we need 2 or more strategies to create the quorums and then add one + cheats.assume(strategiesAndWeightingMultipliers.length > 1); + + // separate strategiesAndWeightingMultipliers into 2 arrays, 1 with the last element and 1 with the rest + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers1 = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - 1); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers2 = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length - 1; i++) { + strategiesAndWeightingMultipliers1[i] = strategiesAndWeightingMultipliers[i]; + } + strategiesAndWeightingMultipliers2[0] = strategiesAndWeightingMultipliers[strategiesAndWeightingMultipliers.length - 1]; + + // create quorum with all but the last element + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers1); + + // add the last element + cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); + voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber+1, strategiesAndWeightingMultipliers2); + } + + function testRemoveStrategiesConsideredAndMultipliers_Valid( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory indicesToRemove + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + // take indices modulo length + for (uint256 i = 0; i < indicesToRemove.length; i++) { + indicesToRemove[i] = indicesToRemove[i] % strategiesAndWeightingMultipliers.length; + } + indicesToRemove = _removeDuplicatesUint256(indicesToRemove); + cheats.assume(indicesToRemove.length > 0); + cheats.assume(indicesToRemove.length < strategiesAndWeightingMultipliers.length); + indicesToRemove = _sortInDescendingOrder(indicesToRemove); + + // create the quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // remove certain strategies + // make sure events are emmitted + for (uint i = 0; i < indicesToRemove.length; i++) { + cheats.expectEmit(true, true, true, true, address(voteWeigher)); + emit StrategyRemovedFromQuorum(quorumNumber, strategiesAndWeightingMultipliers[indicesToRemove[i]].strategy); + } + voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, indicesToRemove); + + // check that the strategies were removed + + // check that the strategies that were not removed are still there + // get all strategies and multipliers form the contracts + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliersFromContract = new IVoteWeigher.StrategyAndWeightingMultiplier[](voteWeigher.strategiesConsideredAndMultipliersLength(quorumNumber)); + for (uint256 i = 0; i < strategiesAndWeightingMultipliersFromContract.length; i++) { + strategiesAndWeightingMultipliersFromContract[i] = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); + } + + // remove indicesToRemove from local strategiesAndWeightingMultipliers + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliersLocal = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - indicesToRemove.length); + uint256 j = 0; + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + for (uint256 k = 0; k < indicesToRemove.length; k++) { + if (i == indicesToRemove[k]) { + break; + } + if (k == indicesToRemove.length - 1) { + strategiesAndWeightingMultipliersLocal[j] = strategiesAndWeightingMultipliers[i]; + j++; + } + } + } + + // sort both arrays by strategy so that they are easily comaparable + strategiesAndWeightingMultipliersFromContract = _sortStrategiesAndWeightingMultipliersByStrategy(strategiesAndWeightingMultipliersFromContract); + strategiesAndWeightingMultipliersLocal = _sortStrategiesAndWeightingMultipliersByStrategy(strategiesAndWeightingMultipliersLocal); + + // check that the arrays are the same + assertEq(strategiesAndWeightingMultipliersFromContract.length, strategiesAndWeightingMultipliersLocal.length); + for (uint256 i = 0; i < strategiesAndWeightingMultipliersFromContract.length; i++) { + assertEq(address(strategiesAndWeightingMultipliersFromContract[i].strategy), address(strategiesAndWeightingMultipliersLocal[i].strategy)); + assertEq(strategiesAndWeightingMultipliersFromContract[i].multiplier, strategiesAndWeightingMultipliersLocal[i].multiplier); + } + + } + + function testRemoveStrategiesConsideredAndMultipliers_NotFromServiceManagerOwner_Reverts( + address notServiceManagerOwner, + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory indicesToRemove + ) public { + cheats.assume(notServiceManagerOwner != serviceManagerOwner); + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.prank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // remove certain strategies + cheats.prank(notServiceManagerOwner); + cheats.expectRevert("VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); + voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, indicesToRemove); + } + + function testRemoveStrategiesConsideredAndMultipliers_ForNonExistantQuorum_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory indicesToRemove + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // remove strategies from a non-existant quorum + cheats.expectRevert("VoteWeigherBase.removeStrategiesConsideredAndMultipliers: quorumNumber is greater than or equal to quorumCount"); + voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber + 1, indicesToRemove); + } + + function testRemoveStrategiesConsideredAndMultipliers_EmptyIndicesToRemove_HasNoEffect( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // remove no strategies + voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, new uint256[](0)); + // make sure the quorum strategies and weights haven't changed + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); + assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); + assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); + } + } + + function testModifyStrategyWeights_NotFromServiceManagerOwner_Reverts( + address notServiceManagerOwner, + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory strategyIndices, + uint96[] memory newWeights + ) public { + cheats.assume(notServiceManagerOwner != serviceManagerOwner); + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.prank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // modify certain strategies + cheats.prank(notServiceManagerOwner); + cheats.expectRevert("VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); + voteWeigher.modifyStrategyWeights(quorumNumber, strategyIndices, newWeights); + } + + function testModifyStrategyWeights_ForNonExistantQuorum_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory strategyIndices, + uint96[] memory newWeights + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // modify certain strategies of a non-existant quorum + cheats.expectRevert("VoteWeigherBase.modifyStrategyWeights: quorumNumber is greater than or equal to quorumCount"); + voteWeigher.modifyStrategyWeights(quorumNumber + 1, strategyIndices, newWeights); + } + + function testModifyStrategyWeights_InconsistentStrategyAndWeightArrayLengths_Reverts( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory strategyIndices, + uint96[] memory newWeights + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // make sure the arrays are of different lengths + cheats.assume(strategyIndices.length != newWeights.length); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // modify certain strategies + cheats.expectRevert("VoteWeigherBase.modifyStrategyWeights: input length mismatch"); + voteWeigher.modifyStrategyWeights(quorumNumber, strategyIndices, newWeights); + } + + function testModifyStrategyWeights_EmptyStrategyIndicesAndWeights_HasNoEffect( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // modify no strategies + voteWeigher.modifyStrategyWeights(quorumNumber, new uint256[](0), new uint96[](0)); + // make sure the quorum strategies and weights haven't changed + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); + assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); + assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); + } + } + + function _removeDuplicates(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) + internal pure + returns(IVoteWeigher.StrategyAndWeightingMultiplier[] memory) + { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory deduplicatedStrategiesAndWeightingMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length); + uint256 numUniqueStrategies = 0; + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + bool isDuplicate = false; + for (uint j = 0; j < deduplicatedStrategiesAndWeightingMultipliers.length; j++) { + if (strategiesAndWeightingMultipliers[i].strategy == deduplicatedStrategiesAndWeightingMultipliers[j].strategy) { + isDuplicate = true; + break; + } + } + if(!isDuplicate) { + deduplicatedStrategiesAndWeightingMultipliers[numUniqueStrategies] = strategiesAndWeightingMultipliers[i]; + numUniqueStrategies++; + } + } + + IVoteWeigher.StrategyAndWeightingMultiplier[] memory trimmedStrategiesAndWeightingMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[](numUniqueStrategies); + for (uint i = 0; i < numUniqueStrategies; i++) { + trimmedStrategiesAndWeightingMultipliers[i] = deduplicatedStrategiesAndWeightingMultipliers[i]; + } + return trimmedStrategiesAndWeightingMultipliers; + } + + function _assumeNoZeroWeights(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal view { + for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + cheats.assume(strategiesAndWeightingMultipliers[i].multiplier != 0); + } + } + + function _sortStrategiesAndWeightingMultipliersByStrategy(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) + internal pure returns(IVoteWeigher.StrategyAndWeightingMultiplier[] memory) + { + uint256 n = strategiesAndWeightingMultipliers.length; + for (uint256 i = 0; i < n - 1; i++) { + uint256 min_idx = i; + for (uint256 j = i + 1; j < n; j++) { + if (uint160(address(strategiesAndWeightingMultipliers[j].strategy)) < uint160(address(strategiesAndWeightingMultipliers[min_idx].strategy))) { + min_idx = j; + } + } + IVoteWeigher.StrategyAndWeightingMultiplier memory temp = strategiesAndWeightingMultipliers[min_idx]; + strategiesAndWeightingMultipliers[min_idx] = strategiesAndWeightingMultipliers[i]; + strategiesAndWeightingMultipliers[i] = temp; + } + return strategiesAndWeightingMultipliers; + } + + function _sortInDescendingOrder(uint256[] memory arr) internal pure returns(uint256[] memory) { + uint256 n = arr.length; + for (uint256 i = 0; i < n - 1; i++) { + uint256 max_idx = i; + for (uint256 j = i + 1; j < n; j++) { + if (arr[j] > arr[max_idx]) { + max_idx = j; + } + } + uint256 temp = arr[max_idx]; + arr[max_idx] = arr[i]; + arr[i] = temp; + } + return arr; + } + + function _removeDuplicatesUint256(uint256[] memory arr) internal pure returns(uint256[] memory) { + uint256[] memory deduplicatedArr = new uint256[](arr.length); + uint256 numUniqueElements = 0; + for (uint i = 0; i < arr.length; i++) { + bool isDuplicate = false; + for (uint j = 0; j < deduplicatedArr.length; j++) { + if (arr[i] == deduplicatedArr[j]) { + isDuplicate = true; + break; + } + } + if(!isDuplicate) { + deduplicatedArr[numUniqueElements] = arr[i]; + numUniqueElements++; + } + } + + uint256[] memory trimmedArr = new uint256[](numUniqueElements); + for (uint i = 0; i < numUniqueElements; i++) { + trimmedArr[i] = deduplicatedArr[i]; + } + return trimmedArr; + } + + function _convertToValidStrategiesAndWeightingMultipliers(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal view returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { + strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); + cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length > 0); + _assumeNoZeroWeights(strategiesAndWeightingMultipliers); + return strategiesAndWeightingMultipliers; + } +} \ No newline at end of file diff --git a/src/test/unit/VoteWeigherUnit.t.sol b/src/test/unit/VoteWeigherUnit.t.sol deleted file mode 100644 index a475b3281..000000000 --- a/src/test/unit/VoteWeigherUnit.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - -import "../../contracts/permissions/PauserRegistry.sol"; -import "../../contracts/interfaces/IStrategyManager.sol"; -import "../../contracts/interfaces/IServiceManager.sol"; -import "../../contracts/interfaces/IVoteWeigher.sol"; -import "../../contracts/middleware/VoteWeigherBase.sol"; - -import "../mocks/StrategyManagerMock.sol"; -import "../mocks/OwnableMock.sol"; - -import "forge-std/Test.sol"; - -contract VoteWeigherUnitTests is Test { - - Vm cheats = Vm(HEVM_ADDRESS); - - ProxyAdmin public proxyAdmin; - PauserRegistry public pauserRegistry; - IStrategyManager public strategyManager; - - address serviceManagerOwner; - IServiceManager public serviceManager; - - IVoteWeigher public voteWeigher; - - IVoteWeigher public voteWeigherImplementation; - - address public pauser = address(555); - address public unpauser = address(999); - - uint256 initialSupply = 1e36; - address initialOwner = address(this); - - function setUp() virtual public { - proxyAdmin = new ProxyAdmin(); - - pauserRegistry = new PauserRegistry(pauser, unpauser); - - strategyManager = new StrategyManagerMock(); - - // make the serviceManagerOwner the owner of the serviceManager contract - cheats.prank(serviceManagerOwner); - serviceManager = IServiceManager(address(new OwnableMock())); - - voteWeigherImplementation = new VoteWeigherBase(strategyManager, serviceManager); - - voteWeigher = IVoteWeigher(address(new TransparentUpgradeableProxy(address(voteWeigherImplementation), address(proxyAdmin), ""))); - } - - function testCorrectConstructionParameters() public { - assertEq(address(voteWeigherImplementation.strategyManager()), address(strategyManager)); - assertEq(address(voteWeigherImplementation.slasher()), address(strategyManager.slasher())); - assertEq(address(voteWeigherImplementation.delegation()), address(strategyManager.delegation())); - assertEq(address(voteWeigherImplementation.serviceManager()), address(serviceManager)); - } - - function testValidCreateQuorum() public { - // create a quorum from the serviceManagerOwner - cheats.prank(serviceManagerOwner); - voteWeigher.createQuorum() - } - -} \ No newline at end of file From 3a8ff37999b7213fa90c5aa42f9fb7f8549eb4c2 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 30 May 2023 14:19:29 -0700 Subject: [PATCH 032/110] add valid modification test --- src/test/unit/VoteWeigherBaseUnit.t.sol | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index 7a3130202..3e8c2d07a 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -45,6 +45,8 @@ contract VoteWeigherBaseUnitTests is Test { event StrategyAddedToQuorum(uint8 indexed quorumNumber, IStrategy strategy, uint96 multiplier); /// @notice emitted when `strategy` has removed from the array at `strategiesConsideredAndMultipliers[quorumNumber]` event StrategyRemovedFromQuorum(uint8 indexed quorumNumber, IStrategy strategy); + /// @notice emitted when `strategy` has its `multiplier` updated in the array at `strategiesConsideredAndMultipliers[quorumNumber]` + event StrategyMultiplierUpdated(uint8 indexed quorumNumber, IStrategy strategy, uint256 multiplier); function setUp() virtual public { proxyAdmin = new ProxyAdmin(); @@ -412,6 +414,56 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.modifyStrategyWeights(quorumNumber, strategyIndices, newWeights); } + function testModifyStrategyWeights_Valid( + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256[] memory strategyIndices, + uint96[] memory newWeights + ) public { + strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + + // take indices modulo length + for (uint256 i = 0; i < strategyIndices.length; i++) { + strategyIndices[i] = strategyIndices[i] % strategiesAndWeightingMultipliers.length; + } + strategyIndices = _removeDuplicatesUint256(strategyIndices); + cheats.assume(strategyIndices.length > 0); + cheats.assume(strategyIndices.length < strategiesAndWeightingMultipliers.length); + + // trim the provided weights to the length of the strategyIndices + uint96[] memory newWeightsTrim = new uint96[](strategyIndices.length); + for (uint256 i = 0; i < strategyIndices.length; i++) { + if(i < newWeights.length) { + newWeightsTrim[i] = newWeights[i]; + } else { + newWeightsTrim[i] = strategiesAndWeightingMultipliers[strategyIndices[i]].multiplier - 1; + } + } + newWeights = newWeightsTrim; + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); + + // modify certain strategies + for (uint i = 0; i < strategyIndices.length; i++) { + cheats.expectEmit(true, true, true, true, address(voteWeigher)); + emit StrategyMultiplierUpdated(quorumNumber, strategiesAndWeightingMultipliers[strategyIndices[i]].strategy, newWeights[i]); + } + voteWeigher.modifyStrategyWeights(quorumNumber, strategyIndices, newWeights); + + // convert the strategies and weighting multipliers to the modified + for (uint i = 0; i < strategyIndices.length; i++) { + strategiesAndWeightingMultipliers[strategyIndices[i]].multiplier = newWeights[i]; + } + // make sure the quorum strategies and weights have changed + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); + assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); + assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); + } + } + function testModifyStrategyWeights_ForNonExistantQuorum_Reverts( IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, uint256[] memory strategyIndices, From a0d802e419df3f668d0bc83c246595d9d0c82ec2 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 30 May 2023 16:56:00 -0700 Subject: [PATCH 033/110] add weight of operator test --- src/contracts/interfaces/IVoteWeigher.sol | 2 +- src/contracts/middleware/RegistryBase.sol | 4 +- src/contracts/middleware/StakeRegistry.sol | 2 +- src/contracts/middleware/VoteWeigherBase.sol | 9 +--- src/test/Delegation.t.sol | 16 +++---- src/test/EigenLayerTestHelper.t.sol | 8 ++-- src/test/harnesses/VoteWeigherBaseHarness.sol | 4 ++ src/test/mocks/DelegationMock.sol | 12 ++--- src/test/unit/VoteWeigherBaseUnit.t.sol | 44 ++++++++++++++++++- 9 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index 6fc1ebeee..025dc156d 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -34,7 +34,7 @@ interface IVoteWeigher { * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber. * @dev returns zero in the case that `quorumNumber` is greater than or equal to `NUMBER_OF_QUORUMS` */ - function weightOfOperator(address operator, uint8 quorumNumber) external returns (uint96); + function weightOfOperator(uint8 quorumNumber, address operator) external returns (uint96); /// @notice Number of quorums that are being used by the middleware. function quorumCount() external view returns (uint8); diff --git a/src/contracts/middleware/RegistryBase.sol b/src/contracts/middleware/RegistryBase.sol index c69078173..8cc4d9a95 100644 --- a/src/contracts/middleware/RegistryBase.sol +++ b/src/contracts/middleware/RegistryBase.sol @@ -603,7 +603,7 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { // evaluate the stake for the operator if(quorumBitmap >> quorumNumber & 1 == 1) { - _operatorStakeUpdate.stake = uint96(weightOfOperator(operator, quorumNumber)); + _operatorStakeUpdate.stake = uint96(weightOfOperator(quorumNumber, operator)); // check if minimum requirement has been met require(_operatorStakeUpdate.stake >= minimumStakeForQuorum[quorumNumber], "RegistryBase._registerStake: Operator does not meet minimum stake requirement for quorum"); // check special case that operator is re-registering (and thus already has some history) @@ -649,7 +649,7 @@ abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { if (quorumBitmap >> quorumNumber & 1 == 1) { // determine new stakes operatorStakeUpdate.updateBlockNumber = uint32(block.number); - operatorStakeUpdate.stake = weightOfOperator(operator, quorumNumber); + operatorStakeUpdate.stake = weightOfOperator(quorumNumber, operator); // check if minimum requirements have been met if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index c2348b7ee..540e091c9 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -462,7 +462,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { // determine new stakes OperatorStakeUpdate memory operatorStakeUpdate; operatorStakeUpdate.updateBlockNumber = uint32(block.number); - operatorStakeUpdate.stake = weightOfOperator(operator, quorumNumber); + operatorStakeUpdate.stake = weightOfOperator(quorumNumber, operator); // check if minimum requirements have been met if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index 878e21e1e..7dd17c0c7 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -52,7 +52,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber. * @dev returns zero in the case that `quorumNumber` is greater than or equal to `quorumCount` */ - function weightOfOperator(address operator, uint8 quorumNumber) public virtual returns (uint96) { + function weightOfOperator(uint8 quorumNumber, address operator) public virtual returns (uint96) { uint96 weight; if (quorumNumber < quorumCount) { @@ -69,12 +69,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { // add the weight from the shares for this strategy to the total weight if (sharesAmount > 0) { - weight += uint96( - ( - (strategyAndMultiplier.strategy).sharesToUnderlying(sharesAmount) - * strategyAndMultiplier.multiplier - ) / WEIGHTING_DIVISOR - ); + weight += uint96(sharesAmount * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR); } unchecked { diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol index 13a2a3b0f..db0d6af7c 100644 --- a/src/test/Delegation.t.sol +++ b/src/test/Delegation.t.sol @@ -133,8 +133,8 @@ contract DelegationTests is EigenLayerTestHelper { } uint256[3] memory amountsBefore; - amountsBefore[0] = voteWeigher.weightOfOperator(operator, 0); - amountsBefore[1] = voteWeigher.weightOfOperator(operator, 1); + amountsBefore[0] = voteWeigher.weightOfOperator(0, operator); + amountsBefore[1] = voteWeigher.weightOfOperator(1, operator); amountsBefore[2] = delegation.operatorShares(operator, wethStrat); //making additional deposits to the strategies @@ -151,8 +151,8 @@ contract DelegationTests is EigenLayerTestHelper { uint256 stakerEthWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[0]); uint256 stakerEigenWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[1]); - uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(operator, 0); - uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(operator, 1); + uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); + uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); assertTrue( operatorEthWeightAfter - amountsBefore[0] == stakerEthWeight, @@ -350,8 +350,8 @@ contract DelegationTests is EigenLayerTestHelper { cheats.assume(staker != operator); cheats.assume(numStratsToAdd > 0 && numStratsToAdd <= 20); - uint96 operatorEthWeightBefore = voteWeigher.weightOfOperator(operator, 0); - uint96 operatorEigenWeightBefore = voteWeigher.weightOfOperator(operator, 1); + uint96 operatorEthWeightBefore = voteWeigher.weightOfOperator(0, operator); + uint96 operatorEigenWeightBefore = voteWeigher.weightOfOperator(1, operator); _testRegisterAsOperator(operator, IDelegationTerms(operator)); _testDepositStrategies(staker, 1e18, numStratsToAdd); @@ -371,8 +371,8 @@ contract DelegationTests is EigenLayerTestHelper { _testDepositEigen(staker, 1e18); _testDelegateToOperator(staker, operator); - uint96 operatorEthWeightAfter = voteWeigher.weightOfOperator(operator, 0); - uint96 operatorEigenWeightAfter = voteWeigher.weightOfOperator(operator, 1); + uint96 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); + uint96 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); assertTrue( operatorEthWeightAfter > operatorEthWeightBefore, "testDelegation: operatorEthWeight did not increase!" ); diff --git a/src/test/EigenLayerTestHelper.t.sol b/src/test/EigenLayerTestHelper.t.sol index 632a2f437..a24ffb498 100644 --- a/src/test/EigenLayerTestHelper.t.sol +++ b/src/test/EigenLayerTestHelper.t.sol @@ -344,8 +344,8 @@ contract EigenLayerTestHelper is EigenLayerDeployer { } uint256[3] memory amountsBefore; - amountsBefore[0] = voteWeigher.weightOfOperator(operator, 0); - amountsBefore[1] = voteWeigher.weightOfOperator(operator, 1); + amountsBefore[0] = voteWeigher.weightOfOperator(0, operator); + amountsBefore[1] = voteWeigher.weightOfOperator(1, operator); amountsBefore[2] = delegation.operatorShares(operator, wethStrat); //making additional deposits to the strategies @@ -362,8 +362,8 @@ contract EigenLayerTestHelper is EigenLayerDeployer { uint256 stakerEthWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[0]); uint256 stakerEigenWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[1]); - uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(operator, 0); - uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(operator, 1); + uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); + uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); assertTrue( operatorEthWeightAfter - amountsBefore[0] == stakerEthWeight, diff --git a/src/test/harnesses/VoteWeigherBaseHarness.sol b/src/test/harnesses/VoteWeigherBaseHarness.sol index 735b53579..9a9d2a308 100644 --- a/src/test/harnesses/VoteWeigherBaseHarness.sol +++ b/src/test/harnesses/VoteWeigherBaseHarness.sol @@ -13,4 +13,8 @@ contract VoteWeigherBaseHarness is VoteWeigherBase { function getMaxWeighingFunctionLength() public pure returns (uint8) { return MAX_WEIGHING_FUNCTION_LENGTH; } + + function getWeightingDivisor() public pure returns (uint256) { + return WEIGHTING_DIVISOR; + } } \ No newline at end of file diff --git a/src/test/mocks/DelegationMock.sol b/src/test/mocks/DelegationMock.sol index 537fcbbfb..3d5ae1020 100644 --- a/src/test/mocks/DelegationMock.sol +++ b/src/test/mocks/DelegationMock.sol @@ -7,11 +7,17 @@ import "../../contracts/interfaces/IDelegationManager.sol"; contract DelegationMock is IDelegationManager, Test { mapping(address => bool) public isOperator; + mapping(address => mapping(IStrategy => uint256)) public operatorShares; function setIsOperator(address operator, bool _isOperatorReturnValue) external { isOperator[operator] = _isOperatorReturnValue; } + /// @notice returns the total number of shares in `strategy` that are delegated to `operator`. + function setOperatorShares(address operator, IStrategy strategy, uint256 shares) external { + operatorShares[operator][strategy] = shares; + } + mapping (address => address) public delegatedTo; function registerAsOperator(IDelegationTerms /*dt*/) external {} @@ -20,10 +26,8 @@ contract DelegationMock is IDelegationManager, Test { delegatedTo[msg.sender] = operator; } - function delegateToBySignature(address /*staker*/, address /*operator*/, uint256 /*expiry*/, bytes memory /*signature*/) external {} - function undelegate(address staker) external { delegatedTo[staker] = address(0); } @@ -31,10 +35,6 @@ contract DelegationMock is IDelegationManager, Test { /// @notice returns the DelegationTerms of the `operator`, which may mediate their interactions with stakers who delegate to them. function delegationTerms(address /*operator*/) external view returns (IDelegationTerms) {} - /// @notice returns the total number of shares in `strategy` that are delegated to `operator`. - function operatorShares(address /*operator*/, IStrategy /*strategy*/) external view returns (uint256) {} - - function increaseDelegatedShares(address /*staker*/, IStrategy /*strategy*/, uint256 /*shares*/) external {} function decreaseDelegatedShares( diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index 3e8c2d07a..d0ff4c976 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -15,6 +15,7 @@ import "../harnesses/VoteWeigherBaseHarness.sol"; import "../mocks/StrategyManagerMock.sol"; import "../mocks/OwnableMock.sol"; +import "../mocks/DelegationMock.sol"; import "forge-std/Test.sol"; @@ -29,6 +30,8 @@ contract VoteWeigherBaseUnitTests is Test { address serviceManagerOwner; IServiceManager public serviceManager; + DelegationMock delegationMock; + VoteWeigherBaseHarness public voteWeigher; VoteWeigherBaseHarness public voteWeigherImplementation; @@ -53,7 +56,15 @@ contract VoteWeigherBaseUnitTests is Test { pauserRegistry = new PauserRegistry(pauser, unpauser); - strategyManager = new StrategyManagerMock(); + StrategyManagerMock strategyManagerMock = new StrategyManagerMock(); + delegationMock = new DelegationMock(); + strategyManagerMock.setAddresses( + delegationMock, + IEigenPodManager(address(uint160(uint256(keccak256(abi.encodePacked("eigenPodManager")))))), + ISlasher(address(uint160(uint256(keccak256(abi.encodePacked("slasher")))))) + ); + + strategyManager = IStrategyManager(address(strategyManagerMock)); // make the serviceManagerOwner the owner of the serviceManager contract cheats.prank(serviceManagerOwner); @@ -521,6 +532,37 @@ contract VoteWeigherBaseUnitTests is Test { } } + function testWeightOfOperator( + address operator, + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndMultipliers, + uint96[] memory shares + ) public { + strategiesAndMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndMultipliers); + cheats.assume(shares.length >= strategiesAndMultipliers.length); + for (uint i = 0; i < strategiesAndMultipliers.length; i++) { + cheats.assume(uint256(shares[i]) * uint256(strategiesAndMultipliers[i].multiplier) <= type(uint96).max); + } + + // set the operator shares + for (uint i = 0; i < strategiesAndMultipliers.length; i++) { + delegationMock.setOperatorShares(operator, strategiesAndMultipliers[i].strategy, shares[i]); + } + + // create a valid quorum + uint8 quorumNumber = voteWeigher.quorumCount(); + cheats.startPrank(serviceManagerOwner); + voteWeigher.createQuorum(strategiesAndMultipliers); + + // make sure the weight of the operator is correct + uint256 expectedWeight = 0; + for (uint i = 0; i < strategiesAndMultipliers.length; i++) { + + expectedWeight += shares[i] * strategiesAndMultipliers[i].multiplier / voteWeigher.getWeightingDivisor(); + } + + assertEq(voteWeigher.weightOfOperator(quorumNumber, operator), expectedWeight); + } + function _removeDuplicates(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal pure returns(IVoteWeigher.StrategyAndWeightingMultiplier[] memory) From d4be2b7f8585ad8ef9171fba9171db253b572e0b Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 10:06:49 -0700 Subject: [PATCH 034/110] reformats, remove some unnecessary fuzzing --- src/contracts/interfaces/IVoteWeigher.sol | 2 +- src/contracts/middleware/VoteWeigherBase.sol | 28 ++- .../middleware/VoteWeigherBaseStorage.sol | 6 +- src/test/harnesses/VoteWeigherBaseHarness.sol | 20 -- src/test/unit/VoteWeigherBaseUnit.t.sol | 182 +++++++++--------- 5 files changed, 104 insertions(+), 134 deletions(-) delete mode 100644 src/test/harnesses/VoteWeigherBaseHarness.sol diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index 025dc156d..4a37583e6 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -37,7 +37,7 @@ interface IVoteWeigher { function weightOfOperator(uint8 quorumNumber, address operator) external returns (uint96); /// @notice Number of quorums that are being used by the middleware. - function quorumCount() external view returns (uint8); + function quorumCount() external view returns (uint16); /** * @notice This defines the earnings split between different quorums. Mapping is quorumNumber => BIPS which the quorum earns, out of the total earnings. diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index 7dd17c0c7..15582e9cd 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -31,6 +31,12 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { _; } + /// @notice when applied to a function, ensures that the `quorumNumber` corresponds to a valid quorum added to the VoteWeigher + modifier validQuorumNumber(uint8 quorumNumber) { + require(quorumNumber < quorumCount, "VoteWeigherBase.validQuorumNumber: quorumNumber is not valid"); + _; + } + /// @notice Sets the (immutable) `strategyManager` and `serviceManager` addresses constructor( IStrategyManager _strategyManager, @@ -92,7 +98,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { function addStrategiesConsideredAndMultipliers( uint8 quorumNumber, StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers - ) external virtual onlyServiceManagerOwner { + ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { _addStrategiesConsideredAndMultipliers(quorumNumber, _newStrategiesConsideredAndMultipliers); } @@ -105,8 +111,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { function removeStrategiesConsideredAndMultipliers( uint8 quorumNumber, uint256[] calldata indicesToRemove - ) external virtual onlyServiceManagerOwner { - require(quorumNumber < quorumCount, "VoteWeigherBase.removeStrategiesConsideredAndMultipliers: quorumNumber is greater than or equal to quorumCount"); + ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { for (uint256 i = 0; i < indicesToRemove.length;) { emit StrategyRemovedFromQuorum(quorumNumber, strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]].strategy); // remove strategy and its associated multiplier @@ -130,8 +135,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { uint8 quorumNumber, uint256[] calldata strategyIndices, uint96[] calldata newMultipliers - ) external virtual onlyServiceManagerOwner { - require(quorumNumber < quorumCount, "VoteWeigherBase.modifyStrategyWeights: quorumNumber is greater than or equal to quorumCount"); + ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { uint256 numStrats = strategyIndices.length; // sanity check on input lengths require(newMultipliers.length == numStrats, @@ -151,11 +155,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { * @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. * @dev Reverts if `quorumNumber` < `NUMBER_OF_QUORUMS`, i.e. the input is out of bounds. */ - function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) public view returns (uint256) { - require( - quorumNumber < quorumCount, - "VoteWeigherBase.strategiesConsideredAndMultipliersLength: quorumNumber input exceeds NUMBER_OF_QUORUMS" - ); + function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) public view validQuorumNumber(quorumNumber) returns (uint256) { return strategiesConsideredAndMultipliers[quorumNumber].length; } @@ -165,14 +165,13 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { function _createQuorum( StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers ) internal { - uint8 quorumNumber = quorumCount; - require(quorumNumber < 255, "VoteWeigherBase._createQuorum: number of quorums cannot 256"); + uint16 quorumCountMem = quorumCount; + require(quorumCountMem < 256, "VoteWeigherBase._createQuorum: number of quorums cannot 256"); + uint8 quorumNumber = uint8(quorumCountMem); // increment quorumCount quorumCount = quorumNumber + 1; - // add the strategies and their associated weights to the quorum _addStrategiesConsideredAndMultipliers(quorumNumber, _strategiesConsideredAndMultipliers); - // emit event emit QuorumCreated(quorumNumber); } @@ -187,7 +186,6 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { uint8 quorumNumber, StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers ) internal { - require(quorumNumber < quorumCount, "VoteWeigherBase._addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); require(_newStrategiesConsideredAndMultipliers.length > 0, "VoteWeigherBase._addStrategiesConsideredAndMultipliers: no strategies provided"); uint256 numStratsToAdd = _newStrategiesConsideredAndMultipliers.length; uint256 numStratsExisting = strategiesConsideredAndMultipliers[quorumNumber].length; diff --git a/src/contracts/middleware/VoteWeigherBaseStorage.sol b/src/contracts/middleware/VoteWeigherBaseStorage.sol index f470e350c..fa18dd1d3 100644 --- a/src/contracts/middleware/VoteWeigherBaseStorage.sol +++ b/src/contracts/middleware/VoteWeigherBaseStorage.sol @@ -16,9 +16,9 @@ import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; */ abstract contract VoteWeigherBaseStorage is Initializable, IVoteWeigher { /// @notice Constant used as a divisor in calculating weights. - uint256 internal constant WEIGHTING_DIVISOR = 1e18; + uint256 public constant WEIGHTING_DIVISOR = 1e18; /// @notice Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping. - uint8 internal constant MAX_WEIGHING_FUNCTION_LENGTH = 32; + uint8 public constant MAX_WEIGHING_FUNCTION_LENGTH = 32; /// @notice Constant used as a divisor in dealing with BIPS amounts. uint256 internal constant MAX_BIPS = 10000; @@ -35,7 +35,7 @@ abstract contract VoteWeigherBaseStorage is Initializable, IVoteWeigher { IServiceManager public immutable serviceManager; /// @notice The number of quorums that the VoteWeigher is considering. - uint8 public quorumCount; + uint16 public quorumCount; /** * @notice mapping from quorum number to the list of strategies considered and their diff --git a/src/test/harnesses/VoteWeigherBaseHarness.sol b/src/test/harnesses/VoteWeigherBaseHarness.sol deleted file mode 100644 index 9a9d2a308..000000000 --- a/src/test/harnesses/VoteWeigherBaseHarness.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "../../contracts/middleware/VoteWeigherBase.sol"; - -// wrapper around the StakeRegistry contract that exposes the internal functions for unit testing. -contract VoteWeigherBaseHarness is VoteWeigherBase { - constructor( - IStrategyManager _strategyManager, - IServiceManager _serviceManager - ) VoteWeigherBase(_strategyManager, _serviceManager) {} - - function getMaxWeighingFunctionLength() public pure returns (uint8) { - return MAX_WEIGHING_FUNCTION_LENGTH; - } - - function getWeightingDivisor() public pure returns (uint256) { - return WEIGHTING_DIVISOR; - } -} \ No newline at end of file diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index d0ff4c976..16e1e541e 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -11,8 +11,6 @@ import "../../contracts/interfaces/IServiceManager.sol"; import "../../contracts/interfaces/IVoteWeigher.sol"; import "../../contracts/middleware/VoteWeigherBase.sol"; -import "../harnesses/VoteWeigherBaseHarness.sol"; - import "../mocks/StrategyManagerMock.sol"; import "../mocks/OwnableMock.sol"; import "../mocks/DelegationMock.sol"; @@ -32,9 +30,9 @@ contract VoteWeigherBaseUnitTests is Test { DelegationMock delegationMock; - VoteWeigherBaseHarness public voteWeigher; + VoteWeigherBase public voteWeigher; - VoteWeigherBaseHarness public voteWeigherImplementation; + VoteWeigherBase public voteWeigherImplementation; address public pauser = address(555); address public unpauser = address(999); @@ -51,6 +49,15 @@ contract VoteWeigherBaseUnitTests is Test { /// @notice emitted when `strategy` has its `multiplier` updated in the array at `strategiesConsideredAndMultipliers[quorumNumber]` event StrategyMultiplierUpdated(uint8 indexed quorumNumber, IStrategy strategy, uint256 multiplier); + // addresses excluded from fuzzing due to abnormal behavior. TODO: @Sidu28 define this better and give it a clearer name + mapping (address => bool) fuzzedAddressMapping; + + //ensures that a passed in address is not set to true in the fuzzedAddressMapping + modifier fuzzedAddress(address addr) virtual { + cheats.assume(fuzzedAddressMapping[addr] == false); + _; + } + function setUp() virtual public { proxyAdmin = new ProxyAdmin(); @@ -70,9 +77,11 @@ contract VoteWeigherBaseUnitTests is Test { cheats.prank(serviceManagerOwner); serviceManager = IServiceManager(address(new OwnableMock())); - voteWeigherImplementation = new VoteWeigherBaseHarness(strategyManager, serviceManager); + voteWeigherImplementation = new VoteWeigherBase(strategyManager, serviceManager); - voteWeigher = VoteWeigherBaseHarness(address(new TransparentUpgradeableProxy(address(voteWeigherImplementation), address(proxyAdmin), ""))); + voteWeigher = VoteWeigherBase(address(new TransparentUpgradeableProxy(address(voteWeigherImplementation), address(proxyAdmin), ""))); + + fuzzedAddressMapping[address(proxyAdmin)] = true; } function testCorrectConstructionParameters() public { @@ -86,7 +95,7 @@ contract VoteWeigherBaseUnitTests is Test { strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); // create a quorum from the serviceManagerOwner // get the quorum count before the quorum is created - uint8 quorumCountBefore = voteWeigher.quorumCount(); + uint8 quorumCountBefore = uint8(voteWeigher.quorumCount()); cheats.prank(serviceManagerOwner); // expect each strategy to be added to the quorum for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { @@ -110,7 +119,7 @@ contract VoteWeigherBaseUnitTests is Test { function testCreateQuorum_FromNotServiceManagerOwner_Reverts( address notServiceManagerOwner, IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers - ) public { + ) public fuzzedAddress(notServiceManagerOwner) { cheats.assume(notServiceManagerOwner != serviceManagerOwner); cheats.prank(notServiceManagerOwner); cheats.expectRevert("VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); @@ -121,9 +130,9 @@ contract VoteWeigherBaseUnitTests is Test { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers ) public { strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); - _assumeNoZeroWeights(strategiesAndWeightingMultipliers); + strategiesAndWeightingMultipliers = _replaceZeroWeights(strategiesAndWeightingMultipliers); - cheats.assume(strategiesAndWeightingMultipliers.length > voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length > voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.prank(serviceManagerOwner); cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: exceed MAX_WEIGHING_FUNCTION_LENGTH"); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -132,9 +141,9 @@ contract VoteWeigherBaseUnitTests is Test { function testCreateQuorum_StrategiesAndWeightingMultipliers_WithDuplicateStrategies_Reverts( IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers ) public { - cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.assume(strategiesAndWeightingMultipliers.length > 1); - _assumeNoZeroWeights(strategiesAndWeightingMultipliers); + strategiesAndWeightingMultipliers = _replaceZeroWeights(strategiesAndWeightingMultipliers); // make sure a duplicate strategy exists bool duplicateExists; @@ -164,7 +173,7 @@ contract VoteWeigherBaseUnitTests is Test { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers ) public { strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); - cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.assume(strategiesAndWeightingMultipliers.length > 0); // make sure a zero weight exists bool zeroWeightExists; @@ -181,15 +190,15 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); } - function testCreateQuorum_MoreThan256Quorums_Reverts( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers - ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + function testCreateQuorum_MoreThan256Quorums_Reverts() public { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); cheats.startPrank(serviceManagerOwner); - for (uint i = 0; i < 255; i++) { + for (uint i = 0; i < 256; i++) { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); } + assertEq(voteWeigher.quorumCount(), 256); + cheats.expectRevert("VoteWeigherBase._createQuorum: number of quorums cannot 256"); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); } @@ -213,7 +222,7 @@ contract VoteWeigherBaseUnitTests is Test { strategiesAndWeightingMultipliers2[i - randomSplit] = strategiesAndWeightingMultipliers[i]; } } - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); // create quorum with the first randomSplit elements cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers1); @@ -234,56 +243,33 @@ contract VoteWeigherBaseUnitTests is Test { } function testAddStrategiesConsideredAndMultipliers_NotFromServiceManagerOwner_Reverts( - address notServiceManagerOwner, - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers - ) public { + address notServiceManagerOwner + ) public fuzzedAddress(notServiceManagerOwner) { cheats.assume(notServiceManagerOwner != serviceManagerOwner); - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); - // we need 2 or more strategies to create the quorums and then add one - cheats.assume(strategiesAndWeightingMultipliers.length > 1); - - // separate strategiesAndWeightingMultipliers into 2 arrays, 1 with the last element and 1 with the rest - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers1 = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - 1); - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers2 = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length - 1; i++) { - strategiesAndWeightingMultipliers1[i] = strategiesAndWeightingMultipliers[i]; - } - strategiesAndWeightingMultipliers2[0] = strategiesAndWeightingMultipliers[strategiesAndWeightingMultipliers.length - 1]; + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create quorum with all but the last element - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.prank(serviceManagerOwner); - voteWeigher.createQuorum(strategiesAndWeightingMultipliers1); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); // add the last element cheats.prank(notServiceManagerOwner); cheats.expectRevert("VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); - voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber, strategiesAndWeightingMultipliers2); + voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber, strategiesAndWeightingMultipliers); } - function testAddStrategiesConsideredAndMultipliers_ForNonExistantQuorum_Reverts( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers - ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); - // we need 2 or more strategies to create the quorums and then add one - cheats.assume(strategiesAndWeightingMultipliers.length > 1); - - // separate strategiesAndWeightingMultipliers into 2 arrays, 1 with the last element and 1 with the rest - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers1 = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - 1); - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers2 = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length - 1; i++) { - strategiesAndWeightingMultipliers1[i] = strategiesAndWeightingMultipliers[i]; - } - strategiesAndWeightingMultipliers2[0] = strategiesAndWeightingMultipliers[strategiesAndWeightingMultipliers.length - 1]; + function testAddStrategiesConsideredAndMultipliers_ForNonexistentQuorum_Reverts() public { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create quorum with all but the last element - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); - voteWeigher.createQuorum(strategiesAndWeightingMultipliers1); + voteWeigher.createQuorum(strategiesAndWeightingMultipliers); // add the last element - cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); - voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber+1, strategiesAndWeightingMultipliers2); + cheats.expectRevert("VoteWeigherBase.addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); + voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber+1, strategiesAndWeightingMultipliers); } function testRemoveStrategiesConsideredAndMultipliers_Valid( @@ -301,7 +287,7 @@ contract VoteWeigherBaseUnitTests is Test { indicesToRemove = _sortInDescendingOrder(indicesToRemove); // create the quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -352,14 +338,13 @@ contract VoteWeigherBaseUnitTests is Test { function testRemoveStrategiesConsideredAndMultipliers_NotFromServiceManagerOwner_Reverts( address notServiceManagerOwner, - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, uint256[] memory indicesToRemove - ) public { + ) public fuzzedAddress(notServiceManagerOwner) { cheats.assume(notServiceManagerOwner != serviceManagerOwner); - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.prank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -369,29 +354,26 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, indicesToRemove); } - function testRemoveStrategiesConsideredAndMultipliers_ForNonExistantQuorum_Reverts( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + function testRemoveStrategiesConsideredAndMultipliers_ForNonexistentQuorum_Reverts( uint256[] memory indicesToRemove ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); - // remove strategies from a non-existant quorum - cheats.expectRevert("VoteWeigherBase.removeStrategiesConsideredAndMultipliers: quorumNumber is greater than or equal to quorumCount"); + // remove strategies from a non-existent quorum + cheats.expectRevert("VoteWeigherBase.validQuorumNumber: quorumNumber is not valid"); voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber + 1, indicesToRemove); } - function testRemoveStrategiesConsideredAndMultipliers_EmptyIndicesToRemove_HasNoEffect( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers - ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + function testRemoveStrategiesConsideredAndMultipliers_EmptyIndicesToRemove_HasNoEffect() public { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -407,15 +389,14 @@ contract VoteWeigherBaseUnitTests is Test { function testModifyStrategyWeights_NotFromServiceManagerOwner_Reverts( address notServiceManagerOwner, - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, uint256[] memory strategyIndices, uint96[] memory newWeights - ) public { + ) public fuzzedAddress(notServiceManagerOwner) { cheats.assume(notServiceManagerOwner != serviceManagerOwner); - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.prank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -452,7 +433,7 @@ contract VoteWeigherBaseUnitTests is Test { newWeights = newWeightsTrim; // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -475,35 +456,33 @@ contract VoteWeigherBaseUnitTests is Test { } } - function testModifyStrategyWeights_ForNonExistantQuorum_Reverts( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + function testModifyStrategyWeights_ForNonexistentQuorum_Reverts( uint256[] memory strategyIndices, uint96[] memory newWeights ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); - // modify certain strategies of a non-existant quorum - cheats.expectRevert("VoteWeigherBase.modifyStrategyWeights: quorumNumber is greater than or equal to quorumCount"); + // modify certain strategies of a non-existent quorum + cheats.expectRevert("VoteWeigherBase.validQuorumNumber: quorumNumber is not valid"); voteWeigher.modifyStrategyWeights(quorumNumber + 1, strategyIndices, newWeights); } function testModifyStrategyWeights_InconsistentStrategyAndWeightArrayLengths_Reverts( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, uint256[] memory strategyIndices, uint96[] memory newWeights ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // make sure the arrays are of different lengths cheats.assume(strategyIndices.length != newWeights.length); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -512,13 +491,11 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.modifyStrategyWeights(quorumNumber, strategyIndices, newWeights); } - function testModifyStrategyWeights_EmptyStrategyIndicesAndWeights_HasNoEffect( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers - ) public { - strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); + function testModifyStrategyWeights_EmptyStrategyIndicesAndWeights_HasNoEffect() public { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); @@ -549,7 +526,7 @@ contract VoteWeigherBaseUnitTests is Test { } // create a valid quorum - uint8 quorumNumber = voteWeigher.quorumCount(); + uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); voteWeigher.createQuorum(strategiesAndMultipliers); @@ -557,7 +534,7 @@ contract VoteWeigherBaseUnitTests is Test { uint256 expectedWeight = 0; for (uint i = 0; i < strategiesAndMultipliers.length; i++) { - expectedWeight += shares[i] * strategiesAndMultipliers[i].multiplier / voteWeigher.getWeightingDivisor(); + expectedWeight += shares[i] * strategiesAndMultipliers[i].multiplier / voteWeigher.WEIGHTING_DIVISOR(); } assertEq(voteWeigher.weightOfOperator(quorumNumber, operator), expectedWeight); @@ -590,10 +567,13 @@ contract VoteWeigherBaseUnitTests is Test { return trimmedStrategiesAndWeightingMultipliers; } - function _assumeNoZeroWeights(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal view { + function _replaceZeroWeights(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal pure returns(IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - cheats.assume(strategiesAndWeightingMultipliers[i].multiplier != 0); + if (strategiesAndWeightingMultipliers[i].multiplier == 0) { + strategiesAndWeightingMultipliers[i].multiplier = 1; + } } + return strategiesAndWeightingMultipliers; } function _sortStrategiesAndWeightingMultipliersByStrategy(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) @@ -656,9 +636,21 @@ contract VoteWeigherBaseUnitTests is Test { function _convertToValidStrategiesAndWeightingMultipliers(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal view returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); - cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.getMaxWeighingFunctionLength()); + cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.assume(strategiesAndWeightingMultipliers.length > 0); - _assumeNoZeroWeights(strategiesAndWeightingMultipliers); + return _replaceZeroWeights(strategiesAndWeightingMultipliers); + } + + function _defaultStrategiesAndWeightingMultipliers() internal view returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[](2); + strategiesAndWeightingMultipliers[0] = IVoteWeigher.StrategyAndWeightingMultiplier({ + strategy: IStrategy(address(uint160(uint256(keccak256("strategy1"))))), + multiplier: 1.04 ether + }); + strategiesAndWeightingMultipliers[1] = IVoteWeigher.StrategyAndWeightingMultiplier({ + strategy: IStrategy(address(uint160(uint256(keccak256("strategy2"))))), + multiplier: 1.69 ether + }); return strategiesAndWeightingMultipliers; } } \ No newline at end of file From 7b14be2012f508f84ff205a7f82cfab9a12d9eb9 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 10:18:27 -0700 Subject: [PATCH 035/110] revert instead on empty arrays for removal/modification --- src/contracts/middleware/VoteWeigherBase.sol | 5 ++++- src/test/unit/VoteWeigherBaseUnit.t.sol | 19 +++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index 15582e9cd..d852b89ba 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -112,7 +112,9 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { uint8 quorumNumber, uint256[] calldata indicesToRemove ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { - for (uint256 i = 0; i < indicesToRemove.length;) { + uint256 indicesToRemoveLength = indicesToRemove.length; + require(indicesToRemoveLength > 0, "VoteWeigherBase.removeStrategiesConsideredAndMultipliers: no indices to remove provided"); + for (uint256 i = 0; i < indicesToRemoveLength;) { emit StrategyRemovedFromQuorum(quorumNumber, strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]].strategy); // remove strategy and its associated multiplier strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]] = strategiesConsideredAndMultipliers[quorumNumber][strategiesConsideredAndMultipliers[quorumNumber] @@ -137,6 +139,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { uint96[] calldata newMultipliers ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { uint256 numStrats = strategyIndices.length; + require(numStrats > 0, "VoteWeigherBase.modifyStrategyWeights: no strategy indices provided"); // sanity check on input lengths require(newMultipliers.length == numStrats, "VoteWeigherBase.modifyStrategyWeights: input length mismatch"); diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index 16e1e541e..aaa64ac00 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -369,7 +369,7 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber + 1, indicesToRemove); } - function testRemoveStrategiesConsideredAndMultipliers_EmptyIndicesToRemove_HasNoEffect() public { + function testRemoveStrategiesConsideredAndMultipliers_EmptyIndicesToRemove_Reverts() public { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum @@ -378,13 +378,8 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); // remove no strategies + cheats.expectRevert("VoteWeigherBase.removeStrategiesConsideredAndMultipliers: no indices to remove provided"); voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, new uint256[](0)); - // make sure the quorum strategies and weights haven't changed - for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); - assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); - assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); - } } function testModifyStrategyWeights_NotFromServiceManagerOwner_Reverts( @@ -421,7 +416,7 @@ contract VoteWeigherBaseUnitTests is Test { cheats.assume(strategyIndices.length > 0); cheats.assume(strategyIndices.length < strategiesAndWeightingMultipliers.length); - // trim the provided weights to the length of the strategyIndices + // trim the provided weights to the length of the strategyIndices and extend if it is shorter uint96[] memory newWeightsTrim = new uint96[](strategyIndices.length); for (uint256 i = 0; i < strategyIndices.length; i++) { if(i < newWeights.length) { @@ -491,7 +486,7 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.modifyStrategyWeights(quorumNumber, strategyIndices, newWeights); } - function testModifyStrategyWeights_EmptyStrategyIndicesAndWeights_HasNoEffect() public { + function testModifyStrategyWeights_EmptyStrategyIndicesAndWeights_Reverts() public { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); // create a valid quorum @@ -500,13 +495,9 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); // modify no strategies + cheats.expectRevert("VoteWeigherBase.modifyStrategyWeights: no strategy indices provided"); voteWeigher.modifyStrategyWeights(quorumNumber, new uint256[](0), new uint96[](0)); // make sure the quorum strategies and weights haven't changed - for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - IVoteWeigher.StrategyAndWeightingMultiplier memory strategyAndWeightingMultiplier = voteWeigher.strategyAndWeightingMultiplierForQuorumByIndex(quorumNumber, i); - assertEq(address(strategyAndWeightingMultiplier.strategy), address(strategiesAndWeightingMultipliers[i].strategy)); - assertEq(strategyAndWeightingMultiplier.multiplier, strategiesAndWeightingMultipliers[i].multiplier); - } } function testWeightOfOperator( From 2c3df9ab884f24281df9c3ef514b72b7edb086b3 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 12:29:21 -0700 Subject: [PATCH 036/110] heavy speedup --- src/contracts/middleware/VoteWeigherBase.sol | 2 +- src/test/unit/VoteWeigherBaseUnit.t.sol | 216 +++++++------------ 2 files changed, 82 insertions(+), 136 deletions(-) diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index d852b89ba..925adf978 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -172,7 +172,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { require(quorumCountMem < 256, "VoteWeigherBase._createQuorum: number of quorums cannot 256"); uint8 quorumNumber = uint8(quorumCountMem); // increment quorumCount - quorumCount = quorumNumber + 1; + quorumCount = quorumCountMem + 1; // add the strategies and their associated weights to the quorum _addStrategiesConsideredAndMultipliers(quorumNumber, _strategiesConsideredAndMultipliers); // emit event diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index aaa64ac00..3cea6e9a2 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -51,6 +51,10 @@ contract VoteWeigherBaseUnitTests is Test { // addresses excluded from fuzzing due to abnormal behavior. TODO: @Sidu28 define this better and give it a clearer name mapping (address => bool) fuzzedAddressMapping; + // strategy => is in current array. used for detecting duplicates + mapping (IStrategy => bool) strategyInCurrentArray; + // uint256 => is in current array + mapping (uint256 => bool) uint256InCurrentArray; //ensures that a passed in address is not set to true in the fuzzedAddressMapping modifier fuzzedAddress(address addr) virtual { @@ -139,23 +143,19 @@ contract VoteWeigherBaseUnitTests is Test { } function testCreateQuorum_StrategiesAndWeightingMultipliers_WithDuplicateStrategies_Reverts( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256 indexFromDuplicate, + uint256 indexToDuplicate ) public { cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.assume(strategiesAndWeightingMultipliers.length > 1); strategiesAndWeightingMultipliers = _replaceZeroWeights(strategiesAndWeightingMultipliers); - // make sure a duplicate strategy exists - bool duplicateExists; - for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - for (uint256 j = i + 1; j < strategiesAndWeightingMultipliers.length; j++) { - if (strategiesAndWeightingMultipliers[i].strategy == strategiesAndWeightingMultipliers[j].strategy) { - duplicateExists = true; - break; - } - } - } - cheats.assume(duplicateExists); + // plant a duplicate strategy + indexToDuplicate = indexToDuplicate % strategiesAndWeightingMultipliers.length; + indexFromDuplicate = indexFromDuplicate % strategiesAndWeightingMultipliers.length; + cheats.assume(indexToDuplicate != indexFromDuplicate); + strategiesAndWeightingMultipliers[indexToDuplicate].strategy = strategiesAndWeightingMultipliers[indexFromDuplicate].strategy; cheats.prank(serviceManagerOwner); cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add same strategy 2x"); @@ -170,20 +170,14 @@ contract VoteWeigherBaseUnitTests is Test { } function testCreateQuorum_StrategiesAndWeightingMultipliers_WithZeroWeight( - IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers + IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, + uint256 indexForZeroMultiplier ) public { strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.assume(strategiesAndWeightingMultipliers.length > 0); - // make sure a zero weight exists - bool zeroWeightExists; - for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - if (strategiesAndWeightingMultipliers[i].multiplier == 0) { - zeroWeightExists = true; - break; - } - } - cheats.assume(zeroWeightExists); + //plant a zero weight + strategiesAndWeightingMultipliers[indexForZeroMultiplier % strategiesAndWeightingMultipliers.length].multiplier = 0; cheats.prank(serviceManagerOwner); cheats.expectRevert("VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add strategy with zero weight"); @@ -268,23 +262,17 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); // add the last element - cheats.expectRevert("VoteWeigherBase.addStrategiesConsideredAndMultipliers: quorumNumber exceeds quorumCount"); + cheats.expectRevert("VoteWeigherBase.validQuorumNumber: quorumNumber is not valid"); voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber+1, strategiesAndWeightingMultipliers); } function testRemoveStrategiesConsideredAndMultipliers_Valid( IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, - uint256[] memory indicesToRemove + uint256 randomness ) public { strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); - // take indices modulo length - for (uint256 i = 0; i < indicesToRemove.length; i++) { - indicesToRemove[i] = indicesToRemove[i] % strategiesAndWeightingMultipliers.length; - } - indicesToRemove = _removeDuplicatesUint256(indicesToRemove); - cheats.assume(indicesToRemove.length > 0); - cheats.assume(indicesToRemove.length < strategiesAndWeightingMultipliers.length); - indicesToRemove = _sortInDescendingOrder(indicesToRemove); + // generate a bunch of random array of valid descending order indices + uint256[] memory indicesToRemove = _generateRandomUniqueIndices(randomness, strategiesAndWeightingMultipliers.length); // create the quorum uint8 quorumNumber = uint8(voteWeigher.quorumCount()); @@ -298,8 +286,6 @@ contract VoteWeigherBaseUnitTests is Test { emit StrategyRemovedFromQuorum(quorumNumber, strategiesAndWeightingMultipliers[indicesToRemove[i]].strategy); } voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, indicesToRemove); - - // check that the strategies were removed // check that the strategies that were not removed are still there // get all strategies and multipliers form the contracts @@ -310,22 +296,18 @@ contract VoteWeigherBaseUnitTests is Test { // remove indicesToRemove from local strategiesAndWeightingMultipliers IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliersLocal = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - indicesToRemove.length); - uint256 j = 0; - for (uint256 i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - for (uint256 k = 0; k < indicesToRemove.length; k++) { - if (i == indicesToRemove[k]) { - break; - } - if (k == indicesToRemove.length - 1) { - strategiesAndWeightingMultipliersLocal[j] = strategiesAndWeightingMultipliers[i]; - j++; - } + + uint256 endIndex = strategiesAndWeightingMultipliers.length - 1; + for (uint256 i = 0; i < indicesToRemove.length; i++) { + strategiesAndWeightingMultipliers[indicesToRemove[i]] = strategiesAndWeightingMultipliers[endIndex]; + if (endIndex > 0) { + endIndex--; } } - // sort both arrays by strategy so that they are easily comaparable - strategiesAndWeightingMultipliersFromContract = _sortStrategiesAndWeightingMultipliersByStrategy(strategiesAndWeightingMultipliersFromContract); - strategiesAndWeightingMultipliersLocal = _sortStrategiesAndWeightingMultipliersByStrategy(strategiesAndWeightingMultipliersLocal); + for (uint256 i = 0; i < strategiesAndWeightingMultipliersLocal.length; i++) { + strategiesAndWeightingMultipliersLocal[i] = strategiesAndWeightingMultipliers[i]; + } // check that the arrays are the same assertEq(strategiesAndWeightingMultipliersFromContract.length, strategiesAndWeightingMultipliersLocal.length); @@ -337,12 +319,13 @@ contract VoteWeigherBaseUnitTests is Test { } function testRemoveStrategiesConsideredAndMultipliers_NotFromServiceManagerOwner_Reverts( - address notServiceManagerOwner, - uint256[] memory indicesToRemove + address notServiceManagerOwner ) public fuzzedAddress(notServiceManagerOwner) { cheats.assume(notServiceManagerOwner != serviceManagerOwner); IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); + uint256[] memory indicesToRemove = new uint256[](1); + // create a valid quorum uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.prank(serviceManagerOwner); @@ -354,11 +337,11 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.removeStrategiesConsideredAndMultipliers(quorumNumber, indicesToRemove); } - function testRemoveStrategiesConsideredAndMultipliers_ForNonexistentQuorum_Reverts( - uint256[] memory indicesToRemove - ) public { + function testRemoveStrategiesConsideredAndMultipliers_ForNonexistentQuorum_Reverts() public { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); + uint256[] memory indicesToRemove = new uint256[](1); + // create a valid quorum uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); @@ -383,13 +366,14 @@ contract VoteWeigherBaseUnitTests is Test { } function testModifyStrategyWeights_NotFromServiceManagerOwner_Reverts( - address notServiceManagerOwner, - uint256[] memory strategyIndices, - uint96[] memory newWeights + address notServiceManagerOwner ) public fuzzedAddress(notServiceManagerOwner) { cheats.assume(notServiceManagerOwner != serviceManagerOwner); IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); + uint256[] memory strategyIndices = new uint256[](1); + uint96[] memory newWeights = new uint96[](1); + // create a valid quorum uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.prank(serviceManagerOwner); @@ -403,18 +387,11 @@ contract VoteWeigherBaseUnitTests is Test { function testModifyStrategyWeights_Valid( IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, - uint256[] memory strategyIndices, - uint96[] memory newWeights + uint96[] memory newWeights, + uint256 randomness ) public { strategiesAndWeightingMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndWeightingMultipliers); - - // take indices modulo length - for (uint256 i = 0; i < strategyIndices.length; i++) { - strategyIndices[i] = strategyIndices[i] % strategiesAndWeightingMultipliers.length; - } - strategyIndices = _removeDuplicatesUint256(strategyIndices); - cheats.assume(strategyIndices.length > 0); - cheats.assume(strategyIndices.length < strategiesAndWeightingMultipliers.length); + uint256[] memory strategyIndices = _generateRandomUniqueIndices(randomness, strategiesAndWeightingMultipliers.length); // trim the provided weights to the length of the strategyIndices and extend if it is shorter uint96[] memory newWeightsTrim = new uint96[](strategyIndices.length); @@ -451,12 +428,12 @@ contract VoteWeigherBaseUnitTests is Test { } } - function testModifyStrategyWeights_ForNonexistentQuorum_Reverts( - uint256[] memory strategyIndices, - uint96[] memory newWeights - ) public { + function testModifyStrategyWeights_ForNonexistentQuorum_Reverts() public { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); + uint256[] memory strategyIndices = new uint256[](1); + uint96[] memory newWeights = new uint96[](1); + // create a valid quorum uint8 quorumNumber = uint8(voteWeigher.quorumCount()); cheats.startPrank(serviceManagerOwner); @@ -475,6 +452,7 @@ contract VoteWeigherBaseUnitTests is Test { // make sure the arrays are of different lengths cheats.assume(strategyIndices.length != newWeights.length); + cheats.assume(strategyIndices.length > 0); // create a valid quorum uint8 quorumNumber = uint8(voteWeigher.quorumCount()); @@ -497,7 +475,6 @@ contract VoteWeigherBaseUnitTests is Test { // modify no strategies cheats.expectRevert("VoteWeigherBase.modifyStrategyWeights: no strategy indices provided"); voteWeigher.modifyStrategyWeights(quorumNumber, new uint256[](0), new uint96[](0)); - // make sure the quorum strategies and weights haven't changed } function testWeightOfOperator( @@ -508,7 +485,9 @@ contract VoteWeigherBaseUnitTests is Test { strategiesAndMultipliers = _convertToValidStrategiesAndWeightingMultipliers(strategiesAndMultipliers); cheats.assume(shares.length >= strategiesAndMultipliers.length); for (uint i = 0; i < strategiesAndMultipliers.length; i++) { - cheats.assume(uint256(shares[i]) * uint256(strategiesAndMultipliers[i].multiplier) <= type(uint96).max); + if(uint256(shares[i]) * uint256(strategiesAndMultipliers[i].multiplier) > type(uint96).max) { + strategiesAndMultipliers[i].multiplier = 1; + } } // set the operator shares @@ -524,7 +503,6 @@ contract VoteWeigherBaseUnitTests is Test { // make sure the weight of the operator is correct uint256 expectedWeight = 0; for (uint i = 0; i < strategiesAndMultipliers.length; i++) { - expectedWeight += shares[i] * strategiesAndMultipliers[i].multiplier / voteWeigher.WEIGHTING_DIVISOR(); } @@ -532,23 +510,24 @@ contract VoteWeigherBaseUnitTests is Test { } function _removeDuplicates(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) - internal pure + internal returns(IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { IVoteWeigher.StrategyAndWeightingMultiplier[] memory deduplicatedStrategiesAndWeightingMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length); uint256 numUniqueStrategies = 0; + // check for duplicates for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { - bool isDuplicate = false; - for (uint j = 0; j < deduplicatedStrategiesAndWeightingMultipliers.length; j++) { - if (strategiesAndWeightingMultipliers[i].strategy == deduplicatedStrategiesAndWeightingMultipliers[j].strategy) { - isDuplicate = true; - break; - } - } - if(!isDuplicate) { - deduplicatedStrategiesAndWeightingMultipliers[numUniqueStrategies] = strategiesAndWeightingMultipliers[i]; - numUniqueStrategies++; + if(strategyInCurrentArray[strategiesAndWeightingMultipliers[i].strategy]) { + continue; } + strategyInCurrentArray[strategiesAndWeightingMultipliers[i].strategy] = true; + deduplicatedStrategiesAndWeightingMultipliers[numUniqueStrategies] = strategiesAndWeightingMultipliers[i]; + numUniqueStrategies++; + } + + // undo storage changes + for (uint i = 0; i < strategiesAndWeightingMultipliers.length; i++) { + strategyInCurrentArray[strategiesAndWeightingMultipliers[i].strategy] = false; } IVoteWeigher.StrategyAndWeightingMultiplier[] memory trimmedStrategiesAndWeightingMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[](numUniqueStrategies); @@ -567,72 +546,39 @@ contract VoteWeigherBaseUnitTests is Test { return strategiesAndWeightingMultipliers; } - function _sortStrategiesAndWeightingMultipliersByStrategy(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) - internal pure returns(IVoteWeigher.StrategyAndWeightingMultiplier[] memory) - { - uint256 n = strategiesAndWeightingMultipliers.length; - for (uint256 i = 0; i < n - 1; i++) { - uint256 min_idx = i; - for (uint256 j = i + 1; j < n; j++) { - if (uint160(address(strategiesAndWeightingMultipliers[j].strategy)) < uint160(address(strategiesAndWeightingMultipliers[min_idx].strategy))) { - min_idx = j; - } - } - IVoteWeigher.StrategyAndWeightingMultiplier memory temp = strategiesAndWeightingMultipliers[min_idx]; - strategiesAndWeightingMultipliers[min_idx] = strategiesAndWeightingMultipliers[i]; - strategiesAndWeightingMultipliers[i] = temp; - } - return strategiesAndWeightingMultipliers; - } - - function _sortInDescendingOrder(uint256[] memory arr) internal pure returns(uint256[] memory) { - uint256 n = arr.length; - for (uint256 i = 0; i < n - 1; i++) { - uint256 max_idx = i; - for (uint256 j = i + 1; j < n; j++) { - if (arr[j] > arr[max_idx]) { - max_idx = j; - } - } - uint256 temp = arr[max_idx]; - arr[max_idx] = arr[i]; - arr[i] = temp; + function _generateRandomUniqueIndices(uint256 randomness, uint256 length) internal pure returns(uint256[] memory) { + uint256[] memory indices = new uint256[](length); + for (uint256 i = 0; i < length; i++) { + indices[i] = length - i - 1; } - return arr; - } - function _removeDuplicatesUint256(uint256[] memory arr) internal pure returns(uint256[] memory) { - uint256[] memory deduplicatedArr = new uint256[](arr.length); - uint256 numUniqueElements = 0; - for (uint i = 0; i < arr.length; i++) { - bool isDuplicate = false; - for (uint j = 0; j < deduplicatedArr.length; j++) { - if (arr[i] == deduplicatedArr[j]) { - isDuplicate = true; - break; - } - } - if(!isDuplicate) { - deduplicatedArr[numUniqueElements] = arr[i]; - numUniqueElements++; + uint256[] memory randomIndices = new uint256[](length); + uint256 numRandomIndices = 0; + // take random indices in ascending order + for (uint256 i = 0; i < length; i++) { + if (uint256(keccak256(abi.encode(randomness, i))) % length < 10) { + randomIndices[numRandomIndices] = indices[i]; + numRandomIndices++; } } - uint256[] memory trimmedArr = new uint256[](numUniqueElements); - for (uint i = 0; i < numUniqueElements; i++) { - trimmedArr[i] = deduplicatedArr[i]; + // trim the array + uint256[] memory trimmedRandomIndices = new uint256[](numRandomIndices); + for (uint256 i = 0; i < numRandomIndices; i++) { + trimmedRandomIndices[i] = randomIndices[i]; } - return trimmedArr; + + return trimmedRandomIndices; } - function _convertToValidStrategiesAndWeightingMultipliers(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal view returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { + function _convertToValidStrategiesAndWeightingMultipliers(IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers) internal returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { strategiesAndWeightingMultipliers = _removeDuplicates(strategiesAndWeightingMultipliers); cheats.assume(strategiesAndWeightingMultipliers.length <= voteWeigher.MAX_WEIGHING_FUNCTION_LENGTH()); cheats.assume(strategiesAndWeightingMultipliers.length > 0); return _replaceZeroWeights(strategiesAndWeightingMultipliers); } - function _defaultStrategiesAndWeightingMultipliers() internal view returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { + function _defaultStrategiesAndWeightingMultipliers() internal pure returns (IVoteWeigher.StrategyAndWeightingMultiplier[] memory) { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[](2); strategiesAndWeightingMultipliers[0] = IVoteWeigher.StrategyAndWeightingMultiplier({ strategy: IStrategy(address(uint160(uint256(keccak256("strategy1"))))), From 7992f656918f7852cf5c9b5e7c383fb2241a1313 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 12:32:56 -0700 Subject: [PATCH 037/110] added comment to testRemoveStrategiesConsideredAndMultipliers_Valid --- src/test/unit/VoteWeigherBaseUnit.t.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index 3cea6e9a2..ce0eb69c4 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -266,6 +266,9 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.addStrategiesConsideredAndMultipliers(quorumNumber+1, strategiesAndWeightingMultipliers); } + // this test generates a psudorandom descending order array of indices to remove + // removes them, and checks that the strategies were removed correctly by computing + // a local copy of the strategies when the removal algorithm is applied and comparing function testRemoveStrategiesConsideredAndMultipliers_Valid( IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers, uint256 randomness @@ -297,6 +300,7 @@ contract VoteWeigherBaseUnitTests is Test { // remove indicesToRemove from local strategiesAndWeightingMultipliers IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliersLocal = new IVoteWeigher.StrategyAndWeightingMultiplier[](strategiesAndWeightingMultipliers.length - indicesToRemove.length); + // run the removal algorithm locally uint256 endIndex = strategiesAndWeightingMultipliers.length - 1; for (uint256 i = 0; i < indicesToRemove.length; i++) { strategiesAndWeightingMultipliers[indicesToRemove[i]] = strategiesAndWeightingMultipliers[endIndex]; @@ -304,7 +308,6 @@ contract VoteWeigherBaseUnitTests is Test { endIndex--; } } - for (uint256 i = 0; i < strategiesAndWeightingMultipliersLocal.length; i++) { strategiesAndWeightingMultipliersLocal[i] = strategiesAndWeightingMultipliers[i]; } From a7687311393ccda79a4ec31136d8e195779c3d47 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 15:06:47 -0700 Subject: [PATCH 038/110] initial write of BLSIndexRegistryCoordinator --- .../interfaces/IBLSPubkeyRegistry.sol | 4 +- src/contracts/interfaces/IIndexRegistry.sol | 4 +- .../interfaces/IRegistryCoordinator.sol | 13 +- .../BLSIndexRegistryCoordinator.sol | 131 ++++++++++- .../middleware/BLSPubkeyRegistry.sol | 213 ------------------ src/contracts/middleware/StakeRegistry.sol | 68 +++--- .../middleware/StakeRegistryStorage.sol | 40 ++++ .../middleware/VoteWeigherBaseStorage.sol | 2 + src/test/unit/BLSPubkeyRegistryUnit.t.sol | 211 ----------------- 9 files changed, 211 insertions(+), 475 deletions(-) delete mode 100644 src/contracts/middleware/BLSPubkeyRegistry.sol create mode 100644 src/contracts/middleware/StakeRegistryStorage.sol delete mode 100644 src/test/unit/BLSPubkeyRegistryUnit.t.sol diff --git a/src/contracts/interfaces/IBLSPubkeyRegistry.sol b/src/contracts/interfaces/IBLSPubkeyRegistry.sol index da4a30823..23b7998ed 100644 --- a/src/contracts/interfaces/IBLSPubkeyRegistry.sol +++ b/src/contracts/interfaces/IBLSPubkeyRegistry.sol @@ -31,7 +31,7 @@ interface IBLSPubkeyRegistry is IRegistry { * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already registered */ - function registerOperator(address operator, uint8[] memory quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); + function registerOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); /** * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`. @@ -47,7 +47,7 @@ interface IBLSPubkeyRegistry is IRegistry { * 5) `quorumNumbers` is the same as the parameter use when registering * 6) `pubkey` is the same as the parameter used when registering */ - function deregisterOperator(address operator, uint8[] memory quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); + function deregisterOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); /// @notice Returns the current APK for the provided `quorumNumber ` function getApkForQuorum(uint8 quorumNumber) external view returns (BN254.G1Point memory); diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index a8d2e0154..0497e1452 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -25,7 +25,7 @@ interface IIndexRegistry is IRegistry { * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator */ - function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external; + function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external; /** * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. @@ -35,7 +35,7 @@ interface IIndexRegistry is IRegistry { * @param globalOperatorListIndex is the index of the operator in the global operator list * @dev Permissioned by RegistryCoordinator */ - function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 6bf0758af..27b7ccdee 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -7,7 +7,7 @@ pragma solidity =0.8.12; */ interface IRegistryCoordinator { // DATA STRUCTURES - enum Status + enum OperatorStatus { // default is inactive INACTIVE, @@ -23,13 +23,10 @@ interface IRegistryCoordinator { // start taskNumber from which the operator has been registered uint32 fromTaskNumber; // indicates whether the operator is actively registered for serving the middleware or not - Status status; + OperatorStatus status; } - /// @notice Returns the bitmap of the quroums the operator is registered for. - function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256); - - /// @notice Returns the stored id for the specified `operator`. + /// @notice Returns the operatorId for the given `operator` function getOperatorId(address operator) external view returns (bytes32); /// @notice Returns task number from when `operator` has been registered. @@ -42,8 +39,8 @@ interface IRegistryCoordinator { function numRegistries() external view returns (uint256); /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data - function registerOperator(bytes memory quorumNumbers, bytes calldata) external returns (bytes32); + function registerOperator(bytes memory quorumNumbers, bytes calldata) external; /// @notice deregisters the sender with additional bytes for registry interaction data - function deregisterOperator(bytes calldata) external returns (bytes32); + function deregisterOperator(bytes calldata) external; } diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index bcc2139d9..e1ea4031d 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -2,9 +2,138 @@ pragma solidity =0.8.12; import "../interfaces/IRegistryCoordinator.sol"; +import "../interfaces/IBLSPubkeyRegistry.sol"; +import "../interfaces/IIndexRegistry.sol"; + +import "../libraries/BytesArrayBitmaps.sol"; + import "./StakeRegistry.sol"; contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { - function registerOperator(bytes memory quorumNumbers, bytes calldata) external returns (bytes32); + using BN254 for BN254.G1Point; + + /// @notice the BLS Pubkey Registry contract that will keep track of operators' BLS public keys + IBLSPubkeyRegistry public immutable blsPubkeyRegistry; + /// @notice the Index Registry contract that will keep track of operators' indexes + IIndexRegistry public immutable indexRegistry; + /// @notice the mapping from operator's operatorId to the bitmap of quorums they are registered for + mapping(bytes32 => uint256) public operatorIdToQuorumBitmap; + /// @notice the mapping from operator's address to the operator struct + mapping(address => Operator) public operators; + /// @notice the dynamic length array of the registries this coordinator is coordinating + address[] public registries; + + constructor( + IStrategyManager _strategyManager, + IServiceManager _serviceManager, + IBLSPubkeyRegistry _blsPubkeyRegistry, + IIndexRegistry _indexRegistry + ) StakeRegistry(_strategyManager, _serviceManager) { + blsPubkeyRegistry = _blsPubkeyRegistry; + indexRegistry = _indexRegistry; + } + + function initialize( + uint96[] memory _minimumStakeForQuorum, + StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers + ) external override initializer { + // the stake registry is this contract itself + registries.push(address(this)); + registries.push(address(blsPubkeyRegistry)); + registries.push(address(indexRegistry)); + + StakeRegistry._initialize(_minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); + } + + /// @notice Returns task number from when `operator` has been registered. + function getFromTaskNumberForOperator(address operator) external view returns (uint32) { + return operators[operator].fromTaskNumber; + } + + /// @notice Returns the operatorId for the given `operator` + function getOperatorId(address operator) external view returns (bytes32) { + return operators[operator].operatorId; + } + + /// @notice Returns the number of registries + function numRegistries() external view returns (uint256) { + return registries.length; + } + + function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external override { + revert("BLSIndexRegistryCoordinator.registerOperator: cannot use overrided StakeRegistry.registerOperator on BLSIndexRegistryCoordinator"); + } + + /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data + function registerOperator(bytes calldata quorumNumbers, bytes calldata registrationData) external { + // get the operator's BLS public key + BN254.G1Point memory pubkey = abi.decode(registrationData, (BN254.G1Point)); + // call internal function to register the operator + _registerOperator(msg.sender, quorumNumbers, pubkey); + } + + function registerOperator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external { + _registerOperator(msg.sender, quorumNumbers, pubkey); + } + + function deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external override { + revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); + } + + function deregisterOperator(bytes calldata deregistrationData) external { + // get the operator's BLS public key + (BN254.G1Point memory pubkey, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) + = abi.decode(deregistrationData, (BN254.G1Point, uint32[], uint32)); + // call internal function to deregister the operator + _deregisterOperator(msg.sender, pubkey, quorumToOperatorListIndexes, globalOperatorListIndex); + } + + function deregisterOperator(BN254.G1Point memory pubkey, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external { + _deregisterOperator(msg.sender, pubkey, quorumToOperatorListIndexes, globalOperatorListIndex); + } + + function _registerOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { + // TODO: check that the sender is not already registered + + // get the quorum bitmap from the quorum numbers + uint256 quorumBitmap = BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers); + require(quorumBitmap != 0, "BLSIndexRegistryCoordinator: quorumBitmap cannot be 0"); + + // register the operator with the BLSPubkeyRegistry and get the operatorId (in this case, the pubkeyHash) back + bytes32 operatorId = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, pubkey); + + // register the operator with the IndexRegistry + indexRegistry.registerOperator(operatorId, quorumNumbers); + + // register the operator with the StakeRegistry + _registerOperator(operator, operatorId, quorumNumbers); + + // set the operatorId to quorum bitmap mapping + operatorIdToQuorumBitmap[operatorId] = quorumBitmap; + + // set the operator struct + operators[operator] = Operator({ + operatorId: operatorId, + fromTaskNumber: serviceManager.taskNumber(), + status: OperatorStatus.ACTIVE + }); + } + + function _deregisterOperator(address operator, BN254.G1Point memory pubkey, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) internal { + // get the operatorId of the operator + bytes32 operatorId = operators[operator].operatorId; + require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperator: operatorId does not match pubkey hash"); + + // get the quorumNumbers of the operator + bytes memory quorumNumbers = BytesArrayBitmaps.bitmapToBytesArray(operatorIdToQuorumBitmap[operatorId]); + + // deregister the operator from the BLSPubkeyRegistry + blsPubkeyRegistry.deregisterOperator(operator, quorumNumbers, pubkey); + + // deregister the operator from the IndexRegistry + indexRegistry.deregisterOperator(operatorId, quorumNumbers, quorumToOperatorListIndexes, globalOperatorListIndex); + // deregister the operator from the StakeRegistry + _deregisterOperator(operator, operatorId, quorumNumbers); + } } \ No newline at end of file diff --git a/src/contracts/middleware/BLSPubkeyRegistry.sol b/src/contracts/middleware/BLSPubkeyRegistry.sol deleted file mode 100644 index 95a7aecd9..000000000 --- a/src/contracts/middleware/BLSPubkeyRegistry.sol +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - - -import "../interfaces/IBLSPubkeyRegistry.sol"; -import "../interfaces/IRegistryCoordinator.sol"; -import "../interfaces/IBLSPublicKeyCompendium.sol"; - -import "../libraries/BN254.sol"; - -import "forge-std/Test.sol"; - - -contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { - using BN254 for BN254.G1Point; - - /// @notice the hash of the zero pubkey aka BN254.G1Point(0,0) - bytes32 internal constant ZERO_PK_HASH = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; - /// @notice the current global aggregate pubkey among all of the quorums - BN254.G1Point public globalApk; - /// @notice the registry coordinator contract - IRegistryCoordinator public registryCoordinator; - /// @notice the BLSPublicKeyCompendium contract against which pubkey ownership is checked - IBLSPublicKeyCompendium public immutable pubkeyCompendium; - - // list of all updates made to the global aggregate pubkey - ApkUpdate[] public globalApkUpdates; - // mapping of quorumNumber => ApkUpdate[], tracking the aggregate pubkey updates of every quorum - mapping(uint8 => ApkUpdate[]) public quorumApkUpdates; - // mapping of quorumNumber => current aggregate pubkey of quorum - mapping(uint8 => BN254.G1Point) private quorumApk; - - event PubkeyAdded( - address operator, - BN254.G1Point pubkey - ); - event PubkeyRemoved( - address operator, - BN254.G1Point pubkey - ); - - modifier onlyRegistryCoordinator() { - require(msg.sender == address(registryCoordinator), "BLSPubkeyRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); - _; - } - - constructor( - IRegistryCoordinator _registryCoordinator, - IBLSPublicKeyCompendium _pubkeyCompendium - ){ - registryCoordinator = _registryCoordinator; - pubkeyCompendium = _pubkeyCompendium; - } - - /** - * @notice Registers the `operator`'s pubkey for the specified `quorumNumbers`. - * @param operator The address of the operator to register. - * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. - * @param pubkey The operator's BLS public key. - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already registered - */ - function registerOperator(address operator, uint8[] memory quorumNumbers, BN254.G1Point memory pubkey) external onlyRegistryCoordinator returns(bytes32){ - // calculate hash of the operator's pubkey - bytes32 pubkeyHash = BN254.hashG1Point(pubkey); - require(pubkeyHash != ZERO_PK_HASH, "BLSPubkeyRegistry.registerOperator: cannot register zero pubkey"); - require(quorumNumbers.length > 0, "BLSPubkeyRegistry.registerOperator: must register for at least one quorum"); - // ensure that the operator owns their public key by referencing the BLSPubkeyCompendium - require(pubkeyCompendium.pubkeyHashToOperator(pubkeyHash) == operator,"BLSPubkeyRegistry.registerOperator: operator does not own pubkey"); - // update each quorum's aggregate pubkey - _processQuorumApkUpdate(quorumNumbers, pubkey); - // update the global aggregate pubkey - _processGlobalApkUpdate(pubkey); - // emit event so offchain actors can update their state - emit PubkeyAdded(operator, pubkey); - return pubkeyHash; - } - - /** - * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`. - * @param operator The address of the operator to deregister. - * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. - * @param pubkey The public key of the operator. - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering - * 6) `pubkey` is the same as the parameter used when registering - */ - function deregisterOperator(address operator, uint8[] memory quorumNumbers, BN254.G1Point memory pubkey) external onlyRegistryCoordinator returns(bytes32){ - bytes32 pubkeyHash = BN254.hashG1Point(pubkey); - require(quorumNumbers.length > 0, "BLSPubkeyRegistry.deregisterOperator: must register for at least one quorum"); - //ensure that the operator owns their public key by referencing the BLSPubkeyCompendium - // TODO: Do we need this check given precondition? - require(pubkeyCompendium.pubkeyHashToOperator(pubkeyHash) == operator,"BLSPubkeyRegistry.deregisterOperator: operator does not own pubkey"); - // update each quorum's aggregate pubkey - _processQuorumApkUpdate(quorumNumbers, pubkey.negate()); - // update the global aggregate pubkey - _processGlobalApkUpdate(pubkey.negate()); - // emit event so offchain actors can update their state - emit PubkeyRemoved(operator, pubkey); - return pubkeyHash; - } - - /// @notice Returns the current APK for the provided `quorumNumber ` - function getApkForQuorum(uint8 quorumNumber) external view returns(BN254.G1Point memory) { - return quorumApk[quorumNumber]; - } - - /// @notice Returns the `ApkUpdate` struct at `index` in the list of APK updates for the `quorumNumber` - function getApkUpdateForQuorumByIndex(uint8 quorumNumber, uint256 index) external view returns (ApkUpdate memory){ - return quorumApkUpdates[quorumNumber][index]; - } - - /** - * @notice get hash of the apk of `quorumNumber` at `blockNumber` using the provided `index`; - * called by checkSignatures in BLSSignatureChecker.sol. - * @param quorumNumber is the quorum whose ApkHash is being retrieved - * @param blockNumber is the number of the block for which the latest ApkHash muust be retrieved - * @param index is the provided witness of the onchain index calculated offchain - */ - function getApkHashForQuorumAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (bytes32){ - ApkUpdate memory quorumApkUpdate = quorumApkUpdates[quorumNumber][index]; - _validateApkHashForQuorumAtBlockNumber(quorumApkUpdate, blockNumber); - return quorumApkUpdate.apkHash; - } - - /** - * @notice get hash of the apk among all quourums at `blockNumber` using the provided `index`; - * called by checkSignatures in BLSSignatureChecker.sol. - * @param blockNumber is the number of the block for which the latest ApkHash muust be retrieved - * @param index is the provided witness of the onchain index calculated offchain - */ - function getGlobalApkHashAtBlockNumberFromIndex(uint32 blockNumber, uint256 index) external view returns (bytes32){ - ApkUpdate memory globalApkUpdate = globalApkUpdates[index]; - _validateApkHashForQuorumAtBlockNumber(globalApkUpdate, blockNumber); - return globalApkUpdate.apkHash; - } - - /// @notice Returns the length of ApkUpdates for the provided `quorumNumber` - function getQuorumApkHistoryLength(uint8 quorumNumber) external view returns(uint32){ - return uint32(quorumApkUpdates[quorumNumber].length); - } - - /// @notice Returns the length of ApkUpdates for the global APK - function getGlobalApkHistoryLength() external view returns(uint32){ - return uint32(globalApkUpdates.length); - } - - function _processGlobalApkUpdate(BN254.G1Point memory point) internal { - // load and store in memory in common case we need to access the length again - uint256 globalApkUpdatesLength = globalApkUpdates.length; - // update the nextUpdateBlockNumber of the previous update - if (globalApkUpdates.length > 0) { - globalApkUpdates[globalApkUpdatesLength - 1].nextUpdateBlockNumber = uint32(block.number); - } - - // accumulate the given point into the globalApk - globalApk = globalApk.plus(point); - // add this update to the list of globalApkUpdates - ApkUpdate memory latestGlobalApkUpdate; - latestGlobalApkUpdate.apkHash = BN254.hashG1Point(globalApk); - latestGlobalApkUpdate.updateBlockNumber = uint32(block.number); - globalApkUpdates.push(latestGlobalApkUpdate); - } - - function _processQuorumApkUpdate(uint8[] memory quorumNumbers, BN254.G1Point memory point) internal { - BN254.G1Point memory apkBeforeUpdate; - BN254.G1Point memory apkAfterUpdate; - - for (uint i = 0; i < quorumNumbers.length; i++) { - uint8 quorumNumber = quorumNumbers[i]; - // load and store in memory in common case we need to access the length again - uint256 quorumApkUpdatesLength = quorumApkUpdates[quorumNumber].length; - if (quorumApkUpdatesLength > 0) { - // update nextUpdateBlockNumber of the current latest ApkUpdate - quorumApkUpdates[quorumNumber][quorumApkUpdates[quorumNumber].length - 1].nextUpdateBlockNumber = uint32(block.number); - } - - // fetch the most apk before adding point - apkBeforeUpdate = quorumApk[quorumNumber]; - // accumulate the given point into the quorum apk - apkAfterUpdate = apkBeforeUpdate.plus(point); - // update aggregate public key for this quorum - quorumApk[quorumNumber] = apkAfterUpdate; - - // create new ApkUpdate to add to the mapping - ApkUpdate memory latestApkUpdate; - latestApkUpdate.apkHash = BN254.hashG1Point(apkAfterUpdate); - latestApkUpdate.updateBlockNumber = uint32(block.number); - quorumApkUpdates[quorumNumber].push(latestApkUpdate); - } - } - - /// @notice validates the the ApkUpdate was in fact the latest update at the given blockNumber - function _validateApkHashForQuorumAtBlockNumber(ApkUpdate memory apkUpdate, uint32 blockNumber) internal pure { - require( - blockNumber >= apkUpdate.updateBlockNumber, - "BLSPubkeyRegistry._validateApkHashForQuorumAtBlockNumber: index too recent" - ); - require( - apkUpdate.nextUpdateBlockNumber == 0 || blockNumber < apkUpdate.nextUpdateBlockNumber, - "BLSPubkeyRegistry._validateApkHashForQuorumAtBlockNumber: not latest apk update" - ); - } -} \ No newline at end of file diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index ccfeb599e..aca98527f 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -5,27 +5,13 @@ import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IServiceManager.sol"; import "../interfaces/IStakeRegistry.sol"; import "../interfaces/IRegistryCoordinator.sol"; -import "./VoteWeigherBase.sol"; +import "./StakeRegistryStorage.sol"; /** * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quroums. * @author Layr Labs, Inc. */ -contract StakeRegistry is VoteWeigherBase, IStakeRegistry { - - IRegistryCoordinator public registryCoordinator; - - // TODO: set these on initialization - /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as - /// evaluated by this contract's 'VoteWeigher' logic. - uint96[256] public minimumStakeForQuorum; - - /// @notice array of the history of the total stakes for each quorum -- marked as internal since getTotalStakeFromIndex is a getter for this - OperatorStakeUpdate[][256] internal totalStakeHistory; - - /// @notice mapping from operator's operatorId to the history of their stake updates - mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public operatorIdToStakeHistory; - +contract StakeRegistry is StakeRegistryStorage { // EVENTS /// @notice emitted whenever the stake of `operator` is updated event StakeUpdate( @@ -34,13 +20,10 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { uint96 stake ); - /** - * @notice Irrevocably sets the (immutable) `delegation` & `strategyManager` addresses, and `NUMBER_OF_QUORUMS` variable. - */ constructor( IStrategyManager _strategyManager, IServiceManager _serviceManager - ) VoteWeigherBase(_strategyManager, _serviceManager) + ) StakeRegistryStorage(_strategyManager, _serviceManager) // solhint-disable-next-line no-empty-blocks { } @@ -50,21 +33,19 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { * to record an initial condition of zero operators with zero total stake. * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with */ + function initialize( + uint96[] memory _minimumStakeForQuorum, + StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers + ) external virtual initializer { + _initialize(_minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); + } + function _initialize( uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers ) internal virtual onlyInitializing { // sanity check lengths require(_minimumStakeForQuorum.length == _quorumStrategiesConsideredAndMultipliers.length, "Registry._initialize: minimumStakeForQuorum length mismatch"); - // push an empty OperatorStakeUpdate struct to the total stake history to record starting with zero stake - // TODO: Address this @ gpsanant - OperatorStakeUpdate memory _totalStakeUpdate; - for (uint quorumNumber = 0; quorumNumber < 256;) { - totalStakeHistory[quorumNumber].push(_totalStakeUpdate); - unchecked { - ++quorumNumber; - } - } // add the strategies considered and multipliers for each quorum for (uint8 quorumNumber = 0; quorumNumber < _quorumStrategiesConsideredAndMultipliers.length;) { @@ -163,6 +144,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { } /// @notice Returns the stake weight from the latest entry in `totalStakeHistory` for quorum `quorumNumber`. + /// @dev Will revert if `totalStakeHistory[quorumNumber]` is empty. function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) { // no chance of underflow / error in next line, since an empty entry is pushed in the constructor return totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake; @@ -274,7 +256,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already registered */ - function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external virtual { + function registerOperator(address operator, bytes32 operatorId, bytes calldata quorumNumbers) external virtual { _registerOperator(operator, operatorId, quorumNumbers); } @@ -291,7 +273,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external virtual { + function deregisterOperator(address operator, bytes32 operatorId, bytes calldata quorumNumbers) external virtual { _deregisterOperator(operator, operatorId, quorumNumbers); } @@ -303,8 +285,9 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { * @param prevElements are the elements before this middleware in the operator's linked list within the slasher * @dev Precondition: * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for + * @dev reverts if there are no operators registered with index out of bounds */ - function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint256[] memory quorumBitmaps, uint256[] memory prevElements) external { + function updateStakes(address[] calldata operators, bytes32[] calldata operatorIds, uint256[] calldata quorumBitmaps, uint256[] calldata prevElements) external { // for each quorum, loop through operators and see if they are apart of the quorum // if they are, get their new weight and update their individual stake history and thes // quorum's total stake history accordingly @@ -352,7 +335,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { function _registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { require( slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, - "RegistryBase._addRegistrant: operator must be opted into slashing by the serviceManager" + "StakeRegistry._registerOperator: operator must be opted into slashing by the serviceManager" ); // calculate stakes for each quorum the operator is trying to join @@ -390,7 +373,13 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { // check if minimum requirement has been met, will be 0 if not require(stake != 0, "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); // add operator stakes to total stake before update (in memory) - _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake + stake; + uint256 totalStakeHistoryLength = totalStakeHistory[quorumNumber].length; + if (totalStakeHistoryLength != 0) { + // only add the stake if there is a previous total stake + // overwrite `stake` variable + stake += totalStakeHistory[quorumNumber][totalStakeHistoryLength - 1].stake; + } + _newTotalStakeUpdate.stake = stake; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); unchecked { @@ -430,7 +419,7 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, _operatorStakeUpdate); // subtract the amounts staked by the operator that is getting deregistered from the total stake before deregistration // copy latest totalStakes to memory - _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1].stake - stakeBeforeUpdate; + _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake - stakeBeforeUpdate; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); @@ -494,8 +483,11 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { /// @notice Records that the `totalStake` is now equal to the input param @_totalStake function _recordTotalStakeUpdate(uint8 quorumNumber, OperatorStakeUpdate memory _totalStake) internal { + uint256 totalStakeHistoryLength = totalStakeHistory[quorumNumber].length; + if (totalStakeHistoryLength != 0) { + totalStakeHistory[quorumNumber][totalStakeHistoryLength - 1].nextUpdateBlockNumber = uint32(block.number); + } _totalStake.updateBlockNumber = uint32(block.number); - totalStakeHistory[quorumNumber][totalStakeHistory.length - 1].nextUpdateBlockNumber = uint32(block.number); totalStakeHistory[quorumNumber].push(_totalStake); } @@ -503,11 +495,11 @@ contract StakeRegistry is VoteWeigherBase, IStakeRegistry { function _validateOperatorStakeUpdateAtBlockNumber(OperatorStakeUpdate memory operatorStakeUpdate, uint32 blockNumber) internal pure { require( operatorStakeUpdate.updateBlockNumber <= blockNumber, - "RegistryBase._validateOperatorStakeAtBlockNumber: operatorStakeUpdate is from after blockNumber" + "StakeRegistry._validateOperatorStakeAtBlockNumber: operatorStakeUpdate is from after blockNumber" ); require( operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber, - "RegistryBase._validateOperatorStakeAtBlockNumber: there is a newer operatorStakeUpdate available before blockNumber" + "StakeRegistry._validateOperatorStakeAtBlockNumber: there is a newer operatorStakeUpdate available before blockNumber" ); } diff --git a/src/contracts/middleware/StakeRegistryStorage.sol b/src/contracts/middleware/StakeRegistryStorage.sol new file mode 100644 index 000000000..461ff93d2 --- /dev/null +++ b/src/contracts/middleware/StakeRegistryStorage.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "../interfaces/IServiceManager.sol"; +import "../interfaces/IStakeRegistry.sol"; +import "../interfaces/IRegistryCoordinator.sol"; +import "./VoteWeigherBase.sol"; + +/** + * @title Storage variables for the `StakeRegistry` contract. + * @author Layr Labs, Inc. + * @notice This storage contract is separate from the logic to simplify the upgrade process. + */ +abstract contract StakeRegistryStorage is VoteWeigherBase, IStakeRegistry { + /// @notice the coordinator contract that this registry is associated with + IRegistryCoordinator public registryCoordinator; + + // TODO: set these on initialization + /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as + /// evaluated by this contract's 'VoteWeigher' logic. + uint96[256] public minimumStakeForQuorum; + + /// @notice array of the history of the total stakes for each quorum -- marked as internal since getTotalStakeFromIndex is a getter for this + OperatorStakeUpdate[][256] internal totalStakeHistory; + + /// @notice mapping from operator's operatorId to the history of their stake updates + mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public operatorIdToStakeHistory; + + constructor( + IStrategyManager _strategyManager, + IServiceManager _serviceManager + ) VoteWeigherBase(_strategyManager, _serviceManager) + // solhint-disable-next-line no-empty-blocks + { + } + + // storage gap + uint256[50] private __GAP; +} \ No newline at end of file diff --git a/src/contracts/middleware/VoteWeigherBaseStorage.sol b/src/contracts/middleware/VoteWeigherBaseStorage.sol index fa18dd1d3..a94e1a48d 100644 --- a/src/contracts/middleware/VoteWeigherBaseStorage.sol +++ b/src/contracts/middleware/VoteWeigherBaseStorage.sol @@ -61,4 +61,6 @@ abstract contract VoteWeigherBaseStorage is Initializable, IVoteWeigher { // disable initializers so that the implementation contract cannot be initialized _disableInitializers(); } + + uint256[50] private __GAP; } diff --git a/src/test/unit/BLSPubkeyRegistryUnit.t.sol b/src/test/unit/BLSPubkeyRegistryUnit.t.sol deleted file mode 100644 index 7a8cbb424..000000000 --- a/src/test/unit/BLSPubkeyRegistryUnit.t.sol +++ /dev/null @@ -1,211 +0,0 @@ -//SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - - -import "forge-std/Test.sol"; -import "../../contracts/middleware/BLSPubkeyRegistry.sol"; -import "../../contracts/interfaces/IRegistryCoordinator.sol"; -import "../mocks/PublicKeyCompendiumMock.sol"; -import "../mocks/RegistryCoordinatorMock.sol"; - - -contract BLSPubkeyRegistryUnitTests is Test { - Vm cheats = Vm(HEVM_ADDRESS); - - address defaultOperator = address(4545); - - bytes32 internal constant ZERO_PK_HASH = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; - - - - BLSPubkeyRegistry public blsPubkeyRegistry; - BLSPublicKeyCompendiumMock public pkCompendium; - RegistryCoordinatorMock public registryCoordinator; - - BN254.G1Point internal defaultPubKey = BN254.G1Point(18260007818883133054078754218619977578772505796600400998181738095793040006897,3432351341799135763167709827653955074218841517684851694584291831827675065899); - - uint8 internal defaulQuorumNumber = 0; - - function setUp() external { - registryCoordinator = new RegistryCoordinatorMock(); - pkCompendium = new BLSPublicKeyCompendiumMock(); - blsPubkeyRegistry = new BLSPubkeyRegistry(registryCoordinator, pkCompendium); - } - - function testConstructorArgs() public { - require(blsPubkeyRegistry.registryCoordinator() == registryCoordinator, "registryCoordinator not set correctly"); - require(blsPubkeyRegistry.pubkeyCompendium() == pkCompendium, "pubkeyCompendium not set correctly"); - } - - function testCallRegisterOperatorFromNonCoordinatorAddress(address nonCoordinatorAddress) public { - cheats.assume(nonCoordinatorAddress != address(registryCoordinator)); - - cheats.startPrank(nonCoordinatorAddress); - cheats.expectRevert(bytes("BLSPubkeyRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); - blsPubkeyRegistry.registerOperator(nonCoordinatorAddress, new uint8[](0), BN254.G1Point(0, 0)); - cheats.stopPrank(); - } - - function testCallDeregisterOperatorFromNonCoordinatorAddress(address nonCoordinatorAddress) public { - cheats.assume(nonCoordinatorAddress != address(registryCoordinator)); - - cheats.startPrank(nonCoordinatorAddress); - cheats.expectRevert(bytes("BLSPubkeyRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); - blsPubkeyRegistry.deregisterOperator(nonCoordinatorAddress, new uint8[](0), BN254.G1Point(0, 0)); - cheats.stopPrank(); - } - - function testOperatorDoesNotOwnPubKeyRegister(address operator) public { - cheats.startPrank(address(registryCoordinator)); - cheats.expectRevert(bytes("BLSPubkeyRegistry.registerOperator: operator does not own pubkey")); - blsPubkeyRegistry.registerOperator(operator, new uint8[](1), BN254.G1Point(1, 0)); - cheats.stopPrank(); - } - function testOperatorDoesNotOwnPubKeyDeregister(address operator) public { - cheats.startPrank(address(registryCoordinator)); - cheats.expectRevert(bytes("BLSPubkeyRegistry.deregisterOperator: operator does not own pubkey")); - blsPubkeyRegistry.deregisterOperator(operator, new uint8[](1), BN254.G1Point(1, 0)); - cheats.stopPrank(); - } - - function testOperatorRegisterZeroPubkey() public { - cheats.startPrank(address(registryCoordinator)); - cheats.expectRevert(bytes("BLSPubkeyRegistry.registerOperator: cannot register zero pubkey")); - blsPubkeyRegistry.registerOperator(defaultOperator, new uint8[](1), BN254.G1Point(0, 0)); - cheats.stopPrank(); - } - function testRegisteringWithNoQuorums() public { - cheats.startPrank(address(registryCoordinator)); - cheats.expectRevert(bytes("BLSPubkeyRegistry.registerOperator: must register for at least one quorum")); - blsPubkeyRegistry.registerOperator(defaultOperator, new uint8[](0), BN254.G1Point(1, 0)); - cheats.stopPrank(); - } - - function testDeregisteringWithNoQuorums() public { - cheats.startPrank(address(registryCoordinator)); - cheats.expectRevert(bytes("BLSPubkeyRegistry.deregisterOperator: must register for at least one quorum")); - blsPubkeyRegistry.deregisterOperator(defaultOperator, new uint8[](0), BN254.G1Point(1, 0)); - cheats.stopPrank(); - } - - function testRegisterOperatorBLSPubkey(address operator) public { - bytes32 pkHash = BN254.hashG1Point(defaultPubKey); - - cheats.startPrank(operator); - pkCompendium.registerPublicKey(defaultPubKey); - cheats.stopPrank(); - - //register for one quorum - uint8[] memory quorumNumbers = new uint8[](1); - quorumNumbers[0] = defaulQuorumNumber; - - cheats.startPrank(address(registryCoordinator)); - bytes32 registeredpkHash = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, defaultPubKey); - cheats.stopPrank(); - - require(registeredpkHash == pkHash, "registeredpkHash not set correctly"); - } - - function testQuorumApkUpdates(uint8[] memory quorumNumbers) public { - uint8[] memory quorumNumbers = new uint8[](2); - quorumNumbers[0] = 1; - quorumNumbers[0] = 2; - - bytes32 pkHash = BN254.hashG1Point(defaultPubKey); - - BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[](quorumNumbers.length); - for(uint8 i = 0; i < quorumNumbers.length; i++){ - quorumApksBefore[i] = blsPubkeyRegistry.getApkForQuorum(quorumNumbers[i]); - } - - cheats.startPrank(defaultOperator); - pkCompendium.registerPublicKey(defaultPubKey); - cheats.stopPrank(); - - cheats.startPrank(address(registryCoordinator)); - blsPubkeyRegistry.registerOperator(defaultOperator, quorumNumbers, defaultPubKey); - cheats.stopPrank(); - - //check quorum apk updates - for(uint8 i = 0; i < quorumNumbers.length; i++){ - BN254.G1Point memory quorumApkAfter = blsPubkeyRegistry.getApkForQuorum(quorumNumbers[i]); - bytes32 temp = BN254.hashG1Point(BN254.plus(quorumApkAfter, BN254.negate(quorumApksBefore[i]))); - require(temp == BN254.hashG1Point(defaultPubKey), "quorum apk not updated correctly"); - } - } - - function testRegisterWithNegativeGlobalApk(address operator) external { - testRegisterOperatorBLSPubkey(operator); - - (uint256 x, uint256 y)= blsPubkeyRegistry.globalApk(); - BN254.G1Point memory globalApk = BN254.G1Point(x, y); - - - BN254.G1Point memory negatedGlobalApk = BN254.negate(globalApk); - - //register for one quorum - uint8[] memory quorumNumbers = new uint8[](1); - quorumNumbers[0] = defaulQuorumNumber; - - cheats.startPrank(operator); - pkCompendium.registerPublicKey(negatedGlobalApk); - cheats.stopPrank(); - - cheats.startPrank(address(registryCoordinator)); - bytes32 registeredpkHash = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, negatedGlobalApk); - cheats.stopPrank(); - - BN254.G1Point memory zeroPk = BN254.G1Point(0,0); - - (x, y)= blsPubkeyRegistry.globalApk(); - BN254.G1Point memory temp = BN254.G1Point(x, y); - - require(BN254.hashG1Point(temp) == ZERO_PK_HASH, "globalApk not set correctly"); - } - - function testRegisterWithNegativeQuorumApk(address operator) external { - testRegisterOperatorBLSPubkey(defaultOperator); - - BN254.G1Point memory quorumApk = blsPubkeyRegistry.getApkForQuorum(defaulQuorumNumber); - - BN254.G1Point memory negatedQuorumApk = BN254.negate(quorumApk); - - //register for one quorum - uint8[] memory quorumNumbers = new uint8[](1); - quorumNumbers[0] = defaulQuorumNumber; - - cheats.startPrank(operator); - pkCompendium.registerPublicKey(negatedQuorumApk); - cheats.stopPrank(); - - cheats.startPrank(address(registryCoordinator)); - bytes32 registeredpkHash = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, negatedQuorumApk); - cheats.stopPrank(); - - BN254.G1Point memory zeroPk = BN254.G1Point(0,0); - require(BN254.hashG1Point(blsPubkeyRegistry.getApkForQuorum(defaulQuorumNumber)) == ZERO_PK_HASH, "quorumApk not set correctly"); - } - - function testQuorumApkUpdatesDeregistration() external { - uint8[] memory quorumNumbers = new uint8[](2); - quorumNumbers[0] = 1; - quorumNumbers[0] = 2; - - testQuorumApkUpdates(quorumNumbers); - - BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[](quorumNumbers.length); - for(uint8 i = 0; i < quorumNumbers.length; i++){ - quorumApksBefore[i] = blsPubkeyRegistry.getApkForQuorum(quorumNumbers[i]); - } - - cheats.startPrank(address(registryCoordinator)); - blsPubkeyRegistry.deregisterOperator(defaultOperator, quorumNumbers, defaultPubKey); - cheats.stopPrank(); - - - for(uint8 i = 0; i < quorumNumbers.length; i++){ - BN254.G1Point memory quorumApkAfter = blsPubkeyRegistry.getApkForQuorum(quorumNumbers[i]); - require(BN254.hashG1Point(BN254.plus(quorumApksBefore[i], BN254.negate(quorumApkAfter))) == BN254.hashG1Point(defaultPubKey), "quorum apk not updated correctly"); - } - } -} \ No newline at end of file From e57eb8bedd70b7ca1ba0ec22665f2c6f799b2339 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 15:26:17 -0700 Subject: [PATCH 039/110] add deregistration logic and silence warnings --- .../interfaces/IRegistryCoordinator.sol | 6 ++--- .../BLSIndexRegistryCoordinator.sol | 8 ++++--- src/test/mocks/RegistryCoordinatorMock.sol | 23 ------------------- 3 files changed, 8 insertions(+), 29 deletions(-) delete mode 100644 src/test/mocks/RegistryCoordinatorMock.sol diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 27b7ccdee..53a047994 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -9,9 +9,9 @@ interface IRegistryCoordinator { // DATA STRUCTURES enum OperatorStatus { - // default is inactive - INACTIVE, - ACTIVE + // default is DEREGISTERED + DEREGISTERED, + REGISTERED } /** diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index e1ea4031d..b1b062298 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -60,7 +60,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { return registries.length; } - function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external override { + function registerOperator(address, bytes32, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.registerOperator: cannot use overrided StakeRegistry.registerOperator on BLSIndexRegistryCoordinator"); } @@ -76,7 +76,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { _registerOperator(msg.sender, quorumNumbers, pubkey); } - function deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external override { + function deregisterOperator(address, bytes32, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); } @@ -115,7 +115,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { operators[operator] = Operator({ operatorId: operatorId, fromTaskNumber: serviceManager.taskNumber(), - status: OperatorStatus.ACTIVE + status: OperatorStatus.REGISTERED }); } @@ -135,5 +135,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { // deregister the operator from the StakeRegistry _deregisterOperator(operator, operatorId, quorumNumbers); + + operators[operator].status = OperatorStatus.DEREGISTERED; } } \ No newline at end of file diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol deleted file mode 100644 index 36a398c82..000000000 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - - -import "../../contracts/interfaces/IRegistryCoordinator.sol"; - - -contract RegistryCoordinatorMock is IRegistryCoordinator { - /// @notice Returns the bitmap of the quroums the operator is registered for. - function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} - - /// @notice Returns the stored id for the specified `operator`. - function getOperatorId(address operator) external view returns (bytes32){} - - /// @notice Returns task number from when `operator` has been registered. - function getFromTaskNumberForOperator(address operator) external view returns (uint32){} - - /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data - function registerOperator(uint8[] memory quorumNumbers, bytes calldata) external returns (bytes32){} - - /// @notice deregisters the sender with additional bytes for registry interaction data - function deregisterOperator(bytes calldata) external returns (bytes32){} -} From d69f30ec857d525b8452d85c6549eee6057bad22 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 31 May 2023 15:43:35 -0700 Subject: [PATCH 040/110] add comments to BLSIndexRegistryCoordinator --- .../BLSIndexRegistryCoordinator.sol | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index b1b062298..b998fbf0b 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -64,7 +64,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { revert("BLSIndexRegistryCoordinator.registerOperator: cannot use overrided StakeRegistry.registerOperator on BLSIndexRegistryCoordinator"); } - /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data + /** + * @notice Registers the operator with the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for + * @param registrationData is the data that is decoded to get the operator's registration information + */ function registerOperator(bytes calldata quorumNumbers, bytes calldata registrationData) external { // get the operator's BLS public key BN254.G1Point memory pubkey = abi.decode(registrationData, (BN254.G1Point)); @@ -72,6 +76,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { _registerOperator(msg.sender, quorumNumbers, pubkey); } + /** + * @notice Registers the operator with the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for + * @param pubkey is the BLS public key of the operator + */ function registerOperator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external { _registerOperator(msg.sender, quorumNumbers, pubkey); } @@ -79,15 +88,25 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { function deregisterOperator(address, bytes32, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); } - + + /** + * @notice Deregisters the operator from the middleware + * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information + */ function deregisterOperator(bytes calldata deregistrationData) external { - // get the operator's BLS public key + // get the operator's deregisteration information (BN254.G1Point memory pubkey, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) = abi.decode(deregistrationData, (BN254.G1Point, uint32[], uint32)); // call internal function to deregister the operator _deregisterOperator(msg.sender, pubkey, quorumToOperatorListIndexes, globalOperatorListIndex); } + /** + * @notice Deregisters the operator from the middleware + * @param pubkey is the BLS public key of the operator + * @param quorumToOperatorListIndexes is the list of the operator's indexes in the quorums they are registered for in the IndexRegistry + * @param globalOperatorListIndex is the operator's index in the global operator list in the IndexRegistry + */ function deregisterOperator(BN254.G1Point memory pubkey, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external { _deregisterOperator(msg.sender, pubkey, quorumToOperatorListIndexes, globalOperatorListIndex); } @@ -120,6 +139,8 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } function _deregisterOperator(address operator, BN254.G1Point memory pubkey, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) internal { + require(operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperator: operator is not registered"); + // get the operatorId of the operator bytes32 operatorId = operators[operator].operatorId; require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperator: operatorId does not match pubkey hash"); @@ -136,6 +157,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { // deregister the operator from the StakeRegistry _deregisterOperator(operator, operatorId, quorumNumbers); + // set the status of the operator to DEREGISTERED operators[operator].status = OperatorStatus.DEREGISTERED; } } \ No newline at end of file From 42234da18a78b710b1fa9a695820d4a6c06edf9e Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 18:09:20 +0530 Subject: [PATCH 041/110] fixed the tests --- src/contracts/interfaces/IIndexRegistry.sol | 4 +- src/contracts/middleware/IndexRegistry.sol | 48 +++++++++++++++------ src/test/unit/IndexRegistryUnit.t.sol | 38 ++++++++-------- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index df15501a3..9ca167a46 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -25,7 +25,7 @@ interface IIndexRegistry is IRegistry { * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator */ - function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external; + function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external; /** * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. @@ -35,7 +35,7 @@ interface IIndexRegistry is IRegistry { * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator */ - function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index ce49b81b8..354595482 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -5,9 +5,10 @@ pragma solidity =0.8.12; import "../interfaces/IIndexRegistry.sol"; import "../interfaces/IRegistryCoordinator.sol"; import "../libraries/BN254.sol"; +import "forge-std/Test.sol"; -contract IndexRegistry is IIndexRegistry { +contract IndexRegistry is IIndexRegistry, Test { IRegistryCoordinator public registryCoordinator; @@ -37,15 +38,21 @@ contract IndexRegistry is IIndexRegistry { * @param operatorId is the id of the operator that is being registered * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already registered */ - function registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) external onlyRegistryCoordinator { + function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external onlyRegistryCoordinator { //add operator to operatorList globalOperatorList.push(operatorId); for (uint i = 0; i < quorumNumbers.length; i++) { - quorumToOperatorList[quorumNumbers[i]].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumbers[i], uint32(quorumToOperatorList[quorumNumbers[i]].length - 1)); - _updateTotalOperatorHistory(quorumNumbers[i]); + uint8 quorumNumber = uint8(quorumNumbers[i]); + quorumToOperatorList[quorumNumber].push(operatorId); + _updateOperatorIdToIndexHistory(operatorId, quorumNumber, uint32(quorumToOperatorList[quorumNumber].length - 1)); + _updateTotalOperatorHistory(quorumNumber); } } @@ -56,16 +63,30 @@ contract IndexRegistry is IIndexRegistry { * @param quorumToOperatorListIndexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already deregistered + * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { require(quorumNumbers.length == quorumToOperatorListIndexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); - for (uint i = 0; i < quorumNumbers.length; i++) { - _removeOperatorFromQuorumToOperatorList(quorumNumbers[i], quorumToOperatorListIndexes[i]); - _updateTotalOperatorHistory(quorumNumbers[i]); + uint8 quorumNumber = uint8(quorumNumbers[i]); + require(quorumToOperatorListIndexes[i] < quorumToOperatorList[quorumNumber].length, "IndexRegistry.deregisterOperator: index out of bounds"); + _removeOperatorFromQuorumToOperatorList(quorumNumber, quorumToOperatorListIndexes[i]); + _updateTotalOperatorHistory(quorumNumber); + //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumbers[i]][operatorIdToIndexHistory[operatorId][quorumNumbers[i]].length - 1].toBlockNumber = uint32(block.number); + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); + // emit log_named_uint("Index", operatorIdToIndexHistory[operatorId][quorumNumber].length - 1); + // emit log_named_uint("block.number", block.number); + // emit log_named_uint("quorumNumber", quorumNumber); + + // emit log_named_uint("f", operatorIdToIndexHistory[operatorId][uint8(quorumNumbers[0])][0].toBlockNumber); + } } @@ -126,9 +147,11 @@ contract IndexRegistry is IIndexRegistry { function _updateTotalOperatorHistory(uint8 quorumNumber) internal { + uint256 totalOperatorsHistoryLength = totalOperatorsHistory[quorumNumber].length; + //if there is a prior entry, update its "toBlockNumber" - if (totalOperatorsHistory[quorumNumber].length > 0) { - totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].toBlockNumber = uint32(block.number); + if (totalOperatorsHistoryLength > 0) { + totalOperatorsHistory[quorumNumber][totalOperatorsHistoryLength - 1].toBlockNumber = uint32(block.number); } OperatorIndex memory totalOperatorUpdate; @@ -143,7 +166,6 @@ contract IndexRegistry is IIndexRegistry { /// @param index the latest index of that operator in quorumToOperatorList function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; - //if there is a prior entry for the operator, set the previous entry's toBlocknumber if (operatorIdToIndexHistoryLength > 0) { operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = uint32(block.number); diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index f3715ddff..fb7d14a73 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -14,6 +14,8 @@ contract IndexRegistryUnitTests is Test { IndexRegistry indexRegistry; RegistryCoordinatorMock registryCoordinatorMock; + uint8 defaultQuorumNumber = 1; + function setUp() public { // deploy the contract @@ -29,8 +31,8 @@ contract IndexRegistryUnitTests is Test { function testRegisterOperatorInIndexRegistry(bytes32 operatorId) public { // register an operator - uint8[] memory quorumNumbers = new uint8[](1); - quorumNumbers[0] = 1; + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); cheats.startPrank(address(registryCoordinatorMock)); indexRegistry.registerOperator(operatorId, quorumNumbers); @@ -47,7 +49,7 @@ contract IndexRegistryUnitTests is Test { function testRegisterOperatorFromNonRegisterCoordinator(address nonRegistryCoordinator) public { cheats.assume(address(registryCoordinatorMock) != nonRegistryCoordinator); // register an operator - uint8[] memory quorumNumbers = new uint8[](1); + bytes memory quorumNumbers = new bytes(defaultQuorumNumber); cheats.startPrank(nonRegistryCoordinator); cheats.expectRevert(bytes("IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); @@ -58,7 +60,7 @@ contract IndexRegistryUnitTests is Test { function testDeregisterOperatorFromNonRegisterCoordinator(address nonRegistryCoordinator) public { cheats.assume(address(registryCoordinatorMock) != nonRegistryCoordinator); // register an operator - uint8[] memory quorumNumbers = new uint8[](1); + bytes memory quorumNumbers = new bytes(defaultQuorumNumber); uint32[] memory quorumToOperatorListIndexes = new uint32[](1); cheats.startPrank(nonRegistryCoordinator); @@ -68,9 +70,11 @@ contract IndexRegistryUnitTests is Test { } function testDeregisterOperatorInIndexRegistry(bytes32 operatorId1, bytes32 operatorId2) public { - uint8[] memory quorumNumbers = new uint8[](2); - quorumNumbers[0] = 1; - quorumNumbers[1] = 2; + cheats.assume(operatorId1 != operatorId2); + bytes memory quorumNumbers = new bytes(2); + quorumNumbers[0] = bytes1(defaultQuorumNumber); + quorumNumbers[1] = bytes1(defaultQuorumNumber + 1); + _registerOperator(operatorId1, quorumNumbers); _registerOperator(operatorId2, quorumNumbers); @@ -82,7 +86,6 @@ contract IndexRegistryUnitTests is Test { quorumToOperatorListIndexes[0] = 0; quorumToOperatorListIndexes[1] = 0; - cheats.roll(block.number + 1); // deregister the operatorId1, removing it from both quorum 1 and 2. @@ -95,19 +98,20 @@ contract IndexRegistryUnitTests is Test { require(indexRegistry.quorumToOperatorList(2, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); require(indexRegistry.globalOperatorList(0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); - (uint32 index1, uint32 toBlockNumber1) = indexRegistry.operatorIdToIndexHistory(operatorId1, 1, 0); + (uint32 toBlockNumber1, uint32 index1) = indexRegistry.operatorIdToIndexHistory(operatorId1, defaultQuorumNumber, 0); + require(toBlockNumber1 == block.number, "toBlockNumber not set correctly"); - require(index1 == 0); + require(index1 == 0, "incorrect index"); - (uint32 index2, uint32 toBlockNumber2) = indexRegistry.operatorIdToIndexHistory(operatorId2, 1, 1); - require(toBlockNumber2 == block.number, "toBlockNumber not set correctly"); - require(index2 == 1); + (uint32 toBlockNumber2, uint32 index2) = indexRegistry.operatorIdToIndexHistory(operatorId2, defaultQuorumNumber, 1); + require(toBlockNumber2 == 0, "toBlockNumber not set correctly"); + require(index2 == 0, "incorrect index"); } function testTotalOperatorUpdatesForOneQuorum(uint8 numOperators) public { - uint8[] memory quorumNumbers = new uint8[](1); - quorumNumbers[0] = 1; + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); uint256 lengthBefore = 0; for (uint256 i = 0; i < numOperators; i++) { @@ -118,13 +122,13 @@ contract IndexRegistryUnitTests is Test { } } - function _registerOperator(bytes32 operatorId, uint8[] memory quorumNumbers) public { + function _registerOperator(bytes32 operatorId, bytes memory quorumNumbers) public { cheats.startPrank(address(registryCoordinatorMock)); indexRegistry.registerOperator(operatorId, quorumNumbers); cheats.stopPrank(); } - function _deregisterOperator(bytes32 operatorId, uint8[] memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 index) public { + function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 index) public { cheats.startPrank(address(registryCoordinatorMock)); indexRegistry.deregisterOperator(operatorId, quorumNumbers, quorumToOperatorListIndexes, index); cheats.stopPrank(); From e7532968cd3d84e32d355d18abc3c5d2e08ec32d Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 20:00:44 +0530 Subject: [PATCH 042/110] added test --- src/test/unit/IndexRegistryUnit.t.sol | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index fb7d14a73..ed416a71d 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -23,7 +23,6 @@ contract IndexRegistryUnitTests is Test { indexRegistry = new IndexRegistry(registryCoordinatorMock); } - function testConstructor() public { // check that the registry coordinator is set correctly assertEq(address(indexRegistry.registryCoordinator()), address(registryCoordinatorMock), "IndexRegistry.constructor: registry coordinator not set correctly"); @@ -122,6 +121,27 @@ contract IndexRegistryUnitTests is Test { } } + function testGettingOperatorIndexForQuorumAtBlockNumber(uint32 numOperators, uint32 operatorToDeregister) external { + cheats.assume(numOperators > 0 && numOperators < 256); + cheats.assume(operatorToDeregister >= 0 && operatorToDeregister < numOperators); + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); + + for (uint256 i = 0; i < numOperators; i++) { + bytes32 operatorId = _getRandomId(i); + _registerOperator(operatorId, quorumNumbers); + } + cheats.roll(block.number + 100); + + bytes32 operator = _getRandomId(operatorToDeregister); + + uint32[] memory quorumToOperatorListIndexes = new uint32[](1); + quorumToOperatorListIndexes[0] = uint32(_generateRandomNumber(operatorToDeregister, numOperators)); + _deregisterOperator(operator, quorumNumbers, quorumToOperatorListIndexes, operatorToDeregister); + require(operatorToDeregister == indexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex(operator, defaultQuorumNumber, uint32(_generateRandomNumber(1, 100)), 0), "wrong index returned"); + } + function _registerOperator(bytes32 operatorId, bytes memory quorumNumbers) public { cheats.startPrank(address(registryCoordinatorMock)); indexRegistry.registerOperator(operatorId, quorumNumbers); @@ -133,4 +153,13 @@ contract IndexRegistryUnitTests is Test { indexRegistry.deregisterOperator(operatorId, quorumNumbers, quorumToOperatorListIndexes, index); cheats.stopPrank(); } + + function _getRandomId(uint256 seed) internal view returns (bytes32) { + return keccak256(abi.encodePacked(block.timestamp, seed)); + } + + function _generateRandomNumber(uint256 seed, uint256 modulus) internal view returns (uint256) { + uint256 randomNumber = uint256(keccak256(abi.encodePacked(block.timestamp, seed))); + return (randomNumber % modulus); + } } From 515d6319bf731adb147faf2c7e13c55c945da6a5 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 20:15:16 +0530 Subject: [PATCH 043/110] added test --- src/test/unit/IndexRegistryUnit.t.sol | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index ed416a71d..e63a9b9d6 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -122,6 +122,34 @@ contract IndexRegistryUnitTests is Test { } function testGettingOperatorIndexForQuorumAtBlockNumber(uint32 numOperators, uint32 operatorToDeregister) external { + cheats.assume(numOperators > 5 && numOperators < 256); + cheats.assume(operatorToDeregister >= 0 && operatorToDeregister < numOperators); + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); + + for (uint256 i = 0; i < numOperators; i++) { + bytes32 operatorId = _getRandomId(i); + _registerOperator(operatorId, quorumNumbers); + } + cheats.roll(block.number + 100); + + bytes32 operator = _getRandomId(operatorToDeregister); + + uint32[] memory quorumToOperatorListIndexes = new uint32[](1); + quorumToOperatorListIndexes[0] = uint32(_generateRandomNumber(operatorToDeregister, numOperators)); + _deregisterOperator(operator, quorumNumbers, quorumToOperatorListIndexes, operatorToDeregister); + require(operatorToDeregister == indexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex(operator, defaultQuorumNumber, uint32(_generateRandomNumber(1, 100)), 0), "wrong index returned"); + + emit log_named_uint("quorumToOperatorListIndexes[0]", quorumToOperatorListIndexes[0]); + emit log_named_uint("total ops", indexRegistry.totalOperatorsForQuorum(defaultQuorumNumber)); + bytes32 swappedOperator = indexRegistry.quorumToOperatorList(defaultQuorumNumber, quorumToOperatorListIndexes[0]); + emit log_named_uint("quorumToOperatorListIndexes[0]", quorumToOperatorListIndexes[0]); + require(quorumToOperatorListIndexes[0] == indexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex(swappedOperator, defaultQuorumNumber, uint32(_generateRandomNumber(1, 100)), 0), "wrong index returned for swapped operator"); + + } + + function testGettingTotalOperatorsForQuorumAtBlockNumber(uint32 numOperators, uint32 operatorToDeregister) external { cheats.assume(numOperators > 0 && numOperators < 256); cheats.assume(operatorToDeregister >= 0 && operatorToDeregister < numOperators); From ba2185ec7471d0734468d707a1fedf25e1ce68da Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 21:18:12 +0530 Subject: [PATCH 044/110] removed quorum To Operator list --- src/contracts/middleware/IndexRegistry.sol | 48 ++++++++-------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 354595482..0cdda831a 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -16,7 +16,7 @@ contract IndexRegistry is IIndexRegistry, Test { bytes32[] public globalOperatorList; // mapping of quorumNumber => list of operators registered for that quorum - mapping(uint8 => bytes32[]) public quorumToOperatorList; + mapping(uint8 => uint32) public quorumToTotalOperatorCount; // mapping of operatorId => quorumNumber => index history of that operator mapping(bytes32 => mapping(uint8 => OperatorIndex[])) public operatorIdToIndexHistory; // mapping of quorumNumber => history of numbers of unique registered operators @@ -50,9 +50,9 @@ contract IndexRegistry is IIndexRegistry, Test { for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - quorumToOperatorList[quorumNumber].push(operatorId); - _updateOperatorIdToIndexHistory(operatorId, quorumNumber, uint32(quorumToOperatorList[quorumNumber].length - 1)); + _updateOperatorIdToIndexHistory(operatorId, quorumNumber, quorumToTotalOperatorCount[quorumNumber]); _updateTotalOperatorHistory(quorumNumber); + quorumToTotalOperatorCount[quorumNumber]++; } } @@ -70,23 +70,17 @@ contract IndexRegistry is IIndexRegistry, Test { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { - require(quorumNumbers.length == quorumToOperatorListIndexes.length, "IndexRegistry.deregisterOperator: quorumNumbers and indexes must be the same length"); + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { + require(quorumNumbers.length == operatorIdsToSwap.length, "IndexRegistry.deregisterOperator: quorumNumbers and operatorIdsToSwap must be the same length"); + _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); + for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - require(quorumToOperatorListIndexes[i] < quorumToOperatorList[quorumNumber].length, "IndexRegistry.deregisterOperator: index out of bounds"); - _removeOperatorFromQuorumToOperatorList(quorumNumber, quorumToOperatorListIndexes[i]); + uint32 indexToRemove = operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; + _processOperatorRemoval(operatorId, quorumNumber, indexToRemove, operatorIdsToSwap[i]); _updateTotalOperatorHistory(quorumNumber); - - //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); - // emit log_named_uint("Index", operatorIdToIndexHistory[operatorId][quorumNumber].length - 1); - // emit log_named_uint("block.number", block.number); - // emit log_named_uint("quorumNumber", quorumNumber); - - // emit log_named_uint("f", operatorIdToIndexHistory[operatorId][uint8(quorumNumbers[0])][0].toBlockNumber); - + quorumToTotalOperatorCount[quorumNumber]--; } } @@ -135,7 +129,7 @@ contract IndexRegistry is IIndexRegistry, Test { } function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32){ - return uint32(quorumToOperatorList[quorumNumber].length); + return quorumToTotalOperatorCount[quorumNumber]; } @@ -179,30 +173,24 @@ contract IndexRegistry is IIndexRegistry, Test { /// quorumToOperatorList[quorumNumber] to the position of the operator we are removing /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove - function _removeOperatorFromQuorumToOperatorList(uint8 quorumNumber, uint32 indexToRemove) internal { - - uint32 quorumToOperatorListLastIndex = uint32(quorumToOperatorList[quorumNumber].length - 1); - + function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 memory operatorIdToSwap) internal { // if the operator is not the last in the list, we must swap the last operator into their positon - if(indexToRemove != quorumToOperatorListLastIndex){ - bytes32 operatorIdToSwap = quorumToOperatorList[quorumNumber][quorumToOperatorListLastIndex]; + if(operatorId != operatorIdToSwap){ //update the swapped operator's operatorIdToIndexHistory list with a new entry, as their index has now changed _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); - - //set last operator in the list to removed operator's position in the array - quorumToOperatorList[quorumNumber][indexToRemove] = operatorIdToSwap; + } else { + //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); } - //removing the swapped operator from the list - quorumToOperatorList[quorumNumber].pop(); } /// @notice remove an operator from the globalOperatorList /// @param indexToRemove index of the operator to remove function _removeOperatorFromGlobalOperatorList(uint32 indexToRemove) internal { uint32 globalOperatorListLastIndex = uint32(globalOperatorList.length - 1); - + bytes32 operatorIdToSwap; if(indexToRemove != globalOperatorListLastIndex){ - bytes32 operatorIdToSwap = globalOperatorList[globalOperatorListLastIndex]; + operatorIdToSwap = globalOperatorList[globalOperatorListLastIndex]; globalOperatorList[indexToRemove] = operatorIdToSwap; } globalOperatorList.pop(); From 5b1d971dbceb9aca9fde5402eb5895aaa6136dce Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 21:20:13 +0530 Subject: [PATCH 045/110] removed quorum To Operator list --- src/contracts/middleware/IndexRegistry.sol | 9 ++--- src/test/unit/IndexRegistryUnit.t.sol | 47 ---------------------- 2 files changed, 4 insertions(+), 52 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 0cdda831a..57873cd07 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -60,7 +60,6 @@ contract IndexRegistry is IIndexRegistry, Test { * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param quorumToOperatorListIndexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator * @dev Preconditions: @@ -150,14 +149,14 @@ contract IndexRegistry is IIndexRegistry, Test { OperatorIndex memory totalOperatorUpdate; // In the case of totalOperatorsHistory, the index parameter is the number of operators in the quorum - totalOperatorUpdate.index = uint32(quorumToOperatorList[quorumNumber].length); + totalOperatorUpdate.index = quorumToTotalOperatorCount[quorumNumber]; totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } /// /// @param operatorId operatorId of the operator to update /// @param quorumNumber quorumNumber of the operator to update - /// @param index the latest index of that operator in quorumToOperatorList + /// @param index the latest index of that operator in the list of operators registered for this quorum function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; //if there is a prior entry for the operator, set the previous entry's toBlocknumber @@ -169,8 +168,8 @@ contract IndexRegistry is IIndexRegistry, Test { operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); } - /// @notice when we remove an operator from quorumToOperatorList, we swap the last operator in - /// quorumToOperatorList[quorumNumber] to the position of the operator we are removing + /// @notice when we remove an operator from a quorum, we simply update the operator's index history + /// as well as any operatorIds we have to swap /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 memory operatorIdToSwap) internal { diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index e63a9b9d6..6e4b4fbe3 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -121,54 +121,7 @@ contract IndexRegistryUnitTests is Test { } } - function testGettingOperatorIndexForQuorumAtBlockNumber(uint32 numOperators, uint32 operatorToDeregister) external { - cheats.assume(numOperators > 5 && numOperators < 256); - cheats.assume(operatorToDeregister >= 0 && operatorToDeregister < numOperators); - bytes memory quorumNumbers = new bytes(1); - quorumNumbers[0] = bytes1(defaultQuorumNumber); - - for (uint256 i = 0; i < numOperators; i++) { - bytes32 operatorId = _getRandomId(i); - _registerOperator(operatorId, quorumNumbers); - } - cheats.roll(block.number + 100); - - bytes32 operator = _getRandomId(operatorToDeregister); - - uint32[] memory quorumToOperatorListIndexes = new uint32[](1); - quorumToOperatorListIndexes[0] = uint32(_generateRandomNumber(operatorToDeregister, numOperators)); - _deregisterOperator(operator, quorumNumbers, quorumToOperatorListIndexes, operatorToDeregister); - require(operatorToDeregister == indexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex(operator, defaultQuorumNumber, uint32(_generateRandomNumber(1, 100)), 0), "wrong index returned"); - - emit log_named_uint("quorumToOperatorListIndexes[0]", quorumToOperatorListIndexes[0]); - emit log_named_uint("total ops", indexRegistry.totalOperatorsForQuorum(defaultQuorumNumber)); - bytes32 swappedOperator = indexRegistry.quorumToOperatorList(defaultQuorumNumber, quorumToOperatorListIndexes[0]); - emit log_named_uint("quorumToOperatorListIndexes[0]", quorumToOperatorListIndexes[0]); - require(quorumToOperatorListIndexes[0] == indexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex(swappedOperator, defaultQuorumNumber, uint32(_generateRandomNumber(1, 100)), 0), "wrong index returned for swapped operator"); - - } - - function testGettingTotalOperatorsForQuorumAtBlockNumber(uint32 numOperators, uint32 operatorToDeregister) external { - cheats.assume(numOperators > 0 && numOperators < 256); - cheats.assume(operatorToDeregister >= 0 && operatorToDeregister < numOperators); - - bytes memory quorumNumbers = new bytes(1); - quorumNumbers[0] = bytes1(defaultQuorumNumber); - - for (uint256 i = 0; i < numOperators; i++) { - bytes32 operatorId = _getRandomId(i); - _registerOperator(operatorId, quorumNumbers); - } - cheats.roll(block.number + 100); - - bytes32 operator = _getRandomId(operatorToDeregister); - - uint32[] memory quorumToOperatorListIndexes = new uint32[](1); - quorumToOperatorListIndexes[0] = uint32(_generateRandomNumber(operatorToDeregister, numOperators)); - _deregisterOperator(operator, quorumNumbers, quorumToOperatorListIndexes, operatorToDeregister); - require(operatorToDeregister == indexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex(operator, defaultQuorumNumber, uint32(_generateRandomNumber(1, 100)), 0), "wrong index returned"); - } function _registerOperator(bytes32 operatorId, bytes memory quorumNumbers) public { cheats.startPrank(address(registryCoordinatorMock)); From 13cf421eae99abcd69b264688acaf44b252797df Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 21:45:59 +0530 Subject: [PATCH 046/110] removed quorum To Operator list --- src/contracts/middleware/IndexRegistry.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 57873cd07..7c9a0390e 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -173,6 +173,9 @@ contract IndexRegistry is IIndexRegistry, Test { /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 memory operatorIdToSwap) internal { + operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index + require(totalOperatorsForQuorum(quorumNumber) - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum") + // if the operator is not the last in the list, we must swap the last operator into their positon if(operatorId != operatorIdToSwap){ //update the swapped operator's operatorIdToIndexHistory list with a new entry, as their index has now changed From f34368250b2d059d98b2df247c5546ab41eef0d7 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 23:29:38 +0530 Subject: [PATCH 047/110] replaced length with variable --- src/contracts/middleware/IndexRegistry.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 7c9a0390e..65359c615 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -52,7 +52,7 @@ contract IndexRegistry is IIndexRegistry, Test { uint8 quorumNumber = uint8(quorumNumbers[i]); _updateOperatorIdToIndexHistory(operatorId, quorumNumber, quorumToTotalOperatorCount[quorumNumber]); _updateTotalOperatorHistory(quorumNumber); - quorumToTotalOperatorCount[quorumNumber]++; + quorumToTotalOperatorCount[quorumNumber] += 1; } } @@ -173,8 +173,8 @@ contract IndexRegistry is IIndexRegistry, Test { /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 memory operatorIdToSwap) internal { - operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index - require(totalOperatorsForQuorum(quorumNumber) - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum") + operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; + require(totalOperatorsForQuorum(quorumNumber) - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon if(operatorId != operatorIdToSwap){ From 7f8a752cbba6b6ab7d29310ff6fe78939ad82ff7 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Fri, 2 Jun 2023 23:31:43 +0530 Subject: [PATCH 048/110] minor improvements --- src/contracts/middleware/IndexRegistry.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 65359c615..5eb615fa9 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -173,7 +173,8 @@ contract IndexRegistry is IIndexRegistry, Test { /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 memory operatorIdToSwap) internal { - operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; + uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; + operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].index; require(totalOperatorsForQuorum(quorumNumber) - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon @@ -182,7 +183,7 @@ contract IndexRegistry is IIndexRegistry, Test { _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); } else { //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = uint32(block.number); } } From 27ae581b571c6a4eceaca24766ef5d7c4d874e36 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Sun, 4 Jun 2023 14:26:51 +0530 Subject: [PATCH 049/110] fixed tests expecept for deregistry one --- src/contracts/interfaces/IIndexRegistry.sol | 4 +- src/contracts/middleware/IndexRegistry.sol | 10 ++- src/test/unit/IndexRegistryUnit.t.sol | 78 +++++++++++++++------ 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 9ca167a46..18de42e8a 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -31,11 +31,11 @@ interface IIndexRegistry is IRegistry { * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param quorumToOperatorListIndexes is an array of indexes for each quorum as witnesses for the last operators to swap for each quorum + * @param operatorIdsToSwap is the list of operatorIds that are to be swapped with the last operator in the list for each quorum * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator */ - function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 globalOperatorListIndex) external; + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external; /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 5eb615fa9..404333237 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -74,6 +74,8 @@ contract IndexRegistry is IIndexRegistry, Test { _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); + + for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); uint32 indexToRemove = operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; @@ -172,10 +174,12 @@ contract IndexRegistry is IIndexRegistry, Test { /// as well as any operatorIds we have to swap /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove - function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 memory operatorIdToSwap) internal { + function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 operatorIdToSwap) internal { uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; - operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].index; - require(totalOperatorsForQuorum(quorumNumber) - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); + emit log("hehe"); + uint32 operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].index; + emit log("hehe"); + require(quorumToTotalOperatorCount[quorumNumber] - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon if(operatorId != operatorIdToSwap){ diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index 6e4b4fbe3..120e78427 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -15,6 +15,7 @@ contract IndexRegistryUnitTests is Test { RegistryCoordinatorMock registryCoordinatorMock; uint8 defaultQuorumNumber = 1; + bytes32 defaultOperator = bytes32(uint256(34)); function setUp() public { @@ -60,11 +61,11 @@ contract IndexRegistryUnitTests is Test { cheats.assume(address(registryCoordinatorMock) != nonRegistryCoordinator); // register an operator bytes memory quorumNumbers = new bytes(defaultQuorumNumber); - uint32[] memory quorumToOperatorListIndexes = new uint32[](1); + bytes32[] memory operatorIdsToSwap = new bytes32[](1); cheats.startPrank(nonRegistryCoordinator); cheats.expectRevert(bytes("IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); - indexRegistry.deregisterOperator(bytes32(0), quorumNumbers, quorumToOperatorListIndexes, 0); + indexRegistry.deregisterOperator(bytes32(0), quorumNumbers, operatorIdsToSwap, 0); cheats.stopPrank(); } @@ -81,20 +82,18 @@ contract IndexRegistryUnitTests is Test { require(indexRegistry.totalOperatorsForQuorum(1) == 2, "IndexRegistry.registerOperator: operator not registered correctly"); require(indexRegistry.totalOperatorsForQuorum(2) == 2, "IndexRegistry.registerOperator: operator not registered correctly"); - uint32[] memory quorumToOperatorListIndexes = new uint32[](2); - quorumToOperatorListIndexes[0] = 0; - quorumToOperatorListIndexes[1] = 0; + bytes32[] memory operatorIdsToSwap = new bytes32[](2); + operatorIdsToSwap[0] = operatorId2; + operatorIdsToSwap[1] = operatorId2; cheats.roll(block.number + 1); - // deregister the operatorId1, removing it from both quorum 1 and 2. + //deregister the operatorId1, removing it from both quorum 1 and 2. cheats.startPrank(address(registryCoordinatorMock)); - indexRegistry.deregisterOperator(operatorId1, quorumNumbers, quorumToOperatorListIndexes, 0); + indexRegistry.deregisterOperator(operatorId1, quorumNumbers, operatorIdsToSwap, 0); cheats.stopPrank(); require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: operator not registered correctly"); - require(indexRegistry.quorumToOperatorList(1, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); - require(indexRegistry.quorumToOperatorList(2, 0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); require(indexRegistry.globalOperatorList(0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); (uint32 toBlockNumber1, uint32 index1) = indexRegistry.operatorIdToIndexHistory(operatorId1, defaultQuorumNumber, 0); @@ -108,6 +107,39 @@ contract IndexRegistryUnitTests is Test { } + function testDeregisterOperatorWithIncorrectOperatorToSwap(bytes32 operatorId1, bytes32 operatorId2, bytes32 operatorId3) public { + cheats.assume(operatorId1 != operatorId2 && operatorId3 != operatorId2 && operatorId3 != operatorId1); + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); + + _registerOperator(operatorId1, quorumNumbers); + _registerOperator(operatorId2, quorumNumbers); + _registerOperator(operatorId3, quorumNumbers); + + bytes32[] memory operatorIdsToSwap = new bytes32[](1); + operatorIdsToSwap[0] = operatorId2; + + cheats.roll(block.number + 1); + + //deregister the operatorId1, removing it from both quorum 1 and 2. + cheats.startPrank(address(registryCoordinatorMock)); + cheats.expectRevert(bytes("IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum")); + indexRegistry.deregisterOperator(operatorId1, quorumNumbers, operatorIdsToSwap, 0); + cheats.stopPrank(); + } + + function testDeregisterOperatorWithMismatchInputLengths() public { + bytes memory quorumNumbers = new bytes(1); + bytes32[] memory operatorIdsToSwap = new bytes32[](2); + + //deregister the operatorId1, removing it from both quorum 1 and 2. + cheats.startPrank(address(registryCoordinatorMock)); + cheats.expectRevert(bytes("IndexRegistry.deregisterOperator: quorumNumbers and operatorIdsToSwap must be the same length")); + indexRegistry.deregisterOperator(defaultOperator, quorumNumbers, operatorIdsToSwap, 0); + cheats.stopPrank(); + } + function testTotalOperatorUpdatesForOneQuorum(uint8 numOperators) public { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -129,18 +161,18 @@ contract IndexRegistryUnitTests is Test { cheats.stopPrank(); } - function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 index) public { - cheats.startPrank(address(registryCoordinatorMock)); - indexRegistry.deregisterOperator(operatorId, quorumNumbers, quorumToOperatorListIndexes, index); - cheats.stopPrank(); - } - - function _getRandomId(uint256 seed) internal view returns (bytes32) { - return keccak256(abi.encodePacked(block.timestamp, seed)); - } - - function _generateRandomNumber(uint256 seed, uint256 modulus) internal view returns (uint256) { - uint256 randomNumber = uint256(keccak256(abi.encodePacked(block.timestamp, seed))); - return (randomNumber % modulus); - } + // function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers, uint32[] memory quorumToOperatorListIndexes, uint32 index) public { + // cheats.startPrank(address(registryCoordinatorMock)); + // indexRegistry.deregisterOperator(operatorId, quorumNumbers, quorumToOperatorListIndexes, index); + // cheats.stopPrank(); + // } + + // function _getRandomId(uint256 seed) internal view returns (bytes32) { + // return keccak256(abi.encodePacked(block.timestamp, seed)); + // } + + // function _generateRandomNumber(uint256 seed, uint256 modulus) internal view returns (uint256) { + // uint256 randomNumber = uint256(keccak256(abi.encodePacked(block.timestamp, seed))); + // return (randomNumber % modulus); + // } } From ba2a270c5143b769ce1ffcdd912abde7d11fb5b5 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Sun, 4 Jun 2023 14:41:47 +0530 Subject: [PATCH 050/110] fixed error in code --- src/contracts/middleware/IndexRegistry.sol | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 404333237..e7fe4df35 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -73,9 +73,6 @@ contract IndexRegistry is IIndexRegistry, Test { require(quorumNumbers.length == operatorIdsToSwap.length, "IndexRegistry.deregisterOperator: quorumNumbers and operatorIdsToSwap must be the same length"); _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); - - - for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); uint32 indexToRemove = operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; @@ -175,10 +172,7 @@ contract IndexRegistry is IIndexRegistry, Test { /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 operatorIdToSwap) internal { - uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; - emit log("hehe"); - uint32 operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistoryLength - 1].index; - emit log("hehe"); + uint32 operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; require(quorumToTotalOperatorCount[quorumNumber] - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon @@ -187,7 +181,7 @@ contract IndexRegistry is IIndexRegistry, Test { _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); } else { //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = uint32(block.number); + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); } } From a85e26a04abcf02b33212e4652aa2f2d2db70a26 Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Sun, 4 Jun 2023 14:43:28 +0530 Subject: [PATCH 051/110] fixed error in code --- src/contracts/middleware/IndexRegistry.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index e7fe4df35..fbda7a024 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -179,10 +179,10 @@ contract IndexRegistry is IIndexRegistry, Test { if(operatorId != operatorIdToSwap){ //update the swapped operator's operatorIdToIndexHistory list with a new entry, as their index has now changed _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); - } else { - //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); - } + } + //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number + operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); + } /// @notice remove an operator from the globalOperatorList From f418291be633394525c3ff14f6225804e2657bb5 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 8 Jun 2023 01:54:57 -0700 Subject: [PATCH 052/110] add index update events --- src/contracts/interfaces/IIndexRegistry.sol | 6 ++++++ src/contracts/middleware/IndexRegistry.sol | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 18de42e8a..3976d2c67 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -8,6 +8,12 @@ import "./IRegistry.sol"; * @author Layr Labs, Inc. */ interface IIndexRegistry is IRegistry { + // EVENTS + // emitted when an operator's index in at quorum operator list is updated + event QuorumIndexUpdate(bytes32 indexed operatorId, uint8 quorumNumber, uint32 newIndex); + // emitted when an operator's index in the global operator list is updated + event GlobalIndexUpdate(bytes32 indexed operatorId, uint32 newIndex); + // DATA STRUCTURES // struct used to give definitive ordering to operators at each blockNumber diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index fbda7a024..07fe21bec 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -165,6 +165,8 @@ contract IndexRegistry is IIndexRegistry, Test { OperatorIndex memory latestOperatorIndex; latestOperatorIndex.index = index; operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); + + emit QuorumIndexUpdate(operatorId, quorumNumber, index); } /// @notice when we remove an operator from a quorum, we simply update the operator's index history @@ -182,7 +184,6 @@ contract IndexRegistry is IIndexRegistry, Test { } //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); - } /// @notice remove an operator from the globalOperatorList @@ -193,6 +194,7 @@ contract IndexRegistry is IIndexRegistry, Test { if(indexToRemove != globalOperatorListLastIndex){ operatorIdToSwap = globalOperatorList[globalOperatorListLastIndex]; globalOperatorList[indexToRemove] = operatorIdToSwap; + emit GlobalIndexUpdate(operatorIdToSwap, indexToRemove); } globalOperatorList.pop(); } From 149b31eea039c0b46048ef7f6c3e684e0232423f Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Tue, 20 Jun 2023 16:57:35 -0700 Subject: [PATCH 053/110] removed quorumToTotalOperatorCount --- src/contracts/middleware/IndexRegistry.sol | 21 +++++++++---------- .../unit/DelayedWithdrawalRouterUnit.t.sol | 1 + src/test/unit/IndexRegistryUnit.t.sol | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 07fe21bec..e3a6175e1 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -15,8 +15,6 @@ contract IndexRegistry is IIndexRegistry, Test { // list of all unique registered operators bytes32[] public globalOperatorList; - // mapping of quorumNumber => list of operators registered for that quorum - mapping(uint8 => uint32) public quorumToTotalOperatorCount; // mapping of operatorId => quorumNumber => index history of that operator mapping(bytes32 => mapping(uint8 => OperatorIndex[])) public operatorIdToIndexHistory; // mapping of quorumNumber => history of numbers of unique registered operators @@ -50,9 +48,11 @@ contract IndexRegistry is IIndexRegistry, Test { for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - _updateOperatorIdToIndexHistory(operatorId, quorumNumber, quorumToTotalOperatorCount[quorumNumber]); - _updateTotalOperatorHistory(quorumNumber); - quorumToTotalOperatorCount[quorumNumber] += 1; + + //this is the would-be index of the operator being registered, the total number of operators for that quorum (which is last index + 1) + uint32 numOperators = totalOperatorsHistory[quorumNumber].length > 0 ? totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index : 0; + _updateOperatorIdToIndexHistory(operatorId, quorumNumber, numOperators); + _updateTotalOperatorHistory(quorumNumber, numOperators + 1); } } @@ -77,8 +77,7 @@ contract IndexRegistry is IIndexRegistry, Test { uint8 quorumNumber = uint8(quorumNumbers[i]); uint32 indexToRemove = operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; _processOperatorRemoval(operatorId, quorumNumber, indexToRemove, operatorIdsToSwap[i]); - _updateTotalOperatorHistory(quorumNumber); - quorumToTotalOperatorCount[quorumNumber]--; + _updateTotalOperatorHistory(quorumNumber, totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index - 1); } } @@ -127,7 +126,7 @@ contract IndexRegistry is IIndexRegistry, Test { } function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32){ - return quorumToTotalOperatorCount[quorumNumber]; + return totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index; } @@ -137,7 +136,7 @@ contract IndexRegistry is IIndexRegistry, Test { } - function _updateTotalOperatorHistory(uint8 quorumNumber) internal { + function _updateTotalOperatorHistory(uint8 quorumNumber, uint32 numOperators) internal { uint256 totalOperatorsHistoryLength = totalOperatorsHistory[quorumNumber].length; @@ -148,7 +147,7 @@ contract IndexRegistry is IIndexRegistry, Test { OperatorIndex memory totalOperatorUpdate; // In the case of totalOperatorsHistory, the index parameter is the number of operators in the quorum - totalOperatorUpdate.index = quorumToTotalOperatorCount[quorumNumber]; + totalOperatorUpdate.index = numOperators; totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } @@ -175,7 +174,7 @@ contract IndexRegistry is IIndexRegistry, Test { /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 operatorIdToSwap) internal { uint32 operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; - require(quorumToTotalOperatorCount[quorumNumber] - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); + require(totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon if(operatorId != operatorIdToSwap){ diff --git a/src/test/unit/DelayedWithdrawalRouterUnit.t.sol b/src/test/unit/DelayedWithdrawalRouterUnit.t.sol index 64cf8b5e9..a533aae91 100644 --- a/src/test/unit/DelayedWithdrawalRouterUnit.t.sol +++ b/src/test/unit/DelayedWithdrawalRouterUnit.t.sol @@ -122,6 +122,7 @@ contract DelayedWithdrawalRouterUnitTests is Test { } function testCreateDelayedWithdrawalZeroAddress(address podOwner) external { + cheats.assume(podOwner != address(proxyAdmin)); uint224 delayedWithdrawalAmount = 0; address podAddress = address(eigenPodManagerMock.getPod(podOwner)); cheats.startPrank(podAddress); diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index 120e78427..61c340ceb 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -39,10 +39,10 @@ contract IndexRegistryUnitTests is Test { cheats.stopPrank(); require(indexRegistry.globalOperatorList(0) == operatorId, "IndexRegistry.registerOperator: operator not registered correctly"); - require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: operator not registered correctly"); - require(indexRegistry.totalOperatorsForQuorum(1) == 1, "IndexRegistry.registerOperator: operator not registered correctly"); + require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: total operators not updated correctly"); + require(indexRegistry.totalOperatorsForQuorum(1) == 1, "IndexRegistry.registerOperator: total operators for quorum not updated correctly"); (uint32 index, uint32 toBlockNumber) = indexRegistry.operatorIdToIndexHistory(operatorId, 1, 0); - require(index == 0, "IndexRegistry.registerOperator: operator not registered correctly"); + require(index == 0, "IndexRegistry.registerOperator: index not 0"); require(toBlockNumber == 0, "block number should not be set"); } From b3adbdd59cf75c206db1ac8978e18b06151a9c3d Mon Sep 17 00:00:00 2001 From: Siddhartha Jagannath Date: Tue, 20 Jun 2023 21:38:40 -0700 Subject: [PATCH 054/110] made two sloads into 1 --- src/contracts/middleware/IndexRegistry.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index e3a6175e1..2a47c8f12 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -50,7 +50,8 @@ contract IndexRegistry is IIndexRegistry, Test { uint8 quorumNumber = uint8(quorumNumbers[i]); //this is the would-be index of the operator being registered, the total number of operators for that quorum (which is last index + 1) - uint32 numOperators = totalOperatorsHistory[quorumNumber].length > 0 ? totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index : 0; + uint256 quorumHistoryLength = totalOperatorsHistory[quorumNumber].length; + uint32 numOperators = quorumHistoryLength > 0 ? totalOperatorsHistory[quorumNumber][quorumHistoryLength - 1].index : 0; _updateOperatorIdToIndexHistory(operatorId, quorumNumber, numOperators); _updateTotalOperatorHistory(quorumNumber, numOperators + 1); } From d34b1bcb1ef6707a2aac02eb4e75388040e1859a Mon Sep 17 00:00:00 2001 From: gpsanant Date: Wed, 21 Jun 2023 11:36:07 -0700 Subject: [PATCH 055/110] make registry coordinator mock work --- src/test/mocks/RegistryCoordinatorMock.sol | 4 ++-- src/test/unit/VoteWeigherBaseUnit.t.sol | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 3eb33926d..16e1b7779 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -16,10 +16,10 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { function getFromTaskNumberForOperator(address operator) external view returns (uint32){} /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data - function registerOperator(uint8[] memory quorumNumbers, bytes calldata) external returns (bytes32){} + function registerOperator(bytes memory quorumNumbers, bytes calldata) external {} /// @notice deregisters the sender with additional bytes for registry interaction data - function deregisterOperator(bytes calldata) external returns (bytes32){} + function deregisterOperator(bytes calldata) external {} function numRegistries() external view returns (uint256){} diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index ce0eb69c4..4c822b7fb 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -65,7 +65,9 @@ contract VoteWeigherBaseUnitTests is Test { function setUp() virtual public { proxyAdmin = new ProxyAdmin(); - pauserRegistry = new PauserRegistry(pauser, unpauser); + address[] memory pausers = new address[](1); + pausers[0] = pauser; + pauserRegistry = new PauserRegistry(pausers, unpauser); StrategyManagerMock strategyManagerMock = new StrategyManagerMock(); delegationMock = new DelegationMock(); From 8e82986faefedc03fca235188ab33cdfc6cd4bd2 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:33:43 -0700 Subject: [PATCH 056/110] removed merge ugliness --- src/contracts/interfaces/IBLSPubkeyRegistry.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/contracts/interfaces/IBLSPubkeyRegistry.sol b/src/contracts/interfaces/IBLSPubkeyRegistry.sol index 5d1e46c26..65eeeccf5 100644 --- a/src/contracts/interfaces/IBLSPubkeyRegistry.sol +++ b/src/contracts/interfaces/IBLSPubkeyRegistry.sol @@ -34,10 +34,6 @@ interface IBLSPubkeyRegistry is IRegistry { } /** -<<<<<<< HEAD -======= - ->>>>>>> multiquorums * @notice Registers the `operator`'s pubkey for the specified `quorumNumbers`. * @param operator The address of the operator to register. * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. From 3fc195b23be66ac823f3e13550a45bf55ecc4887 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:38:40 -0700 Subject: [PATCH 057/110] made IndexRegistry comment changes and propagated them --- src/contracts/interfaces/IIndexRegistry.sol | 14 +++++++++++++- src/contracts/middleware/IndexRegistry.sol | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 3976d2c67..ae4112dd6 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -30,6 +30,11 @@ interface IIndexRegistry is IRegistry { * @param operatorId is the id of the operator that is being registered * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already registered */ function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external; @@ -37,9 +42,16 @@ interface IIndexRegistry is IRegistry { * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param operatorIdsToSwap is the list of operatorIds that are to be swapped with the last operator in the list for each quorum + * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` + * they will be swapped the operators current index * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator + * @dev Preconditions: + * 1) `quorumNumbers` has no duplicates + * 2) `quorumNumbers.length` != 0 + * 3) `quorumNumbers` is ordered in ascending order + * 4) the operator is not already deregistered + * 5) `quorumNumbers` is the same as the parameter use when registering */ function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external; diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 2a47c8f12..7faad5409 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -56,11 +56,13 @@ contract IndexRegistry is IIndexRegistry, Test { _updateTotalOperatorHistory(quorumNumber, numOperators + 1); } } - + /** * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for + * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` + * they will be swapped the operators current index * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator * @dev Preconditions: From 3304f12016c0455644619133f670db950103375f Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:42:06 -0700 Subject: [PATCH 058/110] update operator status and add not registered check --- src/contracts/interfaces/IRegistryCoordinator.sol | 7 ++++--- src/contracts/middleware/BLSIndexRegistryCoordinator.sol | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 53a047994..6ba99b4d2 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -9,9 +9,10 @@ interface IRegistryCoordinator { // DATA STRUCTURES enum OperatorStatus { - // default is DEREGISTERED - DEREGISTERED, - REGISTERED + // default is NEVER_REGISTERED + NEVER_REGISTERED, + REGISTERED, + DEREGISTERED } /** diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 6eb5ca105..4aa039f9c 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -112,7 +112,8 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } function _registerOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { - // TODO: check that the sender is not already registered + // check that the sender is not already registered + require(operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator: operator already registered"); // get the quorum bitmap from the quorum numbers uint256 quorumBitmap = BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers); From 5b85ce5b16a675682779c98fa3bc92a2eb7d491f Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:47:28 -0700 Subject: [PATCH 059/110] updated IVoteWeigher to allow constants --- src/contracts/interfaces/IVoteWeigher.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index 23d5c7a7c..a3b151aca 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -22,6 +22,11 @@ interface IVoteWeigher { uint96 multiplier; } + /// @notice Constant used as a divisor in calculating weights. + function WEIGHTING_DIVISOR() external pure returns (uint256); + /// @notice Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping. + function MAX_WEIGHING_FUNCTION_LENGTH() external pure returns (uint8); + /// @notice Returns the strategy manager contract. function strategyManager() external view returns (IStrategyManager); /// @notice Returns the stake registry contract. From f616be4a4eacaadcc5b99c15c5d296736a8bd284 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:48:01 -0700 Subject: [PATCH 060/110] remove max length from IVoteWeigher --- src/contracts/interfaces/IVoteWeigher.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index a3b151aca..bc2c54ed2 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -24,8 +24,6 @@ interface IVoteWeigher { /// @notice Constant used as a divisor in calculating weights. function WEIGHTING_DIVISOR() external pure returns (uint256); - /// @notice Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping. - function MAX_WEIGHING_FUNCTION_LENGTH() external pure returns (uint8); /// @notice Returns the strategy manager contract. function strategyManager() external view returns (IStrategyManager); From 8c37e88fcacdbfffcddc8953a38292171928ea99 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:50:41 -0700 Subject: [PATCH 061/110] update IVoteWeigher.modifyStrategyWeights comment --- src/contracts/interfaces/IVoteWeigher.sol | 7 ++++--- src/contracts/middleware/VoteWeigherBase.sol | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index bc2c54ed2..c50f3f7fd 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -76,9 +76,10 @@ interface IVoteWeigher { /** * @notice This function is used for modifying the weights of strategies that are already in the - * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber. - * @param strategyIndices is a correctness-check input -- the supplied values must match the indices of the - * strategiesToModifyWeightsOf in strategiesConsideredAndMultipliers[quorumNumber] + * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber + * @param quorumNumber is the quorum number to change the strategy for + * @param strategyIndices are the indices of the strategies to change + * @param newMultipliers are the new multipliers for the strategies */ function modifyStrategyWeights( uint8 quorumNumber, diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index 3b1db6015..ee56dda2a 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -130,9 +130,10 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { /** * @notice This function is used for modifying the weights of strategies that are already in the - * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber. - * @param strategyIndices is a correctness-check input -- the supplied values must match the indices of the - * strategiesToModifyWeightsOf in strategiesConsideredAndMultipliers[quorumNumber] + * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber + * @param quorumNumber is the quorum number to change the strategy for + * @param strategyIndices are the indices of the strategies to change + * @param newMultipliers are the new multipliers for the strategies */ function modifyStrategyWeights( uint8 quorumNumber, From fa1d1cf99e47ca16a6d5422be8ea794fe8f3a494 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 09:51:43 -0700 Subject: [PATCH 062/110] index operatorId in StakeRegistry --- src/contracts/middleware/StakeRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index aca98527f..93309ebd6 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -15,7 +15,7 @@ contract StakeRegistry is StakeRegistryStorage { // EVENTS /// @notice emitted whenever the stake of `operator` is updated event StakeUpdate( - bytes32 operatorId, + bytes32 indexed operatorId, uint8 quorumNumber, uint96 stake ); From 8673c2394e750d95ab4d167c7beab58593768058 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 10:04:36 -0700 Subject: [PATCH 063/110] make registry coordinator immutable, and set in stake registry initializer --- src/contracts/interfaces/IVoteWeigher.sol | 2 +- src/contracts/middleware/BLSIndexRegistryCoordinator.sol | 5 +++-- src/contracts/middleware/BLSPubkeyRegistry.sol | 2 +- src/contracts/middleware/IndexRegistry.sol | 4 ++-- src/contracts/middleware/StakeRegistry.sol | 6 +++++- src/contracts/middleware/VoteWeigherBase.sol | 2 +- src/test/harnesses/StakeRegistryHarness.sol | 3 ++- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index c50f3f7fd..30e49ac17 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -76,7 +76,7 @@ interface IVoteWeigher { /** * @notice This function is used for modifying the weights of strategies that are already in the - * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber + * mapping strategiesConsideredAndMultipliers for a specific * @param quorumNumber is the quorum number to change the strategy for * @param strategyIndices are the indices of the strategies to change * @param newMultipliers are the new multipliers for the strategies diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 4aa039f9c..75ac32e39 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -36,13 +36,14 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { function initialize( uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers - ) external override initializer { + ) external initializer { // the stake registry is this contract itself registries.push(address(this)); registries.push(address(blsPubkeyRegistry)); registries.push(address(indexRegistry)); - StakeRegistry._initialize(_minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); + // this contract is the registry coordinator for the stake registry + StakeRegistry._initialize(IRegistryCoordinator(address(this)), _minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); } /// @notice Returns task number from when `operator` has been registered. diff --git a/src/contracts/middleware/BLSPubkeyRegistry.sol b/src/contracts/middleware/BLSPubkeyRegistry.sol index 31f34009c..98fc14f3f 100644 --- a/src/contracts/middleware/BLSPubkeyRegistry.sol +++ b/src/contracts/middleware/BLSPubkeyRegistry.sol @@ -19,7 +19,7 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { /// @notice the current aggregate pubkey of all operators registered in this contract, regardless of quorum BN254.G1Point public globalApk; /// @notice the registry coordinator contract - IRegistryCoordinator public registryCoordinator; + IRegistryCoordinator public immutable registryCoordinator; /// @notice the BLSPublicKeyCompendium contract against which pubkey ownership is checked IBLSPublicKeyCompendium public immutable pubkeyCompendium; diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 7faad5409..5b30d77d7 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -10,7 +10,7 @@ import "forge-std/Test.sol"; contract IndexRegistry is IIndexRegistry, Test { - IRegistryCoordinator public registryCoordinator; + IRegistryCoordinator public immutable registryCoordinator; // list of all unique registered operators bytes32[] public globalOperatorList; @@ -56,7 +56,7 @@ contract IndexRegistry is IIndexRegistry, Test { _updateTotalOperatorHistory(quorumNumber, numOperators + 1); } } - + /** * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. * @param operatorId is the id of the operator that is being deregistered diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 93309ebd6..db59a2ec4 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -34,16 +34,20 @@ contract StakeRegistry is StakeRegistryStorage { * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with */ function initialize( + IRegistryCoordinator _registryCoordinator, uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers ) external virtual initializer { - _initialize(_minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); + _initialize(_registryCoordinator, _minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); } function _initialize( + IRegistryCoordinator _registryCoordinator, uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers ) internal virtual onlyInitializing { + // store the coordinator + registryCoordinator = _registryCoordinator; // sanity check lengths require(_minimumStakeForQuorum.length == _quorumStrategiesConsideredAndMultipliers.length, "Registry._initialize: minimumStakeForQuorum length mismatch"); diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index ee56dda2a..7b8bed340 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -130,7 +130,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { /** * @notice This function is used for modifying the weights of strategies that are already in the - * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber + * mapping strategiesConsideredAndMultipliers for a specific * @param quorumNumber is the quorum number to change the strategy for * @param strategyIndices are the indices of the strategies to change * @param newMultipliers are the new multipliers for the strategies diff --git a/src/test/harnesses/StakeRegistryHarness.sol b/src/test/harnesses/StakeRegistryHarness.sol index 241ca0566..c0ab81932 100644 --- a/src/test/harnesses/StakeRegistryHarness.sol +++ b/src/test/harnesses/StakeRegistryHarness.sol @@ -8,7 +8,8 @@ contract StakeRegistryHarness is StakeRegistry { constructor( IStrategyManager _strategyManager, IServiceManager _serviceManager - ) StakeRegistry(_strategyManager, _serviceManager) {} + ) StakeRegistry(_strategyManager, _serviceManager) { + } function recordOperatorStakeUpdate(bytes32 operatorId, uint8 quorumNumber, OperatorStakeUpdate memory operatorStakeUpdate) external returns(uint96) { return _recordOperatorStakeUpdate(operatorId, quorumNumber, operatorStakeUpdate); From ad65b84c82f9fe7ee378a51634af79109f35b89e Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 10:06:17 -0700 Subject: [PATCH 064/110] remove test import --- src/contracts/middleware/IndexRegistry.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 5b30d77d7..d180af186 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -5,10 +5,8 @@ pragma solidity =0.8.12; import "../interfaces/IIndexRegistry.sol"; import "../interfaces/IRegistryCoordinator.sol"; import "../libraries/BN254.sol"; -import "forge-std/Test.sol"; - -contract IndexRegistry is IIndexRegistry, Test { +contract IndexRegistry is IIndexRegistry { IRegistryCoordinator public immutable registryCoordinator; From 3d5bfdf6e5389bb4c03bc0405f36f5951bcddf05 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 10:09:22 -0700 Subject: [PATCH 065/110] hyphenate --- src/contracts/middleware/BLSIndexRegistryCoordinator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 75ac32e39..940a912d8 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -20,7 +20,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { mapping(bytes32 => uint256) public operatorIdToQuorumBitmap; /// @notice the mapping from operator's address to the operator struct mapping(address => Operator) public operators; - /// @notice the dynamic length array of the registries this coordinator is coordinating + /// @notice the dynamic-length array of the registries this coordinator is coordinating address[] public registries; constructor( From e2108d632d39630c83095a29d5f29b54e9854e93 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 11:17:17 -0700 Subject: [PATCH 066/110] remove revert on strategiesConsideredAndMultipliersLength --- src/contracts/interfaces/IVoteWeigher.sol | 9 +-------- src/contracts/middleware/VoteWeigherBase.sol | 7 ++----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index 30e49ac17..2edbcfd1a 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -87,13 +87,6 @@ interface IVoteWeigher { uint96[] calldata newMultipliers ) external; - /** - * @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. - * @dev Reverts if `quorumNumber` < `NUMBER_OF_QUORUMS`, i.e. the input is out of bounds. - */ + /// @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) external view returns (uint256); - - - - } diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index 7b8bed340..de8204939 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -156,11 +156,8 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { } } - /** - * @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. - * @dev Reverts if `quorumNumber` < `NUMBER_OF_QUORUMS`, i.e. the input is out of bounds. - */ - function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) public view validQuorumNumber(quorumNumber) returns (uint256) { + /// @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. + function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) public view returns (uint256) { return strategiesConsideredAndMultipliers[quorumNumber].length; } From e9b878193f2dc85ed78710dfcdf79a26bdcfc5d4 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 11:19:39 -0700 Subject: [PATCH 067/110] comment disabled function --- src/contracts/middleware/BLSIndexRegistryCoordinator.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 940a912d8..089f1cac8 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -61,6 +61,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { return registries.length; } + /// @notice disabled function on the StakeRegistry because this is the registry coordinator function registerOperator(address, bytes32, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.registerOperator: cannot use overrided StakeRegistry.registerOperator on BLSIndexRegistryCoordinator"); } @@ -86,6 +87,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { _registerOperator(msg.sender, quorumNumbers, pubkey); } + /// @notice disabled function on the StakeRegistry because this is the registry coordinator function deregisterOperator(address, bytes32, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); } From 4f13cb5ce588cef908b9218011843ce63177706c Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 12:53:13 -0700 Subject: [PATCH 068/110] add more to registry coordinator interface --- .../interfaces/IRegistryCoordinator.sol | 6 ++++++ .../BLSIndexRegistryCoordinator.sol | 21 ++++++++++++------- src/test/mocks/RegistryCoordinatorMock.sol | 2 ++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 6ba99b4d2..2134cf805 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -27,9 +27,15 @@ interface IRegistryCoordinator { OperatorStatus status; } + /// @notice Returns the operator struct for the given `operator` + function getOperator(address operator) external view returns (Operator memory); + /// @notice Returns the operatorId for the given `operator` function getOperatorId(address operator) external view returns (bytes32); + /// @notice Returns the quorum bitmap for the given `operator` + function operatorIdToQuorumBitmap(bytes32 operatorId) external view returns (uint256); + /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32); diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 089f1cac8..2114bbd51 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -19,7 +19,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { /// @notice the mapping from operator's operatorId to the bitmap of quorums they are registered for mapping(bytes32 => uint256) public operatorIdToQuorumBitmap; /// @notice the mapping from operator's address to the operator struct - mapping(address => Operator) public operators; + mapping(address => Operator) internal _operators; /// @notice the dynamic-length array of the registries this coordinator is coordinating address[] public registries; @@ -48,12 +48,17 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32) { - return operators[operator].fromTaskNumber; + return _operators[operator].fromTaskNumber; + } + + /// @notice Returns the operator struct for the given `operator` + function getOperator(address operator) external view returns (Operator memory) { + return _operators[operator]; } /// @notice Returns the operatorId for the given `operator` function getOperatorId(address operator) external view returns (bytes32) { - return operators[operator].operatorId; + return _operators[operator].operatorId; } /// @notice Returns the number of registries @@ -116,7 +121,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { function _registerOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { // check that the sender is not already registered - require(operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator: operator already registered"); + require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator: operator already registered"); // get the quorum bitmap from the quorum numbers uint256 quorumBitmap = BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers); @@ -135,7 +140,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { operatorIdToQuorumBitmap[operatorId] = quorumBitmap; // set the operator struct - operators[operator] = Operator({ + _operators[operator] = Operator({ operatorId: operatorId, fromTaskNumber: serviceManager.taskNumber(), status: OperatorStatus.REGISTERED @@ -143,10 +148,10 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } function _deregisterOperator(address operator, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { - require(operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperator: operator is not registered"); + require(_operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperator: operator is not registered"); // get the operatorId of the operator - bytes32 operatorId = operators[operator].operatorId; + bytes32 operatorId = _operators[operator].operatorId; require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperator: operatorId does not match pubkey hash"); // get the quorumNumbers of the operator @@ -162,6 +167,6 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { _deregisterOperator(operator, operatorId, quorumNumbers); // set the status of the operator to DEREGISTERED - operators[operator].status = OperatorStatus.DEREGISTERED; + _operators[operator].status = OperatorStatus.DEREGISTERED; } } \ No newline at end of file diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 16e1b7779..e0189bfd3 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -9,6 +9,8 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { /// @notice Returns the bitmap of the quroums the operator is registered for. function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} + function getOperator(address operator) external view returns (Operator memory){} + /// @notice Returns the stored id for the specified `operator`. function getOperatorId(address operator) external view returns (bytes32){} From a5ec4e86e040c1ff5050b486ac2362b0d3224455 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 12:57:39 -0700 Subject: [PATCH 069/110] rename totalStakeHistory --- src/contracts/middleware/StakeRegistry.sol | 40 +++++++++---------- .../middleware/StakeRegistryStorage.sol | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index db59a2ec4..7ea70e4b0 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -29,7 +29,7 @@ contract StakeRegistry is StakeRegistryStorage { } /** - * @notice Adds empty first entries to the dynamic arrays `totalStakeHistory` and `totalOperatorsHistory`, + * @notice Adds empty first entries to the dynamic arrays `_totalStakeHistory`, * to record an initial condition of zero operators with zero total stake. * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with */ @@ -77,12 +77,12 @@ contract StakeRegistry is StakeRegistryStorage { } /** - * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. + * @notice Returns the `index`-th entry in the dynamic array of total stake, `_totalStakeHistory` for quorum `quorumNumber`. * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. + * @param index Array index for lookup, within the dynamic array `_totalStakeHistory[quorumNumber]`. */ function getTotalStakeUpdateForQuorumFromIndex(uint8 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) { - return totalStakeHistory[quorumNumber][index]; + return _totalStakeHistory[quorumNumber][index]; } /** @@ -107,14 +107,14 @@ contract StakeRegistry is StakeRegistryStorage { /** * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the - * `totalStakeHistory[quorumNumber]` array if it was the stake at `blockNumber`. Reverts otherwise. + * `_totalStakeHistory[quorumNumber]` array if it was the stake at `blockNumber`. Reverts otherwise. * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. + * @param index Array index for lookup, within the dynamic array `_totalStakeHistory[quorumNumber]`. * @param blockNumber Block number to make sure the stake is from. * @dev Function will revert if `index` is out-of-bounds. */ function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96) { - OperatorStakeUpdate memory totalStakeUpdate = totalStakeHistory[quorumNumber][index]; + OperatorStakeUpdate memory totalStakeUpdate = _totalStakeHistory[quorumNumber][index]; _validateOperatorStakeUpdateAtBlockNumber(totalStakeUpdate, blockNumber); return totalStakeUpdate.stake; } @@ -147,11 +147,11 @@ contract StakeRegistry is StakeRegistryStorage { return operatorStakeUpdate.stake; } - /// @notice Returns the stake weight from the latest entry in `totalStakeHistory` for quorum `quorumNumber`. - /// @dev Will revert if `totalStakeHistory[quorumNumber]` is empty. + /// @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`. + /// @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty. function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) { // no chance of underflow / error in next line, since an empty entry is pushed in the constructor - return totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake; + return _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake; } function getLengthOfOperatorIdStakeHistoryForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint256) { @@ -159,7 +159,7 @@ contract StakeRegistry is StakeRegistryStorage { } function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) { - return totalStakeHistory[quorumNumber].length; + return _totalStakeHistory[quorumNumber].length; } /** @@ -303,7 +303,7 @@ contract StakeRegistry is StakeRegistryStorage { if (quorumBitmaps[i] >> quorumNumber & 1 == 1) { // if the total stake has not been loaded yet, load it if (totalStakeUpdate.updateBlockNumber == 0) { - totalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; + totalStakeUpdate = _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1]; } bytes32 operatorId = operatorIds[i]; // update the operator's stake based on current state @@ -377,11 +377,11 @@ contract StakeRegistry is StakeRegistryStorage { // check if minimum requirement has been met, will be 0 if not require(stake != 0, "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); // add operator stakes to total stake before update (in memory) - uint256 totalStakeHistoryLength = totalStakeHistory[quorumNumber].length; - if (totalStakeHistoryLength != 0) { + uint256 _totalStakeHistoryLength = _totalStakeHistory[quorumNumber].length; + if (_totalStakeHistoryLength != 0) { // only add the stake if there is a previous total stake // overwrite `stake` variable - stake += totalStakeHistory[quorumNumber][totalStakeHistoryLength - 1].stake; + stake += _totalStakeHistory[quorumNumber][_totalStakeHistoryLength - 1].stake; } _newTotalStakeUpdate.stake = stake; // update storage of total stake @@ -423,7 +423,7 @@ contract StakeRegistry is StakeRegistryStorage { uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, _operatorStakeUpdate); // subtract the amounts staked by the operator that is getting deregistered from the total stake before deregistration // copy latest totalStakes to memory - _newTotalStakeUpdate.stake = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake - stakeBeforeUpdate; + _newTotalStakeUpdate.stake = _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake - stakeBeforeUpdate; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); @@ -487,12 +487,12 @@ contract StakeRegistry is StakeRegistryStorage { /// @notice Records that the `totalStake` is now equal to the input param @_totalStake function _recordTotalStakeUpdate(uint8 quorumNumber, OperatorStakeUpdate memory _totalStake) internal { - uint256 totalStakeHistoryLength = totalStakeHistory[quorumNumber].length; - if (totalStakeHistoryLength != 0) { - totalStakeHistory[quorumNumber][totalStakeHistoryLength - 1].nextUpdateBlockNumber = uint32(block.number); + uint256 _totalStakeHistoryLength = _totalStakeHistory[quorumNumber].length; + if (_totalStakeHistoryLength != 0) { + _totalStakeHistory[quorumNumber][_totalStakeHistoryLength - 1].nextUpdateBlockNumber = uint32(block.number); } _totalStake.updateBlockNumber = uint32(block.number); - totalStakeHistory[quorumNumber].push(_totalStake); + _totalStakeHistory[quorumNumber].push(_totalStake); } /// @notice Validates that the `operatorStake` was accurate at the given `blockNumber` diff --git a/src/contracts/middleware/StakeRegistryStorage.sol b/src/contracts/middleware/StakeRegistryStorage.sol index 461ff93d2..8becd5063 100644 --- a/src/contracts/middleware/StakeRegistryStorage.sol +++ b/src/contracts/middleware/StakeRegistryStorage.sol @@ -22,7 +22,7 @@ abstract contract StakeRegistryStorage is VoteWeigherBase, IStakeRegistry { uint96[256] public minimumStakeForQuorum; /// @notice array of the history of the total stakes for each quorum -- marked as internal since getTotalStakeFromIndex is a getter for this - OperatorStakeUpdate[][256] internal totalStakeHistory; + OperatorStakeUpdate[][256] internal _totalStakeHistory; /// @notice mapping from operator's operatorId to the history of their stake updates mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public operatorIdToStakeHistory; From 2ca877a7a6c83b75d834206ceeabb3d66c03d842 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:12:59 -0700 Subject: [PATCH 070/110] update stake registry comment --- src/contracts/middleware/StakeRegistry.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 7ea70e4b0..583bd6871 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -8,7 +8,12 @@ import "../interfaces/IRegistryCoordinator.sol"; import "./StakeRegistryStorage.sol"; /** - * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quroums. + * @title A `Registry` that keeps track of stakes of operators for up to 256 quroums. + * Specifically, it keeps track of + * 1) The stake of each operator in all the quorums they are apart of for block ranges + * 2) The total stake of all operators in each quorum for block ranges + * 3) The minimum stake required to register for each quorum + * It allows an additional functionality (in addition to registering and deregistering) to update the stake of an operator. * @author Layr Labs, Inc. */ contract StakeRegistry is StakeRegistryStorage { From c88b68b5642335e11ec03289c9035a466cba2d17 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:17:53 -0700 Subject: [PATCH 071/110] fix iindexregistry comments --- src/contracts/middleware/IndexRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index d180af186..f307305f5 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -30,7 +30,7 @@ contract IndexRegistry is IIndexRegistry { } /** - * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. + * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. * @param operatorId is the id of the operator that is being registered * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator From 34e236280e94f54c46c1a47c5e85a83c8b5dceec Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:18:07 -0700 Subject: [PATCH 072/110] fix iindexregistry comments --- src/contracts/middleware/IndexRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index f307305f5..65f600467 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -56,7 +56,7 @@ contract IndexRegistry is IIndexRegistry { } /** - * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. + * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. * @param operatorId is the id of the operator that is being deregistered * @param quorumNumbers is the quorum numbers the operator is deregistered for * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` From b6abc46f2986de3de4965a8a4ad95d0baa573fb7 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:26:46 -0700 Subject: [PATCH 073/110] top level BLSIndexRegistryCoordinator comment --- src/contracts/middleware/BLSIndexRegistryCoordinator.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 2114bbd51..2c1252160 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -9,6 +9,14 @@ import "../libraries/BytesArrayBitmaps.sol"; import "./StakeRegistry.sol"; +/** + * @title A `RegistryCoordinator` that has three registries: + * 1) a `StakeRegistry` that keeps track of operators' stakes (this is actually the contract itself, via inheritance) + * 2) a `BLSPubkeyRegistry` that keeps track of operators' BLS public keys and aggregate BLS public keys for each quorum + * 3) an `IndexRegistry` that keeps track of an ordered list of operators for each quorum + * + * @author Layr Labs, Inc. + */ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { using BN254 for BN254.G1Point; From 0c907f468e08792b5d0db933ba1a12c64c4c9d32 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:33:19 -0700 Subject: [PATCH 074/110] modified register/deregister function names in registrycoordinator --- .../interfaces/IRegistryCoordinator.sol | 4 ++-- .../middleware/BLSIndexRegistryCoordinator.sol | 18 +++++++++--------- src/test/mocks/RegistryCoordinatorMock.sol | 10 ++++------ 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 2134cf805..6124705a3 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -46,8 +46,8 @@ interface IRegistryCoordinator { function numRegistries() external view returns (uint256); /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data - function registerOperator(bytes memory quorumNumbers, bytes calldata) external; + function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata) external; /// @notice deregisters the sender with additional bytes for registry interaction data - function deregisterOperator(bytes calldata) external; + function deregisterOperatorWithCoordinator(bytes calldata) external; } diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 2c1252160..2419d1458 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -84,11 +84,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for * @param registrationData is the data that is decoded to get the operator's registration information */ - function registerOperator(bytes calldata quorumNumbers, bytes calldata registrationData) external { + function registerOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata registrationData) external { // get the operator's BLS public key BN254.G1Point memory pubkey = abi.decode(registrationData, (BN254.G1Point)); // call internal function to register the operator - _registerOperator(msg.sender, quorumNumbers, pubkey); + _registerOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey); } /** @@ -97,7 +97,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { * @param pubkey is the BLS public key of the operator */ function registerOperator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external { - _registerOperator(msg.sender, quorumNumbers, pubkey); + _registerOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey); } /// @notice disabled function on the StakeRegistry because this is the registry coordinator @@ -109,12 +109,12 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { * @notice Deregisters the operator from the middleware * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information */ - function deregisterOperator(bytes calldata deregistrationData) external { + function deregisterOperatorWithCoordinator(bytes calldata deregistrationData) external { // get the operator's deregisteration information (BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) = abi.decode(deregistrationData, (BN254.G1Point, bytes32[], uint32)); // call internal function to deregister the operator - _deregisterOperator(msg.sender, pubkey, operatorIdsToSwap, globalOperatorListIndex); + _deregisterOperatorWithCoordinator(msg.sender, pubkey, operatorIdsToSwap, globalOperatorListIndex); } /** @@ -123,11 +123,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { * @param operatorIdsToSwap is the list of the operator ids that the should swap for the deregistering operator's index * @param globalOperatorListIndex is the operator's index in the global operator list in the IndexRegistry */ - function deregisterOperator(BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external { - _deregisterOperator(msg.sender, pubkey, operatorIdsToSwap, globalOperatorListIndex); + function deregisterOperatorWithCoordinator(BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external { + _deregisterOperatorWithCoordinator(msg.sender, pubkey, operatorIdsToSwap, globalOperatorListIndex); } - function _registerOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { + function _registerOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { // check that the sender is not already registered require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator: operator already registered"); @@ -155,7 +155,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { }); } - function _deregisterOperator(address operator, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { + function _deregisterOperatorWithCoordinator(address operator, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { require(_operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperator: operator is not registered"); // get the operatorId of the operator diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index e0189bfd3..96ab2fe6e 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -17,13 +17,11 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32){} - /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data - function registerOperator(bytes memory quorumNumbers, bytes calldata) external {} - - /// @notice deregisters the sender with additional bytes for registry interaction data - function deregisterOperator(bytes calldata) external {} - function numRegistries() external view returns (uint256){} function registries(uint256) external view returns (address){} + + function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata) external {} + + function deregisterOperatorWithCoordinator(bytes calldata) external {} } From 4b8f0edb7ec925effd8c53f0bfd74e7f255a5d64 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:40:33 -0700 Subject: [PATCH 075/110] preconditions comment --- src/contracts/interfaces/IBLSPubkeyRegistry.sol | 4 ++-- src/contracts/interfaces/IIndexRegistry.sol | 4 ++-- src/contracts/interfaces/IStakeRegistry.sol | 4 ++-- src/contracts/middleware/IndexRegistry.sol | 4 ++-- src/contracts/middleware/StakeRegistry.sol | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/contracts/interfaces/IBLSPubkeyRegistry.sol b/src/contracts/interfaces/IBLSPubkeyRegistry.sol index 65eeeccf5..d5b41c9c8 100644 --- a/src/contracts/interfaces/IBLSPubkeyRegistry.sol +++ b/src/contracts/interfaces/IBLSPubkeyRegistry.sol @@ -39,7 +39,7 @@ interface IBLSPubkeyRegistry is IRegistry { * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. * @param pubkey The operator's BLS public key. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -53,7 +53,7 @@ interface IBLSPubkeyRegistry is IRegistry { * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @param pubkey The public key of the operator. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index ae4112dd6..3aa71b60f 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -30,7 +30,7 @@ interface IIndexRegistry is IRegistry { * @param operatorId is the id of the operator that is being registered * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -46,7 +46,7 @@ interface IIndexRegistry is IRegistry { * they will be swapped the operators current index * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 57129aace..4182ad60d 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -27,7 +27,7 @@ interface IStakeRegistry is IRegistry { * @param operatorId The id of the operator to register. * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -41,7 +41,7 @@ interface IStakeRegistry is IRegistry { * @param operatorId The id of the operator to deregister. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 65f600467..d08cb9255 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -34,7 +34,7 @@ contract IndexRegistry is IIndexRegistry { * @param operatorId is the id of the operator that is being registered * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -63,7 +63,7 @@ contract IndexRegistry is IIndexRegistry { * they will be swapped the operators current index * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 583bd6871..59d612d87 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -259,7 +259,7 @@ contract StakeRegistry is StakeRegistryStorage { * @param operatorId The id of the operator to register. * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -275,7 +275,7 @@ contract StakeRegistry is StakeRegistryStorage { * @param operatorId The id of the operator to deregister. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order From dd761a6eb784192b055037ef94993bd6c48f6741 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:46:12 -0700 Subject: [PATCH 076/110] clarify register/deregister comments --- src/contracts/interfaces/IRegistryCoordinator.sol | 15 +++++++++++---- .../middleware/BLSIndexRegistryCoordinator.sol | 8 ++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 6124705a3..791180038 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -45,9 +45,16 @@ interface IRegistryCoordinator { /// @notice Returns the number of registries function numRegistries() external view returns (uint256); - /// @notice registers the sender as an operator for the `quorumNumbers` with additional bytes for registry interaction data - function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata) external; + /** + * @notice Registers msg.sender as an operator with the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for + * @param registrationData is the data that is decoded to get the operator's registration information + */ + function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata registrationData) external; - /// @notice deregisters the sender with additional bytes for registry interaction data - function deregisterOperatorWithCoordinator(bytes calldata) external; + /** + * @notice Deregisters the msg.sender as an operator from the middleware + * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information + */ + function deregisterOperatorWithCoordinator(bytes calldata deregistrationData) external; } diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 2419d1458..cf74ee266 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -80,7 +80,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } /** - * @notice Registers the operator with the middleware + * @notice Registers msg.sender as an operator with the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for * @param registrationData is the data that is decoded to get the operator's registration information */ @@ -92,7 +92,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } /** - * @notice Registers the operator with the middleware + * @notice Registers msg.sender as an operator with the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for * @param pubkey is the BLS public key of the operator */ @@ -106,7 +106,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } /** - * @notice Deregisters the operator from the middleware + * @notice Deregisters the msg.sender as an operator from the middleware * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information */ function deregisterOperatorWithCoordinator(bytes calldata deregistrationData) external { @@ -118,7 +118,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } /** - * @notice Deregisters the operator from the middleware + * @notice Deregisters the msg.sender as an operator from the middleware * @param pubkey is the BLS public key of the operator * @param operatorIdsToSwap is the list of the operator ids that the should swap for the deregistering operator's index * @param globalOperatorListIndex is the operator's index in the global operator list in the IndexRegistry From b6444796572af6887351eb3ea8166f446b4abb4e Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 14:47:40 -0700 Subject: [PATCH 077/110] clarify register/deregister data structure --- src/contracts/middleware/BLSIndexRegistryCoordinator.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index cf74ee266..30dd0e0bb 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -83,6 +83,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { * @notice Registers msg.sender as an operator with the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for * @param registrationData is the data that is decoded to get the operator's registration information + * @dev `registrationData` should be a G1 point representing the operator's BLS public key */ function registerOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata registrationData) external { // get the operator's BLS public key @@ -108,6 +109,8 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { /** * @notice Deregisters the msg.sender as an operator from the middleware * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information + * @dev `deregistrationData` should be a tuple of the operator's BLS public key, the list of operator ids to swap, + * and the operator's index in the global operator list */ function deregisterOperatorWithCoordinator(bytes calldata deregistrationData) external { // get the operator's deregisteration information From 9674a65ef7e048e97ade7746317a47e620c05aa6 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Thu, 22 Jun 2023 15:01:50 -0700 Subject: [PATCH 078/110] add quorumNumbers to registry coordinator --- .../interfaces/IRegistryCoordinator.sol | 3 ++- .../middleware/BLSIndexRegistryCoordinator.sol | 17 ++++++++++------- src/test/mocks/RegistryCoordinatorMock.sol | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 791180038..9b4ae21a0 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -54,7 +54,8 @@ interface IRegistryCoordinator { /** * @notice Deregisters the msg.sender as an operator from the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information */ - function deregisterOperatorWithCoordinator(bytes calldata deregistrationData) external; + function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external; } diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 30dd0e0bb..a94a12dd2 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -108,26 +108,28 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { /** * @notice Deregisters the msg.sender as an operator from the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information - * @dev `deregistrationData` should be a tuple of the operator's BLS public key, the list of operator ids to swap, + * @dev `deregistrationData` should be a tuple of the operator's BLS public key, the list of operator ids to swap, * and the operator's index in the global operator list */ - function deregisterOperatorWithCoordinator(bytes calldata deregistrationData) external { + function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external { // get the operator's deregisteration information (BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) = abi.decode(deregistrationData, (BN254.G1Point, bytes32[], uint32)); // call internal function to deregister the operator - _deregisterOperatorWithCoordinator(msg.sender, pubkey, operatorIdsToSwap, globalOperatorListIndex); + _deregisterOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey, operatorIdsToSwap, globalOperatorListIndex); } /** * @notice Deregisters the msg.sender as an operator from the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for * @param pubkey is the BLS public key of the operator * @param operatorIdsToSwap is the list of the operator ids that the should swap for the deregistering operator's index * @param globalOperatorListIndex is the operator's index in the global operator list in the IndexRegistry */ - function deregisterOperatorWithCoordinator(BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external { - _deregisterOperatorWithCoordinator(msg.sender, pubkey, operatorIdsToSwap, globalOperatorListIndex); + function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external { + _deregisterOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey, operatorIdsToSwap, globalOperatorListIndex); } function _registerOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { @@ -158,7 +160,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { }); } - function _deregisterOperatorWithCoordinator(address operator, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { + function _deregisterOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { require(_operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperator: operator is not registered"); // get the operatorId of the operator @@ -166,7 +168,8 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperator: operatorId does not match pubkey hash"); // get the quorumNumbers of the operator - bytes memory quorumNumbers = BytesArrayBitmaps.bitmapToBytesArray(operatorIdToQuorumBitmap[operatorId]); + require(operatorIdToQuorumBitmap[operatorId] == BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers), + "BLSIndexRegistryCoordinator._deregisterOperator: quorumNumbers does not match storage"); // deregister the operator from the BLSPubkeyRegistry blsPubkeyRegistry.deregisterOperator(operator, quorumNumbers, pubkey); diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 96ab2fe6e..9c91e69b6 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -23,5 +23,5 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata) external {} - function deregisterOperatorWithCoordinator(bytes calldata) external {} + function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata) external {} } From c7d5d8426b9052b3a115333a406a561ee89cd6fb Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 23 Jun 2023 12:58:50 -0700 Subject: [PATCH 079/110] add complete deregistration functionality --- .../interfaces/IBLSPubkeyRegistry.sol | 13 +++++++--- src/contracts/interfaces/IIndexRegistry.sol | 5 ++-- src/contracts/interfaces/IStakeRegistry.sol | 3 ++- .../BLSIndexRegistryCoordinator.sol | 8 +++--- .../middleware/BLSPubkeyRegistry.sol | 26 ++++++++++++------- src/contracts/middleware/IndexRegistry.sol | 9 +++++-- src/contracts/middleware/StakeRegistry.sol | 18 ++++++++----- src/test/unit/BLSPubkeyRegistryUnit.t.sol | 10 +++---- src/test/unit/IndexRegistryUnit.t.sol | 8 +++--- 9 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/contracts/interfaces/IBLSPubkeyRegistry.sol b/src/contracts/interfaces/IBLSPubkeyRegistry.sol index d5b41c9c8..2c17536f8 100644 --- a/src/contracts/interfaces/IBLSPubkeyRegistry.sol +++ b/src/contracts/interfaces/IBLSPubkeyRegistry.sol @@ -20,7 +20,13 @@ interface IBLSPubkeyRegistry is IRegistry { event PubkeyRemoved( address operator, BN254.G1Point pubkey - ); + ); + + // Emitted when an operator pubkey is removed from a set of quorums + event PubkeyRemoveFromQuorums( + address operator, + bytes quorumNumbers + ); /// @notice Data structure used to track the history of the Aggregate Public Key of all operators @@ -50,6 +56,7 @@ interface IBLSPubkeyRegistry is IRegistry { /** * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. + * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @param pubkey The public key of the operator. * @dev access restricted to the RegistryCoordinator @@ -60,8 +67,8 @@ interface IBLSPubkeyRegistry is IRegistry { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering * 6) `pubkey` is the same as the parameter used when registering - */ - function deregisterOperator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); + */ + function deregisterOperator(address operator, bool completeDeregistration, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); /// @notice Returns the current APK for the provided `quorumNumber ` function getApkForQuorum(uint8 quorumNumber) external view returns (BN254.G1Point memory); diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 3aa71b60f..0836ea5e4 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -39,8 +39,9 @@ interface IIndexRegistry is IRegistry { function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external; /** - * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. + * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. * @param operatorId is the id of the operator that is being deregistered + * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers is the quorum numbers the operator is deregistered for * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` * they will be swapped the operators current index @@ -53,7 +54,7 @@ interface IIndexRegistry is IRegistry { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external; + function deregisterOperator(bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external; /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 4182ad60d..682bebeaa 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -39,6 +39,7 @@ interface IStakeRegistry is IRegistry { * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. + * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): @@ -48,7 +49,7 @@ interface IStakeRegistry is IRegistry { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external; + function deregisterOperator(address operator, bytes32 operatorId, bool completeDeregistration, bytes memory quorumNumbers) external; function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256); diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index a94a12dd2..8888962fc 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -102,7 +102,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } /// @notice disabled function on the StakeRegistry because this is the registry coordinator - function deregisterOperator(address, bytes32, bytes calldata) external override pure { + function deregisterOperator(address, bytes32, bool, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); } @@ -172,13 +172,13 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { "BLSIndexRegistryCoordinator._deregisterOperator: quorumNumbers does not match storage"); // deregister the operator from the BLSPubkeyRegistry - blsPubkeyRegistry.deregisterOperator(operator, quorumNumbers, pubkey); + blsPubkeyRegistry.deregisterOperator(operator, true, quorumNumbers, pubkey); // deregister the operator from the IndexRegistry - indexRegistry.deregisterOperator(operatorId, quorumNumbers, operatorIdsToSwap, globalOperatorListIndex); + indexRegistry.deregisterOperator(operatorId, true, quorumNumbers, operatorIdsToSwap, globalOperatorListIndex); // deregister the operator from the StakeRegistry - _deregisterOperator(operator, operatorId, quorumNumbers); + _deregisterOperator(operator, operatorId, true, quorumNumbers); // set the status of the operator to DEREGISTERED _operators[operator].status = OperatorStatus.DEREGISTERED; diff --git a/src/contracts/middleware/BLSPubkeyRegistry.sol b/src/contracts/middleware/BLSPubkeyRegistry.sol index 98fc14f3f..858358540 100644 --- a/src/contracts/middleware/BLSPubkeyRegistry.sol +++ b/src/contracts/middleware/BLSPubkeyRegistry.sol @@ -49,7 +49,7 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. * @param pubkey The operator's BLS public key. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -63,7 +63,7 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { //ensure that the operator owns their public key by referencing the BLSPubkeyCompendium require(pubkeyCompendium.pubkeyHashToOperator(pubkeyHash) == operator,"BLSPubkeyRegistry.registerOperator: operator does not own pubkey"); // update each quorum's aggregate pubkey - _processQuorumApkUpdate(quorumNumbers, pubkey); + _processQuorumApkUpdate(operator, quorumNumbers, pubkey); // update the global aggregate pubkey _processGlobalApkUpdate(pubkey); // emit event so offchain actors can update their state @@ -74,10 +74,11 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { /** * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. + * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @param pubkey The public key of the operator. * @dev access restricted to the RegistryCoordinator - * @dev Preconditions: + * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order @@ -85,17 +86,20 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { * 5) `quorumNumbers` is the same as the parameter use when registering * 6) `pubkey` is the same as the parameter used when registering */ - function deregisterOperator(address operator, bytes memory quorumNumbers, BN254.G1Point memory pubkey) external onlyRegistryCoordinator returns(bytes32){ + function deregisterOperator(address operator, bool completeDeregistration, bytes memory quorumNumbers, BN254.G1Point memory pubkey) external onlyRegistryCoordinator returns(bytes32){ bytes32 pubkeyHash = BN254.hashG1Point(pubkey); require(pubkeyCompendium.pubkeyHashToOperator(pubkeyHash) == operator,"BLSPubkeyRegistry.registerOperator: operator does not own pubkey"); // update each quorum's aggregate pubkey - _processQuorumApkUpdate(quorumNumbers, pubkey.negate()); - // update the global aggregate pubkey - _processGlobalApkUpdate(pubkey.negate()); - // emit event so offchain actors can update their state - emit PubkeyRemoved(operator, pubkey); + _processQuorumApkUpdate(operator, quorumNumbers, pubkey.negate()); + + if(completeDeregistration){ + // update the global aggregate pubkey + _processGlobalApkUpdate(pubkey.negate()); + // emit event so offchain actors can update their state + emit PubkeyRemoved(operator, pubkey); + } return pubkeyHash; } @@ -157,7 +161,7 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { globalApkUpdates.push(latestGlobalApkUpdate); } - function _processQuorumApkUpdate(bytes memory quorumNumbers, BN254.G1Point memory point) internal { + function _processQuorumApkUpdate(address operator, bytes memory quorumNumbers, BN254.G1Point memory point) internal { BN254.G1Point memory apkAfterUpdate; for (uint i = 0; i < quorumNumbers.length;) { @@ -183,6 +187,8 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { ++i; } } + + emit PubkeyRemoveFromQuorums(operator, quorumNumbers); } function _validateApkHashForQuorumAtBlockNumber(ApkUpdate memory apkUpdate, uint32 blockNumber) internal pure { diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index d08cb9255..9d2cc9d1f 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -58,6 +58,7 @@ contract IndexRegistry is IIndexRegistry { /** * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. * @param operatorId is the id of the operator that is being deregistered + * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers is the quorum numbers the operator is deregistered for * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` * they will be swapped the operators current index @@ -70,16 +71,20 @@ contract IndexRegistry is IIndexRegistry { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { + function deregisterOperator(bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { require(quorumNumbers.length == operatorIdsToSwap.length, "IndexRegistry.deregisterOperator: quorumNumbers and operatorIdsToSwap must be the same length"); - _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); uint32 indexToRemove = operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; _processOperatorRemoval(operatorId, quorumNumber, indexToRemove, operatorIdsToSwap[i]); _updateTotalOperatorHistory(quorumNumber, totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index - 1); } + + // remove operator from globalOperatorList if this is a complete deregistration + if(completeDeregistration){ + _removeOperatorFromGlobalOperatorList(globalOperatorListIndex); + } } /** diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 59d612d87..0e744789c 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -273,6 +273,7 @@ contract StakeRegistry is StakeRegistryStorage { * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. + * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): @@ -282,8 +283,8 @@ contract StakeRegistry is StakeRegistryStorage { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, bytes calldata quorumNumbers) external virtual { - _deregisterOperator(operator, operatorId, quorumNumbers); + function deregisterOperator(address operator, bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers) external virtual { + _deregisterOperator(operator, operatorId, completeDeregistration, quorumNumbers); } /** @@ -397,15 +398,18 @@ contract StakeRegistry is StakeRegistryStorage { } } - function _deregisterOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { + function _deregisterOperator(address operator, bytes32 operatorId, bool completeDeregistration, bytes memory quorumNumbers) internal { // remove the operator's stake _removeOperatorStake(operatorId, quorumNumbers); - // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); + // if the operator is deregistering from all quorums, revoke ther service's slashing ability + if(completeDeregistration) { + // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges + uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - // record a stake update unbonding the operator after `latestServeUntilBlock` - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); + // record a stake update unbonding the operator after `latestServeUntilBlock` + serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); + } } /** diff --git a/src/test/unit/BLSPubkeyRegistryUnit.t.sol b/src/test/unit/BLSPubkeyRegistryUnit.t.sol index 2c4ee53ee..b5bbfdd1a 100644 --- a/src/test/unit/BLSPubkeyRegistryUnit.t.sol +++ b/src/test/unit/BLSPubkeyRegistryUnit.t.sol @@ -53,7 +53,7 @@ contract BLSPubkeyRegistryUnitTests is Test { cheats.startPrank(nonCoordinatorAddress); cheats.expectRevert(bytes("BLSPubkeyRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); - blsPubkeyRegistry.deregisterOperator(nonCoordinatorAddress, new bytes(0), BN254.G1Point(0, 0)); + blsPubkeyRegistry.deregisterOperator(nonCoordinatorAddress, true, new bytes(0), BN254.G1Point(0, 0)); cheats.stopPrank(); } @@ -186,7 +186,7 @@ contract BLSPubkeyRegistryUnitTests is Test { } cheats.startPrank(address(registryCoordinator)); - blsPubkeyRegistry.deregisterOperator(defaultOperator, quorumNumbers, defaultPubKey); + blsPubkeyRegistry.deregisterOperator(defaultOperator, true, quorumNumbers, defaultPubKey); cheats.stopPrank(); @@ -213,7 +213,7 @@ contract BLSPubkeyRegistryUnitTests is Test { cheats.prank(address(registryCoordinator)); - blsPubkeyRegistry.deregisterOperator(defaultOperator, quorumNumbers, globalApkBefore); + blsPubkeyRegistry.deregisterOperator(defaultOperator, true, quorumNumbers, globalApkBefore); (x, y)= blsPubkeyRegistry.globalApk(); require(x == 0, "global apk not set to zero"); @@ -234,7 +234,7 @@ contract BLSPubkeyRegistryUnitTests is Test { cheats.stopPrank(); cheats.prank(address(registryCoordinator)); - blsPubkeyRegistry.deregisterOperator(defaultOperator, quorumNumbers, quorumApksBefore); + blsPubkeyRegistry.deregisterOperator(defaultOperator, true, quorumNumbers, quorumApksBefore); BN254.G1Point memory pk = blsPubkeyRegistry.getApkForQuorum(defaultQuorumNumber); require(pk.X == 0, "global apk not set to zero"); @@ -368,7 +368,7 @@ contract BLSPubkeyRegistryUnitTests is Test { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); cheats.startPrank(address(registryCoordinator)); - blsPubkeyRegistry.deregisterOperator(defaultOperator, quorumNumbers, BN254.hashToG1(pk)); + blsPubkeyRegistry.deregisterOperator(defaultOperator, true, quorumNumbers, BN254.hashToG1(pk)); cheats.stopPrank(); } diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index 61c340ceb..49910ca4a 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -65,7 +65,7 @@ contract IndexRegistryUnitTests is Test { cheats.startPrank(nonRegistryCoordinator); cheats.expectRevert(bytes("IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator")); - indexRegistry.deregisterOperator(bytes32(0), quorumNumbers, operatorIdsToSwap, 0); + indexRegistry.deregisterOperator(bytes32(0), true, quorumNumbers, operatorIdsToSwap, 0); cheats.stopPrank(); } @@ -90,7 +90,7 @@ contract IndexRegistryUnitTests is Test { //deregister the operatorId1, removing it from both quorum 1 and 2. cheats.startPrank(address(registryCoordinatorMock)); - indexRegistry.deregisterOperator(operatorId1, quorumNumbers, operatorIdsToSwap, 0); + indexRegistry.deregisterOperator(operatorId1, true, quorumNumbers, operatorIdsToSwap, 0); cheats.stopPrank(); require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: operator not registered correctly"); @@ -125,7 +125,7 @@ contract IndexRegistryUnitTests is Test { //deregister the operatorId1, removing it from both quorum 1 and 2. cheats.startPrank(address(registryCoordinatorMock)); cheats.expectRevert(bytes("IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum")); - indexRegistry.deregisterOperator(operatorId1, quorumNumbers, operatorIdsToSwap, 0); + indexRegistry.deregisterOperator(operatorId1, true, quorumNumbers, operatorIdsToSwap, 0); cheats.stopPrank(); } @@ -136,7 +136,7 @@ contract IndexRegistryUnitTests is Test { //deregister the operatorId1, removing it from both quorum 1 and 2. cheats.startPrank(address(registryCoordinatorMock)); cheats.expectRevert(bytes("IndexRegistry.deregisterOperator: quorumNumbers and operatorIdsToSwap must be the same length")); - indexRegistry.deregisterOperator(defaultOperator, quorumNumbers, operatorIdsToSwap, 0); + indexRegistry.deregisterOperator(defaultOperator, true, quorumNumbers, operatorIdsToSwap, 0); cheats.stopPrank(); } From f9b30122c9afffa40d9e709e3243d95b6f43ef1c Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 23 Jun 2023 15:27:52 -0700 Subject: [PATCH 080/110] change to uin192 quorum numbers --- src/contracts/interfaces/IIndexRegistry.sol | 2 +- src/contracts/interfaces/IQuorumRegistry.sol | 2 +- .../interfaces/IRegistryCoordinator.sol | 4 +- src/contracts/interfaces/IStakeRegistry.sol | 2 +- .../BLSIndexRegistryCoordinator.sol | 44 +- src/contracts/middleware/BLSRegistry.sol | 410 -------- src/contracts/middleware/RegistryBase.sol | 707 ------------- src/contracts/middleware/StakeRegistry.sol | 2 +- .../middleware/example/ECDSARegistry.sol | 226 ----- .../middleware/example/HashThreshold.sol | 140 --- src/test/Delegation.t.sol | 960 +++++++++--------- src/test/Registration.t.sol | 176 ++-- src/test/Whitelister.t.sol | 609 ++++++----- src/test/Withdrawals.t.sol | 698 ++++++------- src/test/mocks/MiddlewareVoteWeigherMock.sol | 49 - src/test/mocks/RegistryCoordinatorMock.sol | 2 + 16 files changed, 1265 insertions(+), 2768 deletions(-) delete mode 100644 src/contracts/middleware/BLSRegistry.sol delete mode 100644 src/contracts/middleware/RegistryBase.sol delete mode 100644 src/contracts/middleware/example/ECDSARegistry.sol delete mode 100644 src/contracts/middleware/example/HashThreshold.sol delete mode 100644 src/test/mocks/MiddlewareVoteWeigherMock.sol diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 0836ea5e4..f741ad757 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -26,7 +26,7 @@ interface IIndexRegistry is IRegistry { } /** - * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumBitmap`. + * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. * @param operatorId is the id of the operator that is being registered * @param quorumNumbers is the quorum numbers the operator is registered for * @dev access restricted to the RegistryCoordinator diff --git a/src/contracts/interfaces/IQuorumRegistry.sol b/src/contracts/interfaces/IQuorumRegistry.sol index 45811d42f..43b0b0573 100644 --- a/src/contracts/interfaces/IQuorumRegistry.sol +++ b/src/contracts/interfaces/IQuorumRegistry.sol @@ -51,7 +51,7 @@ interface IQuorumRegistry { uint96 stake; } - function pubkeyHashToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256); + function pubkeyHashToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint192); function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256); diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 9b4ae21a0..3cc8abe54 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -33,8 +33,8 @@ interface IRegistryCoordinator { /// @notice Returns the operatorId for the given `operator` function getOperatorId(address operator) external view returns (bytes32); - /// @notice Returns the quorum bitmap for the given `operator` - function operatorIdToQuorumBitmap(bytes32 operatorId) external view returns (uint256); + /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + function getQuorumBitmapOfOperatorAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32); diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 682bebeaa..5116a602a 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -153,5 +153,5 @@ interface IStakeRegistry is IRegistry { * @dev Precondition: * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for */ - function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint256[] memory quorumBitmaps, uint256[] memory prevElements) external; + function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint192[] memory quorumBitmaps, uint256[] memory prevElements) external; } \ No newline at end of file diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 8888962fc..42e68297a 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -20,12 +20,18 @@ import "./StakeRegistry.sol"; contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { using BN254 for BN254.G1Point; + struct QuorumBitmapUpdate { + uint32 updateBlockNumber; + uint32 nextUpdateBlockNumber; + uint192 quorumBitmap; + } + /// @notice the BLS Pubkey Registry contract that will keep track of operators' BLS public keys IBLSPubkeyRegistry public immutable blsPubkeyRegistry; /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; - /// @notice the mapping from operator's operatorId to the bitmap of quorums they are registered for - mapping(bytes32 => uint256) public operatorIdToQuorumBitmap; + /// @notice the mapping from operator's operatorId to the updates of the bitmap of quorums they are registered for + mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorIdToQuorumBitmapHistory; /// @notice the mapping from operator's address to the operator struct mapping(address => Operator) internal _operators; /// @notice the dynamic-length array of the registries this coordinator is coordinating @@ -69,6 +75,20 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { return _operators[operator].operatorId; } + /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + function getQuorumBitmapOfOperatorAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192) { + QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorIdToQuorumBitmapHistory[operatorId][index]; + require( + quorumBitmapUpdate.updateBlockNumber <= blockNumber, + "BLSRegistryCoordinator.getQuorumBitmapOfOperatorAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" + ); + require( + quorumBitmapUpdate.nextUpdateBlockNumber > blockNumber, + "BLSRegistryCoordinator.getQuorumBitmapOfOperatorAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" + ); + return quorumBitmapUpdate.quorumBitmap; + } + /// @notice Returns the number of registries function numRegistries() external view returns (uint256) { return registries.length; @@ -97,7 +117,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for * @param pubkey is the BLS public key of the operator */ - function registerOperator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external { + function registerOperatorWithCoordinator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external { _registerOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey); } @@ -137,7 +157,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator: operator already registered"); // get the quorum bitmap from the quorum numbers - uint256 quorumBitmap = BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers); + uint192 quorumBitmap = uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)); require(quorumBitmap != 0, "BLSIndexRegistryCoordinator: quorumBitmap cannot be 0"); // register the operator with the BLSPubkeyRegistry and get the operatorId (in this case, the pubkeyHash) back @@ -149,8 +169,12 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { // register the operator with the StakeRegistry _registerOperator(operator, operatorId, quorumNumbers); - // set the operatorId to quorum bitmap mapping - operatorIdToQuorumBitmap[operatorId] = quorumBitmap; + // set the operatorId to quorum bitmap history + _operatorIdToQuorumBitmapHistory[operatorId].push(QuorumBitmapUpdate({ + updateBlockNumber: uint32(block.number), + nextUpdateBlockNumber: 0, + quorumBitmap: quorumBitmap + })); // set the operator struct _operators[operator] = Operator({ @@ -168,8 +192,14 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperator: operatorId does not match pubkey hash"); // get the quorumNumbers of the operator - require(operatorIdToQuorumBitmap[operatorId] == BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers), + uint256 operatorQuorumBitmapHistoryLengthMinusOne = _operatorIdToQuorumBitmapHistory[operatorId].length - 1; + // check that the quorumNumbers of the operator matches the quorumNumbers passed in + require( + _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].quorumBitmap == + uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)), "BLSIndexRegistryCoordinator._deregisterOperator: quorumNumbers does not match storage"); + // set the toBlockNumber of the operator's quorum bitmap update + _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].nextUpdateBlockNumber = uint32(block.number); // deregister the operator from the BLSPubkeyRegistry blsPubkeyRegistry.deregisterOperator(operator, true, quorumNumbers, pubkey); diff --git a/src/contracts/middleware/BLSRegistry.sol b/src/contracts/middleware/BLSRegistry.sol deleted file mode 100644 index 4d59d7f6a..000000000 --- a/src/contracts/middleware/BLSRegistry.sol +++ /dev/null @@ -1,410 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "./RegistryBase.sol"; -import "../interfaces/IBLSPublicKeyCompendium.sol"; -import "../interfaces/IBLSRegistry.sol"; -import "../libraries/BN254.sol"; - -/** - * @title A Registry-type contract using aggregate BLS signatures. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice This contract is used for - * - registering new operators - * - committing to and finalizing de-registration as an operator - * - updating the stakes of the operator - */ -contract BLSRegistry is RegistryBase, IBLSRegistry { - - // Hash of the zero public key - bytes32 internal constant ZERO_PK_HASH = hex"012893657d8eb2efad4de0a91bcd0e39ad9837745dec3ea923737ea803fc8e3d"; - - /// @notice contract used for looking up operators' BLS public keys - IBLSPublicKeyCompendium public immutable pubkeyCompendium; - - /** - * @notice list of keccak256(apk_x, apk_y) of operators, and the block numbers at which the aggregate - * pubkeys were updated. This occurs whenever a new operator registers or deregisters. - */ - ApkUpdate[] internal _apkUpdates; - - /** - * @dev Initialized value of APK is the point at infinity: (0, 0) - * @notice used for storing current aggregate public key - */ - BN254.G1Point public apk; - - /// @notice the address that can whitelist people - address public operatorWhitelister; - /// @notice toggle of whether the operator whitelist is on or off - bool public operatorWhitelistEnabled; - /// @notice operator => are they whitelisted (can they register with the middleware) - mapping(address => bool) public whitelisted; - - // EVENTS - /** - * @notice Emitted upon the registration of a new operator for the middleware - * @param operator Address of the new operator - * @param pkHash The keccak256 hash of the operator's public key - * @param pk The operator's public key itself - * @param apkHashIndex The index of the latest (i.e. the new) APK update - * @param apkHash The keccak256 hash of the new Aggregate Public Key - */ - event Registration( - address indexed operator, - bytes32 pkHash, - BN254.G1Point pk, - uint32 apkHashIndex, - bytes32 apkHash, - string socket - ); - - /// @notice Emitted when the `operatorWhitelister` role is transferred. - event OperatorWhitelisterTransferred(address previousAddress, address newAddress); - - /// @notice Modifier that restricts a function to only be callable by the `whitelister` role. - modifier onlyOperatorWhitelister{ - require(operatorWhitelister == msg.sender, "BLSRegistry.onlyOperatorWhitelister: not operatorWhitelister"); - _; - } - - constructor( - IStrategyManager _strategyManager, - IServiceManager _serviceManager, - IBLSPublicKeyCompendium _pubkeyCompendium - ) - RegistryBase( - _strategyManager, - _serviceManager - ) - { - //set compendium - pubkeyCompendium = _pubkeyCompendium; - } - - /// @notice Initialize the APK, the payment split between quorums, and the quorum strategies + multipliers. - function initialize( - address _operatorWhitelister, - bool _operatorWhitelistEnabled, - uint96[] memory _minimumStakeForQuorums, - StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers - ) public virtual initializer { - _setOperatorWhitelister(_operatorWhitelister); - operatorWhitelistEnabled = _operatorWhitelistEnabled; - // process an apk update to get index and totalStake arrays to the same length - _processApkUpdate(BN254.G1Point(0, 0)); - RegistryBase._initialize( - _minimumStakeForQuorums, - _quorumStrategiesConsideredAndMultipliers - ); - } - - /** - * @notice Called by the service manager owner to transfer the whitelister role to another address - */ - function setOperatorWhitelister(address _operatorWhitelister) external onlyServiceManagerOwner { - _setOperatorWhitelister(_operatorWhitelister); - } - - /** - * @notice Callable only by the service manager owner, this function toggles the whitelist on or off - * @param _operatorWhitelistEnabled true if turning whitelist on, false otherwise - */ - function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external onlyServiceManagerOwner { - operatorWhitelistEnabled = _operatorWhitelistEnabled; - } - - /** - * @notice Called by the whitelister, adds a list of operators to the whitelist - * @param operators the operators to add to the whitelist - */ - function addToOperatorWhitelist(address[] calldata operators) external onlyOperatorWhitelister { - for (uint i = 0; i < operators.length; i++) { - whitelisted[operators[i]] = true; - } - } - - /** - * @notice Called by the whitelister, removes a list of operators to the whitelist - * @param operators the operators to remove from the whitelist - */ - function removeFromWhitelist(address[] calldata operators) external onlyOperatorWhitelister { - for (uint i = 0; i < operators.length; i++) { - whitelisted[operators[i]] = false; - } - } - - /** - * @notice called for registering as an operator - * @param quorumBitmap has a bit set for each quorum the operator wants to register for (if the ith least significant bit is set, the operator wants to register for the ith quorum) - * @param pk is the operator's G1 public key - * @param socket is the socket address of the operator - */ - function registerOperator(uint8 quorumBitmap, BN254.G1Point memory pk, string calldata socket) external virtual { - _registerOperator(msg.sender, quorumBitmap, pk, socket); - } - - /** - * @param operator is the node who is registering to be a operator - * @param quorumBitmap has a bit set for each quorum the operator wants to register for (if the ith least significant bit is set, the operator wants to register for the ith quorum) - * @param pk is the operator's G1 public key - * @param socket is the socket address of the operator - */ - function _registerOperator(address operator, uint256 quorumBitmap, BN254.G1Point memory pk, string calldata socket) - internal - { - if(operatorWhitelistEnabled) { - require(whitelisted[operator], "BLSRegistry._registerOperator: not whitelisted"); - } - - // getting pubkey hash - bytes32 pubkeyHash = BN254.hashG1Point(pk); - - require(pubkeyHash != ZERO_PK_HASH, "BLSRegistry._registerOperator: Cannot register with 0x0 public key"); - - require( - pubkeyCompendium.pubkeyHashToOperator(pubkeyHash) == operator, - "BLSRegistry._registerOperator: operator does not own pubkey" - ); - - // the new aggregate public key is the current one added to registering operator's public key - BN254.G1Point memory newApk = BN254.plus(apk, pk); - - // record the APK update and get the hash of the new APK - bytes32 newApkHash = _processApkUpdate(newApk); - - // add the operator to the list of registrants and do accounting - _addRegistrant(operator, pubkeyHash, quorumBitmap); - - emit Registration(operator, pubkeyHash, pk, uint32(_apkUpdates.length - 1), newApkHash, socket); - } - - /** - * @notice Used by an operator to de-register itself from providing service to the middleware. - * @param pkToRemove is the sender's pubkey in affine coordinates - * @param index is the sender's location in the dynamic array `operatorList` - */ - function deregisterOperator(BN254.G1Point memory pkToRemove, uint32 index) external virtual returns (bool) { - _deregisterOperator(msg.sender, pkToRemove, index); - return true; - } - - /** - * @notice Used to process de-registering an operator from providing service to the middleware. - * @param operator The operator to be deregistered - * @param pkToRemove is the sender's pubkey - * @param index is the sender's location in the dynamic array `operatorList` - */ - function _deregisterOperator(address operator, BN254.G1Point memory pkToRemove, uint32 index) internal { - // verify that the `operator` is an active operator and that they've provided the correct `index` - _deregistrationCheck(operator, index); - - - /// @dev Fetch operator's stored pubkeyHash - bytes32 pubkeyHash = registry[operator].pubkeyHash; - /// @dev Verify that the stored pubkeyHash matches the 'pubkeyToRemoveAff' input - require( - pubkeyHash == BN254.hashG1Point(pkToRemove), - "BLSRegistry._deregisterOperator: pubkey input does not match stored pubkeyHash" - ); - - // Perform necessary updates for removing operator, including updating operator list and index histories - _removeOperator(operator, pubkeyHash, index); - - // the new apk is the current one minus the sender's pubkey (apk = apk + (-pk)) - BN254.G1Point memory newApk = BN254.plus(apk, BN254.negate(pkToRemove)); - // update the aggregate public key of all registered operators and record this update in history - _processApkUpdate(newApk); - } - - /** - * @notice Used for updating information on deposits of nodes. - * @param operators are the nodes whose deposit information is getting updated - * @param prevElements are the elements before this middleware in the operator's linked list within the slasher - */ - function updateStakes(address[] memory operators, uint256[] memory prevElements) external { - // load all operator structs into memory - Operator[] memory operatorStructs = new Operator[](operators.length); - for (uint i = 0; i < operators.length;) { - operatorStructs[i] = registry[operators[i]]; - unchecked { - ++i; - } - } - - // load all operator quorum bitmaps into memory - uint256[] memory quorumBitmaps = new uint256[](operators.length); - for (uint i = 0; i < operators.length;) { - quorumBitmaps[i] = pubkeyHashToQuorumBitmap[operatorStructs[i].pubkeyHash]; - unchecked { - ++i; - } - } - - // for each quorum, loop through operators and see if they are apart of the quorum - // if they are, get their new weight and update their individual stake history and the - // quorum's total stake history accordingly - for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { - OperatorStakeUpdate memory totalStakeUpdate; - // for each operator - for(uint i = 0; i < operators.length;) { - // if the operator is apart of the quorum - if (quorumBitmaps[i] >> quorumNumber & 1 == 1) { - // if the total stake has not been loaded yet, load it - if (totalStakeUpdate.updateBlockNumber == 0) { - totalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; - } - // get the operator's pubkeyHash - bytes32 pubkeyHash = operatorStructs[i].pubkeyHash; - // get the operator's current stake - OperatorStakeUpdate memory prevStakeUpdate = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1]; - // update the operator's stake based on current state - OperatorStakeUpdate memory newStakeUpdate = _updateOperatorStake(operators[i], pubkeyHash, quorumBitmaps[i], quorumNumber, prevStakeUpdate.updateBlockNumber); - // calculate the new total stake for the quorum - totalStakeUpdate.stake = totalStakeUpdate.stake - prevStakeUpdate.stake + newStakeUpdate.stake; - } - unchecked { - ++i; - } - } - // if the total stake for this quorum was updated, record it in storage - if (totalStakeUpdate.updateBlockNumber != 0) { - // update the total stake history for the quorum - _recordTotalStakeUpdate(quorumNumber, totalStakeUpdate); - } - unchecked { - ++quorumNumber; - } - } - - // record stake updates in the EigenLayer Slasher - for (uint i = 0; i < operators.length;) { - serviceManager.recordStakeUpdate(operators[i], uint32(block.number), serviceManager.latestServeUntilBlock(), prevElements[i]); - unchecked { - ++i; - } - } - } - - //TODO: The subgraph doesnt like uint256[4][] argument here. Figure this out laters - // // TODO: de-dupe code copied from `updateStakes`, if reasonably possible - // /** - // * @notice Used for removing operators that no longer meet the minimum requirements - // * @param operators are the nodes who will potentially be booted - // */ - // function bootOperators( - // address[] calldata operators, - // uint256[4][] memory pubkeysToRemoveAff, - // uint32[] memory indices - // ) - // external - // { - // // copy total stake to memory - // OperatorStake memory _totalStake = totalStakeHistory[totalStakeHistory.length - 1]; - - // // placeholders reused inside of loop - // OperatorStake memory currentStakes; - // bytes32 pubkeyHash; - // uint256 operatorsLength = operators.length; - // // iterating over all the tuples that are to be updated - // for (uint256 i = 0; i < operatorsLength;) { - // // get operator's pubkeyHash - // pubkeyHash = registry[operators[i]].pubkeyHash; - // // fetch operator's existing stakes - // currentStakes = pubkeyHashToStakeHistory[pubkeyHash][pubkeyHashToStakeHistory[pubkeyHash].length - 1]; - // // decrease _totalStake by operator's existing stakes - // _totalStake.firstQuorumStake -= currentStakes.firstQuorumStake; - // _totalStake.secondQuorumStake -= currentStakes.secondQuorumStake; - - // // update the stake for the i-th operator - // currentStakes = _updateOperatorStake(operators[i], pubkeyHash, currentStakes); - - // // remove the operator from the list of operators if they do *not* meet the minimum requirements - // if ( - // (currentStakes.firstQuorumStake < minimumStakeFirstQuorum) - // && (currentStakes.secondQuorumStake < minimumStakeSecondQuorum) - // ) { - // // TODO: optimize better if possible? right now this pushes an APK update for each operator removed. - // _deregisterOperator(operators[i], pubkeysToRemoveAff[i], indices[i]); - // } - // // in the case that the operator *does indeed* meet the minimum requirements - // else { - // // increase _totalStake by operator's updated stakes - // _totalStake.firstQuorumStake += currentStakes.firstQuorumStake; - // _totalStake.secondQuorumStake += currentStakes.secondQuorumStake; - // } - - // unchecked { - // ++i; - // } - // } - - // // update storage of total stake - // _recordTotalStakeUpdate(_totalStake); - // } - - /** - * @notice Updates the stored APK to `newApk`, calculates its hash, and pushes new entries to the `_apkUpdates` array - * @param newApk The updated APK. This will be the `apk` after this function runs! - */ - function _processApkUpdate(BN254.G1Point memory newApk) internal returns (bytes32) { - // update stored aggregate public key - // slither-disable-next-line costly-loop - apk = newApk; - - // find the hash of aggregate pubkey - bytes32 newApkHash = BN254.hashG1Point(newApk); - - // store the apk hash and the current block number in which the aggregated pubkey is being updated - _apkUpdates.push(ApkUpdate({ - apkHash: newApkHash, - blockNumber: uint32(block.number) - })); - - return newApkHash; - } - - function _setOperatorWhitelister(address _operatorWhitelister) internal { - require(_operatorWhitelister != address(0), "BLSRegistry.initialize: cannot set operatorWhitelister to zero address"); - emit OperatorWhitelisterTransferred(operatorWhitelister, _operatorWhitelister); - operatorWhitelister = _operatorWhitelister; - } - - /** - * @notice get hash of a historical aggregated public key corresponding to a given index; - * called by checkSignatures in BLSSignatureChecker.sol. - */ - function getApkHashAtBlockNumberFromIndex(uint32 blockNumber, uint256 index) external view returns (bytes32) { - // check that the `index`-th APK update occurred at or before `blockNumber` - require(blockNumber >= _apkUpdates[index].blockNumber, "BLSRegistry.getApkAtBlockNumberFromIndex: index too recent"); - - // if not last update - if (index != _apkUpdates.length - 1) { - // check that there was not *another APK update* that occurred at or before `blockNumber` - require(blockNumber < _apkUpdates[index + 1].blockNumber, "BLSRegistry.getApkAtBlockNumberFromIndex: Not latest valid apk update"); - } - - return _apkUpdates[index].apkHash; - } - - /// @notice returns the total number of APK updates that have ever occurred (including one for initializing the pubkey as the generator) - function getApkUpdatesLength() external view returns (uint256) { - return _apkUpdates.length; - } - - /// @notice returns the `ApkUpdate` struct at `index` in the list of APK updates - function apkUpdates(uint256 index) external view returns (ApkUpdate memory) { - return _apkUpdates[index]; - } - - /// @notice returns the APK hash that resulted from the `index`th APK update - function apkHashes(uint256 index) external view returns (bytes32) { - return _apkUpdates[index].apkHash; - } - - /// @notice returns the block number at which the `index`th APK update occurred - function apkUpdateBlockNumbers(uint256 index) external view returns (uint32) { - return _apkUpdates[index].blockNumber; - } -} diff --git a/src/contracts/middleware/RegistryBase.sol b/src/contracts/middleware/RegistryBase.sol deleted file mode 100644 index 53db094e6..000000000 --- a/src/contracts/middleware/RegistryBase.sol +++ /dev/null @@ -1,707 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "../interfaces/IServiceManager.sol"; -import "../interfaces/IQuorumRegistry.sol"; -import "./VoteWeigherBase.sol"; - -/** - * @title An abstract Registry-type contract that is signature scheme agnostic. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice This contract is used for - * - registering new operators - * - committing to and finalizing de-registration as an operator - * - updating the stakes of the operator - * @dev This contract is missing key functions. See `BLSRegistry` or `ECDSARegistry` for examples that inherit from this contract. - */ -abstract contract RegistryBase is VoteWeigherBase, IQuorumRegistry { - - // TODO: set these on initialization - /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as - /// evaluated by this contract's 'VoteWeigher' logic. - uint96[256] public minimumStakeForQuorum; - - /// @notice used for storing Operator info on each operator while registration - mapping(address => Operator) public registry; - - /// @notice used for storing the list of current and past registered operators - address[] public operatorList; - - /// @notice used for storing the quorums which the operator is participating in - mapping(bytes32 => uint256) public pubkeyHashToQuorumBitmap; - - /// @notice array of the history of the total stakes for each quorum -- marked as internal since getTotalStakeFromIndex is a getter for this - OperatorStakeUpdate[][256] internal totalStakeHistory; - - /// @notice array of the history of the number of operators, and the taskNumbers at which the number of operators changed - OperatorIndex[] public totalOperatorsHistory; - - /// @notice mapping from operator's pubkeyhash to the history of their stake updates - mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public pubkeyHashToStakeHistory; - - /// @notice mapping from operator's pubkeyhash to the history of their index in the array of all operators - mapping(bytes32 => OperatorIndex[]) public pubkeyHashToIndexHistory; - - // EVENTS - /// @notice emitted when `operator` updates their socket address to `socket` - event SocketUpdate(address operator, string socket); - - /// @notice emitted whenever the stake of `operator` is updated - event StakeUpdate( - address operator, - uint8 quorumNumber, - uint96 stake, - uint32 updateBlockNumber, - uint32 prevUpdateBlockNumber - ); - - /** - * @notice Emitted whenever an operator deregisters. - * The `swapped` address is the address returned by an internal call to the `_popRegistrant` function. - */ - event Deregistration(address operator, address swapped, uint32 deregisteredIndex); - - /** - * @notice Irrevocably sets the (immutable) `delegation` & `strategyManager` addresses, and `NUMBER_OF_QUORUMS` variable. - */ - constructor( - IStrategyManager _strategyManager, - IServiceManager _serviceManager - ) VoteWeigherBase(_strategyManager, _serviceManager) - // solhint-disable-next-line no-empty-blocks - { - } - - /** - * @notice Adds empty first entries to the dynamic arrays `totalStakeHistory` and `totalOperatorsHistory`, - * to record an initial condition of zero operators with zero total stake. - * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with - */ - function _initialize( - uint96[] memory _minimumStakeForQuorum, - StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers - ) internal virtual onlyInitializing { - // sanity check lengths - require(_minimumStakeForQuorum.length == _quorumStrategiesConsideredAndMultipliers.length, "Registry._initialize: minimumStakeForQuorum length mismatch"); - // push an empty OperatorStakeUpdate struct to the total stake history to record starting with zero stake - // TODO: Address this @ gpsanant - OperatorStakeUpdate memory _totalStakeUpdate; - for (uint quorumNumber = 0; quorumNumber < 256;) { - totalStakeHistory[quorumNumber].push(_totalStakeUpdate); - unchecked { - ++quorumNumber; - } - } - - // push an empty OperatorIndex struct to the total operators history to record starting with zero operators - OperatorIndex memory _totalOperators; - totalOperatorsHistory.push(_totalOperators); - - // add the strategies considered and multipliers for each quorum - for (uint8 quorumNumber = 0; quorumNumber < _quorumStrategiesConsideredAndMultipliers.length;) { - minimumStakeForQuorum[quorumNumber] = _minimumStakeForQuorum[quorumNumber]; - _createQuorum(_quorumStrategiesConsideredAndMultipliers[quorumNumber]); - unchecked { - ++quorumNumber; - } - } - } - - /** - * @notice Looks up the `operator`'s index in the dynamic array `operatorList` at the specified `blockNumber`. - * @param index Used to specify the entry within the dynamic array `pubkeyHashToIndexHistory[pubkeyHash]` to - * read data from, where `pubkeyHash` is looked up from `operator`'s registration info - * @param blockNumber Is the desired block number at which we wish to query the operator's position in the `operatorList` array - * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the - * array `pubkeyHashToIndexHistory[pubkeyHash]` to pull the info from. - */ - function getOperatorIndex(address operator, uint32 blockNumber, uint32 index) external view returns (uint32) { - // look up the operator's stored pubkeyHash - bytes32 pubkeyHash = getOperatorPubkeyHash(operator); - - /** - * Since the 'to' field represents the blockNumber at which a new index started, it is OK if the - * previous array entry has 'to' == blockNumber, so we check not strict inequality here - */ - require( - index == 0 || pubkeyHashToIndexHistory[pubkeyHash][index - 1].toBlockNumber <= blockNumber, - "RegistryBase.getOperatorIndex: Operator indexHistory index is too high" - ); - OperatorIndex memory operatorIndex = pubkeyHashToIndexHistory[pubkeyHash][index]; - /** - * When deregistering, the operator does *not* serve the current block number -- 'to' gets set (from zero) to the current block number. - * Since the 'to' field represents the blocknumber at which a new index started, we want to check strict inequality here. - */ - require( - operatorIndex.toBlockNumber == 0 || blockNumber < operatorIndex.toBlockNumber, - "RegistryBase.getOperatorIndex: indexHistory index is too low" - ); - return operatorIndex.index; - } - - /** - * @notice Looks up the number of total operators at the specified `blockNumber`. - * @param index Input used to specify the entry within the dynamic array `totalOperatorsHistory` to read data from. - * @dev This function will revert if the provided `index` is out of bounds. - */ - function getTotalOperators(uint32 blockNumber, uint32 index) external view returns (uint32) { - /** - * Since the 'to' field represents the blockNumber at which a new index started, it is OK if the - * previous array entry has 'to' == blockNumber, so we check not strict inequality here - */ - require( - index == 0 || totalOperatorsHistory[index - 1].toBlockNumber <= blockNumber, - "RegistryBase.getTotalOperators: TotalOperatorsHistory index is too high" - ); - - - OperatorIndex memory operatorIndex = totalOperatorsHistory[index]; - - // since the 'to' field represents the blockNumber at which a new index started, we want to check strict inequality here - - require( - operatorIndex.toBlockNumber == 0 || blockNumber < operatorIndex.toBlockNumber, - "RegistryBase.getTotalOperators: TotalOperatorsHistory index is too low" - ); - return operatorIndex.index; - } - - /// @notice Returns whether or not the `operator` is currently an active operator, i.e. is "registered". - function isActiveOperator(address operator) external view virtual returns (bool) { - return (registry[operator].status == IQuorumRegistry.Status.ACTIVE); - } - - /// @notice Returns the stored pubkeyHash for the specified `operator`. - function getOperatorPubkeyHash(address operator) public view returns (bytes32) { - return registry[operator].pubkeyHash; - } - - /** - * @notice Returns the `index`-th entry in the `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber]` array. - * @param quorumNumber The quorum number to get the stake for. - * @param pubkeyHash Hash of the public key of the operator of interest. - * @param index Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber]`. - * @dev Function will revert if `index` is out-of-bounds. - */ - function getStakeUpdateForQuorumFromPubkeyHashAndIndex(uint8 quorumNumber, bytes32 pubkeyHash, uint256 index) - external - view - returns (OperatorStakeUpdate memory) - { - return pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index]; - } - - /** - * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. - * @dev Function will revert in the event that `index` is out-of-bounds. - */ - function getTotalStakeUpdateForQuorumFromIndex(uint256 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) { - return totalStakeHistory[quorumNumber][index]; - } - - /** - * @notice Returns the stake weight corresponding to `pubkeyHash` for quorum `quorumNumber` at `blockNumber`. Reverts otherwise. - * @param quorumNumber The quorum number to get the stake for. - * @param pubkeyHash Hash of the public key of the operator of interest. - * @param index Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber]`. - * @param blockNumber Block number to make sure the stake is from. - * @dev Function will revert if `index` is out-of-bounds. - */ - function getStakeForQuorumAtBlockNumberFromPubkeyHashAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 pubkeyHash, uint256 index) - external - view - returns (uint96) - { - OperatorStakeUpdate memory operatorStakeUpdate = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index]; - _validateOperatorStakeUpdateAtBlockNumber(operatorStakeUpdate, blockNumber); - return operatorStakeUpdate.stake; - } - - /** - * @notice Returns the total stake weight for quorum `quorumNumber` at `blockNumber`. Reverts otherwise. - * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. - * @param blockNumber Block number to make sure the stake is from. - * @dev Function will revert if `index` is out-of-bounds. - */ - function getTotalStakeAtBlockNumberFromIndex(uint256 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96) { - OperatorStakeUpdate memory totalStakeUpdate = totalStakeHistory[quorumNumber][index]; - _validateOperatorStakeUpdateAtBlockNumber(totalStakeUpdate, blockNumber); - return totalStakeUpdate.stake; - } - - /** - * @notice Returns the most recent stake weight for the `operator` for a certain quorum - * @dev Function returns an OperatorStakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history - */ - function getMostRecentStakeUpdateByOperator(address operator, uint8 quorumNumber) public view returns (OperatorStakeUpdate memory) { - bytes32 pubkeyHash = getOperatorPubkeyHash(operator); - uint256 historyLength = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length; - OperatorStakeUpdate memory operatorStakeUpdate; - if (historyLength == 0) { - return operatorStakeUpdate; - } else { - operatorStakeUpdate = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][historyLength - 1]; - return operatorStakeUpdate; - } - } - - function getStakeHistoryLengthForQuorumNumber(bytes32 pubkeyHash, uint8 quorumNumber) external view returns (uint256) { - return pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length; - } - - /** - * @notice Returns the most recent stake weight for the `operator` for quorum `quorumNumber` - * @dev Function returns weight of **0** in the event that the operator has no stake history - */ - function getCurrentOperatorStakeForQuorum(address operator, uint8 quorumNumber) external view returns (uint96) { - OperatorStakeUpdate memory operatorStakeUpdate = getMostRecentStakeUpdateByOperator(operator, quorumNumber); - return operatorStakeUpdate.stake; - } - - /// @notice Returns the stake weight from the latest entry in `totalStakeHistory` for quorum `quorumNumber`. - function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) { - // no chance of underflow / error in next line, since an empty entry is pushed in the constructor - return totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1].stake; - } - - function getLengthOfPubkeyHashStakeHistoryForQuorum(bytes32 pubkeyHash, uint8 quorumNumber) external view returns (uint256) { - return pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length; - } - - function getLengthOfPubkeyHashIndexHistory(bytes32 pubkeyHash) external view returns (uint256) { - return pubkeyHashToIndexHistory[pubkeyHash].length; - } - - function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) { - return totalStakeHistory[quorumNumber].length; - } - - function getLengthOfTotalOperatorsHistory() external view returns (uint256) { - return totalOperatorsHistory.length; - } - - /// @notice Returns task number from when `operator` has been registered. - function getFromTaskNumberForOperator(address operator) external view returns (uint32) { - return registry[operator].fromTaskNumber; - } - - /// @notice Returns the current number of operators of this service. - function numOperators() public view returns (uint32) { - return uint32(operatorList.length); - } - - /** - * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. - * @param operator is the operator of interest - * @param blockNumber is the block number of interest - * @param quorumNumber is the quorum number which the operator had stake in - * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up - * in `registry[operator].pubkeyHash` - * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index].updateBlockNumber <= blockNumber` - * 2) `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index].stake > 0` i.e. the operator had nonzero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorActiveAtBlockNumber( - address operator, - uint256 blockNumber, - uint8 quorumNumber, - uint256 stakeHistoryIndex - ) external view returns (bool) - { - // fetch the `operator`'s pubkey hash - bytes32 pubkeyHash = registry[operator].pubkeyHash; - // pull the stake history entry specified by `stakeHistoryIndex` - OperatorStakeUpdate memory operatorStakeUpdate = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][stakeHistoryIndex]; - return ( - // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber` - (operatorStakeUpdate.updateBlockNumber <= blockNumber) - && - // if there is a next update, then check that the next update occurred strictly after `blockNumber` - (operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber) - && - /// verify that the stake was non-zero at the time (note: here was use the assumption that the operator was 'inactive' - /// once their stake fell to zero) - operatorStakeUpdate.stake != 0 // this implicitly checks that the quorum bitmap was 1 for the quorum of interest - ); - } - - /** - * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` for `quorumNumber` as proof. - * @param operator is the operator of interest - * @param blockNumber is the block number of interest - * @param quorumNumber is the quorum number which the operator had no stake in - * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up - * in `registry[operator].pubkeyHash` - * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index].updateBlockNumber <= blockNumber` - * 2) `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][index].stake == 0` i.e. the operator had zero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorInactiveAtBlockNumber( - address operator, - uint256 blockNumber, - uint8 quorumNumber, - uint256 stakeHistoryIndex - ) external view returns (bool) - { - bytes32 pubkeyHash = registry[operator].pubkeyHash; - - require(pubkeyHashToQuorumBitmap[pubkeyHash] >> quorumNumber & 1 == 1, "RegistryBase._checkOperatorInactiveAtBlockNumber: operator was not part of quorum"); - // special case for `pubkeyHashToStakeHistory[pubkeyHash]` having lenght zero -- in which case we know the operator was never registered - if (pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length == 0) { - return true; - } - // pull the stake history entry specified by `stakeHistoryIndex` - OperatorStakeUpdate memory operatorStakeUpdate = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][stakeHistoryIndex]; - return ( - // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber` - (operatorStakeUpdate.updateBlockNumber <= blockNumber) - && - // if there is a next update, then check that the next update occurred strictly after `blockNumber` - (operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber) - && - /// verify that the stake was zero at the time (note: here was use the assumption that the operator was 'inactive' - /// once their stake fell to zero) - operatorStakeUpdate.stake == 0 - ); - } - - // MUTATING FUNCTIONS - - /// @notice Adjusts the `minimumStakeFirstQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 1st quorum. - function setMinimumStakeForQuorum(uint8 quorumNumber, uint96 minimumStake) external onlyServiceManagerOwner { - minimumStakeForQuorum[quorumNumber] = minimumStake; - } - - function updateSocket(string calldata newSocket) external { - require( - registry[msg.sender].status == IQuorumRegistry.Status.ACTIVE, - "RegistryBase.updateSocket: Can only update socket if active on the service" - ); - emit SocketUpdate(msg.sender, newSocket); - } - - - // INTERNAL FUNCTIONS - /** - * @notice Called when the total number of operators has changed. - * Sets the `toBlockNumber` field on the last entry *so far* in thedynamic array `totalOperatorsHistory` to the current `block.number`, - * recording that the previous entry is *no longer the latest* and the block number at which the next was added. - * Pushes a new entry to `totalOperatorsHistory`, with `index` field set equal to the new amount of operators, recording the new number - * of total operators (and leaving the `toBlockNumber` field at zero, signaling that this is the latest entry in the array) - */ - function _updateTotalOperatorsHistory() internal { - // set the 'toBlockNumber' field on the last entry *so far* in 'totalOperatorsHistory' to the current block number - totalOperatorsHistory[totalOperatorsHistory.length - 1].toBlockNumber = uint32(block.number); - // push a new entry to 'totalOperatorsHistory', with 'index' field set equal to the new amount of operators - OperatorIndex memory _totalOperators; - _totalOperators.index = uint32(operatorList.length); - totalOperatorsHistory.push(_totalOperators); - } - - /** - * @notice Remove the operator from active status. Removes the operator with the given `pubkeyHash` from the `index` in `operatorList`, - * updates operatorList and index histories, and performs other necessary updates for removing operator - */ - function _removeOperator(address operator, bytes32 pubkeyHash, uint32 index) internal virtual { - // remove the operator's stake - _removeOperatorStake(pubkeyHash); - - // store blockNumber at which operator index changed (stopped being applicable) - pubkeyHashToIndexHistory[pubkeyHash][pubkeyHashToIndexHistory[pubkeyHash].length - 1].toBlockNumber = - uint32(block.number); - - // remove the operator at `index` from the `operatorList` - address swappedOperator = _removeOperatorFromOperatorListAndIndex(index); - - // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - // committing to not signing off on any more middleware tasks - registry[operator].status = IQuorumRegistry.Status.INACTIVE; - - // record a stake update unbonding the operator after `latestServeUntilBlock` - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); - - // Emit `Deregistration` event - emit Deregistration(operator, swappedOperator, index); - } - - /** - * @notice Removes the stakes of the operator - */ - function _removeOperatorStake(bytes32 pubkeyHash) internal { - // loop through the operator's quorum bitmap and remove the operator's stake for each quorum - uint256 quorumBitmap = pubkeyHashToQuorumBitmap[pubkeyHash]; - for (uint quorumNumber = 0; quorumNumber < quorumCount;) { - if (quorumBitmap >> quorumNumber & 1 == 1) { - _removeOperatorStakeForQuorum(pubkeyHash, uint8(quorumNumber)); - } - unchecked { - quorumNumber++; - } - } - - } - - /** - * @notice Removes the stakes of the operator with pubkeyHash `pubkeyHash` for the quorum with number `quorumNumber` - */ - function _removeOperatorStakeForQuorum(bytes32 pubkeyHash, uint8 quorumNumber) internal { - // gas saving by caching length here - uint256 pubkeyHashToStakeHistoryLengthMinusOne = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1; - - // determine current stakes - OperatorStakeUpdate memory currentStakeUpdate = - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistoryLengthMinusOne]; - //set nextUpdateBlockNumber in current stakes - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistoryLengthMinusOne].nextUpdateBlockNumber = - uint32(block.number); - - /** - * @notice recording the information pertaining to change in stake for this operator in the history. operator stakes are set to 0 here. - */ - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push( - OperatorStakeUpdate({ - // recording the current block number where the operator stake got updated - updateBlockNumber: uint32(block.number), - // mark as 0 since the next update has not yet occurred - nextUpdateBlockNumber: 0, - // setting the operator's stake to 0 - stake: 0 - }) - ); - - // subtract the amounts staked by the operator that is getting deregistered from the total stake - // copy latest totalStakes to memory - OperatorStakeUpdate memory currentTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory.length - 1]; - currentTotalStakeUpdate.stake -= currentStakeUpdate.stake; - // update storage of total stake - _recordTotalStakeUpdate(quorumNumber, currentTotalStakeUpdate); - - emit StakeUpdate( - msg.sender, - // new stakes are zero - quorumNumber, - 0, - uint32(block.number), - currentStakeUpdate.updateBlockNumber - ); - } - - /** - * @notice Removes the registrant at the given `index` from the `operatorList` - * @return swappedOperator is the operator who was swapped with the removed operator in the operatorList, - * or the *zero address* in the case that the removed operator was already the list operator in the operatorList. - */ - function _removeOperatorFromOperatorListAndIndex(uint32 index) internal returns (address swappedOperator) { - // gas saving by caching length here - uint256 operatorListLengthMinusOne = operatorList.length - 1; - // Update index info for operator at end of list, if they are not the same as the removed operator - if (index < operatorListLengthMinusOne) { - // get existing operator at end of list, and retrieve their pubkeyHash - swappedOperator = operatorList[operatorListLengthMinusOne]; - Operator memory registrant = registry[swappedOperator]; - bytes32 pubkeyHash = registrant.pubkeyHash; - // store blockNumber at which operator index changed - // same operation as above except pubkeyHash is now different (since different operator) - pubkeyHashToIndexHistory[pubkeyHash][pubkeyHashToIndexHistory[pubkeyHash].length - 1].toBlockNumber = - uint32(block.number); - // push new 'OperatorIndex' struct to operator's array of historical indices, with 'index' set equal to 'index' input - OperatorIndex memory operatorIndex; - operatorIndex.index = index; - pubkeyHashToIndexHistory[pubkeyHash].push(operatorIndex); - - // move 'swappedOperator' into 'index' slot in operatorList (swapping them with removed operator) - operatorList[index] = swappedOperator; - } - - // slither-disable-next-line costly-loop - operatorList.pop(); - - // Update totalOperatorsHistory - _updateTotalOperatorsHistory(); - - return swappedOperator; - } - - /// @notice Adds the Operator `operator` with the given `pubkeyHash` to the `operatorList` and performs necessary related updates. - function _addRegistrant( - address operator, - bytes32 pubkeyHash, - uint256 quorumBitmap - ) - internal virtual - { - - require( - slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, - "RegistryBase._addRegistrant: operator must be opted into slashing by the serviceManager" - ); - // store the Operator's info in mapping - registry[operator] = Operator({ - pubkeyHash: pubkeyHash, - status: IQuorumRegistry.Status.ACTIVE, - fromTaskNumber: serviceManager.taskNumber() - }); - - // store the operator's quorum bitmap - pubkeyHashToQuorumBitmap[pubkeyHash] = quorumBitmap; - - // add the operator to the list of operators - operatorList.push(operator); - - // calculate stakes for each quorum the operator is trying to join - _registerStake(operator, pubkeyHash, quorumBitmap); - - // record `operator`'s index in list of operators - OperatorIndex memory operatorIndex; - operatorIndex.index = uint32(operatorList.length - 1); - pubkeyHashToIndexHistory[pubkeyHash].push(operatorIndex); - - // Update totalOperatorsHistory array - _updateTotalOperatorsHistory(); - - // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet - serviceManager.recordFirstStakeUpdate(operator, 0); - } - - /** - * TODO: critique: "Currently only `_registrationStakeEvaluation` uses the `uint256 registrantType` input -- we should **EITHER** store this - * and keep using it in other places as well, **OR** stop using it altogether" - */ - /** - * @notice Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`. - * @dev This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere. - */ - function _registerStake(address operator, bytes32 pubkeyHash, uint256 quorumBitmap) - internal - { - // verify that the `operator` is not already registered - require( - registry[operator].status == IQuorumRegistry.Status.INACTIVE, - "RegistryBase._registerStake: Operator is already registered" - ); - - OperatorStakeUpdate memory _operatorStakeUpdate; - // add the `updateBlockNumber` info - _operatorStakeUpdate.updateBlockNumber = uint32(block.number); - OperatorStakeUpdate memory _newTotalStakeUpdate; - // add the `updateBlockNumber` info - _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); - // for each quorum, evaluate stake and add to total stake - for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { - // evaluate the stake for the operator - if(quorumBitmap >> quorumNumber & 1 == 1) { - _operatorStakeUpdate.stake = uint96(weightOfOperator(quorumNumber, operator)); - // check if minimum requirement has been met - require(_operatorStakeUpdate.stake >= minimumStakeForQuorum[quorumNumber], "RegistryBase._registerStake: Operator does not meet minimum stake requirement for quorum"); - // check special case that operator is re-registering (and thus already has some history) - if (pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length != 0) { - // correctly set the 'nextUpdateBlockNumber' field for the re-registering operator's oldest history entry - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1].nextUpdateBlockNumber - = uint32(block.number); - } - // push the new stake for the operator to storage - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push(_operatorStakeUpdate); - - // get the total stake for the quorum - _newTotalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; - // add operator stakes to total stake (in memory) - _newTotalStakeUpdate.stake = uint96(_newTotalStakeUpdate.stake + _operatorStakeUpdate.stake); - // update storage of total stake - _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); - - emit StakeUpdate( - operator, - quorumNumber, - _operatorStakeUpdate.stake, - uint32(block.number), - // no previous update block number -- use 0 instead - 0 // TODO: Decide whether this needs to be set in re-registration edge case - ); - } - unchecked { - ++quorumNumber; - } - } - } - - /** - * @notice Finds the updated stake for `operator`, stores it and records the update. - * @dev **DOES NOT UPDATE `totalStake` IN ANY WAY** -- `totalStake` updates must be done elsewhere. - */ - function _updateOperatorStake(address operator, bytes32 pubkeyHash, uint256 quorumBitmap, uint8 quorumNumber, uint32 prevUpdateBlockNumber) - internal - returns (OperatorStakeUpdate memory operatorStakeUpdate) - { - // if the operator is part of the quorum - if (quorumBitmap >> quorumNumber & 1 == 1) { - // determine new stakes - operatorStakeUpdate.updateBlockNumber = uint32(block.number); - operatorStakeUpdate.stake = weightOfOperator(quorumNumber, operator); - - // check if minimum requirements have been met - if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { - operatorStakeUpdate.stake = uint96(0); - } - - // set nextUpdateBlockNumber in prev stakes - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1].nextUpdateBlockNumber = - uint32(block.number); - // push new stake to storage - pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].push(operatorStakeUpdate); - - emit StakeUpdate( - operator, - quorumNumber, - operatorStakeUpdate.stake, - uint32(block.number), - prevUpdateBlockNumber - ); - } - } - - /// @notice Records that the `totalStake` is now equal to the input param @_totalStake - function _recordTotalStakeUpdate(uint8 quorumNumber, OperatorStakeUpdate memory _totalStake) internal { - _totalStake.updateBlockNumber = uint32(block.number); - totalStakeHistory[quorumNumber][totalStakeHistory.length - 1].nextUpdateBlockNumber = uint32(block.number); - totalStakeHistory[quorumNumber].push(_totalStake); - } - - /// @notice Validates that the `operatorStake` was accurate at the given `blockNumber` - function _validateOperatorStakeUpdateAtBlockNumber(OperatorStakeUpdate memory operatorStakeUpdate, uint32 blockNumber) internal pure { - require( - operatorStakeUpdate.updateBlockNumber <= blockNumber, - "RegistryBase._validateOperatorStakeAtBlockNumber: operatorStakeUpdate is from after blockNumber" - ); - require( - operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber, - "RegistryBase._validateOperatorStakeAtBlockNumber: there is a newer operatorStakeUpdate available before blockNumber" - ); - } - - /// @notice Verify that the `operator` is an active operator and that they've provided the correct `index` - function _deregistrationCheck(address operator, uint32 index) internal view { - require( - registry[operator].status == IQuorumRegistry.Status.ACTIVE, - "RegistryBase._deregistrationCheck: Operator is not registered" - ); - - require(operator == operatorList[index], "RegistryBase._deregistrationCheck: Incorrect index supplied"); - } - - // storage gap - uint256[50] private __GAP; -} \ No newline at end of file diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 0e744789c..42455ccd5 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -297,7 +297,7 @@ contract StakeRegistry is StakeRegistryStorage { * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for * @dev reverts if there are no operators registered with index out of bounds */ - function updateStakes(address[] calldata operators, bytes32[] calldata operatorIds, uint256[] calldata quorumBitmaps, uint256[] calldata prevElements) external { + function updateStakes(address[] calldata operators, bytes32[] calldata operatorIds, uint192[] calldata quorumBitmaps, uint256[] calldata prevElements) external { // for each quorum, loop through operators and see if they are apart of the quorum // if they are, get their new weight and update their individual stake history and thes // quorum's total stake history accordingly diff --git a/src/contracts/middleware/example/ECDSARegistry.sol b/src/contracts/middleware/example/ECDSARegistry.sol deleted file mode 100644 index 7efa2cc5b..000000000 --- a/src/contracts/middleware/example/ECDSARegistry.sol +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "../RegistryBase.sol"; - -/** - * @title A Registry-type contract identifying operators by their Ethereum address, with only 1 quorum. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice This contract is used for - * - registering new operators - * - committing to and finalizing de-registration as an operator - * - updating the stakes of the operator - */ -contract ECDSARegistry is RegistryBase { - - /// @notice the address that can whitelist people - address public operatorWhitelister; - /// @notice toggle of whether the operator whitelist is on or off - bool public operatorWhitelistEnabled; - /// @notice operator => are they whitelisted (can they register with the middleware) - mapping(address => bool) public whitelisted; - - // EVENTS - /** - * @notice Emitted upon the registration of a new operator for the middleware - * @param operator Address of the new operator - * @param socket The ip:port of the operator - */ - event Registration( - address indexed operator, - string socket - ); - - /// @notice Emitted when the `operatorWhitelister` role is transferred. - event OperatorWhitelisterTransferred(address previousAddress, address newAddress); - - /// @notice Modifier that restricts a function to only be callable by the `whitelister` role. - modifier onlyOperatorWhitelister { - require(operatorWhitelister == msg.sender, "BLSRegistry.onlyOperatorWhitelister: not operatorWhitelister"); - _; - } - - constructor( - IStrategyManager _strategyManager, - IServiceManager _serviceManager - ) - RegistryBase( - _strategyManager, - _serviceManager - ) - {} - - /// @notice Initialize whitelister and the quorum strategies + multipliers. - function initialize( - address _operatorWhitelister, - bool _operatorWhitelistEnabled, - uint96[] memory _minimumStakeForQuorums, - StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers - ) public virtual initializer { - _setOperatorWhitelister(_operatorWhitelister); - operatorWhitelistEnabled = _operatorWhitelistEnabled; - - RegistryBase._initialize( - _minimumStakeForQuorums, - _quorumStrategiesConsideredAndMultipliers - ); - } - - /** - * @notice Called by the service manager owner to transfer the whitelister role to another address - */ - function setOperatorWhitelister(address _operatorWhitelister) external onlyServiceManagerOwner { - _setOperatorWhitelister(_operatorWhitelister); - } - - /** - * @notice Callable only by the service manager owner, this function toggles the whitelist on or off - * @param _operatorWhitelistEnabled true if turning whitelist on, false otherwise - */ - function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external onlyServiceManagerOwner { - operatorWhitelistEnabled = _operatorWhitelistEnabled; - } - - /** - * @notice Called by the operatorWhitelister, adds a list of operators to the whitelist - * @param operators the operators to add to the whitelist - */ - function addToOperatorWhitelist(address[] calldata operators) external onlyOperatorWhitelister { - for (uint i = 0; i < operators.length; i++) { - whitelisted[operators[i]] = true; - } - } - - /** - * @notice Called by the operatorWhitelister, removes a list of operators to the whitelist - * @param operators the operators to remove from the whitelist - */ - function removeFromWhitelist(address[] calldata operators) external onlyOperatorWhitelister { - for (uint i = 0; i < operators.length; i++) { - whitelisted[operators[i]] = false; - } - } - /** - * @notice called for registering as an operator - * @param quorumBitmap is the bitmap of quorums that the operator is registering for - * @param socket is the socket address of the operator - */ - function registerOperator(uint256 quorumBitmap, string calldata socket) external virtual { - _registerOperator(msg.sender, quorumBitmap, socket); - } - - /** - * @param operator is the node who is registering to be a operator - * @param socket is the socket address of the operator - */ - function _registerOperator(address operator, uint256 quorumBitmap, string calldata socket) - internal - { - if(operatorWhitelistEnabled) { - require(whitelisted[operator], "BLSRegistry._registerOperator: not whitelisted"); - } - - // add the operator to the list of registrants and do accounting - _addRegistrant(operator, bytes32(uint256(uint160(operator))), quorumBitmap); - - emit Registration(operator, socket); - } - - /** - * @notice Used by an operator to de-register itself from providing service to the middleware. - * @param index is the sender's location in the dynamic array `operatorList` - */ - function deregisterOperator(uint32 index) external virtual returns (bool) { - _deregisterOperator(msg.sender, index); - return true; - } - - /** - * @notice Used to process de-registering an operator from providing service to the middleware. - * @param operator The operator to be deregistered - * @param index is the sender's location in the dynamic array `operatorList` - */ - function _deregisterOperator(address operator, uint32 index) internal { - // verify that the `operator` is an active operator and that they've provided the correct `index` - _deregistrationCheck(operator, index); - - // Perform necessary updates for removing operator, including updating operator list and index histories - _removeOperator(operator, bytes32(uint256(uint160(operator))), index); - } - - /** - * @notice Used for updating information on deposits of nodes. - * @param operators are the nodes whose deposit information is getting updated - * @param prevElements are the elements before this middleware in the operator's linked list within the slasher - */ - function updateStakes(address[] memory operators, uint256[] memory prevElements) external { - // load all operator structs into memory - Operator[] memory operatorStructs = new Operator[](operators.length); - for (uint i = 0; i < operators.length;) { - operatorStructs[i] = registry[operators[i]]; - unchecked { - ++i; - } - } - - // load all operator quorum bitmaps into memory - uint256[] memory quorumBitmaps = new uint256[](operators.length); - for (uint i = 0; i < operators.length;) { - quorumBitmaps[i] = pubkeyHashToQuorumBitmap[operatorStructs[i].pubkeyHash]; - unchecked { - ++i; - } - } - - // for each quorum, loop through operators and see if they are apart of the quorum - // if they are, get their new weight and update their individual stake history and the - // quorum's total stake history accordingly - for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { - OperatorStakeUpdate memory totalStakeUpdate; - // for each operator - for(uint i = 0; i < operators.length;) { - // if the operator is apart of the quorum - if (quorumBitmaps[i] >> quorumNumber & 1 == 1) { - // if the total stake has not been loaded yet, load it - if (totalStakeUpdate.updateBlockNumber == 0) { - totalStakeUpdate = totalStakeHistory[quorumNumber][totalStakeHistory[quorumNumber].length - 1]; - } - // get the operator's pubkeyHash - bytes32 pubkeyHash = operatorStructs[i].pubkeyHash; - // get the operator's current stake - OperatorStakeUpdate memory prevStakeUpdate = pubkeyHashToStakeHistory[pubkeyHash][quorumNumber][pubkeyHashToStakeHistory[pubkeyHash][quorumNumber].length - 1]; - // update the operator's stake based on current state - OperatorStakeUpdate memory newStakeUpdate = _updateOperatorStake(operators[i], pubkeyHash, quorumBitmaps[i], quorumNumber, prevStakeUpdate.updateBlockNumber); - // calculate the new total stake for the quorum - totalStakeUpdate.stake = totalStakeUpdate.stake - prevStakeUpdate.stake + newStakeUpdate.stake; - } - unchecked { - ++i; - } - } - // if the total stake for this quorum was updated, record it in storage - if (totalStakeUpdate.updateBlockNumber != 0) { - // update the total stake history for the quorum - _recordTotalStakeUpdate(quorumNumber, totalStakeUpdate); - } - unchecked { - ++quorumNumber; - } - } - - // record stake updates in the EigenLayer Slasher - for (uint i = 0; i < operators.length;) { - serviceManager.recordStakeUpdate(operators[i], uint32(block.number), serviceManager.latestServeUntilBlock(), prevElements[i]); - unchecked { - ++i; - } - } - } - - function _setOperatorWhitelister(address _operatorWhitelister) internal { - require(_operatorWhitelister != address(0), "BLSRegistry.initialize: cannot set operatorWhitelister to zero address"); - emit OperatorWhitelisterTransferred(operatorWhitelister, _operatorWhitelister); - operatorWhitelister = _operatorWhitelister; - } -} diff --git a/src/contracts/middleware/example/HashThreshold.sol b/src/contracts/middleware/example/HashThreshold.sol deleted file mode 100644 index 34e27b20a..000000000 --- a/src/contracts/middleware/example/HashThreshold.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import "../../interfaces/IServiceManager.sol"; -import "./ECDSARegistry.sol"; - -/** - * @title An EigenLayer middleware example service manager that slashes validators that sign a message that, when hashed 10 times starts with less than a certain number of 0s. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - */ -contract HashThreshold is Ownable, IServiceManager { - uint32 constant disputePeriodBlocks = 1 days / 12 seconds; - uint8 constant numZeroes = 5; - ISlasher public immutable slasher; - ECDSARegistry public immutable registry; - - - struct CertifiedMessageMetadata { - bytes32 signaturesHash; - uint32 validAfterBlock; - } - - uint32 public taskNumber = 0; - uint32 public latestServeUntilBlock = 0; - mapping(bytes32 => CertifiedMessageMetadata) public certifiedMessageMetadatas; - - event MessageCertified(bytes32); - - modifier onlyRegistry { - require(msg.sender == address(registry), "Only registry can call this function"); - _; - } - - constructor( - ISlasher _slasher, - ECDSARegistry _registry - ) { - slasher = _slasher; - registry = _registry; - } - - function owner() public view override(Ownable, IServiceManager) returns (address) { - return Ownable.owner(); - } - - function decaHash(bytes32 message) public pure returns (bytes32) { - bytes32 hash = message; - for (uint256 i = 0; i < 10; i++) { - hash = keccak256(abi.encodePacked(hash)); - } - return hash; - } - - /** - * This function is called by anyone to certify a message. Signers are certifying that the decahashed message starts with at least `numZeros` 0s. - * - * @param message The message to certify - * @param signatures The signatures of the message, certifying it - */ - function submitSignatures(bytes32 message, bytes calldata signatures) external { - // we check that the message has not already been certified - require(certifiedMessageMetadatas[message].validAfterBlock == 0, "Message already certified"); - // this makes it so that the signatures are viewable in calldata - require(msg.sender == tx.origin, "EOA must call this function"); - uint128 stakeSigned = 0; - for(uint256 i = 0; i < signatures.length; i += 65) { - // we fetch all the signers and check their signatures and their stake - address signer = ECDSA.recover(message, signatures[i:i+65]); - require(registry.isActiveOperator(signer), "Signer is not an active operator"); - stakeSigned += registry.getCurrentOperatorStakeForQuorum(signer, 0); - } - // We require that 2/3 of the stake signed the message - // We only take the first quorum stake because this is a single quorum middleware - uint96 totalStake = registry.getCurrentTotalStakeForQuorum(0); - require(stakeSigned >= 666667 * uint256(totalStake) / 1000000, "Need more than 2/3 of stake to sign"); - - uint32 newLatestServeUntilBlock = uint32(block.number + disputePeriodBlocks); - - certifiedMessageMetadatas[message] = CertifiedMessageMetadata({ - validAfterBlock: newLatestServeUntilBlock, - signaturesHash: keccak256(signatures) - }); - - // increment global service manager values - taskNumber++; - // Note: latestServeUntilBlock is the latest block at which anyone currently staked on the middleware can be frozen - latestServeUntilBlock = newLatestServeUntilBlock; - - emit MessageCertified(message); - } - - - /** - * This function is called by anyone to slash the signers of an invalid message that has been certified. - * - * @param message The message to slash the signers of - * @param signatures The signatures that certified the message - */ - function slashSigners(bytes32 message, bytes calldata signatures) external { - CertifiedMessageMetadata memory certifiedMessageMetadata = certifiedMessageMetadatas[message]; - // we check that the message has been certified - require(certifiedMessageMetadata.validAfterBlock > block.number, "Dispute period has passed"); - // we check that the signatures match the ones that were certified - require(certifiedMessageMetadata.signaturesHash == keccak256(signatures), "Signatures do not match"); - // we check that the message hashes to enough zeroes - require(decaHash(message) >> (256 - numZeroes) == 0, "Message does not hash to enough zeroes"); - // we freeze all the signers - for (uint i = 0; i < signatures.length; i += 65) { - // this is eigenlayer's means of escalating an operators stake for review for slashing - // this immediately prevents all withdrawals for the operator and stakers delegated to them - slasher.freezeOperator(ECDSA.recover(message, signatures[i:i+65])); - } - // we invalidate the message - certifiedMessageMetadatas[message].validAfterBlock = type(uint32).max; - } - - /// @inheritdoc IServiceManager - function freezeOperator(address operator) external onlyRegistry { - slasher.freezeOperator(operator); - } - - /// @inheritdoc IServiceManager - function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external onlyRegistry { - slasher.recordFirstStakeUpdate(operator, serveUntilBlock); - } - - /// @inheritdoc IServiceManager - function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external onlyRegistry { - slasher.recordLastStakeUpdateAndRevokeSlashingAbility(operator, serveUntilBlock); - } - - /// @inheritdoc IServiceManager - function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external onlyRegistry { - slasher.recordStakeUpdate(operator, updateBlock, serveUntilBlock, prevElement); - } -} diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol index db0d6af7c..4b4a4044e 100644 --- a/src/test/Delegation.t.sol +++ b/src/test/Delegation.t.sol @@ -1,531 +1,531 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; +// // SPDX-License-Identifier: BUSL-1.1 +// pragma solidity =0.8.12; -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol"; +// import "@openzeppelin/contracts/utils/math/Math.sol"; +// import "@openzeppelin/contracts/utils/Address.sol"; +// import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol"; -import "../test/EigenLayerTestHelper.t.sol"; +// import "../test/EigenLayerTestHelper.t.sol"; -import "./mocks/MiddlewareRegistryMock.sol"; -import "./mocks/MiddlewareVoteWeigherMock.sol"; -import "./mocks/ServiceManagerMock.sol"; +// import "./mocks/MiddlewareRegistryMock.sol"; +// import "./mocks/MiddlewareVoteWeigherMock.sol"; +// import "./mocks/ServiceManagerMock.sol"; -import "./SigP/DelegationTerms.sol"; +// import "./SigP/DelegationTerms.sol"; -contract DelegationTests is EigenLayerTestHelper { - using Math for uint256; +// contract DelegationTests is EigenLayerTestHelper { +// using Math for uint256; - uint256 public PRIVATE_KEY = 420; +// uint256 public PRIVATE_KEY = 420; - uint32 serveUntil = 100; +// uint32 serveUntil = 100; - ServiceManagerMock public serviceManager; - MiddlewareVoteWeigherMock public voteWeigher; - MiddlewareVoteWeigherMock public voteWeigherImplementation; +// ServiceManagerMock public serviceManager; +// MiddlewareVoteWeigherMock public voteWeigher; +// MiddlewareVoteWeigherMock public voteWeigherImplementation; - modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) { - cheats.assume(ethAmount >= 0 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18); - _; - } +// modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) { +// cheats.assume(ethAmount >= 0 && ethAmount <= 1e18); +// cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18); +// _; +// } - function setUp() public virtual override { - EigenLayerDeployer.setUp(); +// function setUp() public virtual override { +// EigenLayerDeployer.setUp(); - initializeMiddlewares(); - } +// initializeMiddlewares(); +// } - function initializeMiddlewares() public { - serviceManager = new ServiceManagerMock(slasher); +// function initializeMiddlewares() public { +// serviceManager = new ServiceManagerMock(slasher); - voteWeigher = MiddlewareVoteWeigherMock( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); +// voteWeigher = MiddlewareVoteWeigherMock( +// address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) +// ); - voteWeigherImplementation = new MiddlewareVoteWeigherMock(delegation, strategyManager, serviceManager); +// voteWeigherImplementation = new MiddlewareVoteWeigherMock(delegation, strategyManager, serviceManager); - { - uint96 multiplier = 1e18; - uint8 _NUMBER_OF_QUORUMS = 2; - uint256[] memory _quorumBips = new uint256[](_NUMBER_OF_QUORUMS); - // split 60% ETH quorum, 40% EIGEN quorum - _quorumBips[0] = 6000; - _quorumBips[1] = 4000; - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); - ethStratsAndMultipliers[0].strategy = wethStrat; - ethStratsAndMultipliers[0].multiplier = multiplier; - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); - eigenStratsAndMultipliers[0].strategy = eigenStrat; - eigenStratsAndMultipliers[0].multiplier = multiplier; +// { +// uint96 multiplier = 1e18; +// uint8 _NUMBER_OF_QUORUMS = 2; +// uint256[] memory _quorumBips = new uint256[](_NUMBER_OF_QUORUMS); +// // split 60% ETH quorum, 40% EIGEN quorum +// _quorumBips[0] = 6000; +// _quorumBips[1] = 4000; +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); +// ethStratsAndMultipliers[0].strategy = wethStrat; +// ethStratsAndMultipliers[0].multiplier = multiplier; +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); +// eigenStratsAndMultipliers[0].strategy = eigenStrat; +// eigenStratsAndMultipliers[0].multiplier = multiplier; - cheats.startPrank(eigenLayerProxyAdmin.owner()); - eigenLayerProxyAdmin.upgradeAndCall( - TransparentUpgradeableProxy(payable(address(voteWeigher))), - address(voteWeigherImplementation), - abi.encodeWithSelector(MiddlewareVoteWeigherMock.initialize.selector, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers) - ); - cheats.stopPrank(); +// cheats.startPrank(eigenLayerProxyAdmin.owner()); +// eigenLayerProxyAdmin.upgradeAndCall( +// TransparentUpgradeableProxy(payable(address(voteWeigher))), +// address(voteWeigherImplementation), +// abi.encodeWithSelector(MiddlewareVoteWeigherMock.initialize.selector, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers) +// ); +// cheats.stopPrank(); - } - } - - /// @notice testing if an operator can register to themselves. - function testSelfOperatorRegister() public { - _testRegisterAdditionalOperator(0, serveUntil); - } - - /// @notice testing if an operator can delegate to themselves. - /// @param sender is the address of the operator. - function testSelfOperatorDelegate(address sender) public { - cheats.assume(sender != address(0)); - cheats.assume(sender != address(eigenLayerProxyAdmin)); - _testRegisterAsOperator(sender, IDelegationTerms(sender)); - } - - function testTwoSelfOperatorsRegister() public { - _testRegisterAdditionalOperator(0, serveUntil); - _testRegisterAdditionalOperator(1, serveUntil); - } - - /// @notice registers a fixed address as a delegate, delegates to it from a second address, - /// and checks that the delegate's voteWeights increase properly - /// @param operator is the operator being delegated to. - /// @param staker is the staker delegating stake to the operator. - function testDelegation(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) - public - fuzzedAddress(operator) - fuzzedAddress(staker) - fuzzedAmounts(ethAmount, eigenAmount) - { - cheats.assume(staker != operator); - // base strategy will revert if these amounts are too small on first deposit - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 1); +// } +// } + +// /// @notice testing if an operator can register to themselves. +// function testSelfOperatorRegister() public { +// _testRegisterAdditionalOperator(0, serveUntil); +// } + +// /// @notice testing if an operator can delegate to themselves. +// /// @param sender is the address of the operator. +// function testSelfOperatorDelegate(address sender) public { +// cheats.assume(sender != address(0)); +// cheats.assume(sender != address(eigenLayerProxyAdmin)); +// _testRegisterAsOperator(sender, IDelegationTerms(sender)); +// } + +// function testTwoSelfOperatorsRegister() public { +// _testRegisterAdditionalOperator(0, serveUntil); +// _testRegisterAdditionalOperator(1, serveUntil); +// } + +// /// @notice registers a fixed address as a delegate, delegates to it from a second address, +// /// and checks that the delegate's voteWeights increase properly +// /// @param operator is the operator being delegated to. +// /// @param staker is the staker delegating stake to the operator. +// function testDelegation(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) +// public +// fuzzedAddress(operator) +// fuzzedAddress(staker) +// fuzzedAmounts(ethAmount, eigenAmount) +// { +// cheats.assume(staker != operator); +// // base strategy will revert if these amounts are too small on first deposit +// cheats.assume(ethAmount >= 1); +// cheats.assume(eigenAmount >= 1); - _testDelegation(operator, staker, ethAmount, eigenAmount, voteWeigher); - } - - /// @notice tests that a when an operator is delegated to, that delegation is properly accounted for. - function testDelegationReceived(address _operator, address staker, uint64 ethAmount, uint64 eigenAmount) - public - fuzzedAddress(_operator) - fuzzedAddress(staker) - fuzzedAmounts(ethAmount, eigenAmount) - { - cheats.assume(staker != _operator); - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 1); - - // use storage to solve stack-too-deep - operator = _operator; - - SigPDelegationTerms dt = new SigPDelegationTerms(); +// _testDelegation(operator, staker, ethAmount, eigenAmount, voteWeigher); +// } + +// /// @notice tests that a when an operator is delegated to, that delegation is properly accounted for. +// function testDelegationReceived(address _operator, address staker, uint64 ethAmount, uint64 eigenAmount) +// public +// fuzzedAddress(_operator) +// fuzzedAddress(staker) +// fuzzedAmounts(ethAmount, eigenAmount) +// { +// cheats.assume(staker != _operator); +// cheats.assume(ethAmount >= 1); +// cheats.assume(eigenAmount >= 1); + +// // use storage to solve stack-too-deep +// operator = _operator; + +// SigPDelegationTerms dt = new SigPDelegationTerms(); - if (!delegation.isOperator(operator)) { - _testRegisterAsOperator(operator, dt); - } - - uint256[3] memory amountsBefore; - amountsBefore[0] = voteWeigher.weightOfOperator(0, operator); - amountsBefore[1] = voteWeigher.weightOfOperator(1, operator); - amountsBefore[2] = delegation.operatorShares(operator, wethStrat); - - //making additional deposits to the strategies - assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate"); - _testDepositWeth(staker, ethAmount); - _testDepositEigen(staker, eigenAmount); - _testDelegateToOperator(staker, operator); - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - - (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) = - strategyManager.getDeposits(staker); - - { - uint256 stakerEthWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[0]); - uint256 stakerEigenWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[1]); - - uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); - uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); - - assertTrue( - operatorEthWeightAfter - amountsBefore[0] == stakerEthWeight, - "testDelegation: operatorEthWeight did not increment by the right amount" - ); - assertTrue( - operatorEigenWeightAfter - amountsBefore[1] == stakerEigenWeight, - "Eigen weights did not increment by the right amount" - ); - } - { - IStrategy _strat = wethStrat; - // IStrategy _strat = strategyManager.stakerStrats(staker, 0); - assertTrue(address(_strat) != address(0), "stakerStrats not updated correctly"); - - assertTrue( - delegation.operatorShares(operator, _strat) - updatedShares[0] == amountsBefore[2], - "ETH operatorShares not updated correctly" - ); - - cheats.startPrank(address(strategyManager)); - - IDelegationTerms expectedDt = delegation.delegationTerms(operator); - assertTrue(address(expectedDt) == address(dt), "failed to set dt"); - delegation.increaseDelegatedShares(staker, _strat, 1); - - // dt.delegate(); - assertTrue(keccak256(dt.isDelegationReceived()) == keccak256(bytes("received")), "failed to fire expected onDelegationReceived callback"); - } - } - - /// @notice tests that a when an operator is undelegated from, that the staker is properly classified as undelegated. - function testUndelegation(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) - public - fuzzedAddress(operator) - fuzzedAddress(staker) - fuzzedAmounts(ethAmount, eigenAmount) - { - cheats.assume(staker != operator); - // base strategy will revert if these amounts are too small on first deposit - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 1); - - _testDelegation(operator, staker, ethAmount, eigenAmount, voteWeigher); - cheats.startPrank(address(strategyManager)); - delegation.undelegate(staker); - cheats.stopPrank(); - - require(delegation.delegatedTo(staker) == address(0), "undelegation unsuccessful"); - } - - /// @notice tests delegation from a staker to operator via ECDSA signature. - function testDelegateToBySignature(address operator, uint96 ethAmount, uint96 eigenAmount, uint256 expiry) - public - fuzzedAddress(operator) - { - address staker = cheats.addr(PRIVATE_KEY); - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - uint256 nonceBefore = delegation.nonces(staker); - - bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, expiry)); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash)); - - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); - - bytes memory signature = abi.encodePacked(r, s, v); +// if (!delegation.isOperator(operator)) { +// _testRegisterAsOperator(operator, dt); +// } + +// uint256[3] memory amountsBefore; +// amountsBefore[0] = voteWeigher.weightOfOperator(0, operator); +// amountsBefore[1] = voteWeigher.weightOfOperator(1, operator); +// amountsBefore[2] = delegation.operatorShares(operator, wethStrat); + +// //making additional deposits to the strategies +// assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate"); +// _testDepositWeth(staker, ethAmount); +// _testDepositEigen(staker, eigenAmount); +// _testDelegateToOperator(staker, operator); +// assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); + +// (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) = +// strategyManager.getDeposits(staker); + +// { +// uint256 stakerEthWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[0]); +// uint256 stakerEigenWeight = strategyManager.stakerStrategyShares(staker, updatedStrategies[1]); + +// uint256 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); +// uint256 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); + +// assertTrue( +// operatorEthWeightAfter - amountsBefore[0] == stakerEthWeight, +// "testDelegation: operatorEthWeight did not increment by the right amount" +// ); +// assertTrue( +// operatorEigenWeightAfter - amountsBefore[1] == stakerEigenWeight, +// "Eigen weights did not increment by the right amount" +// ); +// } +// { +// IStrategy _strat = wethStrat; +// // IStrategy _strat = strategyManager.stakerStrats(staker, 0); +// assertTrue(address(_strat) != address(0), "stakerStrats not updated correctly"); + +// assertTrue( +// delegation.operatorShares(operator, _strat) - updatedShares[0] == amountsBefore[2], +// "ETH operatorShares not updated correctly" +// ); + +// cheats.startPrank(address(strategyManager)); + +// IDelegationTerms expectedDt = delegation.delegationTerms(operator); +// assertTrue(address(expectedDt) == address(dt), "failed to set dt"); +// delegation.increaseDelegatedShares(staker, _strat, 1); + +// // dt.delegate(); +// assertTrue(keccak256(dt.isDelegationReceived()) == keccak256(bytes("received")), "failed to fire expected onDelegationReceived callback"); +// } +// } + +// /// @notice tests that a when an operator is undelegated from, that the staker is properly classified as undelegated. +// function testUndelegation(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) +// public +// fuzzedAddress(operator) +// fuzzedAddress(staker) +// fuzzedAmounts(ethAmount, eigenAmount) +// { +// cheats.assume(staker != operator); +// // base strategy will revert if these amounts are too small on first deposit +// cheats.assume(ethAmount >= 1); +// cheats.assume(eigenAmount >= 1); + +// _testDelegation(operator, staker, ethAmount, eigenAmount, voteWeigher); +// cheats.startPrank(address(strategyManager)); +// delegation.undelegate(staker); +// cheats.stopPrank(); + +// require(delegation.delegatedTo(staker) == address(0), "undelegation unsuccessful"); +// } + +// /// @notice tests delegation from a staker to operator via ECDSA signature. +// function testDelegateToBySignature(address operator, uint96 ethAmount, uint96 eigenAmount, uint256 expiry) +// public +// fuzzedAddress(operator) +// { +// address staker = cheats.addr(PRIVATE_KEY); +// _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); + +// uint256 nonceBefore = delegation.nonces(staker); + +// bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, expiry)); +// bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash)); + +// (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); + +// bytes memory signature = abi.encodePacked(r, s, v); - if (expiry < block.timestamp) { - cheats.expectRevert("DelegationManager.delegateToBySignature: delegation signature expired"); - } - delegation.delegateToBySignature(staker, operator, expiry, signature); - if (expiry >= block.timestamp) { - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - assertTrue(nonceBefore + 1 == delegation.nonces(staker), "nonce not incremented correctly"); - assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator"); - } - } - - /// @notice tries delegating using a signature and an EIP 1271 compliant wallet - function testDelegateToBySignature_WithContractWallet_Successfully(address operator, uint96 ethAmount, uint96 eigenAmount) - public - fuzzedAddress(operator) - { - address staker = cheats.addr(PRIVATE_KEY); - - // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); - ERC1271WalletMock wallet = new ERC1271WalletMock(staker); - cheats.stopPrank(); - staker = address(wallet); - - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); +// if (expiry < block.timestamp) { +// cheats.expectRevert("DelegationManager.delegateToBySignature: delegation signature expired"); +// } +// delegation.delegateToBySignature(staker, operator, expiry, signature); +// if (expiry >= block.timestamp) { +// assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); +// assertTrue(nonceBefore + 1 == delegation.nonces(staker), "nonce not incremented correctly"); +// assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator"); +// } +// } + +// /// @notice tries delegating using a signature and an EIP 1271 compliant wallet +// function testDelegateToBySignature_WithContractWallet_Successfully(address operator, uint96 ethAmount, uint96 eigenAmount) +// public +// fuzzedAddress(operator) +// { +// address staker = cheats.addr(PRIVATE_KEY); + +// // deploy ERC1271WalletMock for staker to use +// cheats.startPrank(staker); +// ERC1271WalletMock wallet = new ERC1271WalletMock(staker); +// cheats.stopPrank(); +// staker = address(wallet); + +// _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - uint256 nonceBefore = delegation.nonces(staker); +// uint256 nonceBefore = delegation.nonces(staker); - bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max)); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash)); +// bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max)); +// bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); +// (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); - bytes memory signature = abi.encodePacked(r, s, v); +// bytes memory signature = abi.encodePacked(r, s, v); - delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - assertTrue(nonceBefore + 1 == delegation.nonces(staker), "nonce not incremented correctly"); - assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator"); - } - - /// @notice tries delegating using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature - function testDelegateToBySignature_WithContractWallet_BadSignature(address operator, uint96 ethAmount, uint96 eigenAmount) - public - fuzzedAddress(operator) - { - address staker = cheats.addr(PRIVATE_KEY); - - // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); - ERC1271WalletMock wallet = new ERC1271WalletMock(staker); - cheats.stopPrank(); - staker = address(wallet); - - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); +// delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); +// assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); +// assertTrue(nonceBefore + 1 == delegation.nonces(staker), "nonce not incremented correctly"); +// assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator"); +// } + +// /// @notice tries delegating using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature +// function testDelegateToBySignature_WithContractWallet_BadSignature(address operator, uint96 ethAmount, uint96 eigenAmount) +// public +// fuzzedAddress(operator) +// { +// address staker = cheats.addr(PRIVATE_KEY); + +// // deploy ERC1271WalletMock for staker to use +// cheats.startPrank(staker); +// ERC1271WalletMock wallet = new ERC1271WalletMock(staker); +// cheats.stopPrank(); +// staker = address(wallet); + +// _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - uint256 nonceBefore = delegation.nonces(staker); +// uint256 nonceBefore = delegation.nonces(staker); - bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max)); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash)); +// bytes32 structHash = keccak256(abi.encode(delegation.DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max)); +// bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); - // mess up the signature by flipping v's parity - v = (v == 27 ? 28 : 27); +// (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); +// // mess up the signature by flipping v's parity +// v = (v == 27 ? 28 : 27); - bytes memory signature = abi.encodePacked(r, s, v); +// bytes memory signature = abi.encodePacked(r, s, v); - cheats.expectRevert(bytes("DelegationManager.delegateToBySignature: ERC1271 signature verification failed")); - delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); - } - - /// @notice tries delegating using a wallet that does not comply with EIP 1271 - function testDelegateToBySignature_WithContractWallet_NonconformingWallet(address operator, uint96 ethAmount, uint96 eigenAmount, uint8 v, bytes32 r, bytes32 s) - public - fuzzedAddress(operator) - { - address staker = cheats.addr(PRIVATE_KEY); - - // deploy non ERC1271-compliant wallet for staker to use - cheats.startPrank(staker); - ERC1271MaliciousMock wallet = new ERC1271MaliciousMock(); - cheats.stopPrank(); - staker = address(wallet); - - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - cheats.assume(staker != operator); - - bytes memory signature = abi.encodePacked(r, s, v); - - cheats.expectRevert(); - delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); - } - - /// @notice tests delegation to EigenLayer via an ECDSA signatures with invalid signature - /// @param operator is the operator being delegated to. - function testDelegateToByInvalidSignature( - address operator, - uint96 ethAmount, - uint96 eigenAmount, - uint8 v, - bytes32 r, - bytes32 s - ) - public - fuzzedAddress(operator) - fuzzedAmounts(ethAmount, eigenAmount) - { - address staker = cheats.addr(PRIVATE_KEY); - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - bytes memory signature = abi.encodePacked(r, s, v); +// cheats.expectRevert(bytes("DelegationManager.delegateToBySignature: ERC1271 signature verification failed")); +// delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); +// } + +// /// @notice tries delegating using a wallet that does not comply with EIP 1271 +// function testDelegateToBySignature_WithContractWallet_NonconformingWallet(address operator, uint96 ethAmount, uint96 eigenAmount, uint8 v, bytes32 r, bytes32 s) +// public +// fuzzedAddress(operator) +// { +// address staker = cheats.addr(PRIVATE_KEY); + +// // deploy non ERC1271-compliant wallet for staker to use +// cheats.startPrank(staker); +// ERC1271MaliciousMock wallet = new ERC1271MaliciousMock(); +// cheats.stopPrank(); +// staker = address(wallet); + +// _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); + +// cheats.assume(staker != operator); + +// bytes memory signature = abi.encodePacked(r, s, v); + +// cheats.expectRevert(); +// delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); +// } + +// /// @notice tests delegation to EigenLayer via an ECDSA signatures with invalid signature +// /// @param operator is the operator being delegated to. +// function testDelegateToByInvalidSignature( +// address operator, +// uint96 ethAmount, +// uint96 eigenAmount, +// uint8 v, +// bytes32 r, +// bytes32 s +// ) +// public +// fuzzedAddress(operator) +// fuzzedAmounts(ethAmount, eigenAmount) +// { +// address staker = cheats.addr(PRIVATE_KEY); +// _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); + +// bytes memory signature = abi.encodePacked(r, s, v); - cheats.expectRevert(); - delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); - } - - /// @notice registers a fixed address as a delegate, delegates to it from a second address, - /// and checks that the delegate's voteWeights increase properly - /// @param operator is the operator being delegated to. - /// @param staker is the staker delegating stake to the operator. - function testDelegationMultipleStrategies(uint8 numStratsToAdd, address operator, address staker) - public - fuzzedAddress(operator) - fuzzedAddress(staker) - { - cheats.assume(staker != operator); - - cheats.assume(numStratsToAdd > 0 && numStratsToAdd <= 20); - uint96 operatorEthWeightBefore = voteWeigher.weightOfOperator(0, operator); - uint96 operatorEigenWeightBefore = voteWeigher.weightOfOperator(1, operator); - _testRegisterAsOperator(operator, IDelegationTerms(operator)); - _testDepositStrategies(staker, 1e18, numStratsToAdd); - - // add strategies to voteWeigher - uint96 multiplier = 1e18; - for (uint16 i = 0; i < numStratsToAdd; ++i) { - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[]( - 1 - ); - ethStratsAndMultipliers[0].strategy = strategies[i]; - ethStratsAndMultipliers[0].multiplier = multiplier; - cheats.startPrank(voteWeigher.serviceManager().owner()); - voteWeigher.addStrategiesConsideredAndMultipliers(0, ethStratsAndMultipliers); - cheats.stopPrank(); - } - - _testDepositEigen(staker, 1e18); - _testDelegateToOperator(staker, operator); - uint96 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); - uint96 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); - assertTrue( - operatorEthWeightAfter > operatorEthWeightBefore, "testDelegation: operatorEthWeight did not increase!" - ); - assertTrue( - operatorEigenWeightAfter > operatorEigenWeightBefore, "testDelegation: operatorEthWeight did not increase!" - ); - } - - /// @notice This function tests to ensure that a delegation contract - /// cannot be intitialized multiple times - function testCannotInitMultipleTimesDelegation() public cannotReinit { - //delegation has already been initialized in the Deployer test contract - delegation.initialize(address(this), eigenLayerPauserReg, 0); - } - - /// @notice This function tests to ensure that a you can't register as a delegate multiple times - /// @param operator is the operator being delegated to. - function testRegisterAsOperatorMultipleTimes(address operator) public fuzzedAddress(operator) { - _testRegisterAsOperator(operator, IDelegationTerms(operator)); - cheats.expectRevert(bytes("DelegationManager.registerAsOperator: operator has already registered")); - _testRegisterAsOperator(operator, IDelegationTerms(operator)); - } - - /// @notice This function tests to ensure that a staker cannot delegate to an unregistered operator - /// @param delegate is the unregistered operator - function testDelegationToUnregisteredDelegate(address delegate) public fuzzedAddress(delegate) { - //deposit into 1 strategy for getOperatorAddress(1), who is delegating to the unregistered operator - _testDepositStrategies(getOperatorAddress(1), 1e18, 1); - _testDepositEigen(getOperatorAddress(1), 1e18); - - cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); - cheats.startPrank(getOperatorAddress(1)); - delegation.delegateTo(delegate); - cheats.stopPrank(); - } - - - /// @notice This function tests to ensure that a delegation contract - /// cannot be intitialized multiple times, test with different caller addresses - function testCannotInitMultipleTimesDelegation(address _attacker) public { - cheats.assume(_attacker != address(eigenLayerProxyAdmin)); - //delegation has already been initialized in the Deployer test contract - vm.prank(_attacker); - cheats.expectRevert(bytes("Initializable: contract is already initialized")); - delegation.initialize(_attacker, eigenLayerPauserReg, 0); - } - - /// @notice This function tests that the delegationTerms cannot be set to address(0) - function testCannotSetDelegationTermsZeroAddress() public{ - cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); - delegation.registerAsOperator(IDelegationTerms(address(0))); - } - - /// @notice This function tests to ensure that an address can only call registerAsOperator() once - function testCannotRegisterAsOperatorTwice(address _operator, address _dt) public fuzzedAddress(_operator) fuzzedAddress(_dt) { - vm.assume(_dt != address(0)); - vm.startPrank(_operator); - delegation.registerAsOperator(IDelegationTerms(_dt)); - vm.expectRevert("DelegationManager.registerAsOperator: operator has already registered"); - delegation.registerAsOperator(IDelegationTerms(_dt)); - cheats.stopPrank(); - } - - /// @notice This function checks that you can only delegate to an address that is already registered. - function testDelegateToInvalidOperator(address _staker, address _unregisteredOperator) public fuzzedAddress(_staker) { - vm.startPrank(_staker); - cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); - delegation.delegateTo(_unregisteredOperator); - cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); - delegation.delegateTo(_staker); - cheats.stopPrank(); +// cheats.expectRevert(); +// delegation.delegateToBySignature(staker, operator, type(uint256).max, signature); +// } + +// /// @notice registers a fixed address as a delegate, delegates to it from a second address, +// /// and checks that the delegate's voteWeights increase properly +// /// @param operator is the operator being delegated to. +// /// @param staker is the staker delegating stake to the operator. +// function testDelegationMultipleStrategies(uint8 numStratsToAdd, address operator, address staker) +// public +// fuzzedAddress(operator) +// fuzzedAddress(staker) +// { +// cheats.assume(staker != operator); + +// cheats.assume(numStratsToAdd > 0 && numStratsToAdd <= 20); +// uint96 operatorEthWeightBefore = voteWeigher.weightOfOperator(0, operator); +// uint96 operatorEigenWeightBefore = voteWeigher.weightOfOperator(1, operator); +// _testRegisterAsOperator(operator, IDelegationTerms(operator)); +// _testDepositStrategies(staker, 1e18, numStratsToAdd); + +// // add strategies to voteWeigher +// uint96 multiplier = 1e18; +// for (uint16 i = 0; i < numStratsToAdd; ++i) { +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[]( +// 1 +// ); +// ethStratsAndMultipliers[0].strategy = strategies[i]; +// ethStratsAndMultipliers[0].multiplier = multiplier; +// cheats.startPrank(voteWeigher.serviceManager().owner()); +// voteWeigher.addStrategiesConsideredAndMultipliers(0, ethStratsAndMultipliers); +// cheats.stopPrank(); +// } + +// _testDepositEigen(staker, 1e18); +// _testDelegateToOperator(staker, operator); +// uint96 operatorEthWeightAfter = voteWeigher.weightOfOperator(0, operator); +// uint96 operatorEigenWeightAfter = voteWeigher.weightOfOperator(1, operator); +// assertTrue( +// operatorEthWeightAfter > operatorEthWeightBefore, "testDelegation: operatorEthWeight did not increase!" +// ); +// assertTrue( +// operatorEigenWeightAfter > operatorEigenWeightBefore, "testDelegation: operatorEthWeight did not increase!" +// ); +// } + +// /// @notice This function tests to ensure that a delegation contract +// /// cannot be intitialized multiple times +// function testCannotInitMultipleTimesDelegation() public cannotReinit { +// //delegation has already been initialized in the Deployer test contract +// delegation.initialize(address(this), eigenLayerPauserReg, 0); +// } + +// /// @notice This function tests to ensure that a you can't register as a delegate multiple times +// /// @param operator is the operator being delegated to. +// function testRegisterAsOperatorMultipleTimes(address operator) public fuzzedAddress(operator) { +// _testRegisterAsOperator(operator, IDelegationTerms(operator)); +// cheats.expectRevert(bytes("DelegationManager.registerAsOperator: operator has already registered")); +// _testRegisterAsOperator(operator, IDelegationTerms(operator)); +// } + +// /// @notice This function tests to ensure that a staker cannot delegate to an unregistered operator +// /// @param delegate is the unregistered operator +// function testDelegationToUnregisteredDelegate(address delegate) public fuzzedAddress(delegate) { +// //deposit into 1 strategy for getOperatorAddress(1), who is delegating to the unregistered operator +// _testDepositStrategies(getOperatorAddress(1), 1e18, 1); +// _testDepositEigen(getOperatorAddress(1), 1e18); + +// cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); +// cheats.startPrank(getOperatorAddress(1)); +// delegation.delegateTo(delegate); +// cheats.stopPrank(); +// } + + +// /// @notice This function tests to ensure that a delegation contract +// /// cannot be intitialized multiple times, test with different caller addresses +// function testCannotInitMultipleTimesDelegation(address _attacker) public { +// cheats.assume(_attacker != address(eigenLayerProxyAdmin)); +// //delegation has already been initialized in the Deployer test contract +// vm.prank(_attacker); +// cheats.expectRevert(bytes("Initializable: contract is already initialized")); +// delegation.initialize(_attacker, eigenLayerPauserReg, 0); +// } + +// /// @notice This function tests that the delegationTerms cannot be set to address(0) +// function testCannotSetDelegationTermsZeroAddress() public{ +// cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); +// delegation.registerAsOperator(IDelegationTerms(address(0))); +// } + +// /// @notice This function tests to ensure that an address can only call registerAsOperator() once +// function testCannotRegisterAsOperatorTwice(address _operator, address _dt) public fuzzedAddress(_operator) fuzzedAddress(_dt) { +// vm.assume(_dt != address(0)); +// vm.startPrank(_operator); +// delegation.registerAsOperator(IDelegationTerms(_dt)); +// vm.expectRevert("DelegationManager.registerAsOperator: operator has already registered"); +// delegation.registerAsOperator(IDelegationTerms(_dt)); +// cheats.stopPrank(); +// } + +// /// @notice This function checks that you can only delegate to an address that is already registered. +// function testDelegateToInvalidOperator(address _staker, address _unregisteredOperator) public fuzzedAddress(_staker) { +// vm.startPrank(_staker); +// cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); +// delegation.delegateTo(_unregisteredOperator); +// cheats.expectRevert(bytes("DelegationManager._delegate: operator has not yet registered as a delegate")); +// delegation.delegateTo(_staker); +// cheats.stopPrank(); - } - - function testUndelegate_SigP_Version(address _operator,address _staker,address _dt) public { - - vm.assume(_operator != address(0)); - vm.assume(_staker != address(0)); - vm.assume(_operator != _staker); - vm.assume(_dt != address(0)); - vm.assume(_operator != address(eigenLayerProxyAdmin)); - vm.assume(_staker != address(eigenLayerProxyAdmin)); - - //setup delegation - vm.prank(_operator); - delegation.registerAsOperator(IDelegationTerms(_dt)); - vm.prank(_staker); - delegation.delegateTo(_operator); - - //operators cannot undelegate from themselves - vm.prank(address(strategyManager)); - cheats.expectRevert(bytes("DelegationManager.undelegate: operators cannot undelegate from themselves")); - delegation.undelegate(_operator); - - //_staker cannot undelegate themselves - vm.prank(_staker); - cheats.expectRevert(); - delegation.undelegate(_operator); +// } + +// function testUndelegate_SigP_Version(address _operator,address _staker,address _dt) public { + +// vm.assume(_operator != address(0)); +// vm.assume(_staker != address(0)); +// vm.assume(_operator != _staker); +// vm.assume(_dt != address(0)); +// vm.assume(_operator != address(eigenLayerProxyAdmin)); +// vm.assume(_staker != address(eigenLayerProxyAdmin)); + +// //setup delegation +// vm.prank(_operator); +// delegation.registerAsOperator(IDelegationTerms(_dt)); +// vm.prank(_staker); +// delegation.delegateTo(_operator); + +// //operators cannot undelegate from themselves +// vm.prank(address(strategyManager)); +// cheats.expectRevert(bytes("DelegationManager.undelegate: operators cannot undelegate from themselves")); +// delegation.undelegate(_operator); + +// //_staker cannot undelegate themselves +// vm.prank(_staker); +// cheats.expectRevert(); +// delegation.undelegate(_operator); - //_operator cannot undelegate themselves - vm.prank(_operator); - cheats.expectRevert(); - delegation.undelegate(_operator); +// //_operator cannot undelegate themselves +// vm.prank(_operator); +// cheats.expectRevert(); +// delegation.undelegate(_operator); - //assert still delegated - assertTrue(delegation.isDelegated(_staker)); - assertFalse(delegation.isNotDelegated(_staker)); - assertTrue(delegation.isOperator(_operator)); +// //assert still delegated +// assertTrue(delegation.isDelegated(_staker)); +// assertFalse(delegation.isNotDelegated(_staker)); +// assertTrue(delegation.isOperator(_operator)); - //strategyManager can undelegate _staker - vm.prank(address(strategyManager)); - delegation.undelegate(_staker); - assertFalse(delegation.isDelegated(_staker)); - assertTrue(delegation.isNotDelegated(_staker)); +// //strategyManager can undelegate _staker +// vm.prank(address(strategyManager)); +// delegation.undelegate(_staker); +// assertFalse(delegation.isDelegated(_staker)); +// assertTrue(delegation.isNotDelegated(_staker)); - } +// } - function _testRegisterAdditionalOperator(uint256 index, uint32 _serveUntil) internal { - address sender = getOperatorAddress(index); +// function _testRegisterAdditionalOperator(uint256 index, uint32 _serveUntil) internal { +// address sender = getOperatorAddress(index); - //register as both ETH and EIGEN operator - uint256 wethToDeposit = 1e18; - uint256 eigenToDeposit = 1e10; - _testDepositWeth(sender, wethToDeposit); - _testDepositEigen(sender, eigenToDeposit); - _testRegisterAsOperator(sender, IDelegationTerms(sender)); +// //register as both ETH and EIGEN operator +// uint256 wethToDeposit = 1e18; +// uint256 eigenToDeposit = 1e10; +// _testDepositWeth(sender, wethToDeposit); +// _testDepositEigen(sender, eigenToDeposit); +// _testRegisterAsOperator(sender, IDelegationTerms(sender)); - cheats.startPrank(sender); +// cheats.startPrank(sender); - //whitelist the serviceManager to slash the operator - slasher.optIntoSlashing(address(serviceManager)); +// //whitelist the serviceManager to slash the operator +// slasher.optIntoSlashing(address(serviceManager)); - voteWeigher.registerOperator(sender, _serveUntil); +// voteWeigher.registerOperator(sender, _serveUntil); - cheats.stopPrank(); - } +// cheats.stopPrank(); +// } - // registers the operator if they are not already registered, and deposits "WETH" + "EIGEN" on behalf of the staker. - function _registerOperatorAndDepositFromStaker(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) internal { - cheats.assume(staker != operator); +// // registers the operator if they are not already registered, and deposits "WETH" + "EIGEN" on behalf of the staker. +// function _registerOperatorAndDepositFromStaker(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) internal { +// cheats.assume(staker != operator); - // if first deposit amount to base strategy is too small, it will revert. ignore that case here. - cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); +// // if first deposit amount to base strategy is too small, it will revert. ignore that case here. +// cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); +// cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); - if (!delegation.isOperator(operator)) { - _testRegisterAsOperator(operator, IDelegationTerms(operator)); - } +// if (!delegation.isOperator(operator)) { +// _testRegisterAsOperator(operator, IDelegationTerms(operator)); +// } - //making additional deposits to the strategies - assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate"); - _testDepositWeth(staker, ethAmount); - _testDepositEigen(staker, eigenAmount); - } -} \ No newline at end of file +// //making additional deposits to the strategies +// assertTrue(delegation.isNotDelegated(staker) == true, "testDelegation: staker is not delegate"); +// _testDepositWeth(staker, ethAmount); +// _testDepositEigen(staker, eigenAmount); +// } +// } \ No newline at end of file diff --git a/src/test/Registration.t.sol b/src/test/Registration.t.sol index 2f654dccd..0eaf16372 100644 --- a/src/test/Registration.t.sol +++ b/src/test/Registration.t.sol @@ -1,123 +1,123 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; +// // SPDX-License-Identifier: BUSL-1.1 +// pragma solidity =0.8.12; -import "./EigenLayerTestHelper.t.sol"; +// import "./EigenLayerTestHelper.t.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; +// import "@openzeppelin/contracts/utils/math/Math.sol"; -import "../contracts/libraries/BytesLib.sol"; +// import "../contracts/libraries/BytesLib.sol"; -import "./mocks/MiddlewareVoteWeigherMock.sol"; -import "./mocks/ServiceManagerMock.sol"; -import "./mocks/PublicKeyCompendiumMock.sol"; -import "./mocks/StrategyManagerMock.sol"; +// import "./mocks/MiddlewareVoteWeigherMock.sol"; +// import "./mocks/ServiceManagerMock.sol"; +// import "./mocks/PublicKeyCompendiumMock.sol"; +// import "./mocks/StrategyManagerMock.sol"; -import "../../src/contracts/middleware/BLSRegistry.sol"; -import "../../src/contracts/middleware/BLSPublicKeyCompendium.sol"; +// import "../../src/contracts/middleware/BLSRegistry.sol"; +// import "../../src/contracts/middleware/BLSPublicKeyCompendium.sol"; -contract RegistrationTests is EigenLayerTestHelper { +// contract RegistrationTests is EigenLayerTestHelper { - BLSRegistry public dlRegImplementation; - BLSPublicKeyCompendiumMock public pubkeyCompendium; +// BLSRegistry public dlRegImplementation; +// BLSPublicKeyCompendiumMock public pubkeyCompendium; - BLSPublicKeyCompendiumMock public pubkeyCompendiumImplementation; - BLSRegistry public dlReg; - ProxyAdmin public dataLayrProxyAdmin; +// BLSPublicKeyCompendiumMock public pubkeyCompendiumImplementation; +// BLSRegistry public dlReg; +// ProxyAdmin public dataLayrProxyAdmin; - ServiceManagerMock public dlsm; - StrategyManagerMock public strategyManagerMock; +// ServiceManagerMock public dlsm; +// StrategyManagerMock public strategyManagerMock; - function setUp() public virtual override { - EigenLayerDeployer.setUp(); - initializeMiddlewares(); - } +// function setUp() public virtual override { +// EigenLayerDeployer.setUp(); +// initializeMiddlewares(); +// } - function initializeMiddlewares() public { - dataLayrProxyAdmin = new ProxyAdmin(); - fuzzedAddressMapping[address(dataLayrProxyAdmin)] = true; +// function initializeMiddlewares() public { +// dataLayrProxyAdmin = new ProxyAdmin(); +// fuzzedAddressMapping[address(dataLayrProxyAdmin)] = true; - pubkeyCompendium = new BLSPublicKeyCompendiumMock(); +// pubkeyCompendium = new BLSPublicKeyCompendiumMock(); - strategyManagerMock = new StrategyManagerMock(); - strategyManagerMock.setAddresses(delegation, eigenPodManager, slasher); +// strategyManagerMock = new StrategyManagerMock(); +// strategyManagerMock.setAddresses(delegation, eigenPodManager, slasher); - dlsm = new ServiceManagerMock(slasher); +// dlsm = new ServiceManagerMock(slasher); - dlReg = BLSRegistry( - address(new TransparentUpgradeableProxy(address(emptyContract), address(dataLayrProxyAdmin), "")) - ); +// dlReg = BLSRegistry( +// address(new TransparentUpgradeableProxy(address(emptyContract), address(dataLayrProxyAdmin), "")) +// ); - dlRegImplementation = new BLSRegistry( - strategyManagerMock, - dlsm, - pubkeyCompendium - ); +// dlRegImplementation = new BLSRegistry( +// strategyManagerMock, +// dlsm, +// pubkeyCompendium +// ); - uint256[] memory _quorumBips = new uint256[](2); - // split 60% ETH quorum, 40% EIGEN quorum - _quorumBips[0] = 6000; - _quorumBips[1] = 4000; +// uint256[] memory _quorumBips = new uint256[](2); +// // split 60% ETH quorum, 40% EIGEN quorum +// _quorumBips[0] = 6000; +// _quorumBips[1] = 4000; - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); - ethStratsAndMultipliers[0].strategy = wethStrat; - ethStratsAndMultipliers[0].multiplier = 1e18; - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); - eigenStratsAndMultipliers[0].strategy = eigenStrat; - eigenStratsAndMultipliers[0].multiplier = 1e18; +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); +// ethStratsAndMultipliers[0].strategy = wethStrat; +// ethStratsAndMultipliers[0].multiplier = 1e18; +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); +// eigenStratsAndMultipliers[0].strategy = eigenStrat; +// eigenStratsAndMultipliers[0].multiplier = 1e18; - dataLayrProxyAdmin.upgradeAndCall( - TransparentUpgradeableProxy(payable(address(dlReg))), - address(dlRegImplementation), - abi.encodeWithSelector(BLSRegistry.initialize.selector, address(this), false, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers) - ); +// dataLayrProxyAdmin.upgradeAndCall( +// TransparentUpgradeableProxy(payable(address(dlReg))), +// address(dlRegImplementation), +// abi.encodeWithSelector(BLSRegistry.initialize.selector, address(this), false, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers) +// ); - } +// } - function testRegisterOperator(address operator, uint32 operatorIndex, string calldata socket) public fuzzedAddress(operator) { - cheats.assume(operatorIndex < 15); - BN254.G1Point memory pk = getOperatorPubkeyG1(operatorIndex); +// function testRegisterOperator(address operator, uint32 operatorIndex, string calldata socket) public fuzzedAddress(operator) { +// cheats.assume(operatorIndex < 15); +// BN254.G1Point memory pk = getOperatorPubkeyG1(operatorIndex); - //register as both ETH and EIGEN operator - uint256 wethToDeposit = 1e18; - uint256 eigenToDeposit = 1e18; - _testDepositWeth(operator, wethToDeposit); - _testDepositEigen(operator, eigenToDeposit); - _testRegisterAsOperator(operator, IDelegationTerms(operator)); +// //register as both ETH and EIGEN operator +// uint256 wethToDeposit = 1e18; +// uint256 eigenToDeposit = 1e18; +// _testDepositWeth(operator, wethToDeposit); +// _testDepositEigen(operator, eigenToDeposit); +// _testRegisterAsOperator(operator, IDelegationTerms(operator)); - cheats.startPrank(operator); - slasher.optIntoSlashing(address(dlsm)); - pubkeyCompendium.registerPublicKey(pk); - dlReg.registerOperator(1, pk, socket); - cheats.stopPrank(); +// cheats.startPrank(operator); +// slasher.optIntoSlashing(address(dlsm)); +// pubkeyCompendium.registerPublicKey(pk); +// dlReg.registerOperator(1, pk, socket); +// cheats.stopPrank(); - bytes32 pubkeyHash = BN254.hashG1Point(pk); +// bytes32 pubkeyHash = BN254.hashG1Point(pk); - (uint32 toBlockNumber, uint32 index) = dlReg.pubkeyHashToIndexHistory(pubkeyHash,0); +// (uint32 toBlockNumber, uint32 index) = dlReg.pubkeyHashToIndexHistory(pubkeyHash,0); - assertTrue(toBlockNumber == 0, "block number set when it shouldn't be"); - assertTrue(index == 0, "index has been set incorrectly"); - assertTrue(dlReg.operatorList(0) == operator, "incorrect operator added"); - } +// assertTrue(toBlockNumber == 0, "block number set when it shouldn't be"); +// assertTrue(index == 0, "index has been set incorrectly"); +// assertTrue(dlReg.operatorList(0) == operator, "incorrect operator added"); +// } - function testDeregisterOperator(address operator, uint32 operatorIndex, string calldata socket) public fuzzedAddress(operator) { - cheats.assume(operatorIndex < 15); - BN254.G1Point memory pk = getOperatorPubkeyG1(operatorIndex); +// function testDeregisterOperator(address operator, uint32 operatorIndex, string calldata socket) public fuzzedAddress(operator) { +// cheats.assume(operatorIndex < 15); +// BN254.G1Point memory pk = getOperatorPubkeyG1(operatorIndex); - testRegisterOperator(operator, operatorIndex, socket); - cheats.startPrank(operator); - dlReg.deregisterOperator(pk, 0); - cheats.stopPrank(); +// testRegisterOperator(operator, operatorIndex, socket); +// cheats.startPrank(operator); +// dlReg.deregisterOperator(pk, 0); +// cheats.stopPrank(); - bytes32 pubkeyHash = BN254.hashG1Point(pk); - (uint32 toBlockNumber, /*uint32 index*/) = dlReg.pubkeyHashToIndexHistory(pubkeyHash,0); - assertTrue(toBlockNumber == block.number, "toBlockNumber has been set incorrectly"); - } +// bytes32 pubkeyHash = BN254.hashG1Point(pk); +// (uint32 toBlockNumber, /*uint32 index*/) = dlReg.pubkeyHashToIndexHistory(pubkeyHash,0); +// assertTrue(toBlockNumber == block.number, "toBlockNumber has been set incorrectly"); +// } -} \ No newline at end of file +// } \ No newline at end of file diff --git a/src/test/Whitelister.t.sol b/src/test/Whitelister.t.sol index c42206eb1..a3f1401a4 100644 --- a/src/test/Whitelister.t.sol +++ b/src/test/Whitelister.t.sol @@ -1,333 +1,330 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; +// // SPDX-License-Identifier: BUSL-1.1 +// pragma solidity =0.8.12; -import "../../src/contracts/interfaces/IStrategyManager.sol"; -import "../../src/contracts/interfaces/IStrategy.sol"; -import "../../src/contracts/interfaces/IDelegationManager.sol"; -import "../../src/contracts/strategies/StrategyBase.sol"; -import "../../src/contracts/middleware/BLSRegistry.sol"; +// import "../../src/contracts/interfaces/IStrategyManager.sol"; +// import "../../src/contracts/interfaces/IStrategy.sol"; +// import "../../src/contracts/interfaces/IDelegationManager.sol"; +// import "../../src/contracts/strategies/StrategyBase.sol"; +// import "../../src/contracts/middleware/BLSRegistry.sol"; -import "../../src/test/mocks/ServiceManagerMock.sol"; -import "../../src/test/mocks/PublicKeyCompendiumMock.sol"; -import "../../src/test/mocks/MiddlewareVoteWeigherMock.sol"; +// import "../../src/test/mocks/ServiceManagerMock.sol"; +// import "../../src/test/mocks/PublicKeyCompendiumMock.sol"; +// import "../../script/whitelist/ERC20PresetMinterPauser.sol"; +// import "../../script/whitelist/Staker.sol"; +// import "../../script/whitelist/Whitelister.sol"; -import "../../script/whitelist/ERC20PresetMinterPauser.sol"; +// import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// import "@openzeppelin/contracts/access/Ownable.sol"; +// import "@openzeppelin/contracts/utils/Create2.sol"; -import "../../script/whitelist/Staker.sol"; -import "../../script/whitelist/Whitelister.sol"; +// import "./EigenLayerTestHelper.t.sol"; +// import "./Delegation.t.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Create2.sol"; +// import "forge-std/Test.sol"; -import "./EigenLayerTestHelper.t.sol"; -import "./Delegation.t.sol"; +// contract WhitelisterTests is EigenLayerTestHelper { -import "forge-std/Test.sol"; +// ERC20PresetMinterPauser dummyToken; +// IStrategy dummyStrat; +// IStrategy dummyStratImplementation; +// Whitelister whiteLister; -contract WhitelisterTests is EigenLayerTestHelper { +// BLSRegistry blsRegistry; +// BLSRegistry blsRegistryImplementation; - ERC20PresetMinterPauser dummyToken; - IStrategy dummyStrat; - IStrategy dummyStratImplementation; - Whitelister whiteLister; - BLSRegistry blsRegistry; - BLSRegistry blsRegistryImplementation; - - - ServiceManagerMock dummyServiceManager; - BLSPublicKeyCompendiumMock dummyCompendium; - MiddlewareRegistryMock dummyReg; +// ServiceManagerMock dummyServiceManager; +// BLSPublicKeyCompendiumMock dummyCompendium; +// MiddlewareRegistryMock dummyReg; - modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) { - cheats.assume(ethAmount >= 0 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18); - _; - } - - uint256 AMOUNT; - - // packed info used to help handle stack-too-deep errors - struct DataForTestWithdrawal { - IStrategy[] delegatorStrategies; - uint256[] delegatorShares; - IStrategyManager.WithdrawerAndNonce withdrawerAndNonce; - } - - function setUp() public virtual override{ - EigenLayerDeployer.setUp(); - - emptyContract = new EmptyContract(); - - dummyCompendium = new BLSPublicKeyCompendiumMock(); - blsRegistry = BLSRegistry( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - - dummyToken = new ERC20PresetMinterPauser("dummy staked ETH", "dsETH"); - dummyStratImplementation = new StrategyBase(strategyManager); - dummyStrat = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(dummyStratImplementation), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, dummyToken, eigenLayerPauserReg) - ) - ) - ); - - whiteLister = new Whitelister(strategyManager, delegation, dummyToken, dummyStrat, blsRegistry); - whiteLister.transferOwnership(theMultiSig); - AMOUNT = whiteLister.DEFAULT_AMOUNT(); - - dummyToken.grantRole(keccak256("MINTER_ROLE"), address(whiteLister)); - dummyToken.grantRole(keccak256("PAUSER_ROLE"), address(whiteLister)); - - dummyToken.grantRole(keccak256("MINTER_ROLE"), theMultiSig); - dummyToken.grantRole(keccak256("PAUSER_ROLE"), theMultiSig); - - dummyToken.revokeRole(keccak256("MINTER_ROLE"), address(this)); - dummyToken.revokeRole(keccak256("PAUSER_ROLE"), address(this)); - - - dummyServiceManager = new ServiceManagerMock(slasher); - blsRegistryImplementation = new BLSRegistry(strategyManager, dummyServiceManager, dummyCompendium); - - uint256[] memory _quorumBips = new uint256[](2); - // split 60% ETH quorum, 40% EIGEN quorum - _quorumBips[0] = 6000; - _quorumBips[1] = 4000; - - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); - ethStratsAndMultipliers[0].strategy = wethStrat; - ethStratsAndMultipliers[0].multiplier = 1e18; - VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = - new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); - eigenStratsAndMultipliers[0].strategy = eigenStrat; - eigenStratsAndMultipliers[0].multiplier = 1e18; +// modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) { +// cheats.assume(ethAmount >= 0 && ethAmount <= 1e18); +// cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18); +// _; +// } + +// uint256 AMOUNT; + +// // packed info used to help handle stack-too-deep errors +// struct DataForTestWithdrawal { +// IStrategy[] delegatorStrategies; +// uint256[] delegatorShares; +// IStrategyManager.WithdrawerAndNonce withdrawerAndNonce; +// } + +// function setUp() public virtual override{ +// EigenLayerDeployer.setUp(); + +// emptyContract = new EmptyContract(); + +// dummyCompendium = new BLSPublicKeyCompendiumMock(); +// blsRegistry = BLSRegistry( +// address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) +// ); + +// dummyToken = new ERC20PresetMinterPauser("dummy staked ETH", "dsETH"); +// dummyStratImplementation = new StrategyBase(strategyManager); +// dummyStrat = StrategyBase( +// address( +// new TransparentUpgradeableProxy( +// address(dummyStratImplementation), +// address(eigenLayerProxyAdmin), +// abi.encodeWithSelector(StrategyBase.initialize.selector, dummyToken, eigenLayerPauserReg) +// ) +// ) +// ); + +// whiteLister = new Whitelister(strategyManager, delegation, dummyToken, dummyStrat, blsRegistry); +// whiteLister.transferOwnership(theMultiSig); +// AMOUNT = whiteLister.DEFAULT_AMOUNT(); + +// dummyToken.grantRole(keccak256("MINTER_ROLE"), address(whiteLister)); +// dummyToken.grantRole(keccak256("PAUSER_ROLE"), address(whiteLister)); + +// dummyToken.grantRole(keccak256("MINTER_ROLE"), theMultiSig); +// dummyToken.grantRole(keccak256("PAUSER_ROLE"), theMultiSig); + +// dummyToken.revokeRole(keccak256("MINTER_ROLE"), address(this)); +// dummyToken.revokeRole(keccak256("PAUSER_ROLE"), address(this)); + + +// dummyServiceManager = new ServiceManagerMock(slasher); +// blsRegistryImplementation = new BLSRegistry(strategyManager, dummyServiceManager, dummyCompendium); + +// uint256[] memory _quorumBips = new uint256[](2); +// // split 60% ETH quorum, 40% EIGEN quorum +// _quorumBips[0] = 6000; +// _quorumBips[1] = 4000; + +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); +// ethStratsAndMultipliers[0].strategy = wethStrat; +// ethStratsAndMultipliers[0].multiplier = 1e18; +// VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = +// new VoteWeigherBaseStorage.StrategyAndWeightingMultiplier[](1); +// eigenStratsAndMultipliers[0].strategy = eigenStrat; +// eigenStratsAndMultipliers[0].multiplier = 1e18; - cheats.startPrank(eigenLayerProxyAdmin.owner()); - eigenLayerProxyAdmin.upgradeAndCall( - TransparentUpgradeableProxy(payable(address(blsRegistry))), - address(blsRegistryImplementation), - abi.encodeWithSelector(BLSRegistry.initialize.selector, address(whiteLister), true, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers) - ); - cheats.stopPrank(); +// cheats.startPrank(eigenLayerProxyAdmin.owner()); +// eigenLayerProxyAdmin.upgradeAndCall( +// TransparentUpgradeableProxy(payable(address(blsRegistry))), +// address(blsRegistryImplementation), +// abi.encodeWithSelector(BLSRegistry.initialize.selector, address(whiteLister), true, _quorumBips, ethStratsAndMultipliers, eigenStratsAndMultipliers) +// ); +// cheats.stopPrank(); - dummyReg = new MiddlewareRegistryMock( - dummyServiceManager, - strategyManager - ); - - fuzzedAddressMapping[address(whiteLister)] = true; - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - } - - function testWhitelistingOperator(address operator) public fuzzedAddress(operator) { - cheats.startPrank(operator); - IDelegationTerms dt = IDelegationTerms(address(89)); - delegation.registerAsOperator(dt); - cheats.stopPrank(); - - cheats.startPrank(theMultiSig); - whiteLister.whitelist(operator); - cheats.stopPrank(); - - assertTrue(blsRegistry.whitelisted(operator) == true, "operator not added to whitelist"); - } - - function testWhitelistDepositIntoStrategy(address operator, uint256 depositAmount) external fuzzedAddress(operator) { - cheats.assume(depositAmount < AMOUNT); - testWhitelistingOperator(operator); - - cheats.startPrank(theMultiSig); - address staker = whiteLister.getStaker(operator); - dummyToken.mint(staker, AMOUNT); - - whiteLister.depositIntoStrategy(staker, dummyStrat, dummyToken, depositAmount); - cheats.stopPrank(); - } - - function testCallStakerFromNonWhitelisterAddress(address nonWhitelister, bytes memory data) external fuzzedAddress(nonWhitelister) { - testWhitelistingOperator(operator); - address staker = whiteLister.getStaker(operator); - - cheats.startPrank(nonWhitelister); - cheats.expectRevert(bytes("Ownable: caller is not the owner")); - Staker(staker).callAddress(address(strategyManager), data); - } - - function testNonWhitelistedOperatorRegistration(BN254.G1Point memory pk, string memory socket ) external { - cheats.startPrank(operator); - IDelegationTerms dt = IDelegationTerms(address(89)); - delegation.registerAsOperator(dt); - cheats.stopPrank(); - - cheats.expectRevert(bytes("BLSRegistry._registerOperator: not whitelisted")); - blsRegistry.registerOperator(1, pk, socket); - } +// dummyReg = new MiddlewareRegistryMock( +// dummyServiceManager, +// strategyManager +// ); + +// fuzzedAddressMapping[address(whiteLister)] = true; + +// // whitelist the strategy for deposit +// cheats.startPrank(strategyManager.strategyWhitelister()); +// IStrategy[] memory _strategy = new IStrategy[](1); +// _strategy[0] = dummyStrat; +// strategyManager.addStrategiesToDepositWhitelist(_strategy); +// cheats.stopPrank(); +// } + +// function testWhitelistingOperator(address operator) public fuzzedAddress(operator) { +// cheats.startPrank(operator); +// IDelegationTerms dt = IDelegationTerms(address(89)); +// delegation.registerAsOperator(dt); +// cheats.stopPrank(); + +// cheats.startPrank(theMultiSig); +// whiteLister.whitelist(operator); +// cheats.stopPrank(); + +// assertTrue(blsRegistry.whitelisted(operator) == true, "operator not added to whitelist"); +// } + +// function testWhitelistDepositIntoStrategy(address operator, uint256 depositAmount) external fuzzedAddress(operator) { +// cheats.assume(depositAmount < AMOUNT); +// testWhitelistingOperator(operator); + +// cheats.startPrank(theMultiSig); +// address staker = whiteLister.getStaker(operator); +// dummyToken.mint(staker, AMOUNT); + +// whiteLister.depositIntoStrategy(staker, dummyStrat, dummyToken, depositAmount); +// cheats.stopPrank(); +// } + +// function testCallStakerFromNonWhitelisterAddress(address nonWhitelister, bytes memory data) external fuzzedAddress(nonWhitelister) { +// testWhitelistingOperator(operator); +// address staker = whiteLister.getStaker(operator); + +// cheats.startPrank(nonWhitelister); +// cheats.expectRevert(bytes("Ownable: caller is not the owner")); +// Staker(staker).callAddress(address(strategyManager), data); +// } + +// function testNonWhitelistedOperatorRegistration(BN254.G1Point memory pk, string memory socket ) external { +// cheats.startPrank(operator); +// IDelegationTerms dt = IDelegationTerms(address(89)); +// delegation.registerAsOperator(dt); +// cheats.stopPrank(); + +// cheats.expectRevert(bytes("BLSRegistry._registerOperator: not whitelisted")); +// blsRegistry.registerOperator(1, pk, socket); +// } - function testWhitelistQueueWithdrawal( - address operator - ) - public fuzzedAddress(operator) - { - - address staker = whiteLister.getStaker(operator); - cheats.assume(staker!=operator); - _testRegisterAsOperator(operator, IDelegationTerms(operator)); - - { - cheats.startPrank(theMultiSig); - whiteLister.whitelist(operator); - cheats.stopPrank(); - - cheats.startPrank(operator); - slasher.optIntoSlashing(address(dummyServiceManager)); - dummyReg.registerOperator(operator, uint32(block.timestamp) + 3 days); - cheats.stopPrank(); - } - - // packed data structure to deal with stack-too-deep issues - DataForTestWithdrawal memory dataForTestWithdrawal; - uint256 expectedTokensOut; - - // scoped block to deal with stack-too-deep issues - { - //delegator-specific information - (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = - strategyManager.getDeposits(staker); - dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; - dataForTestWithdrawal.delegatorShares = delegatorShares; - - IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = - IStrategyManager.WithdrawerAndNonce({ - withdrawer: staker, - // harcoded nonce value - nonce: 0 - } - ); - dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce; - // find the expected amount out - expectedTokensOut = delegatorStrategies[0].sharesToUnderlying(delegatorShares[0]); - emit log_named_uint("expectedTokensOut", expectedTokensOut); - } - - uint256[] memory strategyIndexes = new uint256[](1); - IERC20[] memory tokensArray = new IERC20[](1); - { - // hardcoded values - strategyIndexes[0] = 0; - tokensArray[0] = dummyToken; - } - - _testQueueWithdrawal( - staker, - dataForTestWithdrawal.delegatorStrategies, - dataForTestWithdrawal.delegatorShares, - strategyIndexes - ); - - { - uint256 balanceBeforeWithdrawal = dummyToken.balanceOf(staker); - - _testCompleteQueuedWithdrawal( - staker, - dataForTestWithdrawal.delegatorStrategies, - tokensArray, - dataForTestWithdrawal.delegatorShares, - operator, - dataForTestWithdrawal.withdrawerAndNonce, - uint32(block.number), - 1 - ); - emit log_named_uint("Balance Before Withdrawal", balanceBeforeWithdrawal); - emit log_named_uint("Balance After Withdrawal", dummyToken.balanceOf(staker)); +// function testWhitelistQueueWithdrawal( +// address operator +// ) +// public fuzzedAddress(operator) +// { + +// address staker = whiteLister.getStaker(operator); +// cheats.assume(staker!=operator); +// _testRegisterAsOperator(operator, IDelegationTerms(operator)); + +// { +// cheats.startPrank(theMultiSig); +// whiteLister.whitelist(operator); +// cheats.stopPrank(); + +// cheats.startPrank(operator); +// slasher.optIntoSlashing(address(dummyServiceManager)); +// dummyReg.registerOperator(operator, uint32(block.timestamp) + 3 days); +// cheats.stopPrank(); +// } + +// // packed data structure to deal with stack-too-deep issues +// DataForTestWithdrawal memory dataForTestWithdrawal; +// uint256 expectedTokensOut; + +// // scoped block to deal with stack-too-deep issues +// { +// //delegator-specific information +// (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = +// strategyManager.getDeposits(staker); +// dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; +// dataForTestWithdrawal.delegatorShares = delegatorShares; + +// IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = +// IStrategyManager.WithdrawerAndNonce({ +// withdrawer: staker, +// // harcoded nonce value +// nonce: 0 +// } +// ); +// dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce; +// // find the expected amount out +// expectedTokensOut = delegatorStrategies[0].sharesToUnderlying(delegatorShares[0]); +// emit log_named_uint("expectedTokensOut", expectedTokensOut); +// } + +// uint256[] memory strategyIndexes = new uint256[](1); +// IERC20[] memory tokensArray = new IERC20[](1); +// { +// // hardcoded values +// strategyIndexes[0] = 0; +// tokensArray[0] = dummyToken; +// } + +// _testQueueWithdrawal( +// staker, +// dataForTestWithdrawal.delegatorStrategies, +// dataForTestWithdrawal.delegatorShares, +// strategyIndexes +// ); + +// { +// uint256 balanceBeforeWithdrawal = dummyToken.balanceOf(staker); + +// _testCompleteQueuedWithdrawal( +// staker, +// dataForTestWithdrawal.delegatorStrategies, +// tokensArray, +// dataForTestWithdrawal.delegatorShares, +// operator, +// dataForTestWithdrawal.withdrawerAndNonce, +// uint32(block.number), +// 1 +// ); +// emit log_named_uint("Balance Before Withdrawal", balanceBeforeWithdrawal); +// emit log_named_uint("Balance After Withdrawal", dummyToken.balanceOf(staker)); - require(dummyToken.balanceOf(staker) == balanceBeforeWithdrawal + expectedTokensOut, "balance not incremented as expected"); - - } - } - - function _testQueueWithdrawal( - address staker, - IStrategy[] memory strategyArray, - uint256[] memory shareAmounts, - uint256[] memory strategyIndexes - ) - internal - { - cheats.startPrank(theMultiSig); - whiteLister.queueWithdrawal( - staker, - strategyIndexes, - strategyArray, - shareAmounts, - staker, - true - ); - cheats.stopPrank(); - } - - function _testCompleteQueuedWithdrawal( - address staker, - IStrategy[] memory strategyArray, - IERC20[] memory tokensArray, - uint256[] memory shareAmounts, - address delegatedTo, - IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce, - uint32 withdrawalStartBlock, - uint256 middlewareTimesIndex - ) - internal - { - IStrategyManager.QueuedWithdrawal memory queuedWithdrawal = IStrategyManager.QueuedWithdrawal({ - strategies: strategyArray, - shares: shareAmounts, - depositor: staker, - withdrawerAndNonce: withdrawerAndNonce, - withdrawalStartBlock: withdrawalStartBlock, - delegatedAddress: delegatedTo - }); - - // emit log("*******************COMPLETE***************************"); - // emit log_named_address("delegatedAddress", delegatedTo); - // emit log_named_uint("withdrawalStartBlock", withdrawalStartBlock); - // emit log_named_uint("withdrawerAndNonce.Nonce", withdrawerAndNonce.nonce); - // emit log_named_address("withdrawerAndNonce.Adress", withdrawerAndNonce.withdrawer); - // emit log_named_address("depositor", staker); - // emit log("***********************************************************************"); - - cheats.startPrank(theMultiSig); - whiteLister.completeQueuedWithdrawal(staker, queuedWithdrawal, tokensArray, middlewareTimesIndex, true); - cheats.stopPrank(); - } +// require(dummyToken.balanceOf(staker) == balanceBeforeWithdrawal + expectedTokensOut, "balance not incremented as expected"); + +// } +// } + +// function _testQueueWithdrawal( +// address staker, +// IStrategy[] memory strategyArray, +// uint256[] memory shareAmounts, +// uint256[] memory strategyIndexes +// ) +// internal +// { +// cheats.startPrank(theMultiSig); +// whiteLister.queueWithdrawal( +// staker, +// strategyIndexes, +// strategyArray, +// shareAmounts, +// staker, +// true +// ); +// cheats.stopPrank(); +// } + +// function _testCompleteQueuedWithdrawal( +// address staker, +// IStrategy[] memory strategyArray, +// IERC20[] memory tokensArray, +// uint256[] memory shareAmounts, +// address delegatedTo, +// IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce, +// uint32 withdrawalStartBlock, +// uint256 middlewareTimesIndex +// ) +// internal +// { +// IStrategyManager.QueuedWithdrawal memory queuedWithdrawal = IStrategyManager.QueuedWithdrawal({ +// strategies: strategyArray, +// shares: shareAmounts, +// depositor: staker, +// withdrawerAndNonce: withdrawerAndNonce, +// withdrawalStartBlock: withdrawalStartBlock, +// delegatedAddress: delegatedTo +// }); + +// // emit log("*******************COMPLETE***************************"); +// // emit log_named_address("delegatedAddress", delegatedTo); +// // emit log_named_uint("withdrawalStartBlock", withdrawalStartBlock); +// // emit log_named_uint("withdrawerAndNonce.Nonce", withdrawerAndNonce.nonce); +// // emit log_named_address("withdrawerAndNonce.Adress", withdrawerAndNonce.withdrawer); +// // emit log_named_address("depositor", staker); +// // emit log("***********************************************************************"); + +// cheats.startPrank(theMultiSig); +// whiteLister.completeQueuedWithdrawal(staker, queuedWithdrawal, tokensArray, middlewareTimesIndex, true); +// cheats.stopPrank(); +// } - function testWhitelistTransfer(address operator, address receiver) public fuzzedAddress(receiver) { - address staker = whiteLister.getStaker(operator); +// function testWhitelistTransfer(address operator, address receiver) public fuzzedAddress(receiver) { +// address staker = whiteLister.getStaker(operator); - testWhitelistQueueWithdrawal(operator); +// testWhitelistQueueWithdrawal(operator); - cheats.startPrank(theMultiSig); +// cheats.startPrank(theMultiSig); - whiteLister.transfer(staker, address(dummyToken), receiver, AMOUNT); - cheats.stopPrank(); - require(dummyToken.balanceOf(receiver) == AMOUNT, "receiver hasn't received tokens"); - } -} \ No newline at end of file +// whiteLister.transfer(staker, address(dummyToken), receiver, AMOUNT); +// cheats.stopPrank(); +// require(dummyToken.balanceOf(receiver) == AMOUNT, "receiver hasn't received tokens"); +// } +// } \ No newline at end of file diff --git a/src/test/Withdrawals.t.sol b/src/test/Withdrawals.t.sol index 257671ce2..d6a337b10 100644 --- a/src/test/Withdrawals.t.sol +++ b/src/test/Withdrawals.t.sol @@ -1,366 +1,366 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; +// // SPDX-License-Identifier: BUSL-1.1 +// pragma solidity =0.8.12; -import "@openzeppelin/contracts/utils/math/Math.sol"; +// import "@openzeppelin/contracts/utils/math/Math.sol"; -import "./mocks/MiddlewareRegistryMock.sol"; -import "./mocks/ServiceManagerMock.sol"; -import "./Delegation.t.sol"; +// import "./mocks/MiddlewareRegistryMock.sol"; +// import "./mocks/ServiceManagerMock.sol"; +// import "./Delegation.t.sol"; -contract WithdrawalTests is DelegationTests { +// contract WithdrawalTests is DelegationTests { - // packed info used to help handle stack-too-deep errors - struct DataForTestWithdrawal { - IStrategy[] delegatorStrategies; - uint256[] delegatorShares; - IStrategyManager.WithdrawerAndNonce withdrawerAndNonce; - } +// // packed info used to help handle stack-too-deep errors +// struct DataForTestWithdrawal { +// IStrategy[] delegatorStrategies; +// uint256[] delegatorShares; +// IStrategyManager.WithdrawerAndNonce withdrawerAndNonce; +// } - MiddlewareRegistryMock public generalReg1; - ServiceManagerMock public generalServiceManager1; +// MiddlewareRegistryMock public generalReg1; +// ServiceManagerMock public generalServiceManager1; - MiddlewareRegistryMock public generalReg2; - ServiceManagerMock public generalServiceManager2; +// MiddlewareRegistryMock public generalReg2; +// ServiceManagerMock public generalServiceManager2; - function initializeGeneralMiddlewares() public { - generalServiceManager1 = new ServiceManagerMock(slasher); +// function initializeGeneralMiddlewares() public { +// generalServiceManager1 = new ServiceManagerMock(slasher); - generalReg1 = new MiddlewareRegistryMock( - generalServiceManager1, - strategyManager - ); +// generalReg1 = new MiddlewareRegistryMock( +// generalServiceManager1, +// strategyManager +// ); - generalServiceManager2 = new ServiceManagerMock(slasher); - - generalReg2 = new MiddlewareRegistryMock( - generalServiceManager2, - strategyManager - ); - } - - //This function helps with stack too deep issues with "testWithdrawal" test - function testWithdrawalWrapper( - address operator, - address depositor, - address withdrawer, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsTokens, - bool RANDAO - ) - public - fuzzedAddress(operator) - fuzzedAddress(depositor) - fuzzedAddress(withdrawer) - { - cheats.assume(depositor != operator); - cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); - - initializeGeneralMiddlewares(); - - if(RANDAO) { - _testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); - } - else{ - _testWithdrawalWithStakeUpdate(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); - } - - } - - /// @notice test staker's ability to undelegate/withdraw from an operator. - /// @param operator is the operator being delegated to. - /// @param depositor is the staker delegating stake to the operator. - function _testWithdrawalAndDeregistration( - address operator, - address depositor, - address withdrawer, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsTokens - ) - internal - { - - testDelegation(operator, depositor, ethAmount, eigenAmount); - - cheats.startPrank(operator); - slasher.optIntoSlashing(address(generalServiceManager1)); - cheats.stopPrank(); - - generalReg1.registerOperator(operator, uint32(block.timestamp) + 3 days); - - address delegatedTo = delegation.delegatedTo(depositor); - - // packed data structure to deal with stack-too-deep issues - DataForTestWithdrawal memory dataForTestWithdrawal; - - // scoped block to deal with stack-too-deep issues - { - //delegator-specific information - (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = - strategyManager.getDeposits(depositor); - dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; - dataForTestWithdrawal.delegatorShares = delegatorShares; - - IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = - IStrategyManager.WithdrawerAndNonce({ - withdrawer: withdrawer, - // harcoded nonce value - nonce: 0 - } - ); - dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce; - } - - uint256[] memory strategyIndexes = new uint256[](2); - IERC20[] memory tokensArray = new IERC20[](2); - { - // hardcoded values - strategyIndexes[0] = 0; - strategyIndexes[1] = 0; - tokensArray[0] = weth; - tokensArray[1] = eigenToken; - } - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.timestamp) + 1 days); - - _testQueueWithdrawal( - depositor, - strategyIndexes, - dataForTestWithdrawal.delegatorStrategies, - dataForTestWithdrawal.delegatorShares, - withdrawer, - true - ); - uint32 queuedWithdrawalBlock = uint32(block.number); +// generalServiceManager2 = new ServiceManagerMock(slasher); + +// generalReg2 = new MiddlewareRegistryMock( +// generalServiceManager2, +// strategyManager +// ); +// } + +// //This function helps with stack too deep issues with "testWithdrawal" test +// function testWithdrawalWrapper( +// address operator, +// address depositor, +// address withdrawer, +// uint96 ethAmount, +// uint96 eigenAmount, +// bool withdrawAsTokens, +// bool RANDAO +// ) +// public +// fuzzedAddress(operator) +// fuzzedAddress(depositor) +// fuzzedAddress(withdrawer) +// { +// cheats.assume(depositor != operator); +// cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); +// cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); + +// initializeGeneralMiddlewares(); + +// if(RANDAO) { +// _testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); +// } +// else{ +// _testWithdrawalWithStakeUpdate(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); +// } + +// } + +// /// @notice test staker's ability to undelegate/withdraw from an operator. +// /// @param operator is the operator being delegated to. +// /// @param depositor is the staker delegating stake to the operator. +// function _testWithdrawalAndDeregistration( +// address operator, +// address depositor, +// address withdrawer, +// uint96 ethAmount, +// uint96 eigenAmount, +// bool withdrawAsTokens +// ) +// internal +// { + +// testDelegation(operator, depositor, ethAmount, eigenAmount); + +// cheats.startPrank(operator); +// slasher.optIntoSlashing(address(generalServiceManager1)); +// cheats.stopPrank(); + +// generalReg1.registerOperator(operator, uint32(block.timestamp) + 3 days); + +// address delegatedTo = delegation.delegatedTo(depositor); + +// // packed data structure to deal with stack-too-deep issues +// DataForTestWithdrawal memory dataForTestWithdrawal; + +// // scoped block to deal with stack-too-deep issues +// { +// //delegator-specific information +// (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = +// strategyManager.getDeposits(depositor); +// dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; +// dataForTestWithdrawal.delegatorShares = delegatorShares; + +// IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = +// IStrategyManager.WithdrawerAndNonce({ +// withdrawer: withdrawer, +// // harcoded nonce value +// nonce: 0 +// } +// ); +// dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce; +// } + +// uint256[] memory strategyIndexes = new uint256[](2); +// IERC20[] memory tokensArray = new IERC20[](2); +// { +// // hardcoded values +// strategyIndexes[0] = 0; +// strategyIndexes[1] = 0; +// tokensArray[0] = weth; +// tokensArray[1] = eigenToken; +// } + +// cheats.warp(uint32(block.timestamp) + 1 days); +// cheats.roll(uint32(block.timestamp) + 1 days); + +// _testQueueWithdrawal( +// depositor, +// strategyIndexes, +// dataForTestWithdrawal.delegatorStrategies, +// dataForTestWithdrawal.delegatorShares, +// withdrawer, +// true +// ); +// uint32 queuedWithdrawalBlock = uint32(block.number); - //now withdrawal block time is before deregistration - cheats.warp(uint32(block.timestamp) + 2 days); - cheats.roll(uint32(block.timestamp) + 2 days); +// //now withdrawal block time is before deregistration +// cheats.warp(uint32(block.timestamp) + 2 days); +// cheats.roll(uint32(block.timestamp) + 2 days); - generalReg1.deregisterOperator(operator); - { - //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point - cheats.warp(uint32(block.timestamp) + 4 days); - cheats.roll(uint32(block.timestamp) + 4 days); - - uint256 middlewareTimeIndex = 1; - if (withdrawAsTokens) { - _testCompleteQueuedWithdrawalTokens( - depositor, - dataForTestWithdrawal.delegatorStrategies, - tokensArray, - dataForTestWithdrawal.delegatorShares, - delegatedTo, - dataForTestWithdrawal.withdrawerAndNonce, - queuedWithdrawalBlock, - middlewareTimeIndex - ); - } else { - _testCompleteQueuedWithdrawalShares( - depositor, - dataForTestWithdrawal.delegatorStrategies, - tokensArray, - dataForTestWithdrawal.delegatorShares, - delegatedTo, - dataForTestWithdrawal.withdrawerAndNonce, - queuedWithdrawalBlock, - middlewareTimeIndex - ); - } - } - } - - /// @notice test staker's ability to undelegate/withdraw from an operator. - /// @param operator is the operator being delegated to. - /// @param depositor is the staker delegating stake to the operator. - function _testWithdrawalWithStakeUpdate( - address operator, - address depositor, - address withdrawer, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsTokens - ) - public - { - testDelegation(operator, depositor, ethAmount, eigenAmount); - - cheats.startPrank(operator); - slasher.optIntoSlashing(address(generalServiceManager1)); - slasher.optIntoSlashing(address(generalServiceManager2)); - cheats.stopPrank(); - - // emit log_named_uint("Linked list element 1", uint256(uint160(address(generalServiceManager1)))); - // emit log_named_uint("Linked list element 2", uint256(uint160(address(generalServiceManager2)))); - // emit log("________________________________________________________________"); - generalReg1.registerOperator(operator, uint32(block.timestamp) + 5 days); - // emit log_named_uint("Middleware 1 Update Block", uint32(block.number)); - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.number) + 1); - - generalReg2.registerOperator(operator, uint32(block.timestamp) + 5 days); - // emit log_named_uint("Middleware 2 Update Block", uint32(block.number)); - - address delegatedTo = delegation.delegatedTo(depositor); - - // packed data structure to deal with stack-too-deep issues - DataForTestWithdrawal memory dataForTestWithdrawal; - - // scoped block to deal with stack-too-deep issues - { - //delegator-specific information - (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = - strategyManager.getDeposits(depositor); - dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; - dataForTestWithdrawal.delegatorShares = delegatorShares; - - IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = - IStrategyManager.WithdrawerAndNonce({ - withdrawer: withdrawer, - // harcoded nonce value - nonce: 0 - } - ); - dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce; - } - - uint256[] memory strategyIndexes = new uint256[](2); - IERC20[] memory tokensArray = new IERC20[](2); - { - // hardcoded values - strategyIndexes[0] = 0; - strategyIndexes[1] = 0; - tokensArray[0] = weth; - tokensArray[1] = eigenToken; - } - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.number) + 1); - - _testQueueWithdrawal( - depositor, - strategyIndexes, - dataForTestWithdrawal.delegatorStrategies, - dataForTestWithdrawal.delegatorShares, - dataForTestWithdrawal.withdrawerAndNonce.withdrawer, - true - ); - uint32 queuedWithdrawalBlock = uint32(block.number); +// generalReg1.deregisterOperator(operator); +// { +// //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point +// cheats.warp(uint32(block.timestamp) + 4 days); +// cheats.roll(uint32(block.timestamp) + 4 days); + +// uint256 middlewareTimeIndex = 1; +// if (withdrawAsTokens) { +// _testCompleteQueuedWithdrawalTokens( +// depositor, +// dataForTestWithdrawal.delegatorStrategies, +// tokensArray, +// dataForTestWithdrawal.delegatorShares, +// delegatedTo, +// dataForTestWithdrawal.withdrawerAndNonce, +// queuedWithdrawalBlock, +// middlewareTimeIndex +// ); +// } else { +// _testCompleteQueuedWithdrawalShares( +// depositor, +// dataForTestWithdrawal.delegatorStrategies, +// tokensArray, +// dataForTestWithdrawal.delegatorShares, +// delegatedTo, +// dataForTestWithdrawal.withdrawerAndNonce, +// queuedWithdrawalBlock, +// middlewareTimeIndex +// ); +// } +// } +// } + +// /// @notice test staker's ability to undelegate/withdraw from an operator. +// /// @param operator is the operator being delegated to. +// /// @param depositor is the staker delegating stake to the operator. +// function _testWithdrawalWithStakeUpdate( +// address operator, +// address depositor, +// address withdrawer, +// uint96 ethAmount, +// uint96 eigenAmount, +// bool withdrawAsTokens +// ) +// public +// { +// testDelegation(operator, depositor, ethAmount, eigenAmount); + +// cheats.startPrank(operator); +// slasher.optIntoSlashing(address(generalServiceManager1)); +// slasher.optIntoSlashing(address(generalServiceManager2)); +// cheats.stopPrank(); + +// // emit log_named_uint("Linked list element 1", uint256(uint160(address(generalServiceManager1)))); +// // emit log_named_uint("Linked list element 2", uint256(uint160(address(generalServiceManager2)))); +// // emit log("________________________________________________________________"); +// generalReg1.registerOperator(operator, uint32(block.timestamp) + 5 days); +// // emit log_named_uint("Middleware 1 Update Block", uint32(block.number)); + +// cheats.warp(uint32(block.timestamp) + 1 days); +// cheats.roll(uint32(block.number) + 1); + +// generalReg2.registerOperator(operator, uint32(block.timestamp) + 5 days); +// // emit log_named_uint("Middleware 2 Update Block", uint32(block.number)); + +// address delegatedTo = delegation.delegatedTo(depositor); + +// // packed data structure to deal with stack-too-deep issues +// DataForTestWithdrawal memory dataForTestWithdrawal; + +// // scoped block to deal with stack-too-deep issues +// { +// //delegator-specific information +// (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = +// strategyManager.getDeposits(depositor); +// dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; +// dataForTestWithdrawal.delegatorShares = delegatorShares; + +// IStrategyManager.WithdrawerAndNonce memory withdrawerAndNonce = +// IStrategyManager.WithdrawerAndNonce({ +// withdrawer: withdrawer, +// // harcoded nonce value +// nonce: 0 +// } +// ); +// dataForTestWithdrawal.withdrawerAndNonce = withdrawerAndNonce; +// } + +// uint256[] memory strategyIndexes = new uint256[](2); +// IERC20[] memory tokensArray = new IERC20[](2); +// { +// // hardcoded values +// strategyIndexes[0] = 0; +// strategyIndexes[1] = 0; +// tokensArray[0] = weth; +// tokensArray[1] = eigenToken; +// } + +// cheats.warp(uint32(block.timestamp) + 1 days); +// cheats.roll(uint32(block.number) + 1); + +// _testQueueWithdrawal( +// depositor, +// strategyIndexes, +// dataForTestWithdrawal.delegatorStrategies, +// dataForTestWithdrawal.delegatorShares, +// dataForTestWithdrawal.withdrawerAndNonce.withdrawer, +// true +// ); +// uint32 queuedWithdrawalBlock = uint32(block.number); - //now withdrawal block time is before deregistration - cheats.warp(uint32(block.timestamp) + 2 days); - cheats.roll(uint32(block.number) + 2); +// //now withdrawal block time is before deregistration +// cheats.warp(uint32(block.timestamp) + 2 days); +// cheats.roll(uint32(block.number) + 2); - uint256 prevElement = uint256(uint160(address(generalServiceManager2))); - generalReg1.propagateStakeUpdate(operator, uint32(block.number), prevElement); +// uint256 prevElement = uint256(uint160(address(generalServiceManager2))); +// generalReg1.propagateStakeUpdate(operator, uint32(block.number), prevElement); - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.number) + 1); +// cheats.warp(uint32(block.timestamp) + 1 days); +// cheats.roll(uint32(block.number) + 1); - prevElement = uint256(uint160(address(generalServiceManager1))); - generalReg2.propagateStakeUpdate(operator, uint32(block.number), prevElement); +// prevElement = uint256(uint160(address(generalServiceManager1))); +// generalReg2.propagateStakeUpdate(operator, uint32(block.number), prevElement); - { - //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point - cheats.warp(uint32(block.timestamp) + 4 days); - cheats.roll(uint32(block.number) + 4); - - uint256 middlewareTimeIndex = 3; - if (withdrawAsTokens) { - _testCompleteQueuedWithdrawalTokens( - depositor, - dataForTestWithdrawal.delegatorStrategies, - tokensArray, - dataForTestWithdrawal.delegatorShares, - delegatedTo, - dataForTestWithdrawal.withdrawerAndNonce, - queuedWithdrawalBlock, - middlewareTimeIndex - ); - } else { - _testCompleteQueuedWithdrawalShares( - depositor, - dataForTestWithdrawal.delegatorStrategies, - tokensArray, - dataForTestWithdrawal.delegatorShares, - delegatedTo, - dataForTestWithdrawal.withdrawerAndNonce, - queuedWithdrawalBlock, - middlewareTimeIndex - ); - } - } - } - - // @notice This function tests to ensure that a delegator can re-delegate to an operator after undelegating. - // @param operator is the operator being delegated to. - // @param staker is the staker delegating stake to the operator. - function testRedelegateAfterWithdrawal( - address operator, - address depositor, - address withdrawer, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsShares - ) - public - fuzzedAddress(operator) - fuzzedAddress(depositor) - fuzzedAddress(withdrawer) - { - cheats.assume(depositor != operator); - //this function performs delegation and subsequent withdrawal - testWithdrawalWrapper(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsShares, true); - - //warps past fraudproof time interval - cheats.warp(block.timestamp + 7 days + 1); - testDelegation(operator, depositor, ethAmount, eigenAmount); - } - - /// @notice test to see if an operator who is slashed/frozen - /// cannot be undelegated from by their stakers. - /// @param operator is the operator being delegated to. - /// @param staker is the staker delegating stake to the operator. - function testSlashedOperatorWithdrawal(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) - public - fuzzedAddress(operator) - fuzzedAddress(staker) - { - cheats.assume(staker != operator); - testDelegation(operator, staker, ethAmount, eigenAmount); - - { - address slashingContract = slasher.owner(); - - cheats.startPrank(operator); - slasher.optIntoSlashing(address(slashingContract)); - cheats.stopPrank(); - - cheats.startPrank(slashingContract); - slasher.freezeOperator(operator); - cheats.stopPrank(); - } - - (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) = - strategyManager.getDeposits(staker); - - uint256[] memory strategyIndexes = new uint256[](2); - strategyIndexes[0] = 0; - strategyIndexes[1] = 1; - - IERC20[] memory tokensArray = new IERC20[](2); - tokensArray[0] = weth; - tokensArray[0] = eigenToken; - - //initiating queued withdrawal - cheats.expectRevert( - bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing") - ); - _testQueueWithdrawal(staker, strategyIndexes, updatedStrategies, updatedShares, staker, true); - } -} +// { +// //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point +// cheats.warp(uint32(block.timestamp) + 4 days); +// cheats.roll(uint32(block.number) + 4); + +// uint256 middlewareTimeIndex = 3; +// if (withdrawAsTokens) { +// _testCompleteQueuedWithdrawalTokens( +// depositor, +// dataForTestWithdrawal.delegatorStrategies, +// tokensArray, +// dataForTestWithdrawal.delegatorShares, +// delegatedTo, +// dataForTestWithdrawal.withdrawerAndNonce, +// queuedWithdrawalBlock, +// middlewareTimeIndex +// ); +// } else { +// _testCompleteQueuedWithdrawalShares( +// depositor, +// dataForTestWithdrawal.delegatorStrategies, +// tokensArray, +// dataForTestWithdrawal.delegatorShares, +// delegatedTo, +// dataForTestWithdrawal.withdrawerAndNonce, +// queuedWithdrawalBlock, +// middlewareTimeIndex +// ); +// } +// } +// } + +// // @notice This function tests to ensure that a delegator can re-delegate to an operator after undelegating. +// // @param operator is the operator being delegated to. +// // @param staker is the staker delegating stake to the operator. +// function testRedelegateAfterWithdrawal( +// address operator, +// address depositor, +// address withdrawer, +// uint96 ethAmount, +// uint96 eigenAmount, +// bool withdrawAsShares +// ) +// public +// fuzzedAddress(operator) +// fuzzedAddress(depositor) +// fuzzedAddress(withdrawer) +// { +// cheats.assume(depositor != operator); +// //this function performs delegation and subsequent withdrawal +// testWithdrawalWrapper(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsShares, true); + +// //warps past fraudproof time interval +// cheats.warp(block.timestamp + 7 days + 1); +// testDelegation(operator, depositor, ethAmount, eigenAmount); +// } + +// /// @notice test to see if an operator who is slashed/frozen +// /// cannot be undelegated from by their stakers. +// /// @param operator is the operator being delegated to. +// /// @param staker is the staker delegating stake to the operator. +// function testSlashedOperatorWithdrawal(address operator, address staker, uint96 ethAmount, uint96 eigenAmount) +// public +// fuzzedAddress(operator) +// fuzzedAddress(staker) +// { +// cheats.assume(staker != operator); +// testDelegation(operator, staker, ethAmount, eigenAmount); + +// { +// address slashingContract = slasher.owner(); + +// cheats.startPrank(operator); +// slasher.optIntoSlashing(address(slashingContract)); +// cheats.stopPrank(); + +// cheats.startPrank(slashingContract); +// slasher.freezeOperator(operator); +// cheats.stopPrank(); +// } + +// (IStrategy[] memory updatedStrategies, uint256[] memory updatedShares) = +// strategyManager.getDeposits(staker); + +// uint256[] memory strategyIndexes = new uint256[](2); +// strategyIndexes[0] = 0; +// strategyIndexes[1] = 1; + +// IERC20[] memory tokensArray = new IERC20[](2); +// tokensArray[0] = weth; +// tokensArray[0] = eigenToken; + +// //initiating queued withdrawal +// cheats.expectRevert( +// bytes("StrategyManager.onlyNotFrozen: staker has been frozen and may be subject to slashing") +// ); +// _testQueueWithdrawal(staker, strategyIndexes, updatedStrategies, updatedShares, staker, true); +// } +// } diff --git a/src/test/mocks/MiddlewareVoteWeigherMock.sol b/src/test/mocks/MiddlewareVoteWeigherMock.sol deleted file mode 100644 index 2a91ec198..000000000 --- a/src/test/mocks/MiddlewareVoteWeigherMock.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "../../contracts/middleware/RegistryBase.sol"; - -import "forge-std/Test.sol"; - -contract MiddlewareVoteWeigherMock is RegistryBase { - uint8 _NUMBER_OF_QUORUMS = 2; - - constructor( - IDelegationManager _delegation, - IStrategyManager _strategyManager, - IServiceManager _serviceManager - ) - RegistryBase(_strategyManager, _serviceManager) - {} - - function initialize( - uint96[] memory _minimumStakeForQuorums, - StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers - ) public virtual initializer { - RegistryBase._initialize( - _minimumStakeForQuorums, - _quorumStrategiesConsideredAndMultipliers - ); - } - - function registerOperator(address operator, uint32 serveUntil) public { - require(slasher.canSlash(operator, address(serviceManager)), "Not opted into slashing"); - serviceManager.recordFirstStakeUpdate(operator, serveUntil); - - } - - function deregisterOperator(address operator) public { - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); - } - - function propagateStakeUpdate(address operator, uint32 blockNumber, uint256 prevElement) external { - uint32 serveUntilBlock = serviceManager.latestServeUntilBlock(); - serviceManager.recordStakeUpdate(operator, blockNumber, serveUntilBlock, prevElement); - } - - // TODO: Fix this - function getTotalStakeForQuorumFromIndex(uint256 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) { - return totalStakeHistory[quorumNumber][index]; - } -} \ No newline at end of file diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 9c91e69b6..3813e63c9 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -17,6 +17,8 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32){} + function getQuorumBitmapOfOperatorAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192){} + function numRegistries() external view returns (uint256){} function registries(uint256) external view returns (address){} From c9d36fc39ba68a314701cec94d169c079d2382e0 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 23 Jun 2023 15:28:18 -0700 Subject: [PATCH 081/110] edit voteweigher to have 192 quorums --- src/contracts/middleware/VoteWeigherBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/middleware/VoteWeigherBase.sol b/src/contracts/middleware/VoteWeigherBase.sol index de8204939..9603829c9 100644 --- a/src/contracts/middleware/VoteWeigherBase.sol +++ b/src/contracts/middleware/VoteWeigherBase.sol @@ -168,7 +168,7 @@ contract VoteWeigherBase is VoteWeigherBaseStorage { StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers ) internal { uint16 quorumCountMem = quorumCount; - require(quorumCountMem < 256, "VoteWeigherBase._createQuorum: number of quorums cannot 256"); + require(quorumCountMem < 192, "VoteWeigherBase._createQuorum: number of quorums cannot 192"); uint8 quorumNumber = uint8(quorumCountMem); // increment quorumCount quorumCount = quorumCountMem + 1; From c938c1452a14b7bf076f63ade3f5653cbc0deb0a Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 23 Jun 2023 17:44:25 -0700 Subject: [PATCH 082/110] add operator churn mechanism --- .../BLSIndexRegistryCoordinator.sol | 142 ++++++++++++++++-- 1 file changed, 128 insertions(+), 14 deletions(-) diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 42e68297a..56a14efeb 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -26,10 +26,26 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { uint192 quorumBitmap; } + struct OperatorSetParam { + uint32 maxOperatorCount; + uint8 kickPercentageOfOperatorStake; + uint8 kickPercentageOfAverageStake; + uint8 kickPercentageOfTotalStake; + } + + struct OperatorKickParam { + address operator; + BN254.G1Point pubkey; + bytes32[] operatorIdsToSwap; // should be a single length array when kicking + uint32 globalOperatorListIndex; + } + /// @notice the BLS Pubkey Registry contract that will keep track of operators' BLS public keys IBLSPubkeyRegistry public immutable blsPubkeyRegistry; /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; + /// @notice the mapping from quorum number to the maximum number of operators that can be registered for that quorum + mapping(uint8 => OperatorSetParam) public quorumOperatorSetParams; /// @notice the mapping from operator's operatorId to the updates of the bitmap of quorums they are registered for mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorIdToQuorumBitmapHistory; /// @notice the mapping from operator's address to the operator struct @@ -48,6 +64,7 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } function initialize( + OperatorSetParam[] memory _operatorSetParams, uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers ) external initializer { @@ -56,6 +73,16 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { registries.push(address(blsPubkeyRegistry)); registries.push(address(indexRegistry)); + // set the operator set params + require( + _operatorSetParams.length == _minimumStakeForQuorum.length, + "BLSIndexRegistryCoordinator.initialize: operator set params and minimum stake for quorum lengths do not match" + ); + + for (uint8 i = 0; i < _operatorSetParams.length; i++) { + quorumOperatorSetParams[i] = _operatorSetParams[i]; + } + // this contract is the registry coordinator for the stake registry StakeRegistry._initialize(IRegistryCoordinator(address(this)), _minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); } @@ -94,6 +121,15 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { return registries.length; } + /** + * @notice Sets parameters of the operator set for the given `quorumNumber` + * @param quorumNumber is the quorum number to set the maximum number of operators for + * @param operatorSetParam is the parameters of the operator set for the `quorumNumber` + */ + function setOperatorSetParams(uint8 quorumNumber, OperatorSetParam memory operatorSetParam) external onlyServiceManagerOwner { + quorumOperatorSetParams[quorumNumber] = operatorSetParam; + } + /// @notice disabled function on the StakeRegistry because this is the registry coordinator function registerOperator(address, bytes32, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.registerOperator: cannot use overrided StakeRegistry.registerOperator on BLSIndexRegistryCoordinator"); @@ -121,6 +157,70 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { _registerOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey); } + /** + * @notice Registers msg.sender as an operator with the middleware + * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for + * @param pubkey is the BLS public key of the operator + */ + function registerOperatorWithCoordinator( + bytes calldata quorumNumbers, + BN254.G1Point memory pubkey, + OperatorKickParam[] calldata operatorKickParams + ) external { + require(quorumNumbers.length == operatorKickParams.length, "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: quorumNumbers and operatorKickParams must be the same length"); + // register the operator + _registerOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey); + + // get the registering operator's operatorId + bytes32 registeringOperatorId = _operators[msg.sender].operatorId; + + // kick the operators + for (uint256 i = 0; i < quorumNumbers.length; i++) { + // check that the quorum has reached the max operator count + uint8 quorumNumber = uint8(quorumNumbers[i]); + OperatorSetParam memory operatorSetParam = quorumOperatorSetParams[quorumNumber]; + { + uint32 numOperatorsForQuorum = indexRegistry.totalOperatorsForQuorum(quorumNumber); + require( + numOperatorsForQuorum == operatorSetParam.maxOperatorCount + 1, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: quorum has not reached max operator count" + ); + + // get the total stake for the quorum + uint96 totalStakeForQuorum = _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake; + bytes32 operatorToKickId = _operators[operatorKickParams[i].operator].operatorId; + uint96 operatorToKickStake = operatorIdToStakeHistory[operatorToKickId][quorumNumber][operatorIdToStakeHistory[operatorToKickId][quorumNumber].length - 1].stake; + uint96 registeringOperatorStake = operatorIdToStakeHistory[registeringOperatorId][quorumNumber][operatorIdToStakeHistory[registeringOperatorId][quorumNumber].length - 1].stake; + + // check the registering operator has more than the kick percentage of the operator to kick's stake + require( + registeringOperatorStake > operatorToKickStake * operatorSetParam.kickPercentageOfOperatorStake / 100, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: registering operator has less than kickPercentageOfOperatorStake" + ); + + // check that the operator to kick has less than the kick percentage of the average stake + require( + operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickPercentageOfAverageStake / 100 / numOperatorsForQuorum, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickPercentageOfAverageStake" + ); + // check the that the operator to kick has lss than the kick percentage of the total stake + require( + operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickPercentageOfTotalStake / 100, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickPercentageOfTotalStake" + ); + } + + // kick the operator + _deregisterOperatorWithCoordinator( + operatorKickParams[i].operator, + quorumNumbers[i:i+1], + operatorKickParams[i].pubkey, + operatorKickParams[i].operatorIdsToSwap, + operatorKickParams[i].globalOperatorListIndex + ); + } + } + /// @notice disabled function on the StakeRegistry because this is the registry coordinator function deregisterOperator(address, bytes32, bool, bytes calldata) external override pure { revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); @@ -154,11 +254,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { function _registerOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { // check that the sender is not already registered - require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator: operator already registered"); + require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._registerOperatorWithCoordinator: operator already registered"); // get the quorum bitmap from the quorum numbers uint192 quorumBitmap = uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)); - require(quorumBitmap != 0, "BLSIndexRegistryCoordinator: quorumBitmap cannot be 0"); + require(quorumBitmap != 0, "BLSIndexRegistryCoordinator._registerOperatorWithCoordinator: quorumBitmap cannot be 0"); // register the operator with the BLSPubkeyRegistry and get the operatorId (in this case, the pubkeyHash) back bytes32 operatorId = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, pubkey); @@ -185,32 +285,46 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } function _deregisterOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { - require(_operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperator: operator is not registered"); + require(_operators[operator].status == OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._deregisterOperatorWithCoordinator: operator is not registered"); // get the operatorId of the operator bytes32 operatorId = _operators[operator].operatorId; - require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperator: operatorId does not match pubkey hash"); + require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperatorWithCoordinator: operatorId does not match pubkey hash"); // get the quorumNumbers of the operator + uint192 quorumsToRemoveBitmap = uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)); uint256 operatorQuorumBitmapHistoryLengthMinusOne = _operatorIdToQuorumBitmapHistory[operatorId].length - 1; + uint192 quorumBitmapBeforeUpdate = _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].quorumBitmap; // check that the quorumNumbers of the operator matches the quorumNumbers passed in require( - _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].quorumBitmap == - uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)), - "BLSIndexRegistryCoordinator._deregisterOperator: quorumNumbers does not match storage"); - // set the toBlockNumber of the operator's quorum bitmap update - _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].nextUpdateBlockNumber = uint32(block.number); + quorumBitmapBeforeUpdate & quorumsToRemoveBitmap == quorumsToRemoveBitmap, + "BLSIndexRegistryCoordinator._deregisterOperatorWithCoordinator: cannot deregister operator for quorums that it is not a part of" + ); + // check if the operator is completely deregistering + bool completeDeregistration = quorumBitmapBeforeUpdate == quorumsToRemoveBitmap; // deregister the operator from the BLSPubkeyRegistry - blsPubkeyRegistry.deregisterOperator(operator, true, quorumNumbers, pubkey); + blsPubkeyRegistry.deregisterOperator(operator, completeDeregistration, quorumNumbers, pubkey); // deregister the operator from the IndexRegistry - indexRegistry.deregisterOperator(operatorId, true, quorumNumbers, operatorIdsToSwap, globalOperatorListIndex); + indexRegistry.deregisterOperator(operatorId, completeDeregistration, quorumNumbers, operatorIdsToSwap, globalOperatorListIndex); // deregister the operator from the StakeRegistry - _deregisterOperator(operator, operatorId, true, quorumNumbers); + _deregisterOperator(operator, operatorId, completeDeregistration, quorumNumbers); - // set the status of the operator to DEREGISTERED - _operators[operator].status = OperatorStatus.DEREGISTERED; + // set the toBlockNumber of the operator's quorum bitmap update + _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].nextUpdateBlockNumber = uint32(block.number); + + // if it is not a complete deregistration, add a new quorum bitmap update + if (!completeDeregistration) { + _operatorIdToQuorumBitmapHistory[operatorId].push(QuorumBitmapUpdate({ + updateBlockNumber: uint32(block.number), + nextUpdateBlockNumber: 0, + quorumBitmap: quorumBitmapBeforeUpdate & ~quorumsToRemoveBitmap // this removes the quorumsToRemoveBitmap from the quorumBitmapBeforeUpdate + })); + } else { + // set the status of the operator to DEREGISTERED + _operators[operator].status = OperatorStatus.DEREGISTERED; + } } } \ No newline at end of file From fb347a508469e292d8b089fd0d64af570db03601 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 23 Jun 2023 18:20:08 -0700 Subject: [PATCH 083/110] separated stakeregistry and coordinator --- src/contracts/interfaces/IStakeRegistry.sol | 16 +++- .../BLSIndexRegistryCoordinator.sol | 87 ++++++++++--------- src/contracts/middleware/StakeRegistry.sol | 60 +++---------- .../middleware/StakeRegistryStorage.sol | 4 +- src/test/harnesses/StakeRegistryHarness.sol | 11 +-- src/test/unit/VoteWeigherBaseUnit.t.sol | 8 +- 6 files changed, 80 insertions(+), 106 deletions(-) diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 5116a602a..8e407fe54 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -37,9 +37,7 @@ interface IStakeRegistry is IRegistry { /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. - * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. - * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): @@ -49,7 +47,7 @@ interface IStakeRegistry is IRegistry { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, bool completeDeregistration, bytes memory quorumNumbers) external; + function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external; function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256); @@ -100,6 +98,18 @@ interface IStakeRegistry is IRegistry { */ function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96); + /** + * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber` + * @dev Function returns weight of **0** in the event that the operator has no stake history + */ + function getCurrentOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96); + + /** + * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`. + * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty. + */ + function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96); + /** * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. * @param operatorId is the id of the operator of interest diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol index 56a14efeb..693adfd8c 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSIndexRegistryCoordinator.sol @@ -1,14 +1,17 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; +import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; + import "../interfaces/IRegistryCoordinator.sol"; +import "../interfaces/IServiceManager.sol"; import "../interfaces/IBLSPubkeyRegistry.sol"; +import "../interfaces/IVoteWeigher.sol"; +import "../interfaces/IStakeRegistry.sol"; import "../interfaces/IIndexRegistry.sol"; import "../libraries/BytesArrayBitmaps.sol"; -import "./StakeRegistry.sol"; - /** * @title A `RegistryCoordinator` that has three registries: * 1) a `StakeRegistry` that keeps track of operators' stakes (this is actually the contract itself, via inheritance) @@ -17,7 +20,7 @@ import "./StakeRegistry.sol"; * * @author Layr Labs, Inc. */ -contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { +contract BLSIndexRegistryCoordinator is Initializable, IRegistryCoordinator { using BN254 for BN254.G1Point; struct QuorumBitmapUpdate { @@ -39,9 +42,14 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { bytes32[] operatorIdsToSwap; // should be a single length array when kicking uint32 globalOperatorListIndex; } - + /// @notice the EigenLayer Slasher + ISlasher public immutable slasher; + /// @notice the Service Manager for the service that this contract is coordinating + IServiceManager public immutable serviceManager; /// @notice the BLS Pubkey Registry contract that will keep track of operators' BLS public keys IBLSPubkeyRegistry public immutable blsPubkeyRegistry; + /// @notice the Stake Registry contract that will keep track of operators' stakes + IStakeRegistry public immutable stakeRegistry; /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; /// @notice the mapping from quorum number to the maximum number of operators that can be registered for that quorum @@ -53,38 +61,36 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { /// @notice the dynamic-length array of the registries this coordinator is coordinating address[] public registries; + modifier onlyServiceManagerOwner { + require(msg.sender == serviceManager.owner(), "BLSIndexRegistryCoordinator.onlyServiceManagerOwner: caller is not the service manager owner"); + _; + } + constructor( - IStrategyManager _strategyManager, + ISlasher _slasher, IServiceManager _serviceManager, + IStakeRegistry _stakeRegistry, IBLSPubkeyRegistry _blsPubkeyRegistry, IIndexRegistry _indexRegistry - ) StakeRegistry(_strategyManager, _serviceManager) { + ) { + slasher = _slasher; + serviceManager = _serviceManager; + stakeRegistry = _stakeRegistry; blsPubkeyRegistry = _blsPubkeyRegistry; indexRegistry = _indexRegistry; } - function initialize( - OperatorSetParam[] memory _operatorSetParams, - uint96[] memory _minimumStakeForQuorum, - StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers - ) external initializer { + function initialize(OperatorSetParam[] memory _operatorSetParams) external initializer { // the stake registry is this contract itself - registries.push(address(this)); + registries.push(address(stakeRegistry)); registries.push(address(blsPubkeyRegistry)); registries.push(address(indexRegistry)); // set the operator set params - require( - _operatorSetParams.length == _minimumStakeForQuorum.length, - "BLSIndexRegistryCoordinator.initialize: operator set params and minimum stake for quorum lengths do not match" - ); - + require(IVoteWeigher(address(stakeRegistry)).quorumCount() == _operatorSetParams.length, "BLSIndexRegistryCoordinator: operator set params length mismatch"); for (uint8 i = 0; i < _operatorSetParams.length; i++) { quorumOperatorSetParams[i] = _operatorSetParams[i]; } - - // this contract is the registry coordinator for the stake registry - StakeRegistry._initialize(IRegistryCoordinator(address(this)), _minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); } /// @notice Returns task number from when `operator` has been registered. @@ -130,11 +136,6 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { quorumOperatorSetParams[quorumNumber] = operatorSetParam; } - /// @notice disabled function on the StakeRegistry because this is the registry coordinator - function registerOperator(address, bytes32, bytes calldata) external override pure { - revert("BLSIndexRegistryCoordinator.registerOperator: cannot use overrided StakeRegistry.registerOperator on BLSIndexRegistryCoordinator"); - } - /** * @notice Registers msg.sender as an operator with the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for @@ -187,10 +188,10 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { ); // get the total stake for the quorum - uint96 totalStakeForQuorum = _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake; + uint96 totalStakeForQuorum = stakeRegistry.getCurrentTotalStakeForQuorum(quorumNumber); bytes32 operatorToKickId = _operators[operatorKickParams[i].operator].operatorId; - uint96 operatorToKickStake = operatorIdToStakeHistory[operatorToKickId][quorumNumber][operatorIdToStakeHistory[operatorToKickId][quorumNumber].length - 1].stake; - uint96 registeringOperatorStake = operatorIdToStakeHistory[registeringOperatorId][quorumNumber][operatorIdToStakeHistory[registeringOperatorId][quorumNumber].length - 1].stake; + uint96 operatorToKickStake = stakeRegistry.getCurrentOperatorStakeForQuorum(operatorToKickId, quorumNumber); + uint96 registeringOperatorStake = stakeRegistry.getCurrentOperatorStakeForQuorum(registeringOperatorId, quorumNumber); // check the registering operator has more than the kick percentage of the operator to kick's stake require( @@ -221,11 +222,6 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } } - /// @notice disabled function on the StakeRegistry because this is the registry coordinator - function deregisterOperator(address, bytes32, bool, bytes calldata) external override pure { - revert("BLSIndexRegistryCoordinator.deregisterOperator: cannot use overrided StakeRegistry.deregisterOperator on BLSIndexRegistryCoordinator"); - } - /** * @notice Deregisters the msg.sender as an operator from the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for @@ -253,6 +249,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { } function _registerOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { + require( + slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, + "StakeRegistry._registerOperator: operator must be opted into slashing by the serviceManager" + ); + // check that the sender is not already registered require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._registerOperatorWithCoordinator: operator already registered"); @@ -263,12 +264,12 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { // register the operator with the BLSPubkeyRegistry and get the operatorId (in this case, the pubkeyHash) back bytes32 operatorId = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, pubkey); + // register the operator with the StakeRegistry + stakeRegistry.registerOperator(operator, operatorId, quorumNumbers); + // register the operator with the IndexRegistry indexRegistry.registerOperator(operatorId, quorumNumbers); - // register the operator with the StakeRegistry - _registerOperator(operator, operatorId, quorumNumbers); - // set the operatorId to quorum bitmap history _operatorIdToQuorumBitmapHistory[operatorId].push(QuorumBitmapUpdate({ updateBlockNumber: uint32(block.number), @@ -282,6 +283,9 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { fromTaskNumber: serviceManager.taskNumber(), status: OperatorStatus.REGISTERED }); + + // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet + serviceManager.recordFirstStakeUpdate(operator, 0); } function _deregisterOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) internal { @@ -306,12 +310,12 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { // deregister the operator from the BLSPubkeyRegistry blsPubkeyRegistry.deregisterOperator(operator, completeDeregistration, quorumNumbers, pubkey); + // deregister the operator from the StakeRegistry + stakeRegistry.deregisterOperator(operatorId, quorumNumbers); + // deregister the operator from the IndexRegistry indexRegistry.deregisterOperator(operatorId, completeDeregistration, quorumNumbers, operatorIdsToSwap, globalOperatorListIndex); - // deregister the operator from the StakeRegistry - _deregisterOperator(operator, operatorId, completeDeregistration, quorumNumbers); - // set the toBlockNumber of the operator's quorum bitmap update _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].nextUpdateBlockNumber = uint32(block.number); @@ -323,6 +327,11 @@ contract BLSIndexRegistryCoordinator is StakeRegistry, IRegistryCoordinator { quorumBitmap: quorumBitmapBeforeUpdate & ~quorumsToRemoveBitmap // this removes the quorumsToRemoveBitmap from the quorumBitmapBeforeUpdate })); } else { + // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges + uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); + + // record a stake update unbonding the operator after `latestServeUntilBlock` + serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); // set the status of the operator to DEREGISTERED _operators[operator].status = OperatorStatus.DEREGISTERED; } diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 42455ccd5..979ed1253 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -26,9 +26,10 @@ contract StakeRegistry is StakeRegistryStorage { ); constructor( + IRegistryCoordinator _registryCoordinator, IStrategyManager _strategyManager, IServiceManager _serviceManager - ) StakeRegistryStorage(_strategyManager, _serviceManager) + ) StakeRegistryStorage(_registryCoordinator, _strategyManager, _serviceManager) // solhint-disable-next-line no-empty-blocks { } @@ -39,20 +40,16 @@ contract StakeRegistry is StakeRegistryStorage { * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with */ function initialize( - IRegistryCoordinator _registryCoordinator, uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers ) external virtual initializer { - _initialize(_registryCoordinator, _minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); + _initialize(_minimumStakeForQuorum, _quorumStrategiesConsideredAndMultipliers); } function _initialize( - IRegistryCoordinator _registryCoordinator, uint96[] memory _minimumStakeForQuorum, StrategyAndWeightingMultiplier[][] memory _quorumStrategiesConsideredAndMultipliers ) internal virtual onlyInitializing { - // store the coordinator - registryCoordinator = _registryCoordinator; // sanity check lengths require(_minimumStakeForQuorum.length == _quorumStrategiesConsideredAndMultipliers.length, "Registry._initialize: minimumStakeForQuorum length mismatch"); @@ -152,8 +149,10 @@ contract StakeRegistry is StakeRegistryStorage { return operatorStakeUpdate.stake; } - /// @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`. - /// @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty. + /** + * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`. + * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty. + */ function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) { // no chance of underflow / error in next line, since an empty entry is pushed in the constructor return _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake; @@ -271,9 +270,7 @@ contract StakeRegistry is StakeRegistryStorage { /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. - * @param operator The address of the operator to deregister. * @param operatorId The id of the operator to deregister. - * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): @@ -283,8 +280,8 @@ contract StakeRegistry is StakeRegistryStorage { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(address operator, bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers) external virtual { - _deregisterOperator(operator, operatorId, completeDeregistration, quorumNumbers); + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external virtual { + _deregisterOperator(operatorId, quorumNumbers); } /** @@ -343,29 +340,6 @@ contract StakeRegistry is StakeRegistryStorage { // INTERNAL FUNCTIONS function _registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { - require( - slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, - "StakeRegistry._registerOperator: operator must be opted into slashing by the serviceManager" - ); - - // calculate stakes for each quorum the operator is trying to join - _registerStake(operator, operatorId, quorumNumbers); - - // record a stake update not bonding the operator at all (unbonded at 0), because they haven't served anything yet - serviceManager.recordFirstStakeUpdate(operator, 0); - } - - /** - * TODO: critique: "Currently only `_registrationStakeEvaluation` uses the `uint256 registrantType` input -- we should **EITHER** store this - * and keep using it in other places as well, **OR** stop using it altogether" - */ - /** - * @notice Used inside of inheriting contracts to validate the registration of `operator` and find their `OperatorStake`. - * @dev This function does **not** update the stored state of the operator's stakes -- storage updates are performed elsewhere. - */ - function _registerStake(address operator, bytes32 operatorId, bytes memory quorumNumbers) - internal - { uint8 quorumNumbersLength = uint8(quorumNumbers.length); // check the operator is registering for only valid quorums require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerStake: greatest quorumNumber must be less than quorumCount"); @@ -398,24 +372,10 @@ contract StakeRegistry is StakeRegistryStorage { } } - function _deregisterOperator(address operator, bytes32 operatorId, bool completeDeregistration, bytes memory quorumNumbers) internal { - // remove the operator's stake - _removeOperatorStake(operatorId, quorumNumbers); - - // if the operator is deregistering from all quorums, revoke ther service's slashing ability - if(completeDeregistration) { - // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - - // record a stake update unbonding the operator after `latestServeUntilBlock` - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); - } - } - /** * @notice Removes the stakes of the operator */ - function _removeOperatorStake(bytes32 operatorId, bytes memory quorumNumbers) internal { + function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) internal { uint8 quorumNumbersLength = uint8(quorumNumbers.length); // check the operator is deregistering from only valid quorums require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerStake: greatest quorumNumber must be less than quorumCount"); diff --git a/src/contracts/middleware/StakeRegistryStorage.sol b/src/contracts/middleware/StakeRegistryStorage.sol index 8becd5063..3231576bb 100644 --- a/src/contracts/middleware/StakeRegistryStorage.sol +++ b/src/contracts/middleware/StakeRegistryStorage.sol @@ -14,7 +14,7 @@ import "./VoteWeigherBase.sol"; */ abstract contract StakeRegistryStorage is VoteWeigherBase, IStakeRegistry { /// @notice the coordinator contract that this registry is associated with - IRegistryCoordinator public registryCoordinator; + IRegistryCoordinator public immutable registryCoordinator; // TODO: set these on initialization /// @notice In order to register, an operator must have at least `minimumStakeFirstQuorum` or `minimumStakeSecondQuorum`, as @@ -28,11 +28,13 @@ abstract contract StakeRegistryStorage is VoteWeigherBase, IStakeRegistry { mapping(bytes32 => mapping(uint8 => OperatorStakeUpdate[])) public operatorIdToStakeHistory; constructor( + IRegistryCoordinator _registryCoordinator, IStrategyManager _strategyManager, IServiceManager _serviceManager ) VoteWeigherBase(_strategyManager, _serviceManager) // solhint-disable-next-line no-empty-blocks { + registryCoordinator = _registryCoordinator; } // storage gap diff --git a/src/test/harnesses/StakeRegistryHarness.sol b/src/test/harnesses/StakeRegistryHarness.sol index c0ab81932..5514a34cf 100644 --- a/src/test/harnesses/StakeRegistryHarness.sol +++ b/src/test/harnesses/StakeRegistryHarness.sol @@ -6,9 +6,10 @@ import "../../contracts/middleware/StakeRegistry.sol"; // wrapper around the StakeRegistry contract that exposes the internal functions for unit testing. contract StakeRegistryHarness is StakeRegistry { constructor( + IRegistryCoordinator _registryCoordinator, IStrategyManager _strategyManager, IServiceManager _serviceManager - ) StakeRegistry(_strategyManager, _serviceManager) { + ) StakeRegistry(_registryCoordinator, _strategyManager, _serviceManager) { } function recordOperatorStakeUpdate(bytes32 operatorId, uint8 quorumNumber, OperatorStakeUpdate memory operatorStakeUpdate) external returns(uint96) { @@ -18,12 +19,4 @@ contract StakeRegistryHarness is StakeRegistry { function updateOperatorStake(address operator, bytes32 operatorId, uint8 quorumNumber) external returns (uint96, uint96) { return _updateOperatorStake(operator, operatorId, quorumNumber); } - - function registerStake(address operator, bytes32 operatorId, bytes memory quorumNumbers) external { - _registerStake(operator, operatorId, quorumNumbers); - } - - function removeOperatorStake(bytes32 operatorId, bytes memory quorumNumbers) external { - _removeOperatorStake(operatorId, quorumNumbers); - } } \ No newline at end of file diff --git a/src/test/unit/VoteWeigherBaseUnit.t.sol b/src/test/unit/VoteWeigherBaseUnit.t.sol index 4c822b7fb..6e9542447 100644 --- a/src/test/unit/VoteWeigherBaseUnit.t.sol +++ b/src/test/unit/VoteWeigherBaseUnit.t.sol @@ -186,16 +186,16 @@ contract VoteWeigherBaseUnitTests is Test { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); } - function testCreateQuorum_MoreThan256Quorums_Reverts() public { + function testCreateQuorum_MoreThan192Quorums_Reverts() public { IVoteWeigher.StrategyAndWeightingMultiplier[] memory strategiesAndWeightingMultipliers = _defaultStrategiesAndWeightingMultipliers(); cheats.startPrank(serviceManagerOwner); - for (uint i = 0; i < 256; i++) { + for (uint i = 0; i < 192; i++) { voteWeigher.createQuorum(strategiesAndWeightingMultipliers); } - assertEq(voteWeigher.quorumCount(), 256); + assertEq(voteWeigher.quorumCount(), 192); - cheats.expectRevert("VoteWeigherBase._createQuorum: number of quorums cannot 256"); + cheats.expectRevert("VoteWeigherBase._createQuorum: number of quorums cannot 192"); voteWeigher.createQuorum(strategiesAndWeightingMultipliers); } From f2c66a0da19b534ddab11277ebf94fffee1c854c Mon Sep 17 00:00:00 2001 From: gpsanant Date: Fri, 23 Jun 2023 18:38:18 -0700 Subject: [PATCH 084/110] propagate bls sig edit updates --- .../IBLSRegistryCoordinatorWithIndices.sol | 42 +++++++++++++++++++ .../interfaces/IRegistryCoordinator.sol | 14 ++++++- ... => BLSRegistryCoordinatorWithIndices.sol} | 34 +++++---------- src/test/mocks/RegistryCoordinatorMock.sol | 9 +++- 4 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol rename src/contracts/middleware/{BLSIndexRegistryCoordinator.sol => BLSRegistryCoordinatorWithIndices.sol} (94%) diff --git a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol new file mode 100644 index 000000000..447cd2e37 --- /dev/null +++ b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "./IRegistryCoordinator.sol"; +import "./IStakeRegistry.sol"; +import "./IBLSPubkeyRegistry.sol"; +import "./IIndexRegistry.sol"; + +/** + * @title Minimal interface for the `IBLSStakeRegistryCoordinator` contract. + * @author Layr Labs, Inc. + */ +interface IBLSRegistryCoordinatorWithIndices is IRegistryCoordinator { + // STRUCTS + + struct QuorumBitmapUpdate { + uint32 updateBlockNumber; + uint32 nextUpdateBlockNumber; + uint192 quorumBitmap; + } + + struct OperatorSetParam { + uint32 maxOperatorCount; + uint8 kickPercentageOfOperatorStake; + uint8 kickPercentageOfAverageStake; + uint8 kickPercentageOfTotalStake; + } + + struct OperatorKickParam { + address operator; + BN254.G1Point pubkey; + bytes32[] operatorIdsToSwap; // should be a single length array when kicking + uint32 globalOperatorListIndex; + } + + /// @notice the stake registry for this corrdinator is the contract itself + function stakeRegistry() external view returns (IStakeRegistry); + /// @notice the BLS Pubkey Registry contract that will keep track of operators' BLS public keys + function blsPubkeyRegistry() external view returns (IBLSPubkeyRegistry); + /// @notice the Index Registry contract that will keep track of operators' indexes + function indexRegistry() external view returns (IIndexRegistry); +} \ No newline at end of file diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 3cc8abe54..6c642c475 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -6,6 +6,13 @@ pragma solidity =0.8.12; * @author Layr Labs, Inc. */ interface IRegistryCoordinator { + // EVENTS + /// Emits when an operator is registered + event OperatorRegistered(address indexed operator, bytes32 indexed operatorId); + + /// Emits when an operator is deregistered + event OperatorDeregistered(address indexed operator, bytes32 indexed operatorId); + // DATA STRUCTURES enum OperatorStatus { @@ -34,7 +41,10 @@ interface IRegistryCoordinator { function getOperatorId(address operator) external view returns (bytes32); /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` - function getQuorumBitmapOfOperatorAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); + function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); + + /// @notice Returns the current quorum bitmap for the given `operatorId` + function getCurrentQuorumBitmapByOperatorId(bytes32 operatorId) external view returns (uint192); /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32); @@ -58,4 +68,4 @@ interface IRegistryCoordinator { * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information */ function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external; -} +} \ No newline at end of file diff --git a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol similarity index 94% rename from src/contracts/middleware/BLSIndexRegistryCoordinator.sol rename to src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 693adfd8c..2f75e80e7 100644 --- a/src/contracts/middleware/BLSIndexRegistryCoordinator.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.12; import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/IRegistryCoordinator.sol"; +import "../interfaces/IBLSRegistryCoordinatorWithIndices.sol"; import "../interfaces/IServiceManager.sol"; import "../interfaces/IBLSPubkeyRegistry.sol"; import "../interfaces/IVoteWeigher.sol"; @@ -20,28 +20,9 @@ import "../libraries/BytesArrayBitmaps.sol"; * * @author Layr Labs, Inc. */ -contract BLSIndexRegistryCoordinator is Initializable, IRegistryCoordinator { +contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordinatorWithIndices { using BN254 for BN254.G1Point; - struct QuorumBitmapUpdate { - uint32 updateBlockNumber; - uint32 nextUpdateBlockNumber; - uint192 quorumBitmap; - } - - struct OperatorSetParam { - uint32 maxOperatorCount; - uint8 kickPercentageOfOperatorStake; - uint8 kickPercentageOfAverageStake; - uint8 kickPercentageOfTotalStake; - } - - struct OperatorKickParam { - address operator; - BN254.G1Point pubkey; - bytes32[] operatorIdsToSwap; // should be a single length array when kicking - uint32 globalOperatorListIndex; - } /// @notice the EigenLayer Slasher ISlasher public immutable slasher; /// @notice the Service Manager for the service that this contract is coordinating @@ -109,19 +90,24 @@ contract BLSIndexRegistryCoordinator is Initializable, IRegistryCoordinator { } /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` - function getQuorumBitmapOfOperatorAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192) { + function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192) { QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorIdToQuorumBitmapHistory[operatorId][index]; require( quorumBitmapUpdate.updateBlockNumber <= blockNumber, - "BLSRegistryCoordinator.getQuorumBitmapOfOperatorAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" + "BLSRegistryCoordinator.getQuorumBitmapByOperatorIdAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" ); require( quorumBitmapUpdate.nextUpdateBlockNumber > blockNumber, - "BLSRegistryCoordinator.getQuorumBitmapOfOperatorAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" + "BLSRegistryCoordinator.getQuorumBitmapByOperatorIdAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" ); return quorumBitmapUpdate.quorumBitmap; } + /// @notice Returns the current quorum bitmap for the given `operatorId` + function getCurrentQuorumBitmapByOperatorId(bytes32 operatorId) external view returns (uint192) { + return _operatorIdToQuorumBitmapHistory[operatorId][_operatorIdToQuorumBitmapHistory[operatorId].length - 1].quorumBitmap; + } + /// @notice Returns the number of registries function numRegistries() external view returns (uint256) { return registries.length; diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 3813e63c9..3304c821a 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -17,7 +17,11 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32){} - function getQuorumBitmapOfOperatorAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192){} + /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192) {} + + /// @notice Returns the current quorum bitmap for the given `operatorId` + function getCurrentQuorumBitmapByOperatorId(bytes32 operatorId) external view returns (uint192) {} function numRegistries() external view returns (uint256){} @@ -26,4 +30,5 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata) external {} function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata) external {} -} + +} \ No newline at end of file From 0eff79589dccefcbdee149433b9769d122483cfc Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 19:38:12 -0700 Subject: [PATCH 085/110] update stakeHistoryIndex comment --- src/contracts/interfaces/IStakeRegistry.sol | 4 ++-- src/contracts/middleware/StakeRegistry.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 8e407fe54..b37a38984 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -115,7 +115,7 @@ interface IStakeRegistry is IRegistry { * @param operatorId is the id of the operator of interest * @param blockNumber is the block number of interest * @param quorumNumber is the quorum number which the operator had stake in - * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` + * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's activity * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` @@ -137,7 +137,7 @@ interface IStakeRegistry is IRegistry { * @param operatorId is the id of the operator of interest * @param blockNumber is the block number of interest * @param quorumNumber is the quorum number which the operator had no stake in - * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` + * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's inactivity * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 979ed1253..f4f6153a6 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -171,7 +171,7 @@ contract StakeRegistry is StakeRegistryStorage { * @param operatorId is the id of the operator of interest * @param blockNumber is the block number of interest * @param quorumNumber is the quorum number which the operator had stake in - * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` + * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's activity * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` @@ -208,7 +208,7 @@ contract StakeRegistry is StakeRegistryStorage { * @param operatorId is the id of the operator of interest * @param blockNumber is the block number of interest * @param quorumNumber is the quorum number which the operator had no stake in - * @param stakeHistoryIndex specifies an index in `operatorIdToStakeHistory[operatorId]` + * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's inactivity * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` From 926aabdfc303677f87da99a8cd5ba845d491a18f Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 19:39:44 -0700 Subject: [PATCH 086/110] update voteweigher protocol contract pointer comments --- src/contracts/interfaces/IVoteWeigher.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol index 2edbcfd1a..3ef56b18c 100644 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ b/src/contracts/interfaces/IVoteWeigher.sol @@ -25,13 +25,13 @@ interface IVoteWeigher { /// @notice Constant used as a divisor in calculating weights. function WEIGHTING_DIVISOR() external pure returns (uint256); - /// @notice Returns the strategy manager contract. + /// @notice Returns the EigenLayer strategy manager contract. function strategyManager() external view returns (IStrategyManager); - /// @notice Returns the stake registry contract. + /// @notice Returns the EigenLayer slasher contract. function slasher() external view returns (ISlasher); - /// @notice Returns the delegation manager contract. + /// @notice Returns the EigenLayer delegation manager contract. function delegation() external view returns (IDelegationManager); - /// @notice Returns the service manager contract. + /// @notice Returns the AVS service manager contract. function serviceManager() external view returns (IServiceManager); /** From 49a9ef100c83d518c28bb663ece687f6be0c2821 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 19:47:00 -0700 Subject: [PATCH 087/110] make operator set params internal --- .../IBLSRegistryCoordinatorWithIndices.sol | 15 +-------------- .../interfaces/IRegistryCoordinator.sol | 18 ++++++++++++++++++ .../BLSRegistryCoordinatorWithIndices.sol | 13 +++++++++---- src/test/mocks/RegistryCoordinatorMock.sol | 2 ++ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol index 447cd2e37..25892047a 100644 --- a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol @@ -11,20 +11,7 @@ import "./IIndexRegistry.sol"; * @author Layr Labs, Inc. */ interface IBLSRegistryCoordinatorWithIndices is IRegistryCoordinator { - // STRUCTS - - struct QuorumBitmapUpdate { - uint32 updateBlockNumber; - uint32 nextUpdateBlockNumber; - uint192 quorumBitmap; - } - - struct OperatorSetParam { - uint32 maxOperatorCount; - uint8 kickPercentageOfOperatorStake; - uint8 kickPercentageOfAverageStake; - uint8 kickPercentageOfTotalStake; - } + // STRUCT struct OperatorKickParam { address operator; diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 6c642c475..d9e7cb4e6 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -22,6 +22,8 @@ interface IRegistryCoordinator { DEREGISTERED } + // STRUCTS + /** * @notice Data structure for storing info on operators */ @@ -34,6 +36,22 @@ interface IRegistryCoordinator { OperatorStatus status; } + struct QuorumBitmapUpdate { + uint32 updateBlockNumber; + uint32 nextUpdateBlockNumber; + uint192 quorumBitmap; + } + + struct OperatorSetParam { + uint32 maxOperatorCount; + uint8 kickPercentageOfOperatorStake; + uint8 kickPercentageOfAverageStake; + uint8 kickPercentageOfTotalStake; + } + + /// @notice Returns the operator set params for the given `quorumNumber` + function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory); + /// @notice Returns the operator struct for the given `operator` function getOperator(address operator) external view returns (Operator memory); diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 2f75e80e7..accc77a51 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -34,7 +34,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; /// @notice the mapping from quorum number to the maximum number of operators that can be registered for that quorum - mapping(uint8 => OperatorSetParam) public quorumOperatorSetParams; + mapping(uint8 => OperatorSetParam) internal _quorumOperatorSetParams; /// @notice the mapping from operator's operatorId to the updates of the bitmap of quorums they are registered for mapping(bytes32 => QuorumBitmapUpdate[]) internal _operatorIdToQuorumBitmapHistory; /// @notice the mapping from operator's address to the operator struct @@ -70,10 +70,15 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin // set the operator set params require(IVoteWeigher(address(stakeRegistry)).quorumCount() == _operatorSetParams.length, "BLSIndexRegistryCoordinator: operator set params length mismatch"); for (uint8 i = 0; i < _operatorSetParams.length; i++) { - quorumOperatorSetParams[i] = _operatorSetParams[i]; + _quorumOperatorSetParams[i] = _operatorSetParams[i]; } } + /// @notice Returns the operator set params for the given `quorumNumber` + function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory) { + return _quorumOperatorSetParams[quorumNumber]; + } + /// @notice Returns task number from when `operator` has been registered. function getFromTaskNumberForOperator(address operator) external view returns (uint32) { return _operators[operator].fromTaskNumber; @@ -119,7 +124,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin * @param operatorSetParam is the parameters of the operator set for the `quorumNumber` */ function setOperatorSetParams(uint8 quorumNumber, OperatorSetParam memory operatorSetParam) external onlyServiceManagerOwner { - quorumOperatorSetParams[quorumNumber] = operatorSetParam; + _quorumOperatorSetParams[quorumNumber] = operatorSetParam; } /** @@ -165,7 +170,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin for (uint256 i = 0; i < quorumNumbers.length; i++) { // check that the quorum has reached the max operator count uint8 quorumNumber = uint8(quorumNumbers[i]); - OperatorSetParam memory operatorSetParam = quorumOperatorSetParams[quorumNumber]; + OperatorSetParam memory operatorSetParam = _quorumOperatorSetParams[quorumNumber]; { uint32 numOperatorsForQuorum = indexRegistry.totalOperatorsForQuorum(quorumNumber); require( diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 3304c821a..558f92c05 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -9,6 +9,8 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { /// @notice Returns the bitmap of the quroums the operator is registered for. function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} + function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory){} + function getOperator(address operator) external view returns (Operator memory){} /// @notice Returns the stored id for the specified `operator`. From f38b7c8b49956ccf142cc24408f02452ce80e9d3 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 19:57:10 -0700 Subject: [PATCH 088/110] make index registry mapping internal --- src/contracts/interfaces/IIndexRegistry.sol | 8 ++- src/contracts/middleware/IndexRegistry.sol | 60 ++++++++++++--------- src/test/unit/IndexRegistryUnit.t.sol | 19 ++++--- 3 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index f741ad757..e1a0f070d 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -17,7 +17,7 @@ interface IIndexRegistry is IRegistry { // DATA STRUCTURES // struct used to give definitive ordering to operators at each blockNumber - struct OperatorIndex { + struct OperatorIndexUpdate { // blockNumber number at which operator index changed // note that the operator's index is different *for this block number*, i.e. the *new* index is *inclusive* of this value uint32 toBlockNumber; @@ -56,6 +56,12 @@ interface IIndexRegistry is IRegistry { */ function deregisterOperator(bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external; + /// @notice Returns the _operatorIdToIndexHistory entry for the specified `operatorId` and `quorumNumber` at the specified `index` + function getOperatorIndexUpdateOfOperatorIdForQuorumAtIndex(bytes32 operatorId, uint8 quorumNumber, uint32 index) external view returns (OperatorIndexUpdate memory); + + /// @notice Returns the _totalOperatorsHistory entry for the specified `quorumNumber` at the specified `index` + function getTotalOperatorsUpdateForQuorumAtIndex(uint8 quorumNumber, uint32 index) external view returns (OperatorIndexUpdate memory); + /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. * @param operatorId is the id of the operator for which the index is desired diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 9d2cc9d1f..029b93325 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -14,9 +14,9 @@ contract IndexRegistry is IIndexRegistry { bytes32[] public globalOperatorList; // mapping of operatorId => quorumNumber => index history of that operator - mapping(bytes32 => mapping(uint8 => OperatorIndex[])) public operatorIdToIndexHistory; + mapping(bytes32 => mapping(uint8 => OperatorIndexUpdate[])) internal _operatorIdToIndexHistory; // mapping of quorumNumber => history of numbers of unique registered operators - mapping(uint8 => OperatorIndex[]) public totalOperatorsHistory; + mapping(uint8 => OperatorIndexUpdate[]) internal _totalOperatorsHistory; modifier onlyRegistryCoordinator() { require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); @@ -48,8 +48,8 @@ contract IndexRegistry is IIndexRegistry { uint8 quorumNumber = uint8(quorumNumbers[i]); //this is the would-be index of the operator being registered, the total number of operators for that quorum (which is last index + 1) - uint256 quorumHistoryLength = totalOperatorsHistory[quorumNumber].length; - uint32 numOperators = quorumHistoryLength > 0 ? totalOperatorsHistory[quorumNumber][quorumHistoryLength - 1].index : 0; + uint256 quorumHistoryLength = _totalOperatorsHistory[quorumNumber].length; + uint32 numOperators = quorumHistoryLength > 0 ? _totalOperatorsHistory[quorumNumber][quorumHistoryLength - 1].index : 0; _updateOperatorIdToIndexHistory(operatorId, quorumNumber, numOperators); _updateTotalOperatorHistory(quorumNumber, numOperators + 1); } @@ -76,9 +76,9 @@ contract IndexRegistry is IIndexRegistry { for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - uint32 indexToRemove = operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; + uint32 indexToRemove = _operatorIdToIndexHistory[operatorId][quorumNumber][_operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; _processOperatorRemoval(operatorId, quorumNumber, indexToRemove, operatorIdsToSwap[i]); - _updateTotalOperatorHistory(quorumNumber, totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index - 1); + _updateTotalOperatorHistory(quorumNumber, _totalOperatorsHistory[quorumNumber][_totalOperatorsHistory[quorumNumber].length - 1].index - 1); } // remove operator from globalOperatorList if this is a complete deregistration @@ -87,25 +87,35 @@ contract IndexRegistry is IIndexRegistry { } } + /// @notice Returns the _operatorIdToIndexHistory entry for the specified `operatorId` and `quorumNumber` at the specified `index` + function getOperatorIndexUpdateOfOperatorIdForQuorumAtIndex(bytes32 operatorId, uint8 quorumNumber, uint32 index) external view returns (OperatorIndexUpdate memory){ + return _operatorIdToIndexHistory[operatorId][quorumNumber][index]; + } + + /// @notice Returns the _totalOperatorsHistory entry for the specified `quorumNumber` at the specified `index` + function getTotalOperatorsUpdateForQuorumAtIndex(uint8 quorumNumber, uint32 index) external view returns (OperatorIndexUpdate memory){ + return _totalOperatorsHistory[quorumNumber][index]; + } + /** * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. * @param operatorId is the id of the operator for which the index is desired * @param quorumNumber is the quorum number for which the operator index is desired * @param blockNumber is the block number at which the index of the operator is desired - * @param index Used to specify the entry within the dynamic array `operatorIdToIndexHistory[operatorId]` to + * @param index Used to specify the entry within the dynamic array `_operatorIdToIndexHistory[operatorId]` to * read data from * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the - * array `operatorIdToIndexHistory[operatorId][quorumNumber]` to pull the info from. + * array `_operatorIdToIndexHistory[operatorId][quorumNumber]` to pull the info from. */ function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ - OperatorIndex memory operatorIndexToCheck = operatorIdToIndexHistory[operatorId][quorumNumber][index]; + OperatorIndexUpdate memory operatorIndexToCheck = _operatorIdToIndexHistory[operatorId][quorumNumber][index]; //blocknumber must be before the "index'th" entry's toBlockNumber require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); //if there is an index update before the "index'th" update, the blocknumber must be after than the previous entry's toBlockNumber if(index != 0){ - OperatorIndex memory previousOperatorIndex = operatorIdToIndexHistory[operatorId][quorumNumber][index - 1]; + OperatorIndexUpdate memory previousOperatorIndex = _operatorIdToIndexHistory[operatorId][quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); } return operatorIndexToCheck.index; @@ -115,24 +125,24 @@ contract IndexRegistry is IIndexRegistry { * @notice Looks up the number of total operators for `quorumNumber` at the specified `blockNumber`. * @param quorumNumber is the quorum number for which the total number of operators is desired * @param blockNumber is the block number at which the total number of operators is desired - * @param index is the index of the entry in the dynamic array `totalOperatorsHistory[quorumNumber]` to read data from + * @param index is the index of the entry in the dynamic array `_totalOperatorsHistory[quorumNumber]` to read data from */ function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ - OperatorIndex memory operatorIndexToCheck = totalOperatorsHistory[quorumNumber][index]; + OperatorIndexUpdate memory operatorIndexToCheck = _totalOperatorsHistory[quorumNumber][index]; //blocknumber must be before the "index'th" entry's toBlockNumber require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); //if there is an index update before the "index'th" update, the blocknumber must be after than the previous entry's toBlockNumber if (index != 0){ - OperatorIndex memory previousOperatorIndex = totalOperatorsHistory[quorumNumber][index - 1]; + OperatorIndexUpdate memory previousOperatorIndex = _totalOperatorsHistory[quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); } return operatorIndexToCheck.index; } function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32){ - return totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index; + return _totalOperatorsHistory[quorumNumber][_totalOperatorsHistory[quorumNumber].length - 1].index; } @@ -144,17 +154,17 @@ contract IndexRegistry is IIndexRegistry { function _updateTotalOperatorHistory(uint8 quorumNumber, uint32 numOperators) internal { - uint256 totalOperatorsHistoryLength = totalOperatorsHistory[quorumNumber].length; + uint256 totalOperatorsHistoryLength = _totalOperatorsHistory[quorumNumber].length; //if there is a prior entry, update its "toBlockNumber" if (totalOperatorsHistoryLength > 0) { - totalOperatorsHistory[quorumNumber][totalOperatorsHistoryLength - 1].toBlockNumber = uint32(block.number); + _totalOperatorsHistory[quorumNumber][totalOperatorsHistoryLength - 1].toBlockNumber = uint32(block.number); } - OperatorIndex memory totalOperatorUpdate; + OperatorIndexUpdate memory totalOperatorUpdate; // In the case of totalOperatorsHistory, the index parameter is the number of operators in the quorum totalOperatorUpdate.index = numOperators; - totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); + _totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } /// @@ -162,14 +172,14 @@ contract IndexRegistry is IIndexRegistry { /// @param quorumNumber quorumNumber of the operator to update /// @param index the latest index of that operator in the list of operators registered for this quorum function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { - uint256 operatorIdToIndexHistoryLength = operatorIdToIndexHistory[operatorId][quorumNumber].length; + uint256 operatorIdToIndexHistoryLength = _operatorIdToIndexHistory[operatorId][quorumNumber].length; //if there is a prior entry for the operator, set the previous entry's toBlocknumber if (operatorIdToIndexHistoryLength > 0) { - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = uint32(block.number); + _operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistoryLength - 1].toBlockNumber = uint32(block.number); } - OperatorIndex memory latestOperatorIndex; + OperatorIndexUpdate memory latestOperatorIndex; latestOperatorIndex.index = index; - operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); + _operatorIdToIndexHistory[operatorId][quorumNumber].push(latestOperatorIndex); emit QuorumIndexUpdate(operatorId, quorumNumber, index); } @@ -179,8 +189,8 @@ contract IndexRegistry is IIndexRegistry { /// @param quorumNumber quorum number of the operator to remove /// @param indexToRemove index of the operator to remove function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 operatorIdToSwap) internal { - uint32 operatorIdToSwapIndex = operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; - require(totalOperatorsHistory[quorumNumber][totalOperatorsHistory[quorumNumber].length - 1].index - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); + uint32 operatorIdToSwapIndex = _operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][_operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; + require(_totalOperatorsHistory[quorumNumber][_totalOperatorsHistory[quorumNumber].length - 1].index - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon if(operatorId != operatorIdToSwap){ @@ -188,7 +198,7 @@ contract IndexRegistry is IIndexRegistry { _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); } //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number - operatorIdToIndexHistory[operatorId][quorumNumber][operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); + _operatorIdToIndexHistory[operatorId][quorumNumber][_operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); } /// @notice remove an operator from the globalOperatorList diff --git a/src/test/unit/IndexRegistryUnit.t.sol b/src/test/unit/IndexRegistryUnit.t.sol index 49910ca4a..f1d147896 100644 --- a/src/test/unit/IndexRegistryUnit.t.sol +++ b/src/test/unit/IndexRegistryUnit.t.sol @@ -41,9 +41,9 @@ contract IndexRegistryUnitTests is Test { require(indexRegistry.globalOperatorList(0) == operatorId, "IndexRegistry.registerOperator: operator not registered correctly"); require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: total operators not updated correctly"); require(indexRegistry.totalOperatorsForQuorum(1) == 1, "IndexRegistry.registerOperator: total operators for quorum not updated correctly"); - (uint32 index, uint32 toBlockNumber) = indexRegistry.operatorIdToIndexHistory(operatorId, 1, 0); - require(index == 0, "IndexRegistry.registerOperator: index not 0"); - require(toBlockNumber == 0, "block number should not be set"); + IIndexRegistry.OperatorIndexUpdate memory indexUpdate = indexRegistry.getOperatorIndexUpdateOfOperatorIdForQuorumAtIndex(operatorId, 1, 0); + require(indexUpdate.index == 0, "IndexRegistry.registerOperator: index not 0"); + require(indexUpdate.toBlockNumber == 0, "block number should not be set"); } function testRegisterOperatorFromNonRegisterCoordinator(address nonRegistryCoordinator) public { @@ -96,14 +96,13 @@ contract IndexRegistryUnitTests is Test { require(indexRegistry.totalOperators() == 1, "IndexRegistry.registerOperator: operator not registered correctly"); require(indexRegistry.globalOperatorList(0) == operatorId2, "IndexRegistry.registerOperator: operator not deregistered and swapped correctly"); - (uint32 toBlockNumber1, uint32 index1) = indexRegistry.operatorIdToIndexHistory(operatorId1, defaultQuorumNumber, 0); + IIndexRegistry.OperatorIndexUpdate memory indexUpdate1 = indexRegistry.getOperatorIndexUpdateOfOperatorIdForQuorumAtIndex(operatorId1, defaultQuorumNumber, 0); + require(indexUpdate1.toBlockNumber == block.number, "toBlockNumber not set correctly"); + require(indexUpdate1.index == 0, "incorrect index"); - require(toBlockNumber1 == block.number, "toBlockNumber not set correctly"); - require(index1 == 0, "incorrect index"); - - (uint32 toBlockNumber2, uint32 index2) = indexRegistry.operatorIdToIndexHistory(operatorId2, defaultQuorumNumber, 1); - require(toBlockNumber2 == 0, "toBlockNumber not set correctly"); - require(index2 == 0, "incorrect index"); + IIndexRegistry.OperatorIndexUpdate memory indexUpdate2 = indexRegistry.getOperatorIndexUpdateOfOperatorIdForQuorumAtIndex(operatorId2, defaultQuorumNumber, 1); + require(indexUpdate2.toBlockNumber == 0, "toBlockNumber not set correctly"); + require(indexUpdate2.index == 0, "incorrect index"); } From d3f5a94e2a28589a4f07f2678ade934288624ad7 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 19:59:19 -0700 Subject: [PATCH 089/110] address abuse of OperatorIndexUpdate --- src/contracts/interfaces/IIndexRegistry.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index e1a0f070d..c1748bbf7 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -16,7 +16,8 @@ interface IIndexRegistry is IRegistry { // DATA STRUCTURES - // struct used to give definitive ordering to operators at each blockNumber + // struct used to give definitive ordering to operators at each blockNumber. + // NOTE: this struct is slightly abused for also storing the total number of operators for each quorum over time struct OperatorIndexUpdate { // blockNumber number at which operator index changed // note that the operator's index is different *for this block number*, i.e. the *new* index is *inclusive* of this value From d6e5a76d940683844f264a1748cfa584fb0e60d4 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:03:44 -0700 Subject: [PATCH 090/110] change kick percentages with kick BIPs --- .../interfaces/IRegistryCoordinator.sol | 6 +++--- .../BLSRegistryCoordinatorWithIndices.sol | 20 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index d9e7cb4e6..3e3a85864 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -44,9 +44,9 @@ interface IRegistryCoordinator { struct OperatorSetParam { uint32 maxOperatorCount; - uint8 kickPercentageOfOperatorStake; - uint8 kickPercentageOfAverageStake; - uint8 kickPercentageOfTotalStake; + uint8 kickBIPsOfOperatorStake; + uint8 kickBIPsOfAverageStake; + uint8 kickBIPsOfTotalStake; } /// @notice Returns the operator set params for the given `quorumNumber` diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index accc77a51..1abf2f19c 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -23,6 +23,8 @@ import "../libraries/BytesArrayBitmaps.sol"; contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordinatorWithIndices { using BN254 for BN254.G1Point; + uint16 internal constant BIPS_DENOMINATOR = 10000; + /// @notice the EigenLayer Slasher ISlasher public immutable slasher; /// @notice the Service Manager for the service that this contract is coordinating @@ -184,21 +186,21 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin uint96 operatorToKickStake = stakeRegistry.getCurrentOperatorStakeForQuorum(operatorToKickId, quorumNumber); uint96 registeringOperatorStake = stakeRegistry.getCurrentOperatorStakeForQuorum(registeringOperatorId, quorumNumber); - // check the registering operator has more than the kick percentage of the operator to kick's stake + // check the registering operator has more than the kick BIPs of the operator to kick's stake require( - registeringOperatorStake > operatorToKickStake * operatorSetParam.kickPercentageOfOperatorStake / 100, - "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: registering operator has less than kickPercentageOfOperatorStake" + registeringOperatorStake > operatorToKickStake * operatorSetParam.kickBIPsOfOperatorStake / BIPS_DENOMINATOR, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: registering operator has less than kickBIPsOfOperatorStake" ); - // check that the operator to kick has less than the kick percentage of the average stake + // check that the operator to kick has less than the kick BIPs of the average stake require( - operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickPercentageOfAverageStake / 100 / numOperatorsForQuorum, - "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickPercentageOfAverageStake" + operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickBIPsOfAverageStake / BIPS_DENOMINATOR / numOperatorsForQuorum, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickBIPsOfAverageStake" ); - // check the that the operator to kick has lss than the kick percentage of the total stake + // check the that the operator to kick has lss than the kick BIPs of the total stake require( - operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickPercentageOfTotalStake / 100, - "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickPercentageOfTotalStake" + operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickBIPsOfTotalStake / BIPS_DENOMINATOR, + "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickBIPSOfTotalStake" ); } From 598d8c6835e34e1881b409281bc0279bf1e9eb7e Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:11:46 -0700 Subject: [PATCH 091/110] add struct comments --- .../IBLSRegistryCoordinatorWithIndices.sol | 22 +++++++++++++++++++ .../interfaces/IRegistryCoordinator.sol | 14 ++++-------- src/test/mocks/RegistryCoordinatorMock.sol | 2 -- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol index 25892047a..e7fbc2806 100644 --- a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol @@ -13,6 +13,26 @@ import "./IIndexRegistry.sol"; interface IBLSRegistryCoordinatorWithIndices is IRegistryCoordinator { // STRUCT + /** + * @notice Data structure for storing operator set params for a given quorum. Specifically the + * `maxOperatorCount` is the maximum number of operators that can be registered for the quorum, + * `kickBIPsOfOperatorStake` is the basis points of a new operator needs to have of an operator they are trying to kick from the quorum, + * `kickBIPsOfAverageStake` is the basis points of the average stake of the quorum that an operator needs to be below to be kicked, + * and `kickBIPsOfTotalStake` is the basis points of the total stake of the quorum that an operator needs to be below to be kicked. + */ + struct OperatorSetParam { + uint32 maxOperatorCount; + uint8 kickBIPsOfOperatorStake; + uint8 kickBIPsOfAverageStake; + uint8 kickBIPsOfTotalStake; + } + + /** + * @notice Data structure for the parameters needed to kick an operator from a quorum, used during registration churn. + * Specifically the `operator` is the address of the operator to kick, `pubkey` is the BLS public key of the operator, + * `operatorIdsToSwap` is the list of operatorIds to swap with the operator being kicked in the indexRegistry, + * and `globalOperatorListIndex` is the index of the operator in the global operator list in the indexRegistry. + */ struct OperatorKickParam { address operator; BN254.G1Point pubkey; @@ -20,6 +40,8 @@ interface IBLSRegistryCoordinatorWithIndices is IRegistryCoordinator { uint32 globalOperatorListIndex; } + /// @notice Returns the operator set params for the given `quorumNumber` + function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory); /// @notice the stake registry for this corrdinator is the contract itself function stakeRegistry() external view returns (IStakeRegistry); /// @notice the BLS Pubkey Registry contract that will keep track of operators' BLS public keys diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 3e3a85864..4dc1aeace 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -36,22 +36,16 @@ interface IRegistryCoordinator { OperatorStatus status; } + /** + * @notice Data structure for storing info on quorum bitmap updates where the `quorumBitmap` is the bitmap of the + * quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber` + */ struct QuorumBitmapUpdate { uint32 updateBlockNumber; uint32 nextUpdateBlockNumber; uint192 quorumBitmap; } - struct OperatorSetParam { - uint32 maxOperatorCount; - uint8 kickBIPsOfOperatorStake; - uint8 kickBIPsOfAverageStake; - uint8 kickBIPsOfTotalStake; - } - - /// @notice Returns the operator set params for the given `quorumNumber` - function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory); - /// @notice Returns the operator struct for the given `operator` function getOperator(address operator) external view returns (Operator memory); diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 558f92c05..3304c821a 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -9,8 +9,6 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { /// @notice Returns the bitmap of the quroums the operator is registered for. function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} - function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory){} - function getOperator(address operator) external view returns (Operator memory){} /// @notice Returns the stored id for the specified `operator`. From 481daabb676aabe76b233992878a3ee566cf89e1 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:13:22 -0700 Subject: [PATCH 092/110] update index update event comment --- src/contracts/interfaces/IIndexRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index c1748bbf7..1f24fff73 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -9,7 +9,7 @@ import "./IRegistry.sol"; */ interface IIndexRegistry is IRegistry { // EVENTS - // emitted when an operator's index in at quorum operator list is updated + // emitted when an operator's index in the orderd operator list for the quorum with number `quorumNumber` is updated event QuorumIndexUpdate(bytes32 indexed operatorId, uint8 quorumNumber, uint32 newIndex); // emitted when an operator's index in the global operator list is updated event GlobalIndexUpdate(bytes32 indexed operatorId, uint32 newIndex); From 4f862c59d143ff36a451a93de69861e89567b1ca Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:19:13 -0700 Subject: [PATCH 093/110] abstract setOperatorSetParams and add events --- .../interfaces/IBLSRegistryCoordinatorWithIndices.sol | 4 ++++ .../middleware/BLSRegistryCoordinatorWithIndices.sol | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol index e7fbc2806..4d35c3904 100644 --- a/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/interfaces/IBLSRegistryCoordinatorWithIndices.sol @@ -40,6 +40,10 @@ interface IBLSRegistryCoordinatorWithIndices is IRegistryCoordinator { uint32 globalOperatorListIndex; } + // EVENTS + + event OperatorSetParamsUpdated(uint8 indexed quorumNumber, OperatorSetParam operatorSetParams); + /// @notice Returns the operator set params for the given `quorumNumber` function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory); /// @notice the stake registry for this corrdinator is the contract itself diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 1abf2f19c..124d035bb 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -72,7 +72,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin // set the operator set params require(IVoteWeigher(address(stakeRegistry)).quorumCount() == _operatorSetParams.length, "BLSIndexRegistryCoordinator: operator set params length mismatch"); for (uint8 i = 0; i < _operatorSetParams.length; i++) { - _quorumOperatorSetParams[i] = _operatorSetParams[i]; + _setOperatorSetParams(i, _operatorSetParams[i]); } } @@ -126,7 +126,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin * @param operatorSetParam is the parameters of the operator set for the `quorumNumber` */ function setOperatorSetParams(uint8 quorumNumber, OperatorSetParam memory operatorSetParam) external onlyServiceManagerOwner { - _quorumOperatorSetParams[quorumNumber] = operatorSetParam; + _setOperatorSetParams(quorumNumber, operatorSetParam); } /** @@ -241,6 +241,13 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin _deregisterOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey, operatorIdsToSwap, globalOperatorListIndex); } + // INTERNAL FUNCTIONS + + function _setOperatorSetParams(uint8 quorumNumber, OperatorSetParam memory operatorSetParam) internal { + _quorumOperatorSetParams[quorumNumber] = operatorSetParam; + emit OperatorSetParamsUpdated(quorumNumber, operatorSetParam); + } + function _registerOperatorWithCoordinator(address operator, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) internal { require( slasher.contractCanSlashOperatorUntilBlock(operator, address(serviceManager)) == type(uint32).max, From ba096da06717b4fed400ed1348b2c3a0c677cc59 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:25:50 -0700 Subject: [PATCH 094/110] improve quorumBitmapUpdate comment and fix check --- .../middleware/BLSRegistryCoordinatorWithIndices.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 124d035bb..9fa8dc207 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -103,8 +103,10 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin quorumBitmapUpdate.updateBlockNumber <= blockNumber, "BLSRegistryCoordinator.getQuorumBitmapByOperatorIdAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" ); + // if the next update is at or before the block number, then the quorum provided index is too early + // if the nex update block number is 0, then this is the latest update require( - quorumBitmapUpdate.nextUpdateBlockNumber > blockNumber, + quorumBitmapUpdate.nextUpdateBlockNumber > blockNumber || quorumBitmapUpdate.nextUpdateBlockNumber == 0, "BLSRegistryCoordinator.getQuorumBitmapByOperatorIdAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" ); return quorumBitmapUpdate.quorumBitmap; From b20e598e84e6619f04036a6adeb616be4aad2d87 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:27:45 -0700 Subject: [PATCH 095/110] improve quorumBitmapUpdate struct comment --- src/contracts/interfaces/IRegistryCoordinator.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 4dc1aeace..4d7597d53 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -39,6 +39,7 @@ interface IRegistryCoordinator { /** * @notice Data structure for storing info on quorum bitmap updates where the `quorumBitmap` is the bitmap of the * quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber` + * @dev nextUpdateBlockNumber is initialized to 0 for the latest update */ struct QuorumBitmapUpdate { uint32 updateBlockNumber; From 88d0c50a4d29ace622cdf2043699c68caffd5f74 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:31:39 -0700 Subject: [PATCH 096/110] add revert for lack of quorumBitmap history --- src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 9fa8dc207..3c6326ed3 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -114,6 +114,9 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin /// @notice Returns the current quorum bitmap for the given `operatorId` function getCurrentQuorumBitmapByOperatorId(bytes32 operatorId) external view returns (uint192) { + if(_operatorIdToQuorumBitmapHistory[operatorId].length == 0) { + revert("BLSRegistryCoordinator.getCurrentQuorumBitmapByOperatorId: no quorum bitmap history for operatorId"); + } return _operatorIdToQuorumBitmapHistory[operatorId][_operatorIdToQuorumBitmapHistory[operatorId].length - 1].quorumBitmap; } From 387481821963372d63d6afd26cf4dff2a9613817 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:34:31 -0700 Subject: [PATCH 097/110] add to registerOperatorWithCoordinator (kick version) comment --- .../middleware/BLSRegistryCoordinatorWithIndices.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 3c6326ed3..85a1f157a 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -157,9 +157,11 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin } /** - * @notice Registers msg.sender as an operator with the middleware + * @notice Registers msg.sender as an operator with the middleware when the quorum operator limit is full. To register + * while maintaining the limit, the operator chooses another registered opeerator with lower stake to kick. * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for * @param pubkey is the BLS public key of the operator + * @param operatorKickParams are the parameters for the deregistration of the operator that is being kicked */ function registerOperatorWithCoordinator( bytes calldata quorumNumbers, From 61db48095b57b7ea6b0fe66cb87061c72ce31d9c Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:36:40 -0700 Subject: [PATCH 098/110] kick logic updates --- .../middleware/BLSRegistryCoordinatorWithIndices.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 85a1f157a..85d51d598 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -201,10 +201,10 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin // check that the operator to kick has less than the kick BIPs of the average stake require( - operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickBIPsOfAverageStake / BIPS_DENOMINATOR / numOperatorsForQuorum, + operatorToKickStake < (totalStakeForQuorum * operatorSetParam.kickBIPsOfAverageStake / BIPS_DENOMINATOR) / numOperatorsForQuorum, "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickBIPsOfAverageStake" ); - // check the that the operator to kick has lss than the kick BIPs of the total stake + // check the that the operator to kick has less than the kick BIPs of the total stake require( operatorToKickStake < totalStakeForQuorum * operatorSetParam.kickBIPsOfTotalStake / BIPS_DENOMINATOR, "BLSIndexRegistryCoordinator.registerOperatorWithCoordinator: operator to kick has more than kickBIPSOfTotalStake" From 2631769ce9169012dc2edc79ac9c7834381de6d6 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 20:45:36 -0700 Subject: [PATCH 099/110] address various stakereg comments --- src/contracts/middleware/StakeRegistry.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index f4f6153a6..85459588d 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -339,6 +339,10 @@ contract StakeRegistry is StakeRegistryStorage { // INTERNAL FUNCTIONS + /** + * @notice Updates the stake for the operator with `operatorId` for the specified `quorumNumbers`. The total stake + * for each quorum is updated accordingly in addition to the operator's individual stake history. + */ function _registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { uint8 quorumNumbersLength = uint8(quorumNumbers.length); // check the operator is registering for only valid quorums @@ -358,12 +362,14 @@ contract StakeRegistry is StakeRegistryStorage { require(stake != 0, "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); // add operator stakes to total stake before update (in memory) uint256 _totalStakeHistoryLength = _totalStakeHistory[quorumNumber].length; + // add calculate the total stake for the quorum + uint96 totalStakeAfterUpdate = stake; if (_totalStakeHistoryLength != 0) { // only add the stake if there is a previous total stake // overwrite `stake` variable - stake += _totalStakeHistory[quorumNumber][_totalStakeHistoryLength - 1].stake; + totalStakeAfterUpdate += _totalStakeHistory[quorumNumber][_totalStakeHistoryLength - 1].stake; } - _newTotalStakeUpdate.stake = stake; + _newTotalStakeUpdate.stake = totalStakeAfterUpdate; // update storage of total stake _recordTotalStakeUpdate(quorumNumber, _newTotalStakeUpdate); unchecked { @@ -373,7 +379,9 @@ contract StakeRegistry is StakeRegistryStorage { } /** - * @notice Removes the stakes of the operator + * @notice Removes the stakes of the operator with `operatorId` from the quorums specified in `quorumNumbers` + * the total stake of the quorums specified in `quorumNumbers` will be updated and so will the operator's individual + * stake updates. These operator's individual stake updates will have a 0 stake value for the latest update. */ function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) internal { uint8 quorumNumbersLength = uint8(quorumNumbers.length); @@ -425,7 +433,7 @@ contract StakeRegistry is StakeRegistryStorage { if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { operatorStakeUpdate.stake = uint96(0); } - // initialize stakeBeforeUpdate to 0 + // get stakeBeforeUpdate and update with new stake uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, operatorStakeUpdate); emit StakeUpdate( From b03c92cfbb9bfd3366ad0bb1c11c20d3cedf29c1 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 21:01:52 -0700 Subject: [PATCH 100/110] address various indexreg comments --- .../BLSRegistryCoordinatorWithIndices.sol | 3 +- src/contracts/middleware/IndexRegistry.sol | 62 +++++++++++-------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 85d51d598..44a2ed784 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -241,7 +241,8 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin * @notice Deregisters the msg.sender as an operator from the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for * @param pubkey is the BLS public key of the operator - * @param operatorIdsToSwap is the list of the operator ids that the should swap for the deregistering operator's index + * @param operatorIdsToSwap is the list of the operator ids tho swap the index of the operator with in each + * quorum when removing the operator from the quorum's ordered list * @param globalOperatorListIndex is the operator's index in the global operator list in the IndexRegistry */ function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external { diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 029b93325..e45a4287e 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -61,7 +61,7 @@ contract IndexRegistry is IIndexRegistry { * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers is the quorum numbers the operator is deregistered for * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` - * they will be swapped the operators current index + * they will be swapped with the operator's current index when the operator is removed from the list * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): @@ -76,8 +76,8 @@ contract IndexRegistry is IIndexRegistry { for (uint i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - uint32 indexToRemove = _operatorIdToIndexHistory[operatorId][quorumNumber][_operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; - _processOperatorRemoval(operatorId, quorumNumber, indexToRemove, operatorIdsToSwap[i]); + uint32 indexOfOperatorToRemove = _operatorIdToIndexHistory[operatorId][quorumNumber][_operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].index; + _processOperatorRemoval(operatorId, quorumNumber, indexOfOperatorToRemove, operatorIdsToSwap[i]); _updateTotalOperatorHistory(quorumNumber, _totalOperatorsHistory[quorumNumber][_totalOperatorsHistory[quorumNumber].length - 1].index - 1); } @@ -98,7 +98,7 @@ contract IndexRegistry is IIndexRegistry { } /** - * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. + * @notice Looks up the `operator`'s index in the set of operators for `quorumNumber` at the specified `blockNumber` using the `index`. * @param operatorId is the id of the operator for which the index is desired * @param quorumNumber is the quorum number for which the operator index is desired * @param blockNumber is the block number at which the index of the operator is desired @@ -110,10 +110,10 @@ contract IndexRegistry is IIndexRegistry { function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndexUpdate memory operatorIndexToCheck = _operatorIdToIndexHistory[operatorId][quorumNumber][index]; - //blocknumber must be before the "index'th" entry's toBlockNumber + // blocknumber must be before the "index'th" entry's toBlockNumber require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); - //if there is an index update before the "index'th" update, the blocknumber must be after than the previous entry's toBlockNumber + // if there is an index update before the "index'th" update, the blocknumber must be after the previous entry's toBlockNumber if(index != 0){ OperatorIndexUpdate memory previousOperatorIndex = _operatorIdToIndexHistory[operatorId][quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getOperatorIndexForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); @@ -130,10 +130,10 @@ contract IndexRegistry is IIndexRegistry { function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32){ OperatorIndexUpdate memory operatorIndexToCheck = _totalOperatorsHistory[quorumNumber][index]; - //blocknumber must be before the "index'th" entry's toBlockNumber + // blocknumber must be before the "index'th" entry's toBlockNumber require(blockNumber <= operatorIndexToCheck.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the future for provided block number"); - //if there is an index update before the "index'th" update, the blocknumber must be after than the previous entry's toBlockNumber + // if there is an index update before the "index'th" update, the blocknumber must be after the previous entry's toBlockNumber if (index != 0){ OperatorIndexUpdate memory previousOperatorIndex = _totalOperatorsHistory[quorumNumber][index - 1]; require(blockNumber > previousOperatorIndex.toBlockNumber, "IndexRegistry.getTotalOperatorsForQuorumAtBlockNumberByIndex: provided index is too far in the past for provided block number"); @@ -151,9 +151,12 @@ contract IndexRegistry is IIndexRegistry { return uint32(globalOperatorList.length); } - + /** + * @notice updates the total numbers of operator in `quorumNumber` to `numOperators` + * @param quorumNumber is the number of the quorum to update + * @param numOperators is the number of operators in the quorum + */ function _updateTotalOperatorHistory(uint8 quorumNumber, uint32 numOperators) internal { - uint256 totalOperatorsHistoryLength = _totalOperatorsHistory[quorumNumber].length; //if there is a prior entry, update its "toBlockNumber" @@ -167,10 +170,11 @@ contract IndexRegistry is IIndexRegistry { _totalOperatorsHistory[quorumNumber].push(totalOperatorUpdate); } - /// - /// @param operatorId operatorId of the operator to update - /// @param quorumNumber quorumNumber of the operator to update - /// @param index the latest index of that operator in the list of operators registered for this quorum + /** + * @param operatorId operatorId of the operator to update + * @param quorumNumber quorumNumber of the operator to update + * @param index the latest index of that operator in the list of operators registered for this quorum + */ function _updateOperatorIdToIndexHistory(bytes32 operatorId, uint8 quorumNumber, uint32 index) internal { uint256 operatorIdToIndexHistoryLength = _operatorIdToIndexHistory[operatorId][quorumNumber].length; //if there is a prior entry for the operator, set the previous entry's toBlocknumber @@ -184,32 +188,36 @@ contract IndexRegistry is IIndexRegistry { emit QuorumIndexUpdate(operatorId, quorumNumber, index); } - /// @notice when we remove an operator from a quorum, we simply update the operator's index history - /// as well as any operatorIds we have to swap - /// @param quorumNumber quorum number of the operator to remove - /// @param indexToRemove index of the operator to remove - function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexToRemove, bytes32 operatorIdToSwap) internal { + /** + * @notice when we remove an operator from a quorum, we simply update the operator's index history + * as well as any operatorIds we have to swap + * @param quorumNumber quorum number of the operator to remove + * @param indexOfOperatorToRemove index of the operator to remove + */ + function _processOperatorRemoval(bytes32 operatorId, uint8 quorumNumber, uint32 indexOfOperatorToRemove, bytes32 operatorIdToSwap) internal { uint32 operatorIdToSwapIndex = _operatorIdToIndexHistory[operatorIdToSwap][quorumNumber][_operatorIdToIndexHistory[operatorIdToSwap][quorumNumber].length - 1].index; require(_totalOperatorsHistory[quorumNumber][_totalOperatorsHistory[quorumNumber].length - 1].index - 1 == operatorIdToSwapIndex, "IndexRegistry._processOperatorRemoval: operatorIdToSwap is not the last operator in the quorum"); // if the operator is not the last in the list, we must swap the last operator into their positon if(operatorId != operatorIdToSwap){ //update the swapped operator's operatorIdToIndexHistory list with a new entry, as their index has now changed - _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexToRemove); + _updateOperatorIdToIndexHistory(operatorIdToSwap, quorumNumber, indexOfOperatorToRemove); } - //marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number + // marking the final entry in the deregistering operator's operatorIdToIndexHistory entry with the deregistration block number _operatorIdToIndexHistory[operatorId][quorumNumber][_operatorIdToIndexHistory[operatorId][quorumNumber].length - 1].toBlockNumber = uint32(block.number); } - /// @notice remove an operator from the globalOperatorList - /// @param indexToRemove index of the operator to remove - function _removeOperatorFromGlobalOperatorList(uint32 indexToRemove) internal { + /** + * @notice remove an operator from the globalOperatorList + * @param indexOfOperatorToRemove index of the operator to remove + */ + function _removeOperatorFromGlobalOperatorList(uint32 indexOfOperatorToRemove) internal { uint32 globalOperatorListLastIndex = uint32(globalOperatorList.length - 1); bytes32 operatorIdToSwap; - if(indexToRemove != globalOperatorListLastIndex){ + if(indexOfOperatorToRemove != globalOperatorListLastIndex){ operatorIdToSwap = globalOperatorList[globalOperatorListLastIndex]; - globalOperatorList[indexToRemove] = operatorIdToSwap; - emit GlobalIndexUpdate(operatorIdToSwap, indexToRemove); + globalOperatorList[indexOfOperatorToRemove] = operatorIdToSwap; + emit GlobalIndexUpdate(operatorIdToSwap, indexOfOperatorToRemove); } globalOperatorList.pop(); } From b3dbc9b717f5759eafc5cf20af08c33d41fa445d Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 21:06:00 -0700 Subject: [PATCH 101/110] quorum mispellings" --- src/contracts/interfaces/IBLSPubkeyRegistry.sol | 2 +- src/contracts/interfaces/IIndexRegistry.sol | 4 ++-- src/contracts/interfaces/IStakeRegistry.sol | 4 ++-- src/contracts/middleware/BLSPubkeyRegistry.sol | 2 +- src/contracts/middleware/IndexRegistry.sol | 2 +- src/contracts/middleware/StakeRegistry.sol | 6 +++--- src/test/mocks/RegistryCoordinatorMock.sol | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/contracts/interfaces/IBLSPubkeyRegistry.sol b/src/contracts/interfaces/IBLSPubkeyRegistry.sol index 2c17536f8..2f8767b20 100644 --- a/src/contracts/interfaces/IBLSPubkeyRegistry.sol +++ b/src/contracts/interfaces/IBLSPubkeyRegistry.sol @@ -57,7 +57,7 @@ interface IBLSPubkeyRegistry is IRegistry { * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. - * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. + * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @param pubkey The public key of the operator. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 1f24fff73..5859f694c 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.12; import "./IRegistry.sol"; /** - * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quroums. + * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums. * @author Layr Labs, Inc. */ interface IIndexRegistry is IRegistry { @@ -44,7 +44,7 @@ interface IIndexRegistry is IRegistry { * @param operatorId is the id of the operator that is being deregistered * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` + * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quorumNumbers` * they will be swapped the operators current index * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index b37a38984..956152662 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.12; import "./IRegistry.sol"; /** - * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quroums. + * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. * @author Layr Labs, Inc. */ interface IStakeRegistry is IRegistry { @@ -38,7 +38,7 @@ interface IStakeRegistry is IRegistry { /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. * @param operatorId The id of the operator to deregister. - * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. + * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates diff --git a/src/contracts/middleware/BLSPubkeyRegistry.sol b/src/contracts/middleware/BLSPubkeyRegistry.sol index 858358540..2e9d04761 100644 --- a/src/contracts/middleware/BLSPubkeyRegistry.sol +++ b/src/contracts/middleware/BLSPubkeyRegistry.sol @@ -75,7 +75,7 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { * @notice Deregisters the `operator`'s pubkey for the specified `quorumNumbers`. * @param operator The address of the operator to deregister. * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. - * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. + * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @param pubkey The public key of the operator. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index e45a4287e..2137cd0f7 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -60,7 +60,7 @@ contract IndexRegistry is IIndexRegistry { * @param operatorId is the id of the operator that is being deregistered * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quroumNumbers` + * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quorumNumbers` * they will be swapped with the operator's current index when the operator is removed from the list * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 85459588d..767093e57 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -8,7 +8,7 @@ import "../interfaces/IRegistryCoordinator.sol"; import "./StakeRegistryStorage.sol"; /** - * @title A `Registry` that keeps track of stakes of operators for up to 256 quroums. + * @title A `Registry` that keeps track of stakes of operators for up to 256 quorums. * Specifically, it keeps track of * 1) The stake of each operator in all the quorums they are apart of for block ranges * 2) The total stake of all operators in each quorum for block ranges @@ -271,7 +271,7 @@ contract StakeRegistry is StakeRegistryStorage { /** * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. * @param operatorId The id of the operator to deregister. - * @param quorumNumbers The quourm numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. + * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): * 1) `quorumNumbers` has no duplicates @@ -352,7 +352,7 @@ contract StakeRegistry is StakeRegistryStorage { _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); // for each quorum, evaluate stake and add to total stake for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbersLength;) { - // get the next quourumNumber + // get the next quorumNumber uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); // evaluate the stake for the operator // since we don't use the first output, this will use 1 extra sload when deregistered operator's register again diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol index 3304c821a..171e0bf7a 100644 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ b/src/test/mocks/RegistryCoordinatorMock.sol @@ -6,7 +6,7 @@ import "../../contracts/interfaces/IRegistryCoordinator.sol"; contract RegistryCoordinatorMock is IRegistryCoordinator { - /// @notice Returns the bitmap of the quroums the operator is registered for. + /// @notice Returns the bitmap of the quorums the operator is registered for. function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} function getOperator(address operator) external view returns (Operator memory){} From 0aa1b58a86c05f0e2888020267abee81618d46d7 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 21:12:32 -0700 Subject: [PATCH 102/110] various stakereg updates --- src/contracts/middleware/StakeRegistry.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 767093e57..b8e646079 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -344,14 +344,13 @@ contract StakeRegistry is StakeRegistryStorage { * for each quorum is updated accordingly in addition to the operator's individual stake history. */ function _registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) internal { - uint8 quorumNumbersLength = uint8(quorumNumbers.length); // check the operator is registering for only valid quorums - require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerStake: greatest quorumNumber must be less than quorumCount"); + require(uint8(quorumNumbers[quorumNumbers.length - 1]) < quorumCount, "StakeRegistry._registerOperator: greatest quorumNumber must be less than quorumCount"); OperatorStakeUpdate memory _newTotalStakeUpdate; // add the `updateBlockNumber` info _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); // for each quorum, evaluate stake and add to total stake - for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbersLength;) { + for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbers.length;) { // get the next quorumNumber uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); // evaluate the stake for the operator @@ -359,7 +358,7 @@ contract StakeRegistry is StakeRegistryStorage { (, uint96 stake) = _updateOperatorStake(operator, operatorId, quorumNumber); // @JEFF: This reverts pretty late, but i think that's fine. wdyt? // check if minimum requirement has been met, will be 0 if not - require(stake != 0, "StakeRegistry._registerStake: Operator does not meet minimum stake requirement for quorum"); + require(stake != 0, "StakeRegistry._registerOperator: Operator does not meet minimum stake requirement for quorum"); // add operator stakes to total stake before update (in memory) uint256 _totalStakeHistoryLength = _totalStakeHistory[quorumNumber].length; // add calculate the total stake for the quorum @@ -386,7 +385,7 @@ contract StakeRegistry is StakeRegistryStorage { function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) internal { uint8 quorumNumbersLength = uint8(quorumNumbers.length); // check the operator is deregistering from only valid quorums - require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerStake: greatest quorumNumber must be less than quorumCount"); + require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerOperator: greatest quorumNumber must be less than quorumCount"); OperatorStakeUpdate memory _operatorStakeUpdate; // add the `updateBlockNumber` info _operatorStakeUpdate.updateBlockNumber = uint32(block.number); From 1a690fa781e87e1c7a88da5f6b80e45f566752af Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 21:26:55 -0700 Subject: [PATCH 103/110] add internal setter and event for minimum stake and other updates --- src/contracts/interfaces/IIndexRegistry.sol | 2 +- .../interfaces/IRegistryCoordinator.sol | 7 ++++-- src/contracts/interfaces/IStakeRegistry.sol | 3 +++ .../BLSRegistryCoordinatorWithIndices.sol | 9 +++++--- src/contracts/middleware/StakeRegistry.sol | 22 +++++++++++-------- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 5859f694c..78f390e26 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -45,7 +45,7 @@ interface IIndexRegistry is IRegistry { * @param completeDeregistration Whether the operator is deregistering from all quorums or just some. * @param quorumNumbers is the quorum numbers the operator is deregistered for * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quorumNumbers` - * they will be swapped the operators current index + * they will be swapped with the operator's current index when the operator is removed from the list * @param globalOperatorListIndex is the index of the operator that is to be removed from the list * @dev access restricted to the RegistryCoordinator * @dev Preconditions (these are assumed, not validated in this contract): diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 4d7597d53..61104d122 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -53,7 +53,10 @@ interface IRegistryCoordinator { /// @notice Returns the operatorId for the given `operator` function getOperatorId(address operator) external view returns (bytes32); - /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + /** + * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + * @dev reverts if `index` is incorrect + */ function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); /// @notice Returns the current quorum bitmap for the given `operatorId` @@ -78,7 +81,7 @@ interface IRegistryCoordinator { /** * @notice Deregisters the msg.sender as an operator from the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for - * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information + * @param deregistrationData is the the data that is decoded to get the operator's deregistration information */ function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external; } \ No newline at end of file diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index 956152662..c89d9dd04 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -21,6 +21,9 @@ interface IStakeRegistry is IRegistry { uint96 stake; } + // EVENTS + event MinimumStakeForQuorumUpdated(uint8 indexed quorumNumber, uint96 minimumStake); + /** * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`. * @param operator The address of the operator to register. diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 44a2ed784..268ae403c 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -96,7 +96,10 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin return _operators[operator].operatorId; } - /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + /** + * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` + * @dev reverts if `index` is incorrect + */ function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192) { QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorIdToQuorumBitmapHistory[operatorId][index]; require( @@ -225,12 +228,12 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin /** * @notice Deregisters the msg.sender as an operator from the middleware * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for - * @param deregistrationData is the the data that is decoded to get the operator's deregisteration information + * @param deregistrationData is the the data that is decoded to get the operator's deregistration information * @dev `deregistrationData` should be a tuple of the operator's BLS public key, the list of operator ids to swap, * and the operator's index in the global operator list */ function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external { - // get the operator's deregisteration information + // get the operator's deregistration information (BN254.G1Point memory pubkey, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) = abi.decode(deregistrationData, (BN254.G1Point, bytes32[], uint32)); // call internal function to deregister the operator diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index b8e646079..912691de2 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -10,7 +10,7 @@ import "./StakeRegistryStorage.sol"; /** * @title A `Registry` that keeps track of stakes of operators for up to 256 quorums. * Specifically, it keeps track of - * 1) The stake of each operator in all the quorums they are apart of for block ranges + * 1) The stake of each operator in all the quorums they are a part of for block ranges * 2) The total stake of all operators in each quorum for block ranges * 3) The minimum stake required to register for each quorum * It allows an additional functionality (in addition to registering and deregistering) to update the stake of an operator. @@ -35,9 +35,8 @@ contract StakeRegistry is StakeRegistryStorage { } /** - * @notice Adds empty first entries to the dynamic arrays `_totalStakeHistory`, - * to record an initial condition of zero operators with zero total stake. - * Adds `_quorumStrategiesConsideredAndMultipliers` for each quorum the Registry is being initialized with + * @notice Sets the minimum stake for each quorum and adds `_quorumStrategiesConsideredAndMultipliers` for each + * quorum the Registry is being initialized with */ function initialize( uint96[] memory _minimumStakeForQuorum, @@ -55,7 +54,7 @@ contract StakeRegistry is StakeRegistryStorage { // add the strategies considered and multipliers for each quorum for (uint8 quorumNumber = 0; quorumNumber < _quorumStrategiesConsideredAndMultipliers.length;) { - minimumStakeForQuorum[quorumNumber] = _minimumStakeForQuorum[quorumNumber]; + _setMinimumStakeForQuorum(quorumNumber, _minimumStakeForQuorum[quorumNumber]); _createQuorum(_quorumStrategiesConsideredAndMultipliers[quorumNumber]); unchecked { ++quorumNumber; @@ -249,7 +248,7 @@ contract StakeRegistry is StakeRegistryStorage { /// @notice Adjusts the `minimumStakeFirstQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 1st quorum. function setMinimumStakeForQuorum(uint8 quorumNumber, uint96 minimumStake) external onlyServiceManagerOwner { - minimumStakeForQuorum[quorumNumber] = minimumStake; + _setMinimumStakeForQuorum(quorumNumber, minimumStake); } /** @@ -295,14 +294,14 @@ contract StakeRegistry is StakeRegistryStorage { * @dev reverts if there are no operators registered with index out of bounds */ function updateStakes(address[] calldata operators, bytes32[] calldata operatorIds, uint192[] calldata quorumBitmaps, uint256[] calldata prevElements) external { - // for each quorum, loop through operators and see if they are apart of the quorum - // if they are, get their new weight and update their individual stake history and thes + // for each quorum, loop through operators and see if they are a part of the quorum + // if they are, get their new weight and update their individual stake history and the // quorum's total stake history accordingly for (uint8 quorumNumber = 0; quorumNumber < quorumCount;) { OperatorStakeUpdate memory totalStakeUpdate; // for each operator for(uint i = 0; i < operatorIds.length;) { - // if the operator is apart of the quorum + // if the operator is a part of the quorum if (quorumBitmaps[i] >> quorumNumber & 1 == 1) { // if the total stake has not been loaded yet, load it if (totalStakeUpdate.updateBlockNumber == 0) { @@ -339,6 +338,11 @@ contract StakeRegistry is StakeRegistryStorage { // INTERNAL FUNCTIONS + function _setMinimumStakeForQuorum(uint8 quorumNumber, uint96 minimumStake) internal { + minimumStakeForQuorum[quorumNumber] = minimumStake; + emit MinimumStakeForQuorumUpdated(quorumNumber, minimumStake); + } + /** * @notice Updates the stake for the operator with `operatorId` for the specified `quorumNumbers`. The total stake * for each quorum is updated accordingly in addition to the operator's individual stake history. From 2d4a033ef22fdb60df4b1bdb26907986e3f6a44a Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 21:33:21 -0700 Subject: [PATCH 104/110] fix lack of quorumNumbers check --- .../middleware/BLSRegistryCoordinatorWithIndices.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol index 268ae403c..5e0ee3d9d 100644 --- a/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol +++ b/src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol @@ -269,8 +269,9 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin require(_operators[operator].status != OperatorStatus.REGISTERED, "BLSIndexRegistryCoordinator._registerOperatorWithCoordinator: operator already registered"); // get the quorum bitmap from the quorum numbers - uint192 quorumBitmap = uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)); + uint256 quorumBitmap = BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers); require(quorumBitmap != 0, "BLSIndexRegistryCoordinator._registerOperatorWithCoordinator: quorumBitmap cannot be 0"); + require(quorumBitmap <= type(uint192).max, "BLSIndexRegistryCoordinator._registerOperatorWithCoordinator: quorumBitmap cant have more than 192 set bits"); // register the operator with the BLSPubkeyRegistry and get the operatorId (in this case, the pubkeyHash) back bytes32 operatorId = blsPubkeyRegistry.registerOperator(operator, quorumNumbers, pubkey); @@ -285,7 +286,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin _operatorIdToQuorumBitmapHistory[operatorId].push(QuorumBitmapUpdate({ updateBlockNumber: uint32(block.number), nextUpdateBlockNumber: 0, - quorumBitmap: quorumBitmap + quorumBitmap: uint192(quorumBitmap) })); // set the operator struct @@ -307,7 +308,8 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin require(operatorId == pubkey.hashG1Point(), "BLSIndexRegistryCoordinator._deregisterOperatorWithCoordinator: operatorId does not match pubkey hash"); // get the quorumNumbers of the operator - uint192 quorumsToRemoveBitmap = uint192(BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers)); + uint256 quorumsToRemoveBitmap = BytesArrayBitmaps.orderedBytesArrayToBitmap_Yul(quorumNumbers); + require(quorumsToRemoveBitmap <= type(uint192).max, "BLSIndexRegistryCoordinator._deregisterOperatorWithCoordinator: quorumsToRemoveBitmap cant have more than 192 set bits"); uint256 operatorQuorumBitmapHistoryLengthMinusOne = _operatorIdToQuorumBitmapHistory[operatorId].length - 1; uint192 quorumBitmapBeforeUpdate = _operatorIdToQuorumBitmapHistory[operatorId][operatorQuorumBitmapHistoryLengthMinusOne].quorumBitmap; // check that the quorumNumbers of the operator matches the quorumNumbers passed in @@ -335,7 +337,7 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin _operatorIdToQuorumBitmapHistory[operatorId].push(QuorumBitmapUpdate({ updateBlockNumber: uint32(block.number), nextUpdateBlockNumber: 0, - quorumBitmap: quorumBitmapBeforeUpdate & ~quorumsToRemoveBitmap // this removes the quorumsToRemoveBitmap from the quorumBitmapBeforeUpdate + quorumBitmap: quorumBitmapBeforeUpdate & ~uint192(quorumsToRemoveBitmap) // this removes the quorumsToRemoveBitmap from the quorumBitmapBeforeUpdate })); } else { // @notice Registrant must continue to serve until the latest block at which an active task expires. this info is used in challenges From fc499a5a79d335dd65d9f9add3e00d4efdc94ec5 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Mon, 26 Jun 2023 21:38:41 -0700 Subject: [PATCH 105/110] fix quorumBitmap lookup bug --- src/contracts/interfaces/IStakeRegistry.sol | 3 +-- src/contracts/middleware/StakeRegistry.sol | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index c89d9dd04..c4242b8c6 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -161,10 +161,9 @@ interface IStakeRegistry is IRegistry { * @notice Used for updating information on deposits of nodes. * @param operators are the addresses of the operators whose stake information is getting updated * @param operatorIds are the ids of the operators whose stake information is getting updated - * @param quorumBitmaps are the bitmap of the quorums that each operator in `operators` is part of * @param prevElements are the elements before this middleware in the operator's linked list within the slasher * @dev Precondition: * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for */ - function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint192[] memory quorumBitmaps, uint256[] memory prevElements) external; + function updateStakes(address[] memory operators, bytes32[] memory operatorIds, uint256[] memory prevElements) external; } \ No newline at end of file diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 912691de2..236eb9e65 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -287,13 +287,12 @@ contract StakeRegistry is StakeRegistryStorage { * @notice Used for updating information on deposits of nodes. * @param operators are the addresses of the operators whose stake information is getting updated * @param operatorIds are the ids of the operators whose stake information is getting updated - * @param quorumBitmaps are the bitmap of the quorums that each operator in `operators` is part of * @param prevElements are the elements before this middleware in the operator's linked list within the slasher * @dev Precondition: * 1) `quorumBitmaps[i]` should be the bitmap that represents the quorums that `operators[i]` registered for * @dev reverts if there are no operators registered with index out of bounds */ - function updateStakes(address[] calldata operators, bytes32[] calldata operatorIds, uint192[] calldata quorumBitmaps, uint256[] calldata prevElements) external { + function updateStakes(address[] calldata operators, bytes32[] calldata operatorIds, uint256[] calldata prevElements) external { // for each quorum, loop through operators and see if they are a part of the quorum // if they are, get their new weight and update their individual stake history and the // quorum's total stake history accordingly @@ -301,8 +300,9 @@ contract StakeRegistry is StakeRegistryStorage { OperatorStakeUpdate memory totalStakeUpdate; // for each operator for(uint i = 0; i < operatorIds.length;) { + uint192 quorumBitmap = registryCoordinator.getCurrentQuorumBitmapByOperatorId(operatorIds[i]); // if the operator is a part of the quorum - if (quorumBitmaps[i] >> quorumNumber & 1 == 1) { + if (quorumBitmap >> quorumNumber & 1 == 1) { // if the total stake has not been loaded yet, load it if (totalStakeUpdate.updateBlockNumber == 0) { totalStakeUpdate = _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1]; From 8280cc553d1cf02df62cea8278015eed5ffd75d9 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 27 Jun 2023 08:49:55 -0700 Subject: [PATCH 106/110] add permissions to stake registry register/deregister --- src/contracts/middleware/StakeRegistry.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 236eb9e65..877956576 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -25,6 +25,12 @@ contract StakeRegistry is StakeRegistryStorage { uint96 stake ); + /// @notice requires that the caller is the RegistryCoordinator + modifier onlyRegistryCoordinator() { + require(msg.sender == address(registryCoordinator), "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + _; + } + constructor( IRegistryCoordinator _registryCoordinator, IStrategyManager _strategyManager, @@ -263,7 +269,7 @@ contract StakeRegistry is StakeRegistryStorage { * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already registered */ - function registerOperator(address operator, bytes32 operatorId, bytes calldata quorumNumbers) external virtual { + function registerOperator(address operator, bytes32 operatorId, bytes calldata quorumNumbers) external virtual onlyRegistryCoordinator { _registerOperator(operator, operatorId, quorumNumbers); } @@ -279,7 +285,7 @@ contract StakeRegistry is StakeRegistryStorage { * 4) the operator is not already deregistered * 5) `quorumNumbers` is the same as the parameter use when registering */ - function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external virtual { + function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external virtual onlyRegistryCoordinator { _deregisterOperator(operatorId, quorumNumbers); } From 4262ada9f18635b63937b546fa994e7964acb10b Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 27 Jun 2023 10:07:35 -0700 Subject: [PATCH 107/110] update operator stake to 1 when below min --- src/contracts/middleware/StakeRegistry.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 877956576..9d633cb04 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -440,7 +440,8 @@ contract StakeRegistry is StakeRegistryStorage { // check if minimum requirements have been met if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { - operatorStakeUpdate.stake = uint96(0); + // set staker to 1 to note they are still active, but miniscule stake + operatorStakeUpdate.stake = uint96(1); } // get stakeBeforeUpdate and update with new stake uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, operatorStakeUpdate); From 932d5cb5944aec9d10374cdaa698e9454243bf72 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 27 Jun 2023 10:51:42 -0700 Subject: [PATCH 108/110] add comment to remove fromTaskNumber --- src/contracts/interfaces/IRegistryCoordinator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol index 61104d122..4e62cecda 100644 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ b/src/contracts/interfaces/IRegistryCoordinator.sol @@ -31,7 +31,7 @@ interface IRegistryCoordinator { // the id of the operator, which is likely the keccak256 hash of the operator's public key if using BLSRegsitry bytes32 operatorId; // start taskNumber from which the operator has been registered - uint32 fromTaskNumber; + uint32 fromTaskNumber; // TODO: REMOVE // indicates whether the operator is actively registered for serving the middleware or not OperatorStatus status; } From f97ac404f08237474a68639a1037f92243dffc0f Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 27 Jun 2023 10:56:12 -0700 Subject: [PATCH 109/110] update deregistration comments --- src/contracts/interfaces/IBLSPubkeyRegistry.sol | 2 +- src/contracts/interfaces/IIndexRegistry.sol | 2 +- src/contracts/middleware/BLSPubkeyRegistry.sol | 2 +- src/contracts/middleware/IndexRegistry.sol | 2 +- src/contracts/middleware/StakeRegistry.sol | 6 ++---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/contracts/interfaces/IBLSPubkeyRegistry.sol b/src/contracts/interfaces/IBLSPubkeyRegistry.sol index 2f8767b20..34be959ae 100644 --- a/src/contracts/interfaces/IBLSPubkeyRegistry.sol +++ b/src/contracts/interfaces/IBLSPubkeyRegistry.sol @@ -65,7 +65,7 @@ interface IBLSPubkeyRegistry is IRegistry { * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering + * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for * 6) `pubkey` is the same as the parameter used when registering */ function deregisterOperator(address operator, bool completeDeregistration, bytes calldata quorumNumbers, BN254.G1Point memory pubkey) external returns(bytes32); diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol index 78f390e26..1e654d112 100644 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ b/src/contracts/interfaces/IIndexRegistry.sol @@ -53,7 +53,7 @@ interface IIndexRegistry is IRegistry { * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering + * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for */ function deregisterOperator(bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external; diff --git a/src/contracts/middleware/BLSPubkeyRegistry.sol b/src/contracts/middleware/BLSPubkeyRegistry.sol index 2e9d04761..3c720d502 100644 --- a/src/contracts/middleware/BLSPubkeyRegistry.sol +++ b/src/contracts/middleware/BLSPubkeyRegistry.sol @@ -83,7 +83,7 @@ contract BLSPubkeyRegistry is IBLSPubkeyRegistry, Test { * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering + * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for * 6) `pubkey` is the same as the parameter used when registering */ function deregisterOperator(address operator, bool completeDeregistration, bytes memory quorumNumbers, BN254.G1Point memory pubkey) external onlyRegistryCoordinator returns(bytes32){ diff --git a/src/contracts/middleware/IndexRegistry.sol b/src/contracts/middleware/IndexRegistry.sol index 2137cd0f7..da132971e 100644 --- a/src/contracts/middleware/IndexRegistry.sol +++ b/src/contracts/middleware/IndexRegistry.sol @@ -69,7 +69,7 @@ contract IndexRegistry is IIndexRegistry { * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering + * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for */ function deregisterOperator(bytes32 operatorId, bool completeDeregistration, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap, uint32 globalOperatorListIndex) external onlyRegistryCoordinator { require(quorumNumbers.length == operatorIdsToSwap.length, "IndexRegistry.deregisterOperator: quorumNumbers and operatorIdsToSwap must be the same length"); diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 9d633cb04..8c846063b 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -283,7 +283,7 @@ contract StakeRegistry is StakeRegistryStorage { * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering + * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for */ function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers) external virtual onlyRegistryCoordinator { _deregisterOperator(operatorId, quorumNumbers); @@ -393,9 +393,7 @@ contract StakeRegistry is StakeRegistryStorage { * stake updates. These operator's individual stake updates will have a 0 stake value for the latest update. */ function _deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) internal { - uint8 quorumNumbersLength = uint8(quorumNumbers.length); // check the operator is deregistering from only valid quorums - require(uint8(quorumNumbers[quorumNumbersLength - 1]) < quorumCount, "StakeRegistry._registerOperator: greatest quorumNumber must be less than quorumCount"); OperatorStakeUpdate memory _operatorStakeUpdate; // add the `updateBlockNumber` info _operatorStakeUpdate.updateBlockNumber = uint32(block.number); @@ -403,7 +401,7 @@ contract StakeRegistry is StakeRegistryStorage { // add the `updateBlockNumber` info _newTotalStakeUpdate.updateBlockNumber = uint32(block.number); // loop through the operator's quorums and remove the operator's stake for each quorum - for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbersLength;) { + for (uint8 quorumNumbersIndex = 0; quorumNumbersIndex < quorumNumbers.length;) { uint8 quorumNumber = uint8(quorumNumbers[quorumNumbersIndex]); // update the operator's stake uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, _operatorStakeUpdate); From caab3f3f65cf22df01ebad02f283ebe1f44338c9 Mon Sep 17 00:00:00 2001 From: gpsanant Date: Tue, 27 Jun 2023 11:09:23 -0700 Subject: [PATCH 110/110] remove check(In)Active functions and revert 0/1 min stake update change --- src/contracts/interfaces/IStakeRegistry.sol | 46 +----------- src/contracts/middleware/StakeRegistry.sol | 83 +-------------------- 2 files changed, 3 insertions(+), 126 deletions(-) diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol index c4242b8c6..a8d3fe668 100644 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ b/src/contracts/interfaces/IStakeRegistry.sol @@ -48,7 +48,7 @@ interface IStakeRegistry is IRegistry { * 2) `quorumNumbers.length` != 0 * 3) `quorumNumbers` is ordered in ascending order * 4) the operator is not already deregistered - * 5) `quorumNumbers` is the same as the parameter use when registering + * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for */ function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external; @@ -113,50 +113,6 @@ interface IStakeRegistry is IRegistry { */ function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96); - /** - * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. - * @param operatorId is the id of the operator of interest - * @param blockNumber is the block number of interest - * @param quorumNumber is the quorum number which the operator had stake in - * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's activity - * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` - * 2) `operatorIdToStakeHistory[operatorId][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `operatorIdToStakeHistory[operatorId][quorumNumber][index].stake > 0` i.e. the operator had nonzero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorActiveAtBlockNumber( - bytes32 operatorId, - uint256 blockNumber, - uint8 quorumNumber, - uint256 stakeHistoryIndex - ) external view returns (bool); - - /** - * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` for `quorumNumber` as proof. - * @param operatorId is the id of the operator of interest - * @param blockNumber is the block number of interest - * @param quorumNumber is the quorum number which the operator had no stake in - * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's inactivity - * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` - * 2) `operatorIdToStakeHistory[operatorId][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `operatorIdToStakeHistory[operatorId][quorumNumber][index].stake == 0` i.e. the operator had zero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorInactiveAtBlockNumber( - bytes32 operatorId, - uint256 blockNumber, - uint8 quorumNumber, - uint256 stakeHistoryIndex - ) external view returns (bool); - /** * @notice Used for updating information on deposits of nodes. * @param operators are the addresses of the operators whose stake information is getting updated diff --git a/src/contracts/middleware/StakeRegistry.sol b/src/contracts/middleware/StakeRegistry.sol index 8c846063b..13882a4c4 100644 --- a/src/contracts/middleware/StakeRegistry.sol +++ b/src/contracts/middleware/StakeRegistry.sol @@ -171,85 +171,6 @@ contract StakeRegistry is StakeRegistryStorage { return _totalStakeHistory[quorumNumber].length; } - /** - * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. - * @param operatorId is the id of the operator of interest - * @param blockNumber is the block number of interest - * @param quorumNumber is the quorum number which the operator had stake in - * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's activity - * @return 'true' if it is succesfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` - * 2) `operatorIdToStakeHistory[operatorId][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `operatorIdToStakeHistory[operatorId][quorumNumber][index].stake > 0` i.e. the operator had nonzero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorActiveAtBlockNumber( - bytes32 operatorId, - uint256 blockNumber, - uint8 quorumNumber, - uint256 stakeHistoryIndex - ) external view returns (bool) - { - // pull the stake history entry specified by `stakeHistoryIndex` - OperatorStakeUpdate memory operatorStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][stakeHistoryIndex]; - return ( - // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber` - (operatorStakeUpdate.updateBlockNumber <= blockNumber) - && - // if there is a next update, then check that the next update occurred strictly after `blockNumber` - (operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber) - && - /// verify that the stake was non-zero at the time (note: here was use the assumption that the operator was 'inactive' - /// once their stake fell to zero) - operatorStakeUpdate.stake != 0 // this implicitly checks that the operator was a part of the quorum of interest - ); - } - - /** - * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` for `quorumNumber` as proof. - * @param operatorId is the id of the operator of interest - * @param blockNumber is the block number of interest - * @param quorumNumber is the quorum number which the operator had no stake in - * @param stakeHistoryIndex specifies the index in `operatorIdToStakeHistory[operatorId]` at which to check the claim of the operator's inactivity - * @return 'true' if it is succesfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `operatorIdToStakeHistory[operatorId][quorumNumber][index].updateBlockNumber <= blockNumber` - * 2) `operatorIdToStakeHistory[operatorId][quorumNumber][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `operatorIdToStakeHistory[operatorId][quorumNumber][index].stake == 0` i.e. the operator had zero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - * @dev One precondition that must be checked is that the operator is a part of the given `quorumNumber` - */ - function checkOperatorInactiveAtBlockNumber( - bytes32 operatorId, - uint256 blockNumber, - uint8 quorumNumber, - uint256 stakeHistoryIndex - ) external view returns (bool) - { - // special case for `operatorIdToStakeHistory[operatorId]` having lenght zero -- in which case we know the operator was never registered - if (operatorIdToStakeHistory[operatorId][quorumNumber].length == 0) { - return true; - } - // pull the stake history entry specified by `stakeHistoryIndex` - OperatorStakeUpdate memory operatorStakeUpdate = operatorIdToStakeHistory[operatorId][quorumNumber][stakeHistoryIndex]; - return ( - // check that the update specified by `stakeHistoryIndex` occurred at or prior to `blockNumber` - (operatorStakeUpdate.updateBlockNumber <= blockNumber) - && - // if there is a next update, then check that the next update occurred strictly after `blockNumber` - (operatorStakeUpdate.nextUpdateBlockNumber == 0 || operatorStakeUpdate.nextUpdateBlockNumber > blockNumber) - && - /// verify that the stake was zero at the time (note: here was use the assumption that the operator was 'inactive' - /// once their stake fell to zero) - operatorStakeUpdate.stake == 0 - ); - } - // MUTATING FUNCTIONS /// @notice Adjusts the `minimumStakeFirstQuorum` -- i.e. the node stake (weight) requirement for inclusion in the 1st quorum. @@ -438,8 +359,8 @@ contract StakeRegistry is StakeRegistryStorage { // check if minimum requirements have been met if (operatorStakeUpdate.stake < minimumStakeForQuorum[quorumNumber]) { - // set staker to 1 to note they are still active, but miniscule stake - operatorStakeUpdate.stake = uint96(1); + // set staker to 0 + operatorStakeUpdate.stake = uint96(0); } // get stakeBeforeUpdate and update with new stake uint96 stakeBeforeUpdate = _recordOperatorStakeUpdate(operatorId, quorumNumber, operatorStakeUpdate);