Skip to content

Commit 86f0928

Browse files
authored
feat(op sets): update stakes when forceUnregister (#300)
* feat: update stakes handle direct deregistration on AVSDirectory * test: update stake for quorum if operator directly unregistered from the AVSDirectory * chore: simplify setup * chore: simplify setup * chore: make service manager immutable on stakeRegistry
1 parent 32148de commit 86f0928

9 files changed

+135
-11
lines changed

src/StakeRegistry.sol

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
pragma solidity ^0.8.12;
33

44
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
5+
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
6+
import {IServiceManager} from "./interfaces/IServiceManager.sol";
57

68
import {StakeRegistryStorage, IStrategy} from "./StakeRegistryStorage.sol";
79

@@ -40,8 +42,10 @@ contract StakeRegistry is StakeRegistryStorage {
4042

4143
constructor(
4244
IRegistryCoordinator _registryCoordinator,
43-
IDelegationManager _delegationManager
44-
) StakeRegistryStorage(_registryCoordinator, _delegationManager) {}
45+
IDelegationManager _delegationManager,
46+
IAVSDirectory _avsDirectory,
47+
IServiceManager _serviceManager
48+
) StakeRegistryStorage(_registryCoordinator, _delegationManager, _avsDirectory, _serviceManager) {}
4549

4650
/*******************************************************************************
4751
EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
@@ -148,6 +152,10 @@ contract StakeRegistry is StakeRegistryStorage {
148152
) external onlyRegistryCoordinator returns (uint192) {
149153
uint192 quorumsToRemove;
150154

155+
bool isOperatorSetAVS = avsDirectory.isOperatorSetAVS(
156+
address(serviceManager)
157+
);
158+
151159
/**
152160
* For each quorum, update the operator's stake and record the delta
153161
* in the quorum's total stake.
@@ -163,9 +171,21 @@ contract StakeRegistry is StakeRegistryStorage {
163171
// Fetch the operator's current stake, applying weighting parameters and checking
164172
// against the minimum stake requirements for the quorum.
165173
(uint96 stakeWeight, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
166-
167174
// If the operator no longer meets the minimum stake, set their stake to zero and mark them for removal
168-
if (!hasMinimumStake) {
175+
/// also handle setting the operator's stake to 0 and remove them from the quorum
176+
/// if they directly unregistered from the AVSDirectory bubbles up info via registry coordinator to deregister them
177+
bool operatorRegistered;
178+
// Convert quorumNumber to operatorSetId
179+
uint32 operatorSetId = uint32(quorumNumber);
180+
181+
// Get the AVSDirectory address from the RegistryCoordinator
182+
// Query the AVSDirectory to check if the operator is directly unregistered
183+
operatorRegistered = avsDirectory.isMember(
184+
operator,
185+
IAVSDirectory.OperatorSet(address(serviceManager), operatorSetId)
186+
);
187+
188+
if (!hasMinimumStake || (isOperatorSetAVS && !operatorRegistered)) {
169189
stakeWeight = 0;
170190
quorumsToRemove = uint192(quorumsToRemove.setBit(quorumNumber));
171191
}

src/StakeRegistryStorage.sol

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
pragma solidity ^0.8.12;
33

44
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
5+
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
6+
import {IServiceManager} from "./interfaces/IServiceManager.sol";
57
import {IStrategyManager, IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol";
68

79
import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
@@ -24,6 +26,12 @@ abstract contract StakeRegistryStorage is IStakeRegistry {
2426
/// @notice The address of the Delegation contract for EigenLayer.
2527
IDelegationManager public immutable delegation;
2628

29+
/// @notice The address of the Delegation contract for EigenLayer.
30+
IAVSDirectory public immutable avsDirectory;
31+
32+
/// @notice the address of the ServiceManager associtated with the stake registries
33+
IServiceManager public immutable serviceManager;
34+
2735
/// @notice the coordinator contract that this registry is associated with
2836
address public immutable registryCoordinator;
2937

@@ -47,10 +55,14 @@ abstract contract StakeRegistryStorage is IStakeRegistry {
4755

4856
constructor(
4957
IRegistryCoordinator _registryCoordinator,
50-
IDelegationManager _delegationManager
58+
IDelegationManager _delegationManager,
59+
IAVSDirectory _avsDirectory,
60+
IServiceManager _serviceManager
5161
) {
5262
registryCoordinator = address(_registryCoordinator);
5363
delegation = _delegationManager;
64+
avsDirectory = _avsDirectory;
65+
serviceManager = _serviceManager;
5466
}
5567

5668
// storage gap for upgradeability

src/interfaces/IRegistryCoordinator.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity ^0.8.12;
33

4+
import {IServiceManager} from "./IServiceManager.sol";
45
import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
56
import {IStakeRegistry} from "./IStakeRegistry.sol";
67
import {IIndexRegistry} from "./IIndexRegistry.sol";
@@ -150,4 +151,6 @@ interface IRegistryCoordinator {
150151

151152
/// @notice The owner of the registry coordinator
152153
function owner() external view returns (address);
154+
155+
function serviceManager() external view returns (IServiceManager);
153156
}

test/harnesses/StakeRegistryHarness.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import "../../src/StakeRegistry.sol";
77
contract StakeRegistryHarness is StakeRegistry {
88
constructor(
99
IRegistryCoordinator _registryCoordinator,
10-
IDelegationManager _delegationManager
11-
) StakeRegistry(_registryCoordinator, _delegationManager) {
10+
IDelegationManager _delegationManager,
11+
IAVSDirectory _avsDirectory,
12+
IServiceManager _serviceManager
13+
) StakeRegistry(_registryCoordinator, _delegationManager, _avsDirectory, _serviceManager) {
1214
}
1315

1416
function recordOperatorStakeUpdate(bytes32 operatorId, uint8 quorumNumber, uint96 newStake) external returns(int256) {

test/integration/IntegrationDeployer.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer {
330330
cheats.stopPrank();
331331

332332
StakeRegistry stakeRegistryImplementation = new StakeRegistry(
333-
IRegistryCoordinator(registryCoordinator), IDelegationManager(delegationManager)
333+
IRegistryCoordinator(registryCoordinator), IDelegationManager(delegationManager), IAVSDirectory(avsDirectory), IServiceManager(serviceManager)
334334
);
335335
BLSApkRegistry blsApkRegistryImplementation =
336336
new BLSApkRegistry(IRegistryCoordinator(registryCoordinator));

test/mocks/RegistryCoordinatorMock.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,6 @@ contract RegistryCoordinatorMock is IRegistryCoordinator {
6868
function quorumUpdateBlockNumber(uint8 quorumNumber) external view returns (uint256) {}
6969

7070
function owner() external view returns (address) {}
71-
}
71+
72+
function serviceManager() external view returns (IServiceManager){}
73+
}

test/unit/RegistryCoordinatorMigration.t.sol

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,89 @@ contract RegistryCoordinatorMigrationUnit is MockAVSDeployer, IServiceManagerBas
197197
assertTrue(avsDirectory.isOperatorSet(address(serviceManager), quorumNumber), "Operator set was not created for the quorum");
198198

199199
}
200+
201+
function test_updateOperatorsForQuorumsAfterDirectUnregister() public {
202+
vm.prank(proxyAdmin.owner());
203+
proxyAdmin.upgrade(
204+
TransparentUpgradeableProxy(payable(address(avsDirectory))),
205+
address(avsDirectoryMock)
206+
);
207+
uint256 pseudoRandomNumber = uint256(keccak256("pseudoRandomNumber"));
208+
_registerRandomOperators(pseudoRandomNumber);
209+
210+
vm.prank(proxyAdmin.owner());
211+
proxyAdmin.upgrade(
212+
TransparentUpgradeableProxy(payable(address(avsDirectory))),
213+
address(avsDirectoryHarness)
214+
);
215+
216+
uint256 quorumCount = registryCoordinator.quorumCount();
217+
for (uint256 i = 0; i < quorumCount; i++) {
218+
uint256 operatorCount = indexRegistry.totalOperatorsForQuorum(uint8(i));
219+
bytes32[] memory operatorIds =
220+
indexRegistry.getOperatorListAtBlockNumber(uint8(i), uint32(block.number));
221+
assertEq(operatorCount, operatorIds.length, "Operator Id length mismatch"); // sanity check
222+
for (uint256 j = 0; j < operatorCount; j++) {
223+
address operatorAddress =
224+
registryCoordinator.blsApkRegistry().getOperatorFromPubkeyHash(operatorIds[j]);
225+
AVSDirectoryHarness(address(avsDirectory)).setAvsOperatorStatus(
226+
address(serviceManager),
227+
operatorAddress,
228+
IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED
229+
);
230+
}
231+
}
232+
233+
(
234+
uint32[] memory operatorSetsToCreate,
235+
uint32[][] memory operatorSetIdsToMigrate,
236+
address[] memory operators
237+
) = serviceManager.getOperatorsToMigrate();
238+
cheats.startPrank(serviceManagerOwner);
239+
serviceManager.migrateAndCreateOperatorSetIds(operatorSetsToCreate);
240+
serviceManager.migrateToOperatorSets(operatorSetIdsToMigrate, operators);
241+
cheats.stopPrank();
242+
243+
bytes32[] memory registeredOperators = indexRegistry.getOperatorListAtBlockNumber(defaultQuorumNumber, uint32(block.number));
244+
uint256 preNumOperators = registeredOperators.length;
245+
address[] memory registeredOperatorAddresses = new address[](registeredOperators.length);
246+
for (uint256 i = 0; i < registeredOperators.length; i++) {
247+
registeredOperatorAddresses[i] = registryCoordinator.blsApkRegistry().pubkeyHashToOperator(registeredOperators[i]);
248+
}
249+
250+
uint32[] memory operatorSetsToUnregister = new uint32[](1);
251+
operatorSetsToUnregister[0] = defaultQuorumNumber;
252+
253+
vm.prank(operators[0]);
254+
avsDirectory.forceDeregisterFromOperatorSets(
255+
operators[0],
256+
address(serviceManager),
257+
operatorSetsToUnregister,
258+
ISignatureUtils.SignatureWithSaltAndExpiry({
259+
signature: new bytes(0),
260+
salt: bytes32(0),
261+
expiry: 0
262+
})
263+
);
264+
// sanity check if the operator was unregistered from the intended operator set
265+
bool operatorIsUnRegistered = !avsDirectory.isMember(operators[0], IAVSDirectory.OperatorSet({
266+
avs: address(serviceManager),
267+
operatorSetId: defaultQuorumNumber
268+
}));
269+
bool isOperatorSetAVS = avsDirectory.isOperatorSetAVS(address(serviceManager));
270+
assertTrue(isOperatorSetAVS, "ServiceManager is not an operator set AVS");
271+
assertTrue(operatorIsUnRegistered, "Operator wasnt unregistered from op set");
272+
273+
address[][] memory registeredOperatorAddresses2D = new address[][](1);
274+
registeredOperatorAddresses2D[0] = registeredOperatorAddresses;
275+
bytes memory quorumNumbers = new bytes(1);
276+
quorumNumbers[0] = bytes1(defaultQuorumNumber);
277+
registryCoordinator.updateOperatorsForQuorum(registeredOperatorAddresses2D, quorumNumbers);
278+
279+
registeredOperators = indexRegistry.getOperatorListAtBlockNumber(defaultQuorumNumber, uint32(block.number));
280+
uint256 postRegisteredOperators = registeredOperators.length;
281+
282+
assertEq(preNumOperators-1, postRegisteredOperators, "");
283+
284+
}
200285
}

test/unit/StakeRegistryUnit.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents {
5252
);
5353

5454
stakeRegistryImplementation = new StakeRegistryHarness(
55-
IRegistryCoordinator(address(registryCoordinator)), delegationMock
55+
IRegistryCoordinator(address(registryCoordinator)), delegationMock, avsDirectoryMock, serviceManager
5656
);
5757

5858
stakeRegistry = StakeRegistryHarness(

test/utils/MockAVSDeployer.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ contract MockAVSDeployer is Test {
222222
cheats.startPrank(proxyAdminOwner);
223223

224224
stakeRegistryImplementation =
225-
new StakeRegistryHarness(IRegistryCoordinator(registryCoordinator), delegationMock);
225+
new StakeRegistryHarness(IRegistryCoordinator(registryCoordinator), delegationMock, avsDirectory, serviceManager);
226226

227227
proxyAdmin.upgrade(
228228
TransparentUpgradeableProxy(payable(address(stakeRegistry))),

0 commit comments

Comments
 (0)