Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/SlashingRegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -705,6 +706,13 @@ contract SlashingRegistryCoordinator is
require(newOperator != operatorToKick, CannotChurnSelf());
require(kickParams.quorumNumber == quorumNumber, QuorumOperatorCountMismatch());

uint192 quorumBitmap;
quorumBitmap = uint192(BitmapUtils.setBit(quorumBitmap, quorumNumber));
require(
quorumBitmap.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);
require(
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/ISlashingRegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
41 changes: 31 additions & 10 deletions test/unit/SlashingRegistryCoordinatorUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1937,25 +1937,48 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is
);
}

/// @dev Asserts that an operator cannot be churned out if it is not registered for the quorum
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});

operatorSetParams = ISlashingRegistryCoordinatorTypes.OperatorSetParam({
maxOperatorCount: 1,
kickBIPsOfOperatorStake: 5000,
kickBIPsOfTotalStake: 5000
});

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 extra operator in the new quorum
registerOperatorInSlashingRegistryCoordinator(extraOperator1, "socket:8545", uint32(2));

// Setup churn data
ISlashingRegistryCoordinatorTypes.OperatorKickParam[] memory operatorKickParams =
new ISlashingRegistryCoordinatorTypes.OperatorKickParam[](quorumNumbers.length);
operatorKickParams[0] = ISlashingRegistryCoordinatorTypes.OperatorKickParam({
operator: unregisteredOperator.key.addr,
quorumNumber: uint8(quorumNumbers[0])
operator: operatorToKick.key.addr,
quorumNumber: uint8(2) // 3rd quorum
});

ISignatureUtilsMixinTypes.SignatureWithSaltAndExpiry memory churnApproverSignature =
_signChurnApproval(
testOperator.key.addr,
testOperatorId,
operatorKickParams,
bytes32(uint256(3)), // Different salt from previous tests
bytes32(uint256(4)),
defaultExpiry
);

Expand All @@ -1965,7 +1988,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",
Expand All @@ -1975,12 +1998,10 @@ 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("OperatorNotRegistered()"));
vm.expectRevert(abi.encodeWithSignature("OperatorNotRegisteredForQuorum()"));
IAllocationManager(coreDeployment.allocationManager).registerForOperatorSets(
testOperator.key.addr, registerParams
);
Expand Down