Skip to content

Commit f6ad20e

Browse files
authored
feat(op sets): register and deregister (#301)
* feat: register and deregister to operator sets * fix: bytecode wrangling * chore: shorter errors to reduce bytecode * test: register after migration * chore: remove stale todos * chore: add back commented line * chore: remain consistent with m2 events * fix: side effect of merge from _deregister function * fix: bytecode massaging * chore: rename for clarity * chore: remove comments and whitespace * docs: add natspec to the library
1 parent 3e4cb2b commit f6ad20e

15 files changed

+494
-133
lines changed

src/RegistryCoordinator.sol

Lines changed: 73 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8.12;
33

44
import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol";
55
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
6-
import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol";
6+
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
77
import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol";
88
import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
99
import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
@@ -14,6 +14,7 @@ import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
1414
import {BitmapUtils} from "./libraries/BitmapUtils.sol";
1515
import {BN254} from "./libraries/BN254.sol";
1616
import {SignatureCheckerLib} from "./libraries/SignatureCheckerLib.sol";
17+
import {QuorumBitmapHistoryLib} from "./libraries/QuorumBitmapHistoryLib.sol";
1718

1819
import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
1920
import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
@@ -58,9 +59,10 @@ contract RegistryCoordinator is
5859
IServiceManager _serviceManager,
5960
IStakeRegistry _stakeRegistry,
6061
IBLSApkRegistry _blsApkRegistry,
61-
IIndexRegistry _indexRegistry
62+
IIndexRegistry _indexRegistry,
63+
IAVSDirectory _avsDirectory
6264
)
63-
RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry)
65+
RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry, _avsDirectory)
6466
EIP712("AVSRegistryCoordinator", "v0.0.1")
6567
{
6668
_disableInitializers();
@@ -158,7 +160,7 @@ contract RegistryCoordinator is
158160

159161
require(
160162
numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
161-
"RegistryCoordinator.registerOperator: operator count exceeds maximum"
163+
"RegistryCoordinator.registerOperator: operator exceeds max"
162164
);
163165
}
164166
}
@@ -333,7 +335,7 @@ contract RegistryCoordinator is
333335
// Prevent duplicate operators
334336
require(
335337
operator > prevOperatorAddress,
336-
"RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order"
338+
"RegistryCoordinator.updateOperatorsForQuorum: operators must be sorted"
337339
);
338340
}
339341

@@ -355,7 +357,7 @@ contract RegistryCoordinator is
355357
function updateSocket(string memory socket) external {
356358
require(
357359
_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED,
358-
"RegistryCoordinator.updateSocket: operator is not registered"
360+
"RegistryCoordinator.updateSocket: not registered"
359361
);
360362
emit OperatorSocketUpdate(_operatorInfo[msg.sender].operatorId, socket);
361363
}
@@ -486,7 +488,7 @@ contract RegistryCoordinator is
486488
uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
487489
uint192 currentBitmap = _currentOperatorBitmap(operatorId);
488490
require(
489-
!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0"
491+
!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap empty"
490492
);
491493
require(
492494
quorumsToAdd.noBitsInCommon(currentBitmap),
@@ -515,9 +517,20 @@ contract RegistryCoordinator is
515517
OperatorInfo({operatorId: operatorId, status: OperatorStatus.REGISTERED});
516518

517519
// Register the operator with the EigenLayer core contracts via this AVS's ServiceManager
518-
serviceManager.registerOperatorToAVS(operator, operatorSignature);
520+
bool operatorSetAVS = avsDirectory.isOperatorSetAVS(address(serviceManager));
521+
if (operatorSetAVS){
522+
bytes memory quorumBytes = BitmapUtils.bitmapToBytesArray(quorumsToAdd);
523+
uint32[] memory operatorSetIds = new uint32[](quorumBytes.length);
524+
for (uint256 i = 0; i < quorumBytes.length; i++) {
525+
operatorSetIds[i] = uint8(quorumBytes[i]);
526+
}
527+
serviceManager.registerOperatorToOperatorSets(operator, operatorSetIds, operatorSignature);
528+
529+
} else {
530+
serviceManager.registerOperatorToAVS(operator, operatorSignature);
531+
emit OperatorRegistered(operator, operatorId);
532+
}
519533

520-
emit OperatorRegistered(operator, operatorId);
521534
}
522535

523536
// Register the operator with the BLSApkRegistry, StakeRegistry, and IndexRegistry
@@ -534,7 +547,7 @@ contract RegistryCoordinator is
534547
* @dev Reverts if the caller is not the ejector
535548
*/
536549
function _checkEjector() internal view {
537-
require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector");
550+
require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: not ejector");
538551
}
539552

540553
/**
@@ -628,7 +641,7 @@ contract RegistryCoordinator is
628641
bytes32 operatorId = operatorInfo.operatorId;
629642
require(
630643
operatorInfo.status == OperatorStatus.REGISTERED,
631-
"RegistryCoordinator._deregisterOperator: operator is not registered"
644+
"RegistryCoordinator._deregisterOperator: not registered"
632645
);
633646

634647
/**
@@ -647,19 +660,54 @@ contract RegistryCoordinator is
647660
);
648661
require(
649662
quorumsToRemove.isSubsetOf(currentBitmap),
650-
"RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums"
663+
"RegistryCoordinator._deregisterOperator: not registered for quorum"
651664
);
652665
uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove));
653666

654667
// Update operator's bitmap and status
655668
_updateOperatorBitmap({operatorId: operatorId, newBitmap: newBitmap});
656669

657-
// If the operator is no longer registered for any quorums, update their status and deregister
658-
// them from the AVS via the EigenLayer core contracts
659-
if (newBitmap.isEmpty()) {
660-
operatorInfo.status = OperatorStatus.DEREGISTERED;
661-
serviceManager.deregisterOperatorFromAVS(operator);
662-
emit OperatorDeregistered(operator, operatorId);
670+
671+
bool operatorSetAVS = IAVSDirectory(serviceManager.avsDirectory()).isOperatorSetAVS(address(serviceManager));
672+
if (operatorSetAVS){
673+
bytes memory quorumBytes = BitmapUtils.bitmapToBytesArray(quorumsToRemove);
674+
uint32[] memory operatorSetIds = new uint32[](quorumBytes.length);
675+
uint256 forceDeregistrationCount;
676+
for (uint256 i = 0; i < quorumBytes.length; i++) {
677+
/// We need to track forceDeregistrations so we don't pass an id that was already deregistered on the AVSDirectory
678+
/// but hasnt yet been recorded in the middleware contracts
679+
if (!avsDirectory.isMember(operator, IAVSDirectory.OperatorSet(address(serviceManager), uint8(quorumBytes[i])))){
680+
forceDeregistrationCount++;
681+
}
682+
operatorSetIds[i] = uint8(quorumBytes[i]);
683+
}
684+
685+
/// Filter out forceDeregistration operator set Ids
686+
if (forceDeregistrationCount > 0 ){
687+
uint32[] memory filteredOperatorSetIds = new uint32[](operatorSetIds.length - forceDeregistrationCount);
688+
uint256 offset;
689+
for (uint256 i; i < operatorSetIds.length; i++){
690+
if (avsDirectory.isMember(operator, IAVSDirectory.OperatorSet(address(serviceManager), operatorSetIds[i]))){
691+
filteredOperatorSetIds[i] = operatorSetIds[i+offset];
692+
} else {
693+
offset++;
694+
}
695+
}
696+
serviceManager.deregisterOperatorFromOperatorSets(operator, filteredOperatorSetIds);
697+
} else {
698+
serviceManager.deregisterOperatorFromOperatorSets(operator, operatorSetIds);
699+
700+
}
701+
702+
703+
} else {
704+
// If the operator is no longer registered for any quorums, update their status and deregister
705+
// them from the AVS via the EigenLayer core contracts
706+
if (newBitmap.isEmpty()) {
707+
operatorInfo.status = OperatorStatus.DEREGISTERED;
708+
serviceManager.deregisterOperatorFromAVS(operator);
709+
emit OperatorDeregistered(operator, operatorId);
710+
}
663711
}
664712

665713
// Deregister operator with each of the registry contracts
@@ -726,11 +774,11 @@ contract RegistryCoordinator is
726774
// make sure the salt hasn't been used already
727775
require(
728776
!isChurnApproverSaltUsed[churnApproverSignature.salt],
729-
"RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used"
777+
"RegistryCoordinator._verifyChurnApproverSignature: salt spent"
730778
);
731779
require(
732780
churnApproverSignature.expiry >= block.timestamp,
733-
"RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired"
781+
"RegistryCoordinator._verifyChurnApproverSignature: signature expired"
734782
);
735783

736784
// set salt used to true
@@ -780,7 +828,6 @@ contract RegistryCoordinator is
780828
indexRegistry.initializeQuorum(quorumNumber);
781829
blsApkRegistry.initializeQuorum(quorumNumber);
782830
// Check if the AVS has migrated to operator sets
783-
AVSDirectory avsDirectory = AVSDirectory(serviceManager.avsDirectory());
784831
if (avsDirectory.isOperatorSetAVS(address(serviceManager))) {
785832
// Create an operator set for the new quorum
786833
uint32[] memory operatorSetIds = new uint32[](1);
@@ -794,50 +841,13 @@ contract RegistryCoordinator is
794841
* @param newBitmap is the most up-to-date set of bitmaps the operator is registered for
795842
*/
796843
function _updateOperatorBitmap(bytes32 operatorId, uint192 newBitmap) internal {
797-
uint256 historyLength = _operatorBitmapHistory[operatorId].length;
798-
799-
if (historyLength == 0) {
800-
// No prior bitmap history - push our first entry
801-
_operatorBitmapHistory[operatorId].push(
802-
QuorumBitmapUpdate({
803-
updateBlockNumber: uint32(block.number),
804-
nextUpdateBlockNumber: 0,
805-
quorumBitmap: newBitmap
806-
})
807-
);
808-
} else {
809-
// We have prior history - fetch our last-recorded update
810-
QuorumBitmapUpdate storage lastUpdate =
811-
_operatorBitmapHistory[operatorId][historyLength - 1];
812-
813-
/**
814-
* If the last update was made in the current block, update the entry.
815-
* Otherwise, push a new entry and update the previous entry's "next" field
816-
*/
817-
if (lastUpdate.updateBlockNumber == uint32(block.number)) {
818-
lastUpdate.quorumBitmap = newBitmap;
819-
} else {
820-
lastUpdate.nextUpdateBlockNumber = uint32(block.number);
821-
_operatorBitmapHistory[operatorId].push(
822-
QuorumBitmapUpdate({
823-
updateBlockNumber: uint32(block.number),
824-
nextUpdateBlockNumber: 0,
825-
quorumBitmap: newBitmap
826-
})
827-
);
828-
}
829-
}
844+
QuorumBitmapHistoryLib.updateOperatorBitmap(_operatorBitmapHistory, operatorId, newBitmap);
830845
}
831846

832847
/// @notice Get the most recent bitmap for the operator, returning an empty bitmap if
833848
/// the operator is not registered.
834849
function _currentOperatorBitmap(bytes32 operatorId) internal view returns (uint192) {
835-
uint256 historyLength = _operatorBitmapHistory[operatorId].length;
836-
if (historyLength == 0) {
837-
return 0;
838-
} else {
839-
return _operatorBitmapHistory[operatorId][historyLength - 1].quorumBitmap;
840-
}
850+
return QuorumBitmapHistoryLib.currentOperatorBitmap(_operatorBitmapHistory, operatorId);
841851
}
842852

843853
/**
@@ -849,21 +859,7 @@ contract RegistryCoordinator is
849859
uint32 blockNumber,
850860
bytes32 operatorId
851861
) internal view returns (uint32 index) {
852-
uint256 length = _operatorBitmapHistory[operatorId].length;
853-
854-
// Traverse the operator's bitmap history in reverse, returning the first index
855-
// corresponding to an update made before or at `blockNumber`
856-
for (uint256 i = 0; i < length; i++) {
857-
index = uint32(length - i - 1);
858-
859-
if (_operatorBitmapHistory[operatorId][index].updateBlockNumber <= blockNumber) {
860-
return index;
861-
}
862-
}
863-
864-
revert(
865-
"RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"
866-
);
862+
return QuorumBitmapHistoryLib.getQuorumBitmapIndexAtBlockNumber(_operatorBitmapHistory,blockNumber, operatorId);
867863
}
868864

869865
function _setOperatorSetParams(
@@ -932,11 +928,7 @@ contract RegistryCoordinator is
932928
uint32 blockNumber,
933929
bytes32[] memory operatorIds
934930
) external view returns (uint32[] memory) {
935-
uint32[] memory indices = new uint32[](operatorIds.length);
936-
for (uint256 i = 0; i < operatorIds.length; i++) {
937-
indices[i] = _getQuorumBitmapIndexAtBlockNumber(blockNumber, operatorIds[i]);
938-
}
939-
return indices;
931+
return QuorumBitmapHistoryLib.getQuorumBitmapIndicesAtBlockNumber(_operatorBitmapHistory, blockNumber, operatorIds);
940932
}
941933

942934
/**
@@ -950,24 +942,7 @@ contract RegistryCoordinator is
950942
uint32 blockNumber,
951943
uint256 index
952944
) external view returns (uint192) {
953-
QuorumBitmapUpdate memory quorumBitmapUpdate = _operatorBitmapHistory[operatorId][index];
954-
955-
/**
956-
* Validate that the update is valid for the given blockNumber:
957-
* - blockNumber should be >= the update block number
958-
* - the next update block number should be either 0 or strictly greater than blockNumber
959-
*/
960-
require(
961-
blockNumber >= quorumBitmapUpdate.updateBlockNumber,
962-
"RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"
963-
);
964-
require(
965-
quorumBitmapUpdate.nextUpdateBlockNumber == 0
966-
|| blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber,
967-
"RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber"
968-
);
969-
970-
return quorumBitmapUpdate.quorumBitmap;
945+
return QuorumBitmapHistoryLib.getQuorumBitmapAtBlockNumberByIndex(_operatorBitmapHistory, operatorId, blockNumber, index);
971946
}
972947

973948
/// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history

src/RegistryCoordinatorStorage.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
55
import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
66
import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
77
import {IServiceManager} from "./interfaces/IServiceManager.sol";
8+
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
89
import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
910

1011
abstract contract RegistryCoordinatorStorage is IRegistryCoordinator {
@@ -39,6 +40,8 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator {
3940
IStakeRegistry public immutable stakeRegistry;
4041
/// @notice the Index Registry contract that will keep track of operators' indexes
4142
IIndexRegistry public immutable indexRegistry;
43+
/// @notice the AVS Directory that tracks operator registrations to AVS and operator sets
44+
IAVSDirectory public immutable avsDirectory;
4245

4346
/*******************************************************************************
4447
STATE
@@ -73,12 +76,14 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator {
7376
IServiceManager _serviceManager,
7477
IStakeRegistry _stakeRegistry,
7578
IBLSApkRegistry _blsApkRegistry,
76-
IIndexRegistry _indexRegistry
79+
IIndexRegistry _indexRegistry,
80+
IAVSDirectory _avsDirectory
7781
) {
7882
serviceManager = _serviceManager;
7983
stakeRegistry = _stakeRegistry;
8084
blsApkRegistry = _blsApkRegistry;
8185
indexRegistry = _indexRegistry;
86+
avsDirectory = _avsDirectory;
8287
}
8388

8489
// storage gap for upgradeability

src/ServiceManagerBase.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,32 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage {
133133
_avsDirectory.deregisterOperatorFromAVS(operator);
134134
}
135135

136+
/**
137+
* @notice Forwards a call to EigenLayer's AVSDirectory contract to register an operator to operator sets
138+
* @param operator The address of the operator to register.
139+
* @param operatorSetIds The IDs of the operator sets.
140+
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
141+
*/
142+
function registerOperatorToOperatorSets(
143+
address operator,
144+
uint32[] calldata operatorSetIds,
145+
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
146+
) public virtual onlyRegistryCoordinator {
147+
_avsDirectory.registerOperatorToOperatorSets(operator, operatorSetIds, operatorSignature);
148+
}
149+
150+
/**
151+
* @notice Forwards a call to EigenLayer's AVSDirectory contract to deregister an operator from operator sets
152+
* @param operator The address of the operator to deregister.
153+
* @param operatorSetIds The IDs of the operator sets.
154+
*/
155+
function deregisterOperatorFromOperatorSets(
156+
address operator,
157+
uint32[] calldata operatorSetIds
158+
) public virtual onlyRegistryCoordinator {
159+
_avsDirectory.deregisterOperatorFromOperatorSets(operator, operatorSetIds);
160+
}
161+
136162
/**
137163
* @notice Sets the rewards initiator address
138164
* @param newRewardsInitiator The new rewards initiator address

0 commit comments

Comments
 (0)