diff --git a/src/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index f0547a2df..cf7e078f9 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -17,35 +17,36 @@ contract OperatorStateRetriever { address operator; bytes32 operatorId; uint96 stake; + uint96 slashableStake; } struct CheckSignaturesIndices { uint32[] nonSignerQuorumBitmapIndices; uint32[] quorumApkIndices; - uint32[] totalStakeIndices; + uint32[] totalStakeIndices; uint32[][] nonSignerStakeIndices; // nonSignerStakeIndices[quorumNumberIndex][nonSignerIndex] } /** * @notice This function is intended to to be called by AVS operators every time a new task is created (i.e.) - * the AVS coordinator makes a request to AVS operators. Since all of the crucial information is kept onchain, + * the AVS coordinator makes a request to AVS operators. Since all of the crucial information is kept onchain, * operators don't need to run indexers to fetch the data. * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from - * @param operatorId the id of the operator to fetch the quorums lists + * @param operatorId the id of the operator to fetch the quorums lists * @param blockNumber is the block number to get the operator state for * @return 1) the quorumBitmap of the operator at the given blockNumber - * 2) 2d array of Operator structs. For each quorum the provided operator + * 2) 2d array of Operator structs. For each quorum the provided operator * was a part of at `blockNumber`, an ordered list of operators. */ function getOperatorState( - IRegistryCoordinator registryCoordinator, - bytes32 operatorId, + IRegistryCoordinator registryCoordinator, + bytes32 operatorId, uint32 blockNumber ) external view returns (uint256, Operator[][] memory) { bytes32[] memory operatorIds = new bytes32[](1); operatorIds[0] = operatorId; uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0]; - + uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); @@ -54,7 +55,7 @@ contract OperatorStateRetriever { } /** - * @notice returns the ordered list of operators (id and stake) for each quorum. The AVS coordinator + * @notice returns the ordered list of operators (id and stake) for each quorum. The AVS coordinator * may call this function directly to get the operator state for a given block number * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from * @param quorumNumbers are the ids of the quorums to get the operator state for @@ -62,8 +63,8 @@ contract OperatorStateRetriever { * @return 2d array of Operators. For each quorum, an ordered list of Operators */ function getOperatorState( - IRegistryCoordinator registryCoordinator, - bytes memory quorumNumbers, + IRegistryCoordinator registryCoordinator, + bytes memory quorumNumbers, uint32 blockNumber ) public view returns(Operator[][] memory) { IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); @@ -79,32 +80,33 @@ contract OperatorStateRetriever { operators[i][j] = Operator({ operator: blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]), operatorId: bytes32(operatorIds[j]), - stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber) + stake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber), + slashableStake: stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber) /// TODO: Fix this when view added }); } } - + return operators; } /** * @notice this is called by the AVS operator to get the relevant indices for the checkSignatures function - * if they are not running an indexer + * if they are not running an indexer * @param registryCoordinator is the registry coordinator to fetch the AVS registry information from * @param referenceBlockNumber is the block number to get the indices for * @param quorumNumbers are the ids of the quorums to get the operator state for * @param nonSignerOperatorIds are the ids of the nonsigning operators * @return 1) the indices of the quorumBitmaps for each of the operators in the @param nonSignerOperatorIds array at the given blocknumber * 2) the indices of the total stakes entries for the given quorums at the given blocknumber - * 3) the indices of the stakes of each of the nonsigners in each of the quorums they were a + * 3) the indices of the stakes of each of the nonsigners in each of the quorums they were a * part of (for each nonsigner, an array of length the number of quorums they were a part of * that are also part of the provided quorumNumbers) at the given blocknumber * 4) the indices of the quorum apks for each of the provided quorums at the given blocknumber */ function getCheckSignaturesIndices( IRegistryCoordinator registryCoordinator, - uint32 referenceBlockNumber, - bytes calldata quorumNumbers, + uint32 referenceBlockNumber, + bytes calldata quorumNumbers, bytes32[] calldata nonSignerOperatorIds ) external view returns (CheckSignaturesIndices memory) { IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry(); @@ -115,7 +117,7 @@ contract OperatorStateRetriever { // get the indices of the totalStake updates for each of the quorums in the quorumNumbers array checkSignaturesIndices.totalStakeIndices = stakeRegistry.getTotalStakeIndicesAtBlockNumber(referenceBlockNumber, quorumNumbers); - + checkSignaturesIndices.nonSignerStakeIndices = new uint32[][](quorumNumbers.length); for (uint8 quorumNumberIndex = 0; quorumNumberIndex < quorumNumbers.length; quorumNumberIndex++) { uint256 numNonSignersForQuorum = 0; @@ -124,15 +126,15 @@ contract OperatorStateRetriever { for (uint i = 0; i < nonSignerOperatorIds.length; i++) { // get the quorumBitmap for the operator at the given blocknumber and index - uint192 nonSignerQuorumBitmap = + uint192 nonSignerQuorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex( - nonSignerOperatorIds[i], - referenceBlockNumber, + nonSignerOperatorIds[i], + referenceBlockNumber, checkSignaturesIndices.nonSignerQuorumBitmapIndices[i] ); - + require(nonSignerQuorumBitmap != 0, "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber"); - + // if the operator was a part of the quorum and the quorum is a part of the provided quorumNumbers if ((nonSignerQuorumBitmap >> uint8(quorumNumbers[quorumNumberIndex])) & 1 == 1) { // get the index of the stake update for the operator at the given blocknumber and quorum number @@ -210,5 +212,5 @@ contract OperatorStateRetriever { operators[i] = registryCoordinator.getOperatorFromId(operatorIds[i]); } } - + } diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index c71e72787..b6f9ab189 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -274,6 +274,17 @@ contract RegistryCoordinator is } } + function updateOperatorAllocationsForQuorum( + address[] memory operators, + uint8 quorumNumber + ) external onlyWhenNotPaused(PAUSED_UPDATE_OPERATOR) { + bytes32[] memory operatorIds = new bytes32[](operators.length); + for (uint256 i = 0; i < operators.length; i++) { + operatorIds[i] = _operatorInfo[operators[i]].operatorId; + } + stakeRegistry.updateOperatorsAllocations(operators, operatorIds, quorumNumber); + } + /** * @notice For each quorum in `quorumNumbers`, updates the StakeRegistry's view of ALL its registered operators' stakes. * Each quorum's `quorumUpdateBlockNumber` is also updated, which tracks the most recent block number when ALL registered diff --git a/src/StakeRegistry.sol b/src/StakeRegistry.sol index e1e4e4492..cf3e132f3 100644 --- a/src/StakeRegistry.sol +++ b/src/StakeRegistry.sol @@ -23,9 +23,8 @@ import {BitmapUtils} from "./libraries/BitmapUtils.sol"; * @author Layr Labs, Inc. */ contract StakeRegistry is StakeRegistryStorage { - using BitmapUtils for *; - + modifier onlyRegistryCoordinator() { _checkRegistryCoordinator(); _; @@ -73,8 +72,8 @@ contract StakeRegistry is StakeRegistryStorage { uint96[] memory currentStakes = new uint96[](quorumNumbers.length); uint96[] memory totalStakes = new uint96[](quorumNumbers.length); - for (uint256 i = 0; i < quorumNumbers.length; i++) { - + for (uint256 i = 0; i < quorumNumbers.length; i++) { + uint8 quorumNumber = uint8(quorumNumbers[i]); _checkQuorumExists(quorumNumber); @@ -88,7 +87,7 @@ contract StakeRegistry is StakeRegistryStorage { // Update the operator's stake int256 stakeDelta = _recordOperatorStakeUpdate({ - operatorId: operatorId, + operatorId: operatorId, quorumNumber: quorumNumber, newStake: currentStake }); @@ -127,8 +126,8 @@ contract StakeRegistry is StakeRegistryStorage { // Update the operator's stake for the quorum and retrieve the shares removed int256 stakeDelta = _recordOperatorStakeUpdate({ - operatorId: operatorId, - quorumNumber: quorumNumber, + operatorId: operatorId, + quorumNumber: quorumNumber, newStake: 0 }); @@ -147,8 +146,8 @@ contract StakeRegistry is StakeRegistryStorage { * and should be deregistered. */ function updateOperatorStake( - address operator, - bytes32 operatorId, + address operator, + bytes32 operatorId, bytes calldata quorumNumbers ) external onlyRegistryCoordinator returns (uint192) { uint192 quorumsToRemove; @@ -173,7 +172,7 @@ contract StakeRegistry is StakeRegistryStorage { // against the minimum stake requirements for the quorum. (uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator); // If the operator no longer meets the minimum stake, set their stake to zero and mark them for removal - /// also handle setting the operator's stake to 0 and remove them from the quorum + /// also handle setting the operator's stake to 0 and remove them from the quorum /// if they directly unregistered from the AVSDirectory bubbles up info via registry coordinator to deregister them bool operatorRegistered; // Convert quorumNumber to operatorSetId @@ -206,6 +205,115 @@ contract StakeRegistry is StakeRegistryStorage { return quorumsToRemove; } + function updateOperatorAllocations( + address operator, + bytes32 operatorId, + uint8 quorumNumber + ) external onlyRegistryCoordinator { + _checkQuorumExists(quorumNumber); + address[] memory operators = new address[](1); + operators[0] = operator; + AllocationUpdate[] memory allocationUpdates = _weightOfOperatorsForQuorum(quorumNumber, operators); + + int96 totalDelegatedStakeDelta; + int96 totalSlashableStakeDelta; + + // Record individual operator allocation updates + (int96 delegatedStakeDelta, int96 slashableStakeDelta) = _recordOperatorAllocationUpdate( + operatorId, + quorumNumber, + allocationUpdates[0].stakeWeight, + allocationUpdates[0].slashableStakeWeight + ); + + // Accumulate total deltas + totalDelegatedStakeDelta += delegatedStakeDelta; + totalSlashableStakeDelta += slashableStakeDelta; + + _recordTotalAllocationUpdate(quorumNumber, totalDelegatedStakeDelta, totalSlashableStakeDelta); + } + + + + function updateOperatorsAllocations( + address[] memory operators, + bytes32[] memory operatorIds, + uint8 quorumNumber + ) external onlyRegistryCoordinator { + _checkQuorumExists(quorumNumber); + AllocationUpdate[] memory allocationUpdates = _weightOfOperatorsForQuorum(quorumNumber, operators); + + int96 totalDelegatedStakeDelta; + int96 totalSlashableStakeDelta; + + for (uint256 i = 0; i < operators.length; i++) { + // Record individual operator allocation updates + (int96 delegatedStakeDelta, int96 slashableStakeDelta) = _recordOperatorAllocationUpdate( + operatorIds[i], + quorumNumber, + allocationUpdates[i].stakeWeight, + allocationUpdates[i].slashableStakeWeight + ); + + // Accumulate total deltas + totalDelegatedStakeDelta += delegatedStakeDelta; + totalSlashableStakeDelta += slashableStakeDelta; + } + + // Record total allocation update for the quorum + _recordTotalAllocationUpdate(quorumNumber, totalDelegatedStakeDelta, totalSlashableStakeDelta); + } + + function _recordOperatorAllocationUpdate(bytes32 operatorId, uint8 quorumNumber, uint96 totalDelegatedStakeWeight, uint96 totalSlashableStakeWeight) internal returns (int96, int96) { + // Get our last-recorded stake update + uint256 historyLength = operatorStakeHistory[operatorId][quorumNumber].length; + StakeUpdate storage lastStakeUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength - 1]; + + // Calculate deltas from previous values + int96 delegatedStakeDelta = int96(totalDelegatedStakeWeight) - int96(lastStakeUpdate.stake); + int96 slashableStakeDelta = int96(totalSlashableStakeWeight) - int96(lastStakeUpdate.totalSlashableStake); + + // Only record update if there are changes + if (delegatedStakeDelta != 0 || slashableStakeDelta != 0) { + // Update the nextUpdateBlockNumber of the previous entry + lastStakeUpdate.nextUpdateBlockNumber = uint32(block.number); + + // Record new update + operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({ + updateBlockNumber: uint32(block.number), + nextUpdateBlockNumber: 0, + stake: totalDelegatedStakeWeight, + totalSlashableStake: totalSlashableStakeWeight + })); + } + + return (delegatedStakeDelta, slashableStakeDelta); + } + + function _recordTotalAllocationUpdate(uint8 quorumNumber, int96 totalDelegatedStakeDelta, int96 totalSlashableStakeDelta) internal { + // Get our last-recorded stake update + uint256 historyLength = _totalStakeHistory[quorumNumber].length; + StakeUpdate storage lastStakeUpdate = _totalStakeHistory[quorumNumber][historyLength - 1]; + + // Only record update if there are changes + if (totalDelegatedStakeDelta != 0 || totalSlashableStakeDelta != 0) { + // Calculate new stake values using _applyDelta + uint96 newDelegatedStake = _applyDelta(lastStakeUpdate.stake, totalDelegatedStakeDelta); + uint96 newSlashableStake = _applyDelta(lastStakeUpdate.totalSlashableStake, totalSlashableStakeDelta); + + // Update the nextUpdateBlockNumber of the previous entry + lastStakeUpdate.nextUpdateBlockNumber = uint32(block.number); + + // Record new update + _totalStakeHistory[quorumNumber].push(StakeUpdate({ + updateBlockNumber: uint32(block.number), + nextUpdateBlockNumber: 0, + stake: newDelegatedStake, + totalSlashableStake: newSlashableStake + })); + } + } + /// @notice Initialize a new quorum and push its first history update function initializeQuorum( uint8 quorumNumber, @@ -219,12 +327,13 @@ contract StakeRegistry is StakeRegistryStorage { _totalStakeHistory[quorumNumber].push(StakeUpdate({ updateBlockNumber: uint32(block.number), nextUpdateBlockNumber: 0, - stake: 0 + stake: 0, + totalSlashableStake: 0 })); } function setMinimumStakeForQuorum( - uint8 quorumNumber, + uint8 quorumNumber, uint96 minimumStake ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) { _setMinimumStakeForQuorum(quorumNumber, minimumStake); @@ -242,14 +351,14 @@ contract StakeRegistry is StakeRegistryStorage { function setSlashableStakeLookahead(uint32 _lookAheadPeriod) external onlyCoordinatorOwner { _setLookAheadPeriod(_lookAheadPeriod); } - /** + /** * @notice Adds strategies and weights to the quorum * @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies). * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a concious choice, * since a middleware may want, e.g., a stablecoin quorum that accepts USDC, USDT, DAI, etc. as underlying assets and trades them as "equivalent". */ function addStrategies( - uint8 quorumNumber, + uint8 quorumNumber, StrategyParams[] memory _strategyParams ) public virtual onlyCoordinatorOwner quorumExists(quorumNumber) { _addStrategyParams(quorumNumber, _strategyParams); @@ -353,11 +462,12 @@ contract StakeRegistry is StakeRegistryStorage { operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({ updateBlockNumber: uint32(block.number), nextUpdateBlockNumber: 0, - stake: newStake + stake: newStake, + totalSlashableStake: 0 })); } else { // We have prior stake history - fetch our last-recorded stake - StakeUpdate storage lastUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength-1]; + StakeUpdate storage lastUpdate = operatorStakeHistory[operatorId][quorumNumber][historyLength-1]; prevStake = lastUpdate.stake; // Short-circuit in case there's no change in stake @@ -368,7 +478,7 @@ contract StakeRegistry is StakeRegistryStorage { /** * If our last stake entry was made in the current block, update the entry * Otherwise, push a new entry and update the previous entry's "next" field - */ + */ if (lastUpdate.updateBlockNumber == uint32(block.number)) { lastUpdate.stake = newStake; } else { @@ -376,7 +486,8 @@ contract StakeRegistry is StakeRegistryStorage { operatorStakeHistory[operatorId][quorumNumber].push(StakeUpdate({ updateBlockNumber: uint32(block.number), nextUpdateBlockNumber: 0, - stake: newStake + stake: newStake, + totalSlashableStake: 0 })); } } @@ -397,7 +508,7 @@ contract StakeRegistry is StakeRegistryStorage { if (stakeDelta == 0) { return lastStakeUpdate.stake; } - + // Calculate the new total stake by applying the delta to our previous stake uint96 newStake = _applyDelta(lastStakeUpdate.stake, stakeDelta); @@ -412,14 +523,15 @@ contract StakeRegistry is StakeRegistryStorage { _totalStakeHistory[quorumNumber].push(StakeUpdate({ updateBlockNumber: uint32(block.number), nextUpdateBlockNumber: 0, - stake: newStake + stake: newStake, + totalSlashableStake: 0 })); } return newStake; } - /** + /** * @notice Adds `strategyParams` to the `quorumNumber`-th quorum. * @dev Checks to make sure that the *same* strategy cannot be added multiple times (checks against both against existing and new strategies). * @dev This function has no check to make sure that the strategies for a single quorum have the same underlying asset. This is a conscious choice, @@ -523,6 +635,64 @@ contract StakeRegistry is StakeRegistryStorage { return (weight, hasMinimumStake); } + struct AllocationUpdate { + address operator; + uint96 stakeWeight; + uint96 slashableStakeWeight; + } + + function _weightOfOperatorsForQuorum(uint8 quorumNumber, address[] memory operators) internal virtual view returns (AllocationUpdate[] memory) { + AllocationUpdate[] memory operatorStakes = new AllocationUpdate[](operators.length); + uint256 minimumStakeForQuorum = minimumStakeForQuorum[quorumNumber]; + uint256 minimumSlashableStakeForQuorum = minimumSlashableStakeForQuorum[quorumNumber]; + uint256 stratsLength = strategyParamsLength(quorumNumber); + StrategyParams memory strategyAndMultiplier; + uint32 beforeTimestamp = uint32(block.timestamp + slashableStakeLookAhead); + + (uint256[][] memory strategyShares, uint256[][] memory slashableStake) = IAllocationManager(serviceManager.allocationManager()).getMinDelegatedAndSlashableOperatorShares( + OperatorSet(address(serviceManager), quorumNumber), + operators, + strategiesPerQuorum[quorumNumber], + beforeTimestamp + ); + + // Initialize operator stakes and calculate weights for each strategy + for (uint256 i = 0; i < operators.length; i++) { + operatorStakes[i].operator = operators[i]; + // Convert quorumNumber to operatorSetId + uint32 operatorSetId = uint32(quorumNumber); + + // Get the AVSDirectory address from the RegistryCoordinator + // Query the AVSDirectory to check if the operator is directly unregistered + bool operatorRegistered = avsDirectory.isMember(operators[i], OperatorSet(address(serviceManager), operatorSetId)); + if (!operatorRegistered) { + /// Skip this operator if they're not in the operator set + /// TODO: This shouldn't be out of sync with the reg coord but might be good to think deeply about this case + continue; + } + + // Calculate weights across all strategies for this operator + for (uint256 j = 0; j < stratsLength; j++) { + strategyAndMultiplier = strategyParams[quorumNumber][j]; + /// TODO: add check for if their weight is 0 we append them to a list and bubble up to registry coordinator + /// to be removed from the operator set/quorum + if (strategyShares[j][i] >= minimumStakeForQuorum) { + operatorStakes[i].stakeWeight += uint96( + strategyShares[j][i] * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR + ); + } + + if (slashableStake[j][i] >= minimumSlashableStakeForQuorum) { + operatorStakes[i].slashableStakeWeight += uint96( + slashableStake[j][i] * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR + ); + } + } + } + + return operatorStakes; + } + /// @notice Returns `true` if the quorum has been initialized function _quorumExists(uint8 quorumNumber) internal view returns (bool) { return _totalStakeHistory[quorumNumber].length != 0; @@ -537,7 +707,7 @@ contract StakeRegistry is StakeRegistryStorage { * @dev reverts if the quorum does not exist */ function weightOfOperatorForQuorum( - uint8 quorumNumber, + uint8 quorumNumber, address operator ) public virtual view quorumExists(quorumNumber) returns (uint96) { (uint96 stake, ) = _weightOfOperatorForQuorum(quorumNumber, operator); @@ -551,7 +721,7 @@ contract StakeRegistry is StakeRegistryStorage { /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber` function strategyParamsByIndex( - uint8 quorumNumber, + uint8 quorumNumber, uint256 index ) public view returns (StrategyParams memory) { @@ -578,7 +748,7 @@ contract StakeRegistry is StakeRegistryStorage { * @param quorumNumber The quorum number to get the stake for. */ function getStakeHistory( - bytes32 operatorId, + bytes32 operatorId, uint8 quorumNumber ) external view returns (StakeUpdate[] memory) { return operatorStakeHistory[operatorId][quorumNumber]; @@ -697,7 +867,7 @@ contract StakeRegistry is StakeRegistryStorage { uint256 index ) external view returns (StakeUpdate memory) { return _totalStakeHistory[quorumNumber][index]; - } + } /** * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the diff --git a/src/StakeRegistryStorage.sol b/src/StakeRegistryStorage.sol index 7fea4fa28..fd0c88981 100644 --- a/src/StakeRegistryStorage.sol +++ b/src/StakeRegistryStorage.sol @@ -15,7 +15,7 @@ import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; * @notice This storage contract is separate from the logic to simplify the upgrade process. */ abstract contract StakeRegistryStorage is IStakeRegistry { - + /// @notice Constant used as a divisor in calculating weights. uint256 public constant WEIGHTING_DIVISOR = 1e18; /// @notice Maximum length of dynamic arrays in the `strategyParams` mapping. @@ -56,8 +56,10 @@ abstract contract StakeRegistryStorage is IStakeRegistry { uint32 public slashableStakeLookAhead; + mapping(uint8 => uint96) public minimumSlashableStakeForQuorum; + constructor( - IRegistryCoordinator _registryCoordinator, + IRegistryCoordinator _registryCoordinator, IDelegationManager _delegationManager, IAVSDirectory _avsDirectory, IServiceManager _serviceManager @@ -70,5 +72,5 @@ abstract contract StakeRegistryStorage is IStakeRegistry { // storage gap for upgradeability // slither-disable-next-line shadowing-state - uint256[44] private __GAP; + uint256[43] private __GAP; } diff --git a/src/interfaces/IStakeRegistry.sol b/src/interfaces/IStakeRegistry.sol index c4ebd195f..7bfdcc0cc 100644 --- a/src/interfaces/IStakeRegistry.sol +++ b/src/interfaces/IStakeRegistry.sol @@ -17,7 +17,7 @@ interface IStakeRegistry is IRegistry { TOTAL_SLASHABLE, BOTH } - + // DATA STRUCTURES /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage @@ -29,6 +29,8 @@ interface IStakeRegistry is IRegistry { uint32 nextUpdateBlockNumber; // stake weight for the quorum uint96 stake; + + uint96 totalSlashableStake; } /** @@ -80,8 +82,8 @@ interface IStakeRegistry is IRegistry { * 4) the operator is not already registered */ function registerOperator( - address operator, - bytes32 operatorId, + address operator, + bytes32 operatorId, bytes memory quorumNumbers ) external returns (uint96[] memory, uint96[] memory); @@ -200,7 +202,7 @@ interface IStakeRegistry is IRegistry { /** * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the - * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry + * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry * corresponds to 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. @@ -215,8 +217,8 @@ interface IStakeRegistry is IRegistry { returns (uint96); /** - * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the - * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total stake at `blockNumber`. + * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the + * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total 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]`. @@ -254,8 +256,20 @@ interface IStakeRegistry is IRegistry { * and should be deregistered. */ function updateOperatorStake( - address operator, - bytes32 operatorId, + address operator, + bytes32 operatorId, bytes calldata quorumNumbers ) external returns (uint192); + + /** + * @notice Called by the registry coordinator to update multiple operators' allocations for a quorum. + * @param operators Array of operator addresses to update allocations for + * @param operatorIds Array of operator IDs corresponding to the addresses + * @param quorumNumber The quorum number to update allocations for + */ + function updateOperatorsAllocations( + address[] memory operators, + bytes32[] memory operatorIds, + uint8 quorumNumber + ) external; } diff --git a/test/mocks/StakeRegistryMock.sol b/test/mocks/StakeRegistryMock.sol index f86b938fc..a4ad4e528 100644 --- a/test/mocks/StakeRegistryMock.sol +++ b/test/mocks/StakeRegistryMock.sol @@ -32,8 +32,8 @@ contract StakeRegistryMock is IStakeRegistry { * 4) the operator is not already registered */ function registerOperator( - address operator, - bytes32 operatorId, + address operator, + bytes32 operatorId, bytes memory quorumNumbers ) external returns (uint96[] memory, uint96[] memory) {} @@ -149,7 +149,7 @@ contract StakeRegistryMock is IStakeRegistry { /** * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the - * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry + * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry * corresponds to 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. @@ -164,8 +164,8 @@ contract StakeRegistryMock is IStakeRegistry { returns (uint96) {} /** - * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the - * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total stake at `blockNumber`. + * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the + * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total 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]`. @@ -201,8 +201,8 @@ contract StakeRegistryMock is IStakeRegistry { * added to the */ function updateOperatorStake( - address /*operator*/, - bytes32 /*operatorId*/, + address /*operator*/, + bytes32 /*operatorId*/, bytes calldata /*quorumNumbers*/ ) external returns (uint192) { return updateOperatorStakeReturnBitmap; @@ -211,4 +211,10 @@ contract StakeRegistryMock is IStakeRegistry { function getMockOperatorId(address operator) external pure returns(bytes32) { return bytes32(uint256(keccak256(abi.encodePacked(operator, "operatorId")))); } + + function updateOperatorsAllocations( + address[] memory /*operators*/, + bytes32[] memory /*operatorIds*/, + uint8 /*quorumNumber*/ + ) external {} }