From 58eda0924696bf5e251d4999a6128acf178d06fd Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 8 Jan 2025 07:52:14 -0500 Subject: [PATCH 1/4] feat: add ejection to the ALM flows for quourms --- src/RegistryCoordinator.sol | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 99073714..c952c32b 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -6,6 +6,7 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IAVSDirectory } from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import {IStrategy } from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; import { IAllocationManager, OperatorSet, IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import { AllocationManager } from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol"; import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol"; import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; import {IStakeRegistry, StakeType} from "./interfaces/IStakeRegistry.sol"; @@ -479,6 +480,40 @@ contract RegistryCoordinator is operatorInfo.status == OperatorStatus.REGISTERED && !quorumsToRemove.isEmpty() && quorumsToRemove.isSubsetOf(currentBitmap) ) { + // If using operator sets, call out to AllocationManager to eject operator + if (isUsingOperatorSets()) { + // Count non-M2 quorums to size array + uint256 operatorSetIdCount; + for (uint256 i = 0; i < quorumNumbers.length; i++) { + if (!isM2Quorum[uint8(quorumNumbers[i])]) { + operatorSetIdCount++; + } + } + + // Get operator sets for quorums being removed + uint32[] memory operatorSetIds = new uint32[](quorumNumbers.length); + uint256 operatorSetIndex = 0; + for (uint256 i = 0; i < quorumNumbers.length; i++) { + if (!isM2Quorum[uint8(quorumNumbers[i])]) { + operatorSetIds[operatorSetIndex] = uint8(quorumNumbers[i]); + operatorSetIndex++; + } + } + + assembly { + mstore(operatorSetIds, operatorSetIdCount) + } + + // Call AllocationManager to deregister operator from sets + AllocationManager(serviceManager.allocationManager()).deregisterFromOperatorSets( + IAllocationManagerTypes.DeregisterParams({ + operator: operator, + avs: address(serviceManager), + operatorSetIds: operatorSetIds + }) + ); + } + _deregisterOperator({operator: operator, quorumNumbers: quorumNumbers}); } } From 65abdb125eaa2a12a1bc442a1ce9e886c6211fcd Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 8 Jan 2025 08:20:25 -0500 Subject: [PATCH 2/4] chore: use interface --- src/RegistryCoordinator.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index c952c32b..2375e791 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -6,7 +6,6 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IAVSDirectory } from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import {IStrategy } from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; import { IAllocationManager, OperatorSet, IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import { AllocationManager } from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol"; import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol"; import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; import {IStakeRegistry, StakeType} from "./interfaces/IStakeRegistry.sol"; @@ -505,7 +504,7 @@ contract RegistryCoordinator is } // Call AllocationManager to deregister operator from sets - AllocationManager(serviceManager.allocationManager()).deregisterFromOperatorSets( + IAllocationManager(serviceManager.allocationManager()).deregisterFromOperatorSets( IAllocationManagerTypes.DeregisterParams({ operator: operator, avs: address(serviceManager), From 0fe885320c87601a94b3474e214464b5b0eb8e96 Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 8 Jan 2025 10:08:12 -0500 Subject: [PATCH 3/4] test: wip mocked state --- src/RegistryCoordinator.sol | 9 +- src/ServiceManagerBase.sol | 4 + src/interfaces/IServiceManager.sol | 6 ++ test/mocks/ECDSAServiceManagerMock.sol | 1 + test/unit/RegistryCoordinatorUnit.t.sol | 124 +++++++++++------------- 5 files changed, 71 insertions(+), 73 deletions(-) diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 2375e791..26ce77b9 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -503,14 +503,15 @@ contract RegistryCoordinator is mstore(operatorSetIds, operatorSetIdCount) } - // Call AllocationManager to deregister operator from sets - IAllocationManager(serviceManager.allocationManager()).deregisterFromOperatorSets( + IAllocationManagerTypes.DeregisterParams memory params = IAllocationManagerTypes.DeregisterParams({ operator: operator, avs: address(serviceManager), operatorSetIds: operatorSetIds - }) - ); + }); + serviceManager.ejectOperators(params); + + } _deregisterOperator({operator: operator, quorumNumbers: quorumNumbers}); diff --git a/src/ServiceManagerBase.sol b/src/ServiceManagerBase.sol index 86c1e937..30a0f287 100644 --- a/src/ServiceManagerBase.sol +++ b/src/ServiceManagerBase.sol @@ -175,6 +175,10 @@ abstract contract ServiceManagerBase is ServiceManagerBaseStorage { })); } + function ejectOperators(IAllocationManagerTypes.DeregisterParams memory params) external onlyRegistryCoordinator { + _allocationManager.deregisterFromOperatorSets(params); + } + /** * @notice Sets the rewards initiator address * @param newRewardsInitiator The new rewards initiator address diff --git a/src/interfaces/IServiceManager.sol b/src/interfaces/IServiceManager.sol index 133c3789..2a4f649f 100644 --- a/src/interfaces/IServiceManager.sol +++ b/src/interfaces/IServiceManager.sol @@ -50,6 +50,12 @@ interface IServiceManager is IServiceManagerUI { function slashOperator(IAllocationManagerTypes.SlashingParams memory params) external; + /** + * @notice Ejects operators from operator sets + * @param params The deregistration parameters containing operator address, AVS address and operator set IDs + */ + function ejectOperators(IAllocationManagerTypes.DeregisterParams memory params) external; + // EVENTS event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator); event SlasherUpdated(address prevSlasher, address newSlasher); diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol index 102d076e..2ec47acf 100644 --- a/test/mocks/ECDSAServiceManagerMock.sol +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -45,4 +45,5 @@ contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { function slashOperator(IAllocationManagerTypes.SlashingParams memory params) external override { // Mock implementation - no actual slashing occurs } + function ejectOperators(IAllocationManagerTypes.DeregisterParams memory params) external{} } diff --git a/test/unit/RegistryCoordinatorUnit.t.sol b/test/unit/RegistryCoordinatorUnit.t.sol index c3c8032d..7b043fff 100644 --- a/test/unit/RegistryCoordinatorUnit.t.sol +++ b/test/unit/RegistryCoordinatorUnit.t.sol @@ -2165,69 +2165,6 @@ contract RegistryCoordinatorUnitTests_AfterMigration is RegistryCoordinatorUnitT registryCoordinator.registerOperator(defaultOperator, operatorSetIds, data); } - function test_registerHook_WithChurn() public { - _deployMockEigenLayerAndAVS(0); - // Enable operator sets first - cheats.prank(registryCoordinatorOwner); - registryCoordinator.enableOperatorSets(); - - // Create quorum params - IRegistryCoordinator.OperatorSetParam memory operatorSetParams = IRegistryCoordinator.OperatorSetParam({ - maxOperatorCount: 10, - kickBIPsOfOperatorStake: 1000, - kickBIPsOfTotalStake: 100 - }); - - uint96 minimumStake = 100; - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](1); - strategyParams[0] = IStakeRegistry.StrategyParams({ - strategy: IStrategy(address(1)), - multiplier: 10000 - }); - - // Create total delegated stake quorum - cheats.prank(registryCoordinatorOwner); - registryCoordinator.createTotalDelegatedStakeQuorum( - operatorSetParams, - 0, - strategyParams - ); - - uint32[] memory operatorSetIds = new uint32[](1); - operatorSetIds[0] = 0; - - string memory socket = "socket"; - IBLSApkRegistry.PubkeyRegistrationParams memory params; - // TODO: - // params = IBLSApkRegistry.PubkeyRegistrationParams({ - // pubkeyG1: defaultPubKey, - // pubkeyG2: defaultPubKeyG2, - // pubkeySignature: defaultPubKeySignature - // }); - - IRegistryCoordinator.OperatorKickParam[] memory operatorKickParams = new IRegistryCoordinator.OperatorKickParam[](1); - operatorKickParams[0] = IRegistryCoordinator.OperatorKickParam({ - operator: address(0x1), - quorumNumber: 0 - }); - - ISignatureUtils.SignatureWithSaltAndExpiry memory churnApproverSignature; - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - - bytes memory registerParams = abi.encode( - socket, - params, - operatorKickParams, - churnApproverSignature, - operatorSignature - ); - - // Prank as allocation manager and call register hook - address allocationManager = address(serviceManager.allocationManager()); - cheats.prank(allocationManager); - registryCoordinator.registerOperator(defaultOperator, operatorSetIds, registerParams); - } - function test_updateStakesForQuorum() public { vm.skip(true); _deployMockEigenLayerAndAVS(0); @@ -2311,7 +2248,7 @@ contract RegistryCoordinatorUnitTests_AfterMigration is RegistryCoordinatorUnitT cheats.stopPrank(); } - function test_registerHook_Reverts_WhenNotALM() public { + function test_registerHook_Reverts_WhenNotALM() public { _deployMockEigenLayerAndAVS(0); // Enable operator sets first @@ -2414,12 +2351,61 @@ contract RegistryCoordinatorUnitTests_AfterMigration is RegistryCoordinatorUnitT } - function test_DeregisterHook_Reverts_WhenM2Quorum() public { - vm.skip(true); - } + function test_ejectOperator_fromOperatorSetQuorum() public { + _deployMockEigenLayerAndAVS(0); + cheats.prank(registryCoordinatorOwner); + registryCoordinator.enableOperatorSets(); - function test_registerHook_Reverts_WhenM2Quorum() public { - vm.skip(true); + IRegistryCoordinator.OperatorSetParam memory operatorSetParams = IRegistryCoordinator.OperatorSetParam({ + maxOperatorCount: 10, + kickBIPsOfOperatorStake: 1000, + kickBIPsOfTotalStake: 100 + }); + + uint96 minimumStake = 100; + IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](1); + strategyParams[0] = IStakeRegistry.StrategyParams({ + strategy: IStrategy(address(1)), + multiplier: 10000 + }); + + cheats.prank(registryCoordinatorOwner); + registryCoordinator.createTotalDelegatedStakeQuorum( + operatorSetParams, + 0, + strategyParams + ); + + // Register operator using operatorSetIds + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 0; + + string memory socket = "socket"; + IBLSApkRegistry.PubkeyRegistrationParams memory params; + bytes memory data = abi.encode(socket, params); + + address allocationManager = address(serviceManager.allocationManager()); + cheats.startPrank(allocationManager); + registryCoordinator.registerOperator(defaultOperator, operatorSetIds, data); + cheats.stopPrank(); + + // Convert operator set ID to quorum bytes for ejection + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(uint8(0)); // Quorum 0 + + // Set ejector address + address ejector = address(0x123); + cheats.prank(registryCoordinatorOwner); + registryCoordinator.setEjector(ejector); + + // Eject operator + cheats.prank(ejector); + registryCoordinator.ejectOperator(defaultOperator, quorumNumbers); + + // Verify operator is deregistered + IRegistryCoordinator.OperatorInfo memory operatorInfo = registryCoordinator.getOperator(defaultOperator); + assertEq(uint8(operatorInfo.status), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + assertEq(registryCoordinator.getCurrentQuorumBitmap(defaultOperatorId), 0); } } From dd01862b9c67e556d5fd779785102f0bd0d34d6b Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 9 Jan 2025 10:10:44 -0500 Subject: [PATCH 4/4] test: wip --- test/unit/RegistryCoordinatorUnit.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/RegistryCoordinatorUnit.t.sol b/test/unit/RegistryCoordinatorUnit.t.sol index 7b043fff..5244fae4 100644 --- a/test/unit/RegistryCoordinatorUnit.t.sol +++ b/test/unit/RegistryCoordinatorUnit.t.sol @@ -2399,6 +2399,7 @@ contract RegistryCoordinatorUnitTests_AfterMigration is RegistryCoordinatorUnitT registryCoordinator.setEjector(ejector); // Eject operator + /// TODO: Mocked state issue cheats.prank(ejector); registryCoordinator.ejectOperator(defaultOperator, quorumNumbers);