diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 6d2ff3f4..28d56d8f 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -492,18 +492,15 @@ contract RegistryCoordinator is emit OperatorSocketUpdate(operatorId, socket); // If the operator wasn't registered for any quorums, update their status - // and register them with this AVS in EigenLayer core (DelegationManager) if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) { _operatorInfo[operator] = OperatorInfo({ operatorId: operatorId, status: OperatorStatus.REGISTERED }); - - // Register the operator with the EigenLayer core contracts via this AVS's ServiceManager - serviceManager.registerOperatorToAVS(operator, operatorSignature); - - emit OperatorRegistered(operator, operatorId); } + // Register the operator with the EigenLayer core contracts via this AVS's ServiceManager + serviceManager.registerOperatorToOperatorSets(operator, quorumNumbers, operatorSignature); + emit OperatorRegistered(operator, operatorId, quorumNumbers); // Register the operator with the BLSApkRegistry, StakeRegistry, and IndexRegistry blsApkRegistry.registerOperator(operator, quorumNumbers); @@ -629,13 +626,12 @@ contract RegistryCoordinator is newBitmap: newBitmap }); - // If the operator is no longer registered for any quorums, update their status and deregister - // them from the AVS via the EigenLayer core contracts + // If the operator is no longer registered for any quorums, update their status if (newBitmap.isEmpty()) { operatorInfo.status = OperatorStatus.DEREGISTERED; - serviceManager.deregisterOperatorFromAVS(operator); - emit OperatorDeregistered(operator, operatorId); } + serviceManager.deregisterOperatorFromOperatorSets(operator, quorumNumbers); + emit OperatorDeregistered(operator, operatorId, quorumNumbers); // Deregister operator with each of the registry contracts blsApkRegistry.deregisterOperator(operator, quorumNumbers); diff --git a/src/ServiceManagerBase.sol b/src/ServiceManagerBase.sol index 3560692b..b9c14e6a 100644 --- a/src/ServiceManagerBase.sol +++ b/src/ServiceManagerBase.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.12; import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; -import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; -import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IRewardsCoordinator} from + "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IOperatorSetManager, IStrategy} from "./interfaces/IOperatorSetManager.sol"; // should be later changed to be import from core import {ServiceManagerBaseStorage} from "./ServiceManagerBaseStorage.sol"; import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; @@ -29,6 +30,15 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { _; } + /// @notice when applied to a function, only allows the StakeRegistry to call it + modifier onlyStakeRegistry() { + require( + msg.sender == address(_stakeRegistry), + "ServiceManagerBase.onlyStakeRegistry: caller is not the stake registry" + ); + _; + } + /// @notice only rewardsInitiator can call createAVSRewardsSubmission modifier onlyRewardsInitiator() { _checkRewardsInitiator(); @@ -44,13 +54,13 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { /// @notice Sets the (immutable) `_registryCoordinator` address constructor( - IAVSDirectory __avsDirectory, + IOperatorSetManager __operatorSetManager, IRewardsCoordinator __rewardsCoordinator, IRegistryCoordinator __registryCoordinator, IStakeRegistry __stakeRegistry ) ServiceManagerBaseStorage( - __avsDirectory, + __operatorSetManager, __rewardsCoordinator, __registryCoordinator, __stakeRegistry @@ -67,13 +77,119 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { _setRewardsInitiator(_rewardsInitiator); } + /** + * @notice called by the AVS StakeRegistry whenever a new IStrategy is added to a quorum/operatorSet + * @dev calls operatorSetManager.addStrategiesToOperatorSet() + */ + function addStrategiesToOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external onlyStakeRegistry { + _operatorSetManager.addStrategiesToOperatorSet(operatorSetID, strategies); + } + + /** + * @notice called by the AVS StakeRegistry whenever a new IStrategy is removed from a quorum/operatorSet + * @dev calls operatorSetManager.removeStrategiesFromOperatorSet() + */ + function removeStrategiesFromOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external onlyStakeRegistry { + _operatorSetManager.removeStrategiesFromOperatorSet(operatorSetID, strategies); + } + + /** + * @notice One-time call that migrates all existing strategies for each quorum to their respective operator sets + * Note: a separate migration per operator must be performed to migrate an existing operator to the operator set + * See migrateOperatorToOperatorSets() below + * @dev calls operatorSetManager.addStrategiesToOperatorSet() + */ + function migrateStrategiesToOperatorSets() external { + uint8 quorumCount = _registryCoordinator.quorumCount(); + for (uint8 i = 0; i < quorumCount; ++i) { + uint256 numStrategies = _stakeRegistry.strategyParamsLength(i); + IStrategy[] memory strategies = new IStrategy[](numStrategies); + // get the strategies for the quorum/operator set + for (uint256 j = 0; j < numStrategies; ++j) { + IStrategy strategy = _stakeRegistry.strategyParamsByIndex(i, j).strategy; + strategies[j] = strategy; + require( + !_operatorSetManager.operatorSetStrategies(address(this), uint32(i), strategy), + "ServiceManagerBase.migrateStrategiesToOperatorSets: strategy already part of OperatorSet" + ); + } + + _operatorSetManager.addStrategiesToOperatorSet(uint32(i), strategies); + emit OperatorSetStrategiesMigrated(uint32(i), strategies); + } + } + + /** + * @notice One-time call to migrate an existing operator to the respective operator sets. + * The operator needs to provide a signature over the operatorSetIds they are currently registered for. + * This can be retrieved externally by calling getOperatorSetIds. + * @param operator the address of the operator to be migrated + * @param signature the signature of the operator on their intent to migrate + * @dev calls operatorSetManager.registerOperatorToOperatorSets() + */ + function migrateOperatorToOperatorSets( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory signature + ) external { + // check if operator is already migrated, that is they are registered operators with 0 operatorSet count + require( + _operatorSetManager.avsOperatorStatus(address(this), operator) + == IOperatorSetManager.OperatorAVSRegistrationStatus.REGISTERED + && _operatorSetManager.operatorAVSOperatorSetCount(address(this), operator) == 0, + "ServiceManagerBase.migrateOperatorToOperatorSets: already migrated" + ); + uint32[] memory operatorSetIds = getOperatorSetIds(operator); + + _operatorSetManager.registerOperatorToOperatorSets(operator, operatorSetIds, signature); + emit OperatorMigratedToOperatorSets(operator, operatorSetIds); + } + + /** + * @notice Once strategies have been migrated and operators have been migrated to operator sets. + * The ServiceManager owner can eject any operators that have yet to completely migrate fully to operator sets. + * This final step of the migration process will ensure the full migration of all operators to operator sets. + * @param operators The list of operators to eject for the given OperatorSet + * @param operatorSetId This AVS's operatorSetId to eject operators from + * @dev The RegistryCoordinator MUST set this ServiceManager contract to be the ejector address for this call to succeed + * @dev Note that operatorSetId should be a valid uint8 as this migration flow is meant for backwards compatability of + * existing AVS quorums that are stored as single bytes1 or uint8 values + */ + function ejectNonmigratedOperators( + address[] calldata operators, + uint32 operatorSetId + ) external onlyOwner { + require( + operatorSetId < _registryCoordinator.quorumCount(), + "ServiceManagerBase.ejectNonmigratedOperators: operatorSet does not exist" + ); + IOperatorSetManager.OperatorSet memory operatorSet = + IOperatorSetManager.OperatorSet({avs: address(this), id: operatorSetId}); + for (uint256 i = 0; i < operators.length; ++i) { + require( + _operatorSetManager.avsOperatorStatus(address(this), operators[i]) + == IOperatorSetManager.OperatorAVSRegistrationStatus.REGISTERED + && !_operatorSetManager.isOperatorInOperatorSet(operators[i], operatorSet), + "ServiceManagerBase.removeNonmigratedOperators: operator already registered to operator set" + ); + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(uint8(operatorSetId)); + _registryCoordinator.ejectOperator(operators[i], quorumNumbers); + } + } + /** * @notice Updates the metadata URI for the AVS * @param _metadataURI is the metadata URI for the AVS * @dev only callable by the owner */ function updateAVSMetadataURI(string memory _metadataURI) public virtual onlyOwner { - _avsDirectory.updateAVSMetadataURI(_metadataURI); + _operatorSetManager.updateAVSMetadataURI(_metadataURI); } /** @@ -87,15 +203,15 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { * @dev This function will revert if the `rewardsSubmission` is malformed, * e.g. if the `strategies` and `weights` arrays are of non-equal lengths */ - function createAVSRewardsSubmission(IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions) - public - virtual - onlyRewardsInitiator - { + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions + ) public virtual onlyRewardsInitiator { for (uint256 i = 0; i < rewardsSubmissions.length; ++i) { // transfer token to ServiceManager and approve RewardsCoordinator to transfer again // in createAVSRewardsSubmission() call - rewardsSubmissions[i].token.transferFrom(msg.sender, address(this), rewardsSubmissions[i].amount); + rewardsSubmissions[i].token.transferFrom( + msg.sender, address(this), rewardsSubmissions[i].amount + ); uint256 allowance = rewardsSubmissions[i].token.allowance(address(this), address(_rewardsCoordinator)); rewardsSubmissions[i].token.approve( @@ -115,15 +231,50 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature ) public virtual onlyRegistryCoordinator { - _avsDirectory.registerOperatorToAVS(operator, operatorSignature); + _operatorSetManager.registerOperatorToAVS(operator, operatorSignature); + } + + /** + * @notice Called by this AVS's RegistryCoordinator to register an operator for its registering operatorSets + * + * @param operator the address of the operator to be added to the operator set + * @param quorumNumbers quorums/operatorSetIds to add the operator to + * @param signature the signature of the operator on their intent to register + * @dev msg.sender is used as the AVS + * @dev operator must not have a pending a deregistration from the operator set + * @dev if this is the first operator set in the AVS that the operator is + * registering for, a OperatorAVSRegistrationStatusUpdated event is emitted with + * a REGISTERED status + */ + function registerOperatorToOperatorSets( + address operator, + bytes calldata quorumNumbers, + ISignatureUtils.SignatureWithSaltAndExpiry memory signature + ) external onlyRegistryCoordinator { + uint32[] memory operatorSetIds = quorumsToOperatorSetIds(quorumNumbers); + _operatorSetManager.registerOperatorToOperatorSets(operator, operatorSetIds, signature); } /** - * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS - * @param operator The address of the operator to deregister. + * @notice Called by this AVS's RegistryCoordinator to deregister an operator for its operatorSets + * + * @param operator the address of the operator to be removed from the + * operator set + * @param quorumNumbers the quorumNumbers/operatorSetIds to deregister the operator for + * + * @dev msg.sender is used as the AVS + * @dev operator must be registered for msg.sender AVS and the given + * operator set + * @dev if this removes operator from all operator sets for the msg.sender AVS + * then an OperatorAVSRegistrationStatusUpdated event is emitted with a DEREGISTERED + * status */ - function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator { - _avsDirectory.deregisterOperatorFromAVS(operator); + function deregisterOperatorFromOperatorSets( + address operator, + bytes calldata quorumNumbers + ) external onlyRegistryCoordinator { + uint32[] memory operatorSetIds = quorumsToOperatorSetIds(quorumNumbers); + _operatorSetManager.deregisterOperatorFromOperatorSets(operator, operatorSetIds); } /** @@ -212,8 +363,30 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { return restakedStrategies; } - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view override returns (address) { - return address(_avsDirectory); + /// @notice converts the quorumNumbers array to operatorSetIds array + function quorumsToOperatorSetIds(bytes memory quorumNumbers) + internal + pure + returns (uint32[] memory) + { + uint256 quorumCount = quorumNumbers.length; + uint32[] memory operatorSetIds = new uint32[](quorumCount); + for (uint256 i = 0; i < quorumCount; i++) { + operatorSetIds[i] = uint32(uint8(quorumNumbers[i])); + } + return operatorSetIds; + } + + /// @notice Returns the operator set IDs for the given operator address by querying the RegistryCoordinator + function getOperatorSetIds(address operator) public view returns (uint32[] memory) { + bytes32 operatorId = _registryCoordinator.getOperatorId(operator); + uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(operatorId); + bytes memory operatorQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap); + return quorumsToOperatorSetIds(operatorQuorums); + } + + /// @notice Returns the EigenLayer OperatorSetManager contract. + function operatorSetManager() external view override returns (address) { + return address(_operatorSetManager); } } diff --git a/src/ServiceManagerBaseStorage.sol b/src/ServiceManagerBaseStorage.sol index e0c1d86a..c7572941 100644 --- a/src/ServiceManagerBaseStorage.sol +++ b/src/ServiceManagerBaseStorage.sol @@ -7,7 +7,7 @@ import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; -import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IOperatorSetManager} from "./interfaces/IOperatorSetManager.sol"; // should be later changed to be import from core import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; /** @@ -21,7 +21,7 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab * CONSTANTS AND IMMUTABLES * */ - IAVSDirectory internal immutable _avsDirectory; + IOperatorSetManager internal immutable _operatorSetManager; IRewardsCoordinator internal immutable _rewardsCoordinator; IRegistryCoordinator internal immutable _registryCoordinator; IStakeRegistry internal immutable _stakeRegistry; @@ -37,12 +37,12 @@ abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeab /// @notice Sets the (immutable) `_avsDirectory`, `_rewardsCoordinator`, `_registryCoordinator`, and `_stakeRegistry` addresses constructor( - IAVSDirectory __avsDirectory, + IOperatorSetManager __operatorSetManager, IRewardsCoordinator __rewardsCoordinator, IRegistryCoordinator __registryCoordinator, IStakeRegistry __stakeRegistry ) { - _avsDirectory = __avsDirectory; + _operatorSetManager = __operatorSetManager; _rewardsCoordinator = __rewardsCoordinator; _registryCoordinator = __registryCoordinator; _stakeRegistry = __stakeRegistry; diff --git a/src/StakeRegistry.sol b/src/StakeRegistry.sol index 929b67d9..24dfba5c 100644 --- a/src/StakeRegistry.sol +++ b/src/StakeRegistry.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.12; import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol"; - +import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; @@ -236,17 +236,20 @@ contract StakeRegistry is StakeRegistryStorage { StrategyParams[] storage _strategyParams = strategyParams[quorumNumber]; IStrategy[] storage _strategiesPerQuorum = strategiesPerQuorum[quorumNumber]; + IStrategy[] memory strategiesToRemove = new IStrategy[](toRemoveLength); for (uint256 i = 0; i < toRemoveLength; i++) { emit StrategyRemovedFromQuorum(quorumNumber, _strategyParams[indicesToRemove[i]].strategy); emit StrategyMultiplierUpdated(quorumNumber, _strategyParams[indicesToRemove[i]].strategy, 0); - + strategiesToRemove[i] = _strategyParams[indicesToRemove[i]].strategy; // Replace index to remove with the last item in the list, then pop the last item _strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1]; _strategyParams.pop(); _strategiesPerQuorum[indicesToRemove[i]] = _strategiesPerQuorum[_strategiesPerQuorum.length - 1]; _strategiesPerQuorum.pop(); } + // push the added strategies to EigenLayer through the ServiceManager + serviceManager.removeStrategiesFromOperatorSet(uint32(quorumNumber), strategiesToRemove); } /** @@ -399,6 +402,7 @@ contract StakeRegistry is StakeRegistryStorage { require(_strategyParams.length > 0, "StakeRegistry._addStrategyParams: no strategies provided"); uint256 numStratsToAdd = _strategyParams.length; uint256 numStratsExisting = strategyParams[quorumNumber].length; + IStrategy[] memory strategiesToAdd = new IStrategy[](numStratsToAdd); require( numStratsExisting + numStratsToAdd <= MAX_WEIGHING_FUNCTION_LENGTH, "StakeRegistry._addStrategyParams: exceed MAX_WEIGHING_FUNCTION_LENGTH" @@ -417,6 +421,7 @@ contract StakeRegistry is StakeRegistryStorage { ); strategyParams[quorumNumber].push(_strategyParams[i]); strategiesPerQuorum[quorumNumber].push(_strategyParams[i].strategy); + strategiesToAdd[i] = _strategyParams[i].strategy; emit StrategyAddedToQuorum(quorumNumber, _strategyParams[i].strategy); emit StrategyMultiplierUpdated( quorumNumber, @@ -424,6 +429,8 @@ contract StakeRegistry is StakeRegistryStorage { _strategyParams[i].multiplier ); } + // push the added strategies to EigenLayer through the ServiceManager + serviceManager.addStrategiesToOperatorSet(uint32(quorumNumber), strategiesToAdd); } /// @notice Returns the change between a previous and current value as a signed int diff --git a/src/StakeRegistryStorage.sol b/src/StakeRegistryStorage.sol index 6ef74e3e..7832820f 100644 --- a/src/StakeRegistryStorage.sol +++ b/src/StakeRegistryStorage.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.12; import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; +import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; @@ -24,6 +25,9 @@ abstract contract StakeRegistryStorage is IStakeRegistry { /// @notice The address of the Delegation contract for EigenLayer. IDelegationManager public immutable delegation; + /// @notice the serviceManager contract that this registry is associated with + IServiceManager public immutable serviceManager; + /// @notice the coordinator contract that this registry is associated with address public immutable registryCoordinator; @@ -49,6 +53,7 @@ abstract contract StakeRegistryStorage is IStakeRegistry { IRegistryCoordinator _registryCoordinator, IDelegationManager _delegationManager ) { + serviceManager = _registryCoordinator.serviceManager(); registryCoordinator = address(_registryCoordinator); delegation = _delegationManager; } diff --git a/src/interfaces/IOperatorSetManager.sol b/src/interfaces/IOperatorSetManager.sol new file mode 100644 index 00000000..cd9ac15b --- /dev/null +++ b/src/interfaces/IOperatorSetManager.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; + +interface IOperatorSetManager is ISignatureUtils { + /** + * + * OperatorSetManager Interface + * + */ + + /// STRUCTS + + struct OperatorSet { + address avs; + uint32 id; + } + + /** + * @notice this struct is used by allocators in order to specify whether they want to register for particular operator sets + * @param operatorSet the operator set to change registration parameters for + * @param allowedToRegister whether or not the AVS is allowed to add them to the given operator set if they are not registered for it + */ + struct RegistrationParam { + OperatorSet operatorSet; + bool allowedToRegister; + } + + /** + * @notice this struct is used in SlashingMagnitudeParam in order to specify an operator's slashability for a certain operator set + * @param operatorSet the operator set to change slashing parameters for + * @param slashableMagnitude the proportional parts of the totalMagnitude that the operator set is getting. This ultimately determines how much slashable stake is delegated to a given AVS. (slashableMagnitude / totalMagnitude) of an operator's delegated stake. + */ + struct OperatorSetSlashingParam { + OperatorSet operatorSet; + uint64 slashableMagnitude; + } + + /** + * @notice A structure defining a set of operator-based slashing configurations to manage slashable stake + * @param strategy each slashable stake is defined within a single strategy + * @param totalMagnitude a virtual "denominator" magnitude from which to base portions of slashable stake to AVSs. + * @param operatorSetSlashingParams the fine grained parameters deiniting the AVSs ability to slash and register the operator for the operator set + */ + struct SlashingMagnitudeParam { + IStrategy strategy; + uint64 totalMagnitude; + OperatorSetSlashingParam[] operatorSetSlashingParams; + } + + /// EVENTS + + event RegistrationParamsUpdated( + address operator, OperatorSet operatorSet, bool allowedToRegister + ); + + event SlashableMagnitudeUpdated( + address operator, + IStrategy strategy, + OperatorSet operatorSet, + uint64 slashableMagnitude, + uint32 effectEpoch + ); + + event TotalMagnitudeUpdated( + address operator, IStrategy strategy, uint64 totalMagnitude, uint32 effectEpoch + ); + + /// EXTERNAL - STATE MODIFYING + + /** + * @notice Called by AVSs to add an operator to an operator set + * + * @param operator the address of the operator to be added to the operator set + * @param operatorSetIDs the IDs of the operator sets + * @param signature the signature of the operator on their intent to register + * @dev msg.sender is used as the AVS + * @dev operator must not have a pending a deregistration from the operator sets + * @dev if this is the first operator set in the AVS that the operator is + * registering for, a OperatorAVSRegistrationStatusUpdated event is emitted with + * a REGISTERED status + */ + function registerOperatorToOperatorSets( + address operator, + uint32[] calldata operatorSetIDs, + ISignatureUtils.SignatureWithSaltAndExpiry memory signature + ) external; + + /** + * @notice Called by AVSs or operators to remove an operator to from operator set + * + * @param operator the address of the operator to be removed from the + * operator set + * @param operatorSetIDs the ID of the operator set + * + * @dev msg.sender is used as the AVS + * @dev operator must be registered for msg.sender AVS and the given + * operator set + * @dev if this removes operator from all operator sets for the msg.sender AVS + * then an OperatorAVSRegistrationStatusUpdated event is emitted with a DEREGISTERED + * status + */ + function deregisterOperatorFromOperatorSets( + address operator, + uint32[] calldata operatorSetIDs + ) external; + + /** + * @notice Called by AVSs to add a strategy to its operator set + * + * @param operatorSetID the ID of the operator set + * @param strategies the list strategies of the operator set to add + * + * @dev msg.sender is used as the AVS + * @dev no storage is updated as the event is used by off-chain services + */ + function addStrategiesToOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external; + + /** + * @notice Called by AVSs to remove a strategy to its operator set + * + * @param operatorSetID the ID of the operator set + * @param strategies the list strategie of the operator set to remove + * + * @dev msg.sender is used as the AVS + * @dev no storage is updated as the event is used by off-chain services + */ + function removeStrategiesFromOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external; + + /// VIEW + + + /** + * @param operator the operator to get the status of + * @param operatorSet the operator set to check whether the operator was in + * + * @return whether the operator was in a given operator set + */ + function isOperatorInOperatorSet( + address operator, + OperatorSet calldata operatorSet + ) external view returns(bool); + + /** + * @notice retrieve the operator status for a given AVS, returns enum for either + * DEREGISTERED or REGISTERED + * @param avs the AVS to get the operator status for + * @param operator the operator to get the status for + */ + function avsOperatorStatus(address avs, address operator) external view returns (OperatorAVSRegistrationStatus); + + /// @notice number of operatorSets the operator is registered for a given AVS + function operatorAVSOperatorSetCount(address avs, address operator) external view returns (uint256); + + /// @notice mapping bool returning whether a strategy is part of an OperatorSet + function operatorSetStrategies(address avs, uint32 operatorSetId, IStrategy strategy) external view returns (bool); + + /** + * @param operator the operator to get allowedToRegister for + * @param operatorSet the operator set to get allowedToRegister for + * + * @return allowedToRegister whether or not operatorSet.avs is allowed to + * add them to the given operator set if they are not registered for it + */ + function getAllowedToRegister( + address operator, + OperatorSet calldata operatorSet + ) external returns (bool allowedToRegister); + + /** + * @param operator the operator to get the slashable bips for + * @param operatorSet the operator set to get the slashable bips for + * @param strategy the strategy to get the slashable bips for + * @param epoch the epoch to get the slashable bips for for + * + * @return slashableBips the slashable bips of the given strategy owned by + * the given OperatorSet for the given operator and epoch + */ + function getSlashableBips( + address operator, + OperatorSet calldata operatorSet, + IStrategy strategy, + uint32 epoch + ) external returns (uint16 slashableBips); + + /** + * + * AVSDirectory Interface + * + */ + + /// @notice Enum representing the status of an operator's registration with an AVS + enum OperatorAVSRegistrationStatus { + UNREGISTERED, // Operator not registered to AVS + REGISTERED // Operator registered to AVS + + } + + /** + * @notice Emitted when @param avs indicates that they are updating their MetadataURI string + * @dev Note that these strings are *never stored in storage* and are instead purely emitted in events for off-chain indexing + */ + event AVSMetadataURIUpdated(address indexed avs, string metadataURI); + + /// @notice Emitted when an operator's registration status for an AVS is updated + event OperatorAVSRegistrationStatusUpdated( + address indexed operator, address indexed avs, OperatorAVSRegistrationStatus status + ); + + /** + * @notice Called by an avs to register an operator with the avs. + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external; + + /** + * @notice Called by an avs to deregister an operator with the avs. + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS(address operator) external; + + /** + * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. + * @param metadataURI The URI for metadata associated with an AVS + * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event + */ + function updateAVSMetadataURI(string calldata metadataURI) external; + + /** + * @notice Returns whether or not the salt has already been used by the operator. + * @dev Salts is used in the `registerOperatorToAVS` function. + */ + function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool); + + /** + * @notice Calculates the digest hash to be signed by an operator to register with an AVS + * @param operator The account registering as an operator + * @param avs The AVS the operator is registering to + * @param salt A unique and single use value associated with the approver signature. + * @param expiry Time after which the approver's signature becomes invalid + */ + function calculateOperatorAVSRegistrationDigestHash( + address operator, + address avs, + bytes32 salt, + uint256 expiry + ) external view returns (bytes32); + + /// @notice The EIP-712 typehash for the Registration struct used by the contract + function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32); +} diff --git a/src/interfaces/IRegistryCoordinator.sol b/src/interfaces/IRegistryCoordinator.sol index 43fa7e0a..dbd1afd9 100644 --- a/src/interfaces/IRegistryCoordinator.sol +++ b/src/interfaces/IRegistryCoordinator.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; +import {IServiceManager} from "./IServiceManager.sol"; import {IBLSApkRegistry} from "./IBLSApkRegistry.sol"; import {IStakeRegistry} from "./IStakeRegistry.sol"; import {IIndexRegistry} from "./IIndexRegistry.sol"; @@ -14,10 +15,10 @@ interface IRegistryCoordinator { // EVENTS /// Emits when an operator is registered - event OperatorRegistered(address indexed operator, bytes32 indexed operatorId); + event OperatorRegistered(address indexed operator, bytes32 indexed operatorId, bytes indexed quorumNumbers); /// Emits when an operator is deregistered - event OperatorDeregistered(address indexed operator, bytes32 indexed operatorId); + event OperatorDeregistered(address indexed operator, bytes32 indexed operatorId, bytes indexed quorumNumbers); event OperatorSetParamsUpdated(uint8 indexed quorumNumber, OperatorSetParam operatorSetParams); @@ -83,6 +84,8 @@ interface IRegistryCoordinator { /// @notice Returns the operator set params for the given `quorumNumber` function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory); + /// @notice the ServiceManager for this AVS, which forwards calls onto EigenLayer's core contracts + function serviceManager() external view returns (IServiceManager); /// @notice the Stake registry contract that will keep track of operators' stakes function stakeRegistry() external view returns (IStakeRegistry); /// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' BLS aggregate pubkeys per quorum diff --git a/src/interfaces/IServiceManager.sol b/src/interfaces/IServiceManager.sol index ad953ec0..202acb35 100644 --- a/src/interfaces/IServiceManager.sol +++ b/src/interfaces/IServiceManager.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IOperatorSetManager, IStrategy} from "./IOperatorSetManager.sol"; // should be later changed to be import from core import {IServiceManagerUI} from "./IServiceManagerUI.sol"; /** @@ -9,6 +11,15 @@ import {IServiceManagerUI} from "./IServiceManagerUI.sol"; * @author Layr Labs, Inc. */ interface IServiceManager is IServiceManagerUI { + + /// EVENTS + + event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator); + event OperatorSetStrategiesMigrated(uint32 operatorSetId, IStrategy[] strategies); + event OperatorMigratedToOperatorSets(address operator, uint32[] indexed operatorSetIds); + + /// EXTERNAL - STATE MODIFYING + /** * @notice Creates a new rewards submission to the EigenLayer RewardsCoordinator contract, to be split amongst the * set of stakers delegated to operators who are registered to this `avs` @@ -20,8 +31,98 @@ interface IServiceManager is IServiceManagerUI { * @dev This function will revert if the `rewardsSubmission` is malformed, * e.g. if the `strategies` and `weights` arrays are of non-equal lengths */ - function createAVSRewardsSubmission(IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions) external; + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions + ) external; - // EVENTS - event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator); + + /** + * @notice called by the AVS StakeRegistry whenever a new IStrategy + * is added to a quorum/operatorSet + * @dev calls operatorSetManager.addStrategiesToOperatorSet() + */ + function addStrategiesToOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external; + + /** + * @notice called by the AVS StakeRegistry whenever a new IStrategy + * is removed from a quorum/operatorSet + * @dev calls operatorSetManager.removeStrategiesFromOperatorSet() + */ + function removeStrategiesFromOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external; + + /** + * @notice One-time call that migrates all existing strategies for each quorum to their respective operator sets + * Note: a separate migration per operator must be performed to migrate an existing operator to the operator set + * See migrateOperatorToOperatorSets() below + * @dev calls operatorSetManager.addStrategiesToOperatorSet() + */ + function migrateStrategiesToOperatorSets() external; + + /** + * @notice One-time call to migrate an existing operator to the respective operator sets. + * The operator needs to provide a signature over the operatorSetIds they are currently registered + * for. This can be retrieved externally by calling getOperatorSetIds. + * @param operator the address of the operator to be migrated + * @param signature the signature of the operator on their intent to migrate + * @dev calls operatorSetManager.registerOperatorToOperatorSets() + */ + function migrateOperatorToOperatorSets( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory signature + ) external; + + /** + * @notice Once strategies have been migrated and operators have been migrated to operator sets. + * The ServiceManager owner can eject any operators that have yet to completely migrate fully to operator sets. + * This final step of the migration process will ensure the full migration of all operators to operator sets. + * @param operators The list of operators to eject for the given OperatorSet + * @param operatorSetId This AVS's operatorSetId to eject operators from + * @dev The RegistryCoordinator MUST set this ServiceManager contract to be the ejector address for this call to succeed + */ + function ejectNonmigratedOperators( + address[] calldata operators, + uint32 operatorSetId + ) external; + + /** + * @notice Called by this AVS's RegistryCoordinator to register an operator for its registering operatorSets + * + * @param operator the address of the operator to be added to the operator set + * @param quorumNumbers quorums/operatorSetIds to add the operator to + * @param signature the signature of the operator on their intent to register + * @dev msg.sender should be the RegistryCoordinator + * @dev calls operatorSetManager.registerOperatorToOperatorSets() + */ + function registerOperatorToOperatorSets( + address operator, + bytes calldata quorumNumbers, + ISignatureUtils.SignatureWithSaltAndExpiry memory signature + ) external; + + /** + * @notice Called by this AVS's RegistryCoordinator to deregister an operator for its operatorSets + * + * @param operator the address of the operator to be removed from the + * operator set + * @param quorumNumbers the quorumNumbers/operatorSetIds to deregister the operator for + * + * @dev msg.sender should be the RegistryCoordinator + * @dev operator must be registered for the given operator sets + * @dev calls operatorSetManager.deregisterOperatorFromOperatorSets() + */ + function deregisterOperatorFromOperatorSets( + address operator, + bytes calldata quorumNumbers + ) external; + + /// VIEW + + /// @notice Returns the operator set IDs for the given operator address by querying the RegistryCoordinator + function getOperatorSetIds(address operator) external view returns (uint32[] memory); } diff --git a/src/interfaces/IServiceManagerUI.sol b/src/interfaces/IServiceManagerUI.sol index 9f06766c..3269bf94 100644 --- a/src/interfaces/IServiceManagerUI.sol +++ b/src/interfaces/IServiceManagerUI.sol @@ -24,21 +24,21 @@ interface IServiceManagerUI { */ function updateAVSMetadataURI(string memory _metadataURI) external; - /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS - * @param operator The address of the operator to register. - * @param operatorSignature The signature, salt, and expiry of the operator's signature. - */ - function registerOperatorToAVS( - address operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) external; + // /** + // * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS + // * @param operator The address of the operator to register. + // * @param operatorSignature The signature, salt, and expiry of the operator's signature. + // */ + // function registerOperatorToAVS( + // address operator, + // ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + // ) external; - /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS - * @param operator The address of the operator to deregister. - */ - function deregisterOperatorFromAVS(address operator) external; + // /** + // * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS + // * @param operator The address of the operator to deregister. + // */ + // function deregisterOperatorFromAVS(address operator) external; /** * @notice Returns the list of strategies that the operator has potentially restaked on the AVS @@ -57,6 +57,6 @@ interface IServiceManagerUI { */ function getRestakeableStrategies() external view returns (address[] memory); - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view returns (address); + /// @notice Returns the EigenLayer OperatorSetManager contract. + function operatorSetManager() external view returns (address); } diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index 6c1e23db..83f18e03 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -103,20 +103,20 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable _createAVSRewardsSubmission(rewardsSubmissions); } - /// @inheritdoc IServiceManagerUI - function registerOperatorToAVS( - address operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) external virtual onlyStakeRegistry { - _registerOperatorToAVS(operator, operatorSignature); - } + // /// @inheritdoc IServiceManagerUI + // function registerOperatorToAVS( + // address operator, + // ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + // ) external virtual onlyStakeRegistry { + // _registerOperatorToAVS(operator, operatorSignature); + // } - /// @inheritdoc IServiceManagerUI - function deregisterOperatorFromAVS( - address operator - ) external virtual onlyStakeRegistry { - _deregisterOperatorFromAVS(operator); - } + // /// @inheritdoc IServiceManagerUI + // function deregisterOperatorFromAVS( + // address operator + // ) external virtual onlyStakeRegistry { + // _deregisterOperatorFromAVS(operator); + // } /// @inheritdoc IServiceManagerUI function getRestakeableStrategies() diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index ab4bdbeb..36c18e01 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -350,7 +350,7 @@ contract ECDSAStakeRegistry is delete _operatorRegistered[_operator]; int256 delta = _updateOperatorWeight(_operator); _updateTotalWeight(delta); - IServiceManager(_serviceManager).deregisterOperatorFromAVS(_operator); + // IServiceManager(_serviceManager).deregisterOperatorFromAVS(_operator); emit OperatorDeregistered(_operator, address(_serviceManager)); } @@ -370,10 +370,10 @@ contract ECDSAStakeRegistry is int256 delta = _updateOperatorWeight(_operator); _updateTotalWeight(delta); _updateOperatorSigningKey(_operator, _signingKey); - IServiceManager(_serviceManager).registerOperatorToAVS( - _operator, - _operatorSignature - ); + // IServiceManager(_serviceManager).registerOperatorToAVS( + // _operator, + // _operatorSignature + // ); emit OperatorRegistered(_operator, _serviceManager); } diff --git a/test/harnesses/StakeRegistryHarness.sol b/test/harnesses/StakeRegistryHarness.sol index 386508d9..c52d91c1 100644 --- a/test/harnesses/StakeRegistryHarness.sol +++ b/test/harnesses/StakeRegistryHarness.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.12; import "../../src/StakeRegistry.sol"; +import {IServiceManager} from "../../src/interfaces/IServiceManager.sol"; + // wrapper around the StakeRegistry contract that exposes the internal functions for unit testing. contract StakeRegistryHarness is StakeRegistry { diff --git a/test/integration/CoreRegistration.t.sol b/test/integration/CoreRegistration.t.sol index 5998025c..41c334fd 100644 --- a/test/integration/CoreRegistration.t.sol +++ b/test/integration/CoreRegistration.t.sol @@ -69,7 +69,7 @@ contract Test_CoreRegistration is MockAVSDeployer { // Deploy New ServiceManager & RegistryCoordinator implementations serviceManagerImplementation = new ServiceManagerMock( - avsDirectory, + operatorSetManagerMock, rewardsCoordinatorMock, registryCoordinator, stakeRegistry diff --git a/test/integration/IntegrationDeployer.t.sol b/test/integration/IntegrationDeployer.t.sol index ec9075e9..d996ffd3 100644 --- a/test/integration/IntegrationDeployer.t.sol +++ b/test/integration/IntegrationDeployer.t.sol @@ -32,6 +32,8 @@ import "src/StakeRegistry.sol"; import "src/IndexRegistry.sol"; import "src/BLSApkRegistry.sol"; import "test/mocks/ServiceManagerMock.sol"; +import "src/interfaces/IOperatorSetManager.sol"; +import "test/mocks/OperatorSetManagerMock.sol"; import "src/OperatorStateRetriever.sol"; // Mocks and More @@ -50,6 +52,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // Core contracts to deploy DelegationManager delegationManager; AVSDirectory public avsDirectory; + IOperatorSetManager operatorSetManager; StrategyManager strategyManager; EigenPodManager eigenPodManager; RewardsCoordinator rewardsCoordinator; @@ -119,6 +122,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { pauserRegistry = new PauserRegistry(pausers, unpauser); // Deploy mocks + operatorSetManager = new OperatorSetManagerMock(); EmptyContract emptyContract = new EmptyContract(); ethPOSDeposit = new ETHPOSDepositMock(); beaconChainOracle = new BeaconChainOracleMock(); @@ -269,6 +273,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { 0 // initialPausedStatus ) ); + // // RewardsCoordinator // proxyAdmin.upgradeAndCall( // TransparentUpgradeableProxy(payable(address(rewardsCoordinator))), @@ -337,7 +342,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { IndexRegistry indexRegistryImplementation = new IndexRegistry(IRegistryCoordinator(registryCoordinator)); ServiceManagerMock serviceManagerImplementation = new ServiceManagerMock( - IAVSDirectory(avsDirectory), + operatorSetManager, rewardsCoordinator, IRegistryCoordinator(registryCoordinator), stakeRegistry diff --git a/test/integration/User.t.sol b/test/integration/User.t.sol index 2f89ab1a..6aea26a4 100644 --- a/test/integration/User.t.sol +++ b/test/integration/User.t.sol @@ -86,7 +86,7 @@ contract User is Test { delegationManager = DelegationManager(address(stakeRegistry.delegation())); strategyManager = StrategyManager(address(delegationManager.strategyManager())); - avsDirectory = AVSDirectory(address(serviceManager.avsDirectory())); + // avsDirectory = AVSDirectory(address(serviceManager.avsDirectory())); timeMachine = deployer.timeMachine(); diff --git a/test/mocks/OperatorSetManagerMock.sol b/test/mocks/OperatorSetManagerMock.sol new file mode 100644 index 00000000..69ace46e --- /dev/null +++ b/test/mocks/OperatorSetManagerMock.sol @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import { + IOperatorSetManager, + ISignatureUtils, + IStrategy +} from "../../src/interfaces/IOperatorSetManager.sol"; + +contract OperatorSetManagerMock is IOperatorSetManager { + + /** + * @notice updates the registration parameters for an operator for a set of + * operator sets. whether or not the AVS is allowed to add them to the given + * operator set if they are not registered for it + * + * @param operator the operator whom the registration parameters are being + * changed + * @param registrationParams the new registration parameters + * @param allocatorSignature if non-empty is the signature of the allocator on + * the modification. if empty, the msg.sender must be the allocator for the + * operator + * + * @dev changes take effect immediately + */ + function updateRegistrationParams( + address operator, + RegistrationParam[] calldata registrationParams, + SignatureWithExpiry calldata allocatorSignature + ) external {} + + /** + * @notice updates the slashing magnitudes for an operator for a set of + * operator sets + * + * @param operator the operator whom the slashing parameters are being + * changed + * @param slashingMagnitudeParams the new slashing parameters + * @param allocatorSignature if non-empty is the signature of the allocator on + * the modification. if empty, the msg.sender must be the allocator for the + * operator + * + * @dev changes take effect in 3 epochs for when this function is called + */ + function updateSlashingMagnitudes( + address operator, + SlashingMagnitudeParam[] calldata slashingMagnitudeParams, + SignatureWithExpiry calldata allocatorSignature + ) external returns(uint32 effectEpoch) {} + + /// @notice a batch call of updateRegistrationParams and updateSlashingMagnitudes + function updateRegistrationParamsAndSlashingMagnitudes( + address operator, + RegistrationParam[] calldata registrationParams, + SlashingMagnitudeParam[] calldata slashingMagnitudeParams, + SignatureWithExpiry calldata allocatorSignature + ) external returns(uint32 effectEpoch) {} + + function registerOperatorToOperatorSets( + address operator, + uint32[] calldata operatorSetIDs, + ISignatureUtils.SignatureWithSaltAndExpiry memory signature + ) external {} + + function deregisterOperatorFromOperatorSets( + address operator, + uint32[] calldata operatorSetIDs + ) external {} + + /** + * @notice Called by AVSs to add a strategy to its operator set + * + * @param operatorSetID the ID of the operator set + * @param strategies the list strategies of the operator set to add + * + * @dev msg.sender is used as the AVS + * @dev no storage is updated as the event is used by off-chain services + */ + function addStrategiesToOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external {} + + /** + * @notice Called by AVSs to remove a strategy to its operator set + * + * @param operatorSetID the ID of the operator set + * @param strategies the list strategie of the operator set to remove + * + * @dev msg.sender is used as the AVS + * @dev no storage is updated as the event is used by off-chain services + */ + function removeStrategiesFromOperatorSet( + uint32 operatorSetID, + IStrategy[] calldata strategies + ) external {} + + /** + * @notice retrieve the operator status for a given AVS, returns enum for either + * DEREGISTERED or REGISTERED + * @param avs the AVS to get the operator status for + * @param operator the operator to get the status for + */ + function avsOperatorStatus(address avs, address operator) external view returns (OperatorAVSRegistrationStatus) {} + + /// @notice number of operatorSets the operator is registered for a given AVS + function operatorAVSOperatorSetCount(address avs, address operator) external view returns (uint256) {} + + /// @notice mapping bool returning whether a strategy is part of an OperatorSet + function operatorSetStrategies(address avs, uint32 operatorSetId, IStrategy strategy) external view returns (bool) {} + + /** + * @param operator the operator to get allowedToRegister for + * @param operatorSet the operator set to get allowedToRegister for + * + * @return allowedToRegister whether or not operatorSet.avs is allowed to + * add them to the given operator set if they are not registered for it + */ + function getAllowedToRegister( + address operator, + OperatorSet calldata operatorSet + ) external returns (bool allowedToRegister) {} + + /** + * @param operator the operator to get the slashable bips for + * @param operatorSet the operator set to get the slashable bips for + * @param strategy the strategy to get the slashable bips for + * @param epoch the epoch to get the slashable bips for for + * + * @return slashableBips the slashable bips of the given strategy owned by + * the given OperatorSet for the given operator and epoch + */ + function getSlashableBips( + address operator, + OperatorSet calldata operatorSet, + IStrategy strategy, + uint32 epoch + ) external returns (uint16 slashableBips) {} + + function isOperatorInOperatorSet( + address operator, + OperatorSet calldata operatorSet + ) external view returns(bool) {} + + /** + * @notice Called by an avs to register an operator with the avs. + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external {} + + /** + * @notice Called by an avs to deregister an operator with the avs. + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS(address operator) external {} + + /** + * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. + * @param metadataURI The URI for metadata associated with an AVS + * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event + */ + function updateAVSMetadataURI(string calldata metadataURI) external {} + + /** + * @notice Returns whether or not the salt has already been used by the operator. + * @dev Salts is used in the `registerOperatorToAVS` function. + */ + function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool) {} + + /** + * @notice Calculates the digest hash to be signed by an operator to register with an AVS + * @param operator The account registering as an operator + * @param avs The AVS the operator is registering to + * @param salt A unique and single use value associated with the approver signature. + * @param expiry Time after which the approver's signature becomes invalid + */ + function calculateOperatorAVSRegistrationDigestHash( + address operator, + address avs, + bytes32 salt, + uint256 expiry + ) external view returns (bytes32) {} + + /// @notice The EIP-712 typehash for the Registration struct used by the contract + function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32) {} +} diff --git a/test/mocks/RegistryCoordinatorMock.sol b/test/mocks/RegistryCoordinatorMock.sol index abee1a6a..e1442033 100644 --- a/test/mocks/RegistryCoordinatorMock.sol +++ b/test/mocks/RegistryCoordinatorMock.sol @@ -6,6 +6,9 @@ import "../../src/interfaces/IRegistryCoordinator.sol"; contract RegistryCoordinatorMock is IRegistryCoordinator { + + function serviceManager() external view returns (IServiceManager) {} + function blsApkRegistry() external view returns (IBLSApkRegistry) {} function ejectOperator( diff --git a/test/mocks/ServiceManagerMock.sol b/test/mocks/ServiceManagerMock.sol index 8af99426..2563fc3c 100644 --- a/test/mocks/ServiceManagerMock.sol +++ b/test/mocks/ServiceManagerMock.sol @@ -5,12 +5,12 @@ import "../../src/ServiceManagerBase.sol"; contract ServiceManagerMock is ServiceManagerBase { constructor( - IAVSDirectory _avsDirectory, + IOperatorSetManager _operatorSetManager, IRewardsCoordinator _rewardsCoordinator, IRegistryCoordinator _registryCoordinator, IStakeRegistry _stakeRegistry ) - ServiceManagerBase(_avsDirectory, _rewardsCoordinator, _registryCoordinator, _stakeRegistry) + ServiceManagerBase(_operatorSetManager, _rewardsCoordinator, _registryCoordinator, _stakeRegistry) {} function initialize( diff --git a/test/unit/ServiceManagerBase.t.sol b/test/unit/ServiceManagerBase.t.sol index 2b04dabd..2c16d294 100644 --- a/test/unit/ServiceManagerBase.t.sol +++ b/test/unit/ServiceManagerBase.t.sol @@ -77,7 +77,7 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve ); // Deploy ServiceManager serviceManagerImplementation = new ServiceManagerMock( - avsDirectory, + operatorSetManagerMock, rewardsCoordinator, registryCoordinatorImplementation, stakeRegistryImplementation diff --git a/test/unit/ServiceManagerRouter.t.sol b/test/unit/ServiceManagerRouter.t.sol index 9fc2c0f7..933d3961 100644 --- a/test/unit/ServiceManagerRouter.t.sol +++ b/test/unit/ServiceManagerRouter.t.sol @@ -16,7 +16,7 @@ contract ServiceManagerRouter_UnitTests is MockAVSDeployer { // Deploy dummy serviceManager dummyServiceManager = new ServiceManagerMock( - avsDirectory, + operatorSetManagerMock, rewardsCoordinatorImplementation, registryCoordinatorImplementation, stakeRegistryImplementation diff --git a/test/utils/MockAVSDeployer.sol b/test/utils/MockAVSDeployer.sol index 23875760..be4652ae 100644 --- a/test/utils/MockAVSDeployer.sol +++ b/test/utils/MockAVSDeployer.sol @@ -28,6 +28,7 @@ import {IServiceManager} from "../../src/interfaces/IServiceManager.sol"; import {StrategyManagerMock} from "eigenlayer-contracts/src/test/mocks/StrategyManagerMock.sol"; import {EigenPodManagerMock} from "eigenlayer-contracts/src/test/mocks/EigenPodManagerMock.sol"; import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; +import {OperatorSetManagerMock} from "../mocks/OperatorSetManagerMock.sol"; import {DelegationMock} from "../mocks/DelegationMock.sol"; import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; @@ -76,6 +77,9 @@ contract MockAVSDeployer is Test { AVSDirectory public avsDirectory; AVSDirectory public avsDirectoryImplementation; AVSDirectoryMock public avsDirectoryMock; + // OperatorSetManager public operatorSetManager; + // OperatorSetManager public operatorSetManagerImplementation; + OperatorSetManagerMock public operatorSetManagerMock; RewardsCoordinator public rewardsCoordinator; RewardsCoordinator public rewardsCoordinatorImplementation; RewardsCoordinatorMock public rewardsCoordinatorMock; @@ -147,6 +151,7 @@ contract MockAVSDeployer is Test { delegationMock = new DelegationMock(); avsDirectoryMock = new AVSDirectoryMock(); + operatorSetManagerMock = new OperatorSetManagerMock(); eigenPodManagerMock = new EigenPodManagerMock(); strategyManagerMock = new StrategyManagerMock(); slasherImplementation = new Slasher(strategyManagerMock, delegationMock); @@ -164,7 +169,6 @@ contract MockAVSDeployer is Test { ) ) ); - avsDirectoryMock = new AVSDirectoryMock(); avsDirectoryImplementation = new AVSDirectory(delegationMock); avsDirectory = AVSDirectory( address( @@ -243,7 +247,7 @@ contract MockAVSDeployer is Test { ); serviceManagerImplementation = new ServiceManagerMock( - avsDirectoryMock, + operatorSetManagerMock, IRewardsCoordinator(address(rewardsCoordinatorMock)), registryCoordinator, stakeRegistry