-
Notifications
You must be signed in to change notification settings - Fork 114
feat: poc for OperatorSets #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
76a4912
feat: add strategies to OSM
8sunyuan 9015229
feat: opsets reg/dereg with strategy migration
8sunyuan 7384f3a
chore: fix add require
8sunyuan db81923
feat: operator set migration
8sunyuan f4b0d57
chore: fmt
8sunyuan 2425104
chore: natspec
8sunyuan b777600
feat: eject nonmigrated operators
8sunyuan 9880a8d
fix: comments
8sunyuan 78f35d4
fix: added require in eject
8sunyuan 5bd70a6
fix: eject just the one operatorSet
8sunyuan 9fbe3c4
Merge branch 'dev' into feat/operator-sets
stevennevins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we just not check this at all and have the call to OSM revert? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also works assuming it will revert |
||
| !_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), | ||
8sunyuan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "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); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.