From ef6fa6d3300cb55bfe23de356e8256375f1f9386 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Wed, 30 Apr 2025 22:22:46 -0400 Subject: [PATCH 1/3] fix: enforce quorum registration on churn --- src/SlashingRegistryCoordinator.sol | 5 +++ .../ISlashingRegistryCoordinator.sol | 2 + .../SlashingRegistryCoordinatorUnit.t.sol | 41 +++++++++++++++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/SlashingRegistryCoordinator.sol b/src/SlashingRegistryCoordinator.sol index 0eae566d..ff5f67aa 100644 --- a/src/SlashingRegistryCoordinator.sol +++ b/src/SlashingRegistryCoordinator.sol @@ -677,6 +677,7 @@ contract SlashingRegistryCoordinator is /** * @notice Validates that an incoming operator is eligible to replace an existing * operator based on the stake of both + * @dev In order to be churned out, the existing operator must be registered for the quorum * @dev In order to churn, the incoming operator needs to have more stake than the * existing operator by a proportion given by `kickBIPsOfOperatorStake` * @dev In order to be churned out, the existing operator needs to have a proportion @@ -704,6 +705,10 @@ contract SlashingRegistryCoordinator is bytes32 idToKick = _operatorInfo[operatorToKick].operatorId; require(newOperator != operatorToKick, CannotChurnSelf()); require(kickParams.quorumNumber == quorumNumber, QuorumOperatorCountMismatch()); + require( + quorumNumber.isSubsetOf(_currentOperatorBitmap(idToKick)), + OperatorNotRegisteredForQuorum() + ); // Get the target operator's stake and check that it is below the kick thresholds uint96 operatorToKickStake = stakeRegistry.getCurrentStake(idToKick, quorumNumber); diff --git a/src/interfaces/ISlashingRegistryCoordinator.sol b/src/interfaces/ISlashingRegistryCoordinator.sol index 1259dea4..6ffd1de9 100644 --- a/src/interfaces/ISlashingRegistryCoordinator.sol +++ b/src/interfaces/ISlashingRegistryCoordinator.sol @@ -59,6 +59,8 @@ interface ISlashingRegistryCoordinatorErrors { error LookAheadPeriodTooLong(); /// @notice Thrown when the number of operators in a quorum would exceed the maximum allowed. error MaxOperatorCountReached(); + /// @notice Thrown when the operator is not registered for the quorum. + error OperatorNotRegisteredForQuorum(); } interface ISlashingRegistryCoordinatorTypes { diff --git a/test/unit/SlashingRegistryCoordinatorUnit.t.sol b/test/unit/SlashingRegistryCoordinatorUnit.t.sol index a2cf0c4e..5af8cd52 100644 --- a/test/unit/SlashingRegistryCoordinatorUnit.t.sol +++ b/test/unit/SlashingRegistryCoordinatorUnit.t.sol @@ -1937,16 +1937,49 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is ); } + /// @dev Asserts that an operator cannot be churned out if it is not registered for the quorum + /// @dev We register the operator to kick in quorum 2 so that it is still registered after deregistration function test_registerOperatorWithChurn_revert_notRegisteredForQuorum() public { _setOperatorWeight(testOperator.key.addr, registeringStake); + _setOperatorWeight(operatorToKick.key.addr, operatorToKickStake); + + // Create a new quorum + IStakeRegistryTypes.StrategyParams[] memory strategyParams = + new IStakeRegistryTypes.StrategyParams[](1); + strategyParams[0] = + IStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 1 ether}); + + vm.startPrank(proxyAdminOwner); + slashingRegistryCoordinator.createTotalDelegatedStakeQuorum( + operatorSetParams, + 1 ether, // minimum stake + strategyParams + ); + vm.stopPrank(); - Operator memory unregisteredOperator = operatorsByID[operatorIds.at(5)]; - _setOperatorWeight(unregisteredOperator.key.addr, operatorToKickStake); + // Register the operatorToKick in the new quorum + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 2; + registerOperatorInSlashingRegistryCoordinator(operatorToKick, "socket:8545", operatorSetIds); + + // Deregister the operatorToKick from quorum 1 + IAllocationManagerTypes.DeregisterParams memory deregisterParams = IAllocationManagerTypes + .DeregisterParams({ + operator: operatorToKick.key.addr, + avs: address(serviceManager), + operatorSetIds: operatorSetIds + }); + vm.prank(operatorToKick.key.addr); + IAllocationManager(coreDeployment.allocationManager).deregisterFromOperatorSets( + deregisterParams + ); + + // Try to churn the operator ISlashingRegistryCoordinatorTypes.OperatorKickParam[] memory operatorKickParams = new ISlashingRegistryCoordinatorTypes.OperatorKickParam[](quorumNumbers.length); operatorKickParams[0] = ISlashingRegistryCoordinatorTypes.OperatorKickParam({ - operator: unregisteredOperator.key.addr, + operator: operatorToKick.key.addr, quorumNumber: uint8(quorumNumbers[0]) }); @@ -1980,7 +2013,7 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is } vm.prank(testOperator.key.addr); - vm.expectRevert(abi.encodeWithSignature("OperatorNotRegistered()")); + vm.expectRevert(abi.encodeWithSignature("OperatorNotRegisteredForQuorum()")); IAllocationManager(coreDeployment.allocationManager).registerForOperatorSets( testOperator.key.addr, registerParams ); From 488b54ab537324c556f2b69e5d0d056cf920796b Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Wed, 30 Apr 2025 23:48:26 -0400 Subject: [PATCH 2/3] chore: fix test --- src/SlashingRegistryCoordinator.sol | 5 ++- .../SlashingRegistryCoordinatorUnit.t.sol | 37 +++++++------------ 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/SlashingRegistryCoordinator.sol b/src/SlashingRegistryCoordinator.sol index ff5f67aa..6915f63c 100644 --- a/src/SlashingRegistryCoordinator.sol +++ b/src/SlashingRegistryCoordinator.sol @@ -705,8 +705,11 @@ contract SlashingRegistryCoordinator is bytes32 idToKick = _operatorInfo[operatorToKick].operatorId; require(newOperator != operatorToKick, CannotChurnSelf()); require(kickParams.quorumNumber == quorumNumber, QuorumOperatorCountMismatch()); + + uint192 quorumBitmap; + quorumBitmap = uint192(BitmapUtils.setBit(quorumBitmap, quorumNumber)); require( - quorumNumber.isSubsetOf(_currentOperatorBitmap(idToKick)), + quorumBitmap.isSubsetOf(_currentOperatorBitmap(idToKick)), OperatorNotRegisteredForQuorum() ); diff --git a/test/unit/SlashingRegistryCoordinatorUnit.t.sol b/test/unit/SlashingRegistryCoordinatorUnit.t.sol index 5af8cd52..f09c1933 100644 --- a/test/unit/SlashingRegistryCoordinatorUnit.t.sol +++ b/test/unit/SlashingRegistryCoordinatorUnit.t.sol @@ -1938,7 +1938,6 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is } /// @dev Asserts that an operator cannot be churned out if it is not registered for the quorum - /// @dev We register the operator to kick in quorum 2 so that it is still registered after deregistration function test_registerOperatorWithChurn_revert_notRegisteredForQuorum() public { _setOperatorWeight(testOperator.key.addr, registeringStake); _setOperatorWeight(operatorToKick.key.addr, operatorToKickStake); @@ -1949,6 +1948,12 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is strategyParams[0] = IStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 1 ether}); + operatorSetParams = ISlashingRegistryCoordinatorTypes.OperatorSetParam({ + maxOperatorCount: 1, + kickBIPsOfOperatorStake: 5000, + kickBIPsOfTotalStake: 5000 + }); + vm.startPrank(proxyAdminOwner); slashingRegistryCoordinator.createTotalDelegatedStakeQuorum( operatorSetParams, @@ -1957,30 +1962,16 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is ); vm.stopPrank(); - // Register the operatorToKick in the new quorum - uint32[] memory operatorSetIds = new uint32[](1); - operatorSetIds[0] = 2; - registerOperatorInSlashingRegistryCoordinator(operatorToKick, "socket:8545", operatorSetIds); + // Register extra operator in the new quorum + registerOperatorInSlashingRegistryCoordinator(extraOperator1, "socket:8545", uint32(2)); - // Deregister the operatorToKick from quorum 1 - IAllocationManagerTypes.DeregisterParams memory deregisterParams = IAllocationManagerTypes - .DeregisterParams({ - operator: operatorToKick.key.addr, - avs: address(serviceManager), - operatorSetIds: operatorSetIds - }); - - vm.prank(operatorToKick.key.addr); - IAllocationManager(coreDeployment.allocationManager).deregisterFromOperatorSets( - deregisterParams - ); - // Try to churn the operator + // Setup churn data ISlashingRegistryCoordinatorTypes.OperatorKickParam[] memory operatorKickParams = new ISlashingRegistryCoordinatorTypes.OperatorKickParam[](quorumNumbers.length); operatorKickParams[0] = ISlashingRegistryCoordinatorTypes.OperatorKickParam({ operator: operatorToKick.key.addr, - quorumNumber: uint8(quorumNumbers[0]) + quorumNumber: uint8(2) // 3rd quorum }); ISignatureUtilsMixinTypes.SignatureWithSaltAndExpiry memory churnApproverSignature = @@ -1988,7 +1979,7 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is testOperator.key.addr, testOperatorId, operatorKickParams, - bytes32(uint256(3)), // Different salt from previous tests + bytes32(uint256(4)), defaultExpiry ); @@ -1998,7 +1989,7 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is IAllocationManagerTypes.RegisterParams memory registerParams = IAllocationManagerTypes .RegisterParams({ avs: address(serviceManager), - operatorSetIds: new uint32[](quorumNumbers.length), + operatorSetIds: new uint32[](1), data: abi.encode( ISlashingRegistryCoordinatorTypes.RegistrationType.CHURN, "socket:8545", @@ -2008,9 +1999,7 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is ) }); - for (uint256 i = 0; i < quorumNumbers.length; i++) { - registerParams.operatorSetIds[i] = uint8(quorumNumbers[i]); - } + registerParams.operatorSetIds[0] = uint32(2); vm.prank(testOperator.key.addr); vm.expectRevert(abi.encodeWithSignature("OperatorNotRegisteredForQuorum()")); From 7f5b9143aa8579ebae514ffe4843d0c205523b40 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Thu, 1 May 2025 10:47:51 -0400 Subject: [PATCH 3/3] chore: fmt --- test/unit/SlashingRegistryCoordinatorUnit.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/SlashingRegistryCoordinatorUnit.t.sol b/test/unit/SlashingRegistryCoordinatorUnit.t.sol index f09c1933..ce9e642b 100644 --- a/test/unit/SlashingRegistryCoordinatorUnit.t.sol +++ b/test/unit/SlashingRegistryCoordinatorUnit.t.sol @@ -1965,7 +1965,6 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is // Register extra operator in the new quorum registerOperatorInSlashingRegistryCoordinator(extraOperator1, "socket:8545", uint32(2)); - // Setup churn data ISlashingRegistryCoordinatorTypes.OperatorKickParam[] memory operatorKickParams = new ISlashingRegistryCoordinatorTypes.OperatorKickParam[](quorumNumbers.length);