diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index bccae530..c65f82de 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit bccae530de8bcbf17cfd3e0a04c7b8b13679d34d +Subproject commit c65f82de3262ec2e7a60649d368eeb10ae83dcd0 diff --git a/src/BLSRegistryCoordinatorWithIndices.sol b/src/BLSRegistryCoordinatorWithIndices.sol index a97d93ce..362b710e 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( @@ -152,7 +157,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr */ function registerOperator( bytes calldata quorumNumbers, - string calldata socket + string calldata socket, + SignatureWithSaltAndExpiry memory operatorSignature ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { bytes32 operatorId = blsPubkeyRegistry.getOperatorId(msg.sender); @@ -161,7 +167,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr operator: msg.sender, operatorId: operatorId, quorumNumbers: quorumNumbers, - socket: socket + socket: socket, + signatureWithSaltAndExpiry: operatorSignature }); for (uint256 i = 0; i < quorumNumbers.length; i++) { @@ -187,12 +194,14 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr * @param operatorKickParams are used to determine which operator is removed to maintain quorum capacity as the * operator registers for quorums. * @param churnApproverSignature is the signature of the churnApprover on the operator kick params + * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager */ function registerOperatorWithChurn( bytes calldata quorumNumbers, string calldata socket, OperatorKickParam[] calldata operatorKickParams, - SignatureWithSaltAndExpiry memory churnApproverSignature + SignatureWithSaltAndExpiry memory churnApproverSignature, + SignatureWithSaltAndExpiry memory operatorSignature ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { require(operatorKickParams.length == quorumNumbers.length, "BLSRegistryCoordinatorWithIndices.registerOperatorWithChurn: input length mismatch"); @@ -210,7 +219,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr operator: msg.sender, operatorId: operatorId, quorumNumbers: quorumNumbers, - socket: socket + socket: socket, + signatureWithSaltAndExpiry: operatorSignature }); uint256 kickIndex = 0; @@ -363,6 +373,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 +400,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 +432,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr status: OperatorStatus.REGISTERED }); + delegationManager.registerOperatorWithAVS(operator, signatureWithSaltAndExpiry); + emit OperatorRegistered(operator, operatorId); } @@ -493,6 +515,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..4c3135e5 100644 --- a/src/StakeRegistry.sol +++ b/src/StakeRegistry.sol @@ -19,7 +19,8 @@ import "src/StakeRegistryStorage.sol"; * @author Layr Labs, Inc. */ contract StakeRegistry is StakeRegistryStorage { - + using BitmapUtils for *; + modifier onlyRegistryCoordinator() { require( msg.sender == address(registryCoordinator), @@ -201,6 +202,8 @@ contract StakeRegistry is StakeRegistryStorage { nextUpdateBlockNumber: 0, stake: 0 })); + + quorumBitmap = uint192(quorumBitmap.plus(uint192(1) << quorumNumber)); } function setMinimumStakeForQuorum( @@ -245,6 +248,11 @@ contract StakeRegistry is StakeRegistryStorage { _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1]; _strategyParams.pop(); } + + // If no strategies exist in quorum, turn bit off + if (_strategyParams.length == 0) { + quorumBitmap = uint192(quorumBitmap.minus(uint192(1) << quorumNumber)); + } } /** @@ -704,4 +712,73 @@ contract StakeRegistry is StakeRegistryStorage { } return indices; } + + /******************************************************************************* + VIEW FUNCTIONS - Operator & AVS Stakes + *******************************************************************************/ + + /** + * @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 + * @dev Strategies may be duplicated in the returned array if the same strategy exists in multiple quorums + */ + 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++){ + StrategyParams[] memory strategyParams = strategyParams[uint8(quorumNumbers[i])]; + for(uint256 j = 0; j < strategyParams.length; j++){ + strategies[index] = strategyParams[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 + * @dev Strategies and share amounts may be duplicated in the returned arrays if the same strategy exists in multiple quorums + */ + function getOperatorRestakedStrategies(address operator) external view returns (IStrategy[] memory, uint256[] memory){ + bytes32 operatorId = registryCoordinator.getOperatorId(operator); + uint192 operatorBitmap = registryCoordinator.getCurrentQuorumBitmap(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 uint256[](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)); + uint256[] memory shares = new uint256[](strategies.length); + + uint256 index = 0; + for(uint256 i = 0; i < restakedQuorums.length; i++) { + StrategyParams[] memory strategyParams = strategyParams[uint8(restakedQuorums[i])]; + for (uint256 j = 0; j < strategyParams.length; j++) { + strategies[index] = strategyParams[j].strategy; + shares[index] = delegation.operatorShares(operator, strategyParams[j].strategy); + index++; + } + } + + return (strategies, shares); + } + + function _getNumStrategiesInBitmap(bytes memory quorums) internal view returns (uint256) { + uint256 strategyCount; + for(uint256 i = 0; i < quorums.length; i++) { + strategyCount += strategyParams[uint8(quorums[i])].length; + } + return strategyCount; + } } diff --git a/src/StakeRegistryStorage.sol b/src/StakeRegistryStorage.sol index e018cecd..8c12f934 100644 --- a/src/StakeRegistryStorage.sol +++ b/src/StakeRegistryStorage.sol @@ -47,6 +47,9 @@ abstract contract StakeRegistryStorage is IStakeRegistry { */ mapping(uint8 => StrategyParams[]) public strategyParams; + /// @notice Bitmap of quorums that have been initialized and more than 1 strategy set + uint192 quorumBitmap; + constructor( IRegistryCoordinator _registryCoordinator, IDelegationManager _delegationManager, diff --git a/src/interfaces/IRegistryCoordinator.sol b/src/interfaces/IRegistryCoordinator.sol index 360b95b1..ed553388 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,19 @@ interface IRegistryCoordinator { /// @notice Returns the number of registries function numRegistries() external view returns (uint256); + + /** + * @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; } \ No newline at end of file diff --git a/src/interfaces/IStakeRegistry.sol b/src/interfaces/IStakeRegistry.sol index 880557b6..fed92a40 100644 --- a/src/interfaces/IStakeRegistry.sol +++ b/src/interfaces/IStakeRegistry.sol @@ -6,6 +6,7 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/ import {IRegistry} from "./IRegistry.sol"; import {IServiceManager} from "./IServiceManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; /** * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. @@ -249,4 +250,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, uint256[] 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 {