diff --git a/src/BLSRegistryCoordinatorWithIndices.sol b/src/BLSRegistryCoordinatorWithIndices.sol index a97d93ce..35631ab2 100644 --- a/src/BLSRegistryCoordinatorWithIndices.sol +++ b/src/BLSRegistryCoordinatorWithIndices.sol @@ -8,6 +8,7 @@ import "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; import "eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol"; import "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol"; import "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; +import "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol"; import "src/interfaces/ISocketUpdater.sol"; @@ -60,6 +61,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr IStakeRegistry public immutable stakeRegistry; /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; + /// @notice The Delegation Manager contract to record operator avs relationships + IDelegationManager public immutable delegationManager; /// @notice the current number of quorums supported by the registry coordinator uint8 public quorumCount; @@ -102,13 +105,15 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr IServiceManager _serviceManager, IStakeRegistry _stakeRegistry, IBLSPubkeyRegistry _blsPubkeyRegistry, - IIndexRegistry _indexRegistry + IIndexRegistry _indexRegistry, + IDelegationManager _delegationManager ) EIP712("AVSRegistryCoordinator", "v0.0.1") { slasher = _slasher; serviceManager = _serviceManager; stakeRegistry = _stakeRegistry; blsPubkeyRegistry = _blsPubkeyRegistry; indexRegistry = _indexRegistry; + delegationManager = _delegationManager; } function initialize( @@ -148,11 +153,40 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr /** * @notice Registers msg.sender as an operator for one or more quorums. If any quorum reaches its maximum * operator capacity, this method will fail. - * @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for + * @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 + * @param signatureWithSaltAndExpiry is the signature of the operator used by the avs to register the operator in the delegation manager + * @dev `registrationData` should be a G1 point representing the operator's BLS public key and their socket */ function registerOperator( bytes calldata quorumNumbers, - string calldata socket + bytes calldata registrationData, + SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry + ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { + // get the operator's BLS public key + (BN254.G1Point memory pubkey, string memory socket) = abi.decode(registrationData, (BN254.G1Point, string)); + // call internal function to register the operator + _registerOperatorWithCoordinatorAndNoOverfilledQuorums({ + operator: msg.sender, + quorumNumbers: quorumNumbers, + pubkey: pubkey, + socket: socket, + signatureWithSaltAndExpiry: signatureWithSaltAndExpiry + }); + } + + /** + * @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 + * @param socket is the socket of the operator + * @param signatureWithSaltAndExpiry is the signature of the operator used by the avs to register the operator in the delegation manager + */ + function registerOperatorWithCoordinator( + bytes calldata quorumNumbers, + BN254.G1Point memory pubkey, + string calldata socket, + SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { bytes32 operatorId = blsPubkeyRegistry.getOperatorId(msg.sender); @@ -161,9 +195,52 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr operator: msg.sender, operatorId: operatorId, quorumNumbers: quorumNumbers, - socket: socket + socket: socket, + signatureWithSaltAndExpiry: signatureWithSaltAndExpiry }); + } + /** + * @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 operator 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 from each + * quorum that will be filled after the operator registers. These parameters should include an operator, their pubkey, + * and ids of the operators to swap with the kicked operator. + * @param operatorSignature is the signature of the operator used by the avs to register the operator in the delegation manager + * @param churnApproverSignature is the signature of the churnApprover on the operator kick params with a salt and expiry + */ + function registerOperatorWithCoordinator( + bytes calldata quorumNumbers, + BN254.G1Point memory pubkey, + string calldata socket, + OperatorKickParam[] calldata operatorKickParams, + SignatureWithSaltAndExpiry memory operatorSignature, + SignatureWithSaltAndExpiry memory churnApproverSignature + ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { + // register the operator + uint32[] memory numOperatorsPerQuorum = _registerOperatorWithCoordinator({ + operator: msg.sender, + quorumNumbers: quorumNumbers, + pubkey: pubkey, + socket: socket, + signatureWithSaltAndExpiry: operatorSignature + }); + + // get the registering operator's operatorId and set the operatorIdsToSwap to it because the registering operator is the one with the greatest index + bytes32[] memory operatorIdsToSwap = new bytes32[](1); + operatorIdsToSwap[0] = pubkey.hashG1Point(); + + // verify the churnApprover's signature + _verifyChurnApproverSignatureOnOperatorChurnApproval({ + registeringOperatorId: operatorIdsToSwap[0], + operatorKickParams: operatorKickParams, + signatureWithSaltAndExpiry: churnApproverSignature + }); + + uint256 operatorToKickParamsIndex = 0; + // kick the operators for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); @@ -363,6 +440,15 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr _setEjector(_ejector); } + /** + * @notice Sets the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + * @dev only callable by the service manager owner + */ + function setMetadataURI(string memory _metadataURI) external onlyServiceManagerOwner { + delegationManager.updateAVSMetadataURI(_metadataURI); + } + /******************************************************************************* INTERNAL FUNCTIONS *******************************************************************************/ @@ -381,8 +467,9 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr address operator, bytes32 operatorId, bytes calldata quorumNumbers, - string memory socket - ) internal virtual returns (RegisterResults memory) { + string memory socket, + SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry + ) internal virtual returns (RegisterResults memory) { /** * Get bitmap of quorums to register for and operator's current bitmap. Validate that: * - we're trying to register for at least 1 quorum @@ -412,6 +499,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr status: OperatorStatus.REGISTERED }); + delegationManager.registerOperatorWithAVS(operator, signatureWithSaltAndExpiry); + emit OperatorRegistered(operator, operatorId); } @@ -431,6 +520,22 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr }); } + function _registerOperatorWithCoordinatorAndNoOverfilledQuorums( + address operator, + bytes calldata quorumNumbers, + BN254.G1Point memory pubkey, + string memory socket, + SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry + ) internal { + uint32[] memory numOperatorsPerQuorum = _registerOperatorWithCoordinator({ + operator: operator, + quorumNumbers: quorumNumbers, + pubkey: pubkey, + socket: socket, + signatureWithSaltAndExpiry: signatureWithSaltAndExpiry + }); + } + function _validateChurn( uint8 quorumNumber, uint96 totalQuorumStake, @@ -493,6 +598,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr // If the operator is no longer registered for any quorums, update their status if (newBitmap.isEmpty()) { operatorInfo.status = OperatorStatus.DEREGISTERED; + delegationManager.deregisterOperatorFromAVS(operator); + emit OperatorDeregistered(operator, operatorId); } diff --git a/src/StakeRegistry.sol b/src/StakeRegistry.sol index 2b493632..bc92112b 100644 --- a/src/StakeRegistry.sol +++ b/src/StakeRegistry.sol @@ -704,4 +704,107 @@ contract StakeRegistry is StakeRegistryStorage { } return indices; } + + /** + * @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) public view returns (uint96) { + OperatorStakeUpdate memory operatorStakeUpdate = getMostRecentStakeUpdateByOperatorId(operatorId, quorumNumber); + return operatorStakeUpdate.stake; + } + + /// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber` + function getStakeForOperatorIdForQuorumAtBlockNumber( + bytes32 operatorId, + uint8 quorumNumber, + uint32 blockNumber + ) external view returns (uint96) { + return + operatorIdToStakeHistory[operatorId][quorumNumber][ + _getStakeUpdateIndexForOperatorIdForQuorumAtBlockNumber(operatorId, quorumNumber, blockNumber) + ].stake; + } + + /** + * @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) { + return _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake; + } + + function getLengthOfOperatorIdStakeHistoryForQuorum( + bytes32 operatorId, + uint8 quorumNumber + ) external view returns (uint256) { + return operatorIdToStakeHistory[operatorId][quorumNumber].length; + } + + function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) { + return _totalStakeHistory[quorumNumber].length; + } + + /** + * @notice Returns a list of all strategies that are restakeable for the middleware + * @dev This function is used by an offchain actor to determine which strategies are restakeable + */ + function getRestakeableStrategies() external view returns (IStrategy[] memory){ + if(quorumBitmap == 0) { + return new IStrategy[](0); + } + + bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); + IStrategy[] memory strategies = new IStrategy[](_getNumStrategiesInBitmap(quorumNumbers)); + uint256 index = 0; + for(uint256 i = 0; i < quorumNumbers.length; i++){ + StrategyAndWeightingMultiplier[] memory strategiesAndMultipliers = strategiesConsideredAndMultipliers[uint8(quorumNumbers[i])]; + for(uint256 j = 0; j < strategiesAndMultipliers.length; j++){ + strategies[index] = strategiesAndMultipliers[j].strategy; + index++; + } + } + + return strategies; + } + + /** + * @notice Return a list of all strategies and corresponding share amounts for which the operator has restaked + * @dev There may strategies for which the operator has restaked on the quorum but has no shares. In this case, + * the strategy is included in the returned array but the share amount is 0 + */ + function getOperatorRestakedStrategies(address operator) external view returns (IStrategy[] memory, uint96[] memory){ + bytes32 operatorId = registryCoordinator.getOperatorId(operator); + uint192 operatorBitmap = registryCoordinator.getCurrentQuorumBitmapByOperatorId(operatorId); + + // Return empty arrays if operator does not have any shares at stake OR no strategies can be restaked + if (operatorBitmap == 0 || quorumBitmap == 0) { + return (new IStrategy[](0), new uint96[](0)); + } + + uint192 operatorRestakedQuorumsBitmap = operatorBitmap & quorumBitmap; // get all strategies that are considered restaked + bytes memory restakedQuorums = BitmapUtils.bitmapToBytesArray(operatorRestakedQuorumsBitmap); + IStrategy[] memory strategies = new IStrategy[](_getNumStrategiesInBitmap(restakedQuorums)); + uint96[] memory shares = new uint96[](strategies.length); + + uint256 index = 0; + for(uint256 i = 0; i < restakedQuorums.length; i++) { + StrategyAndWeightingMultiplier[] memory strategiesAndMultipliers = strategiesConsideredAndMultipliers[uint8(restakedQuorums[i])]; + for (uint256 j = 0; j < strategiesAndMultipliers.length; j++) { + strategies[index] = strategiesAndMultipliers[j].strategy; + shares[index] = getCurrentOperatorStakeForQuorum(operatorId, uint8(restakedQuorums[i])); + index++; + } + } + + return (strategies, shares); + } + + function _getNumStrategiesInBitmap(bytes memory quorums) internal view returns (uint256) { + uint256 strategyCount; + for(uint256 i = 0; i < quorums.length; i++) { + strategyCount += strategiesConsideredAndMultipliersLength(uint8(quorums[i])); + } + return strategyCount; + } } diff --git a/src/VoteWeigherBase.sol b/src/VoteWeigherBase.sol new file mode 100644 index 00000000..f41216b2 --- /dev/null +++ b/src/VoteWeigherBase.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; +import "src/VoteWeigherBaseStorage.sol"; + +/** + * @title A simple implementation of the `IVoteWeigher` interface. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice This contract is used for + * - computing the total weight of an operator for any of the quorums that are considered + * by the middleware + * - addition and removal of strategies and the associated weighting criteria that are assigned + * by the middleware for each of the quorum(s) + */ +contract VoteWeigherBase is VoteWeigherBaseStorage { + /// @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(), "VoteWeigherBase.onlyServiceManagerOwner: caller is not the owner of the serviceManager"); + _; + } + + /// @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, + IServiceManager _serviceManager + ) VoteWeigherBaseStorage(_strategyManager, _serviceManager) {} + + /******************************************************************************* + EXTERNAL FUNCTIONS - SERVICE MANAGER OWNER + *******************************************************************************/ + + /// @notice Create a new quorum and add the strategies and their associated weights to the quorum. + function createQuorum( + StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers + ) external virtual onlyServiceManagerOwner { + _createQuorum(_strategiesConsideredAndMultipliers); + } + + /// @notice Adds new strategies and the associated multipliers to the @param quorumNumber. + function addStrategiesConsideredAndMultipliers( + uint8 quorumNumber, + StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers + ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { + _addStrategiesConsideredAndMultipliers(quorumNumber, _newStrategiesConsideredAndMultipliers); + } + + /** + * @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, + uint256[] calldata indicesToRemove + ) external virtual onlyServiceManagerOwner validQuorumNumber(quorumNumber) { + 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); + emit StrategyMultiplierUpdated(quorumNumber, strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]].strategy, 0); + // remove strategy and its associated multiplier + strategiesConsideredAndMultipliers[quorumNumber][indicesToRemove[i]] = strategiesConsideredAndMultipliers[ + quorumNumber + ][strategiesConsideredAndMultipliers[quorumNumber].length - 1]; + strategiesConsideredAndMultipliers[quorumNumber].pop(); + + unchecked { + ++i; + } + } + + // update the quorumBitmap + if (strategiesConsideredAndMultipliers[quorumNumber].length == 0) { + quorumBitmap = quorumBitmap & ~(uint192(1) << quorumNumber); + } + } + + /** + * @notice This function is used for modifying the weights of strategies that are already in the + * 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 + */ + function modifyStrategyWeights( + uint8 quorumNumber, + uint256[] calldata strategyIndices, + 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"); + + for (uint256 i = 0; i < numStrats; ) { + // change the strategy's associated multiplier + strategiesConsideredAndMultipliers[quorumNumber][strategyIndices[i]].multiplier = newMultipliers[i]; + emit StrategyMultiplierUpdated(quorumNumber, strategiesConsideredAndMultipliers[quorumNumber][strategyIndices[i]].strategy, newMultipliers[i]); + unchecked { + ++i; + } + } + } + + /******************************************************************************* + INTERNAL FUNCTIONS + *******************************************************************************/ + + /** + * @notice Creates a quorum with the given_strategiesConsideredAndMultipliers. + */ + function _createQuorum( + StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers + ) internal { + uint16 quorumCountMem = quorumCount; + require(quorumCountMem < MAX_QUORUM_COUNT, "VoteWeigherBase._createQuorum: number of quorums cannot exceed MAX_QUORUM_COUNT"); + uint8 quorumNumber = uint8(quorumCountMem); + // increment quorumCount + quorumCount = quorumCountMem + 1; + // add the strategies and their associated weights to the quorum + _addStrategiesConsideredAndMultipliers(quorumNumber, _strategiesConsideredAndMultipliers); + // emit event + emit QuorumCreated(quorumNumber); + } + + /** + * @notice Adds `_newStrategiesConsideredAndMultipliers` 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 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 _addStrategiesConsideredAndMultipliers( + uint8 quorumNumber, + StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers + ) internal { + require(_newStrategiesConsideredAndMultipliers.length > 0, "VoteWeigherBase._addStrategiesConsideredAndMultipliers: no strategies provided"); + uint256 numStratsToAdd = _newStrategiesConsideredAndMultipliers.length; + uint256 numStratsExisting = strategiesConsideredAndMultipliers[quorumNumber].length; + require( + numStratsExisting + numStratsToAdd <= MAX_WEIGHING_FUNCTION_LENGTH, + "VoteWeigherBase._addStrategiesConsideredAndMultipliers: exceed MAX_WEIGHING_FUNCTION_LENGTH" + ); + for (uint256 i = 0; i < numStratsToAdd; ) { + // fairly gas-expensive internal loop to make sure that the *same* strategy cannot be added multiple times + for (uint256 j = 0; j < (numStratsExisting + i); ) { + require( + strategiesConsideredAndMultipliers[quorumNumber][j].strategy != + _newStrategiesConsideredAndMultipliers[i].strategy, + "VoteWeigherBase._addStrategiesConsideredAndMultipliers: cannot add same strategy 2x" + ); + unchecked { + ++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 StrategyMultiplierUpdated( + quorumNumber, + _newStrategiesConsideredAndMultipliers[i].strategy, + _newStrategiesConsideredAndMultipliers[i].multiplier + ); + unchecked { + ++i; + } + } + + // update the quorumBitmap + quorumBitmap = quorumBitmap | (uint192(1) << quorumNumber); + } + + /******************************************************************************* + VIEW FUNCTIONS + *******************************************************************************/ + + /// @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. + function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) public view returns (uint256) { + return strategiesConsideredAndMultipliers[quorumNumber].length; + } + + /// @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 reverts in the case that `quorumNumber` is greater than or equal to `quorumCount` + */ + function weightOfOperatorForQuorum( + uint8 quorumNumber, + address operator + ) public virtual view validQuorumNumber(quorumNumber) returns (uint96) { + uint96 weight; + uint256 stratsLength = strategiesConsideredAndMultipliersLength(quorumNumber); + StrategyAndWeightingMultiplier memory strategyAndMultiplier; + + for (uint256 i = 0; i < stratsLength;) { + // accessing i^th StrategyAndWeightingMultiplier struct for the quorumNumber + strategyAndMultiplier = strategiesConsideredAndMultipliers[quorumNumber][i]; + + // shares of the operator in the strategy + uint256 sharesAmount = delegation.operatorShares(operator, strategyAndMultiplier.strategy); + + // add the weight from the shares for this strategy to the total weight + if (sharesAmount > 0) { + weight += uint96(sharesAmount * strategyAndMultiplier.multiplier / WEIGHTING_DIVISOR); + } + + unchecked { + ++i; + } + } + + return weight; + } +} diff --git a/src/VoteWeigherBaseStorage.sol b/src/VoteWeigherBaseStorage.sol new file mode 100644 index 00000000..07ce44d7 --- /dev/null +++ b/src/VoteWeigherBaseStorage.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; +import "src/interfaces/IServiceManager.sol"; +import "src/interfaces/IVoteWeigher.sol"; + +import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; + +/** + * @title Storage variables for the `VoteWeigherBase` contract. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice This storage contract is separate from the logic to simplify the upgrade process. + */ +abstract contract VoteWeigherBaseStorage is Initializable, IVoteWeigher { + /// @notice Constant used as a divisor in calculating weights. + uint256 public constant WEIGHTING_DIVISOR = 1e18; + /// @notice Maximum length of dynamic arrays in the `strategiesConsideredAndMultipliers` mapping. + 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; + /// @notice The maximum number of quorums that the VoteWeigher is considering. + uint8 public constant MAX_QUORUM_COUNT = 192; + + /// @notice The address of the Delegation contract for EigenLayer. + IDelegationManager public immutable delegation; + + /// @notice The address of the StrategyManager contract for EigenLayer. + IStrategyManager public immutable strategyManager; + + /// @notice The address of the Slasher contract for EigenLayer. + ISlasher public immutable slasher; + + /// @notice The ServiceManager contract for this middleware, where tasks are created / initiated. + IServiceManager public immutable serviceManager; + + /// @notice The number of quorums that the VoteWeigher is considering. + uint16 public quorumCount; + + /// @notice Bitmap of quorums that are being used by the middleware. + uint192 public quorumBitmap; + + /** + * @notice mapping from quorum number to the list of strategies considered and their + * corresponding multipliers for that specific quorum + */ + mapping(uint8 => StrategyAndWeightingMultiplier[]) public strategiesConsideredAndMultipliers; + + constructor( + IStrategyManager _strategyManager, + IServiceManager _serviceManager + ) { + strategyManager = _strategyManager; + delegation = _strategyManager.delegation(); + slasher = _strategyManager.slasher(); + serviceManager = _serviceManager; + // disable initializers so that the implementation contract cannot be initialized + _disableInitializers(); + } + + // storage gap for upgradeability + // slither-disable-next-line shadowing-state + uint256[49] private __GAP; +} diff --git a/src/interfaces/IRegistryCoordinator.sol b/src/interfaces/IRegistryCoordinator.sol index 360b95b1..8f7b9792 100644 --- a/src/interfaces/IRegistryCoordinator.sol +++ b/src/interfaces/IRegistryCoordinator.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; + /** * @title Interface for a contract that coordinates between various registries for an AVS. * @author Layr Labs, Inc. */ -interface IRegistryCoordinator { +interface IRegistryCoordinator is ISignatureUtils { // EVENTS /// Emits when an operator is registered event OperatorRegistered(address indexed operator, bytes32 indexed operatorId); @@ -83,4 +85,22 @@ interface IRegistryCoordinator { /// @notice Returns the number of registries function numRegistries() external view returns (uint256); +<<<<<<< HEAD +======= + + /** + * @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 + * @param signatureWithSaltAndExpiry is the signature of the operator used by the avs to register the operator in the delegation manager + */ + function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata registrationData, SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry) external; + + /** + * @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 deregistration information + */ + function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external; +>>>>>>> 3e28473 (Feat: Add avs registration & operator/strategy accounting) } \ No newline at end of file diff --git a/src/interfaces/IStakeRegistry.sol b/src/interfaces/IStakeRegistry.sol index 880557b6..6dbc2f4b 100644 --- a/src/interfaces/IStakeRegistry.sol +++ b/src/interfaces/IStakeRegistry.sol @@ -5,7 +5,11 @@ import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IRegistry} from "./IRegistry.sol"; +<<<<<<< HEAD import {IServiceManager} from "./IServiceManager.sol"; +======= +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +>>>>>>> 3e28473 (Feat: Add avs registration & operator/strategy accounting) /** * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. @@ -249,4 +253,15 @@ interface IStakeRegistry is IRegistry { bytes32 operatorId, bytes calldata quorumNumbers ) external returns (uint192); + + /** + * @notice Returns a list of strategies that an operator has restaked into the AVS + * @param operator is the address of the operator whose restaked strategies are being queried + */ + function getOperatorRestakedStrategies(address operator) external view returns (IStrategy[] memory, uint96[] memory); + + /** + * @notice Returns the strategies that this AVS supports restaking for + */ + function getRestakeableStrategies() external view returns (IStrategy[] memory); } \ No newline at end of file diff --git a/test/harnesses/BLSRegistryCoordinatorWithIndicesHarness.sol b/test/harnesses/BLSRegistryCoordinatorWithIndicesHarness.sol index c6ec3b98..34b7ff7d 100644 --- a/test/harnesses/BLSRegistryCoordinatorWithIndicesHarness.sol +++ b/test/harnesses/BLSRegistryCoordinatorWithIndicesHarness.sol @@ -10,8 +10,9 @@ contract BLSRegistryCoordinatorWithIndicesHarness is BLSRegistryCoordinatorWithI IServiceManager _serviceManager, IStakeRegistry _stakeRegistry, IBLSPubkeyRegistry _blsPubkeyRegistry, - IIndexRegistry _indexRegistry - ) BLSRegistryCoordinatorWithIndices(_slasher, _serviceManager, _stakeRegistry, _blsPubkeyRegistry, _indexRegistry) { + IIndexRegistry _indexRegistry, + IDelegationManager _delegationManager + ) BLSRegistryCoordinatorWithIndices(_slasher, _serviceManager, _stakeRegistry, _blsPubkeyRegistry, _indexRegistry, _delegationManager) { } function setQuorumCount(uint8 count) external {