Skip to content

Commit 5a838d6

Browse files
eigenmikemypatil12
authored andcommitted
refactor: operators can deregister keys if not slashable (#1480)
**Motivation:** Operators should be able to deregister their keys in the `KeyRegistrar` if they aren't slashable **Modifications:** Changed permissions on `deregisterKey` **Result:** Operators can deregister their keys in the `KeyRegistrar` if they aren't slashable
1 parent 7b904f1 commit 5a838d6

File tree

4 files changed

+82
-10
lines changed

4 files changed

+82
-10
lines changed

src/contracts/interfaces/IKeyRegistrar.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface IKeyRegistrarErrors {
1616
error ConfigurationAlreadySet();
1717
error OperatorSetNotConfigured();
1818
error KeyNotFound(OperatorSet operatorSet, address operator);
19+
error OperatorStillSlashable(OperatorSet operatorSet, address operator);
1920
}
2021

2122
interface IKeyRegistrarTypes {
@@ -69,8 +70,9 @@ interface IKeyRegistrar is IKeyRegistrarErrors, IKeyRegistrarEvents, ISemVerMixi
6970
* @notice Deregisters a cryptographic key for an operator with a specific operator set
7071
* @param operator Address of the operator to deregister key for
7172
* @param operatorSet The operator set to deregister the key from
72-
* @dev Can be called by avs directly or by addresses they've authorized via PermissionController
73+
* @dev Can be called by the operator directly or by addresses they've authorized via PermissionController
7374
* @dev Reverts if key was not registered
75+
* @dev Reverts if operator is still slashable for the operator set (prevents key rotation while slashable)
7476
* @dev Keys remain in global key registry to prevent reuse
7577
*/
7678
function deregisterKey(address operator, OperatorSet memory operatorSet) external;

src/contracts/permissions/KeyRegistrar.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import "../mixins/PermissionControllerMixin.sol";
99
import "../mixins/SignatureUtilsMixin.sol";
1010
import "../interfaces/IPermissionController.sol";
1111
import "../interfaces/IAllocationManager.sol";
12+
import "../interfaces/IKeyRegistrar.sol";
1213
import "../libraries/OperatorSetLib.sol";
1314
import "./KeyRegistrarStorage.sol";
1415

@@ -88,8 +89,11 @@ contract KeyRegistrar is KeyRegistrarStorage, PermissionControllerMixin, Signatu
8889
}
8990

9091
/// @inheritdoc IKeyRegistrar
91-
function deregisterKey(address operator, OperatorSet memory operatorSet) external {
92-
require(address(allocationManager.getAVSRegistrar(operatorSet.avs)) == msg.sender, InvalidPermissions());
92+
function deregisterKey(address operator, OperatorSet memory operatorSet) external checkCanCall(operator) {
93+
// Operators can only deregister if they are not slashable for this operator set
94+
require(
95+
!allocationManager.isOperatorSlashable(operator, operatorSet), OperatorStillSlashable(operatorSet, operator)
96+
);
9397

9498
CurveType curveType = operatorSetCurveTypes[operatorSet.key()];
9599
require(curveType != CurveType.NONE, OperatorSetNotConfigured());

src/test/mocks/AllocationManagerMock.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ contract AllocationManagerMock is Test {
2626
mapping(bytes32 operatorSetKey => IStrategy[] strategies) internal _strategies;
2727
mapping(bytes32 operatorSetKey => mapping(address operator => mapping(IStrategy strategy => uint minimumSlashableStake))) internal
2828
_minimumSlashableStake;
29+
mapping(bytes32 operatorSetKey => mapping(address operator => bool)) internal _isOperatorSlashable;
2930

3031
function getSlashCount(OperatorSet memory operatorSet) external view returns (uint) {
3132
return _getSlashCount[operatorSet.key()];
@@ -149,4 +150,12 @@ contract AllocationManagerMock is Test {
149150

150151
return minimumSlashableStake;
151152
}
153+
154+
function isOperatorSlashable(address operator, OperatorSet memory operatorSet) external view returns (bool) {
155+
return _isOperatorSlashable[operatorSet.key()][operator];
156+
}
157+
158+
function setIsOperatorSlashable(address operator, OperatorSet memory operatorSet, bool isSlashable) external {
159+
_isOperatorSlashable[operatorSet.key()][operator] = isSlashable;
160+
}
152161
}

src/test/unit/KeyRegistrarUnit.t.sol

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup {
438438
function testDeregisterKey_RevertOperatorSetNotConfigured() public {
439439
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
440440

441-
vm.prank(avs1);
441+
vm.prank(operator1);
442442
vm.expectRevert(IKeyRegistrarErrors.OperatorSetNotConfigured.selector);
443443
keyRegistrar.deregisterKey(operator1, operatorSet);
444444
}
@@ -540,7 +540,10 @@ contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup {
540540
bytes32 keyHash = keccak256(ecdsaKey1);
541541
assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash));
542542

543-
vm.prank(avs1);
543+
// Set operator as not slashable
544+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet1, false);
545+
546+
vm.prank(operator1);
544547
keyRegistrar.deregisterKey(operator1, operatorSet1);
545548

546549
// Key should still be globally registered after deregistration
@@ -571,7 +574,10 @@ contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup {
571574
bytes32 keyHash = BN254.hashG1Point(bn254G1Key1);
572575
assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash));
573576

574-
vm.prank(avs1);
577+
// Set operator as not slashable
578+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet1, false);
579+
580+
vm.prank(operator1);
575581
keyRegistrar.deregisterKey(operator1, operatorSet1);
576582

577583
// Key should still be globally registered after deregistration
@@ -787,7 +793,10 @@ contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup {
787793
vm.prank(operator1);
788794
keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature);
789795

790-
vm.prank(avs1);
796+
// Set operator as not slashable
797+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet, false);
798+
799+
vm.prank(operator1);
791800
vm.expectEmit(true, true, true, true);
792801
emit KeyDeregistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.ECDSA);
793802
keyRegistrar.deregisterKey(operator1, operatorSet);
@@ -806,7 +815,10 @@ contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup {
806815
vm.prank(operator1);
807816
keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, signature);
808817

809-
vm.prank(avs1);
818+
// Set operator as not slashable
819+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet, false);
820+
821+
vm.prank(operator1);
810822
vm.expectEmit(true, true, true, true);
811823
emit KeyDeregistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.BN254);
812824
keyRegistrar.deregisterKey(operator1, operatorSet);
@@ -820,16 +832,61 @@ contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup {
820832
vm.prank(avs1);
821833
keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA);
822834

823-
vm.prank(avs1);
835+
vm.prank(operator1);
824836
vm.expectRevert(abi.encodeWithSelector(IKeyRegistrarErrors.KeyNotFound.selector, operatorSet, operator1));
825837
keyRegistrar.deregisterKey(operator1, operatorSet);
826838
}
827839

828840
function testDeregisterKey_RevertUnauthorized() public {
829841
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
830842

831-
vm.prank(operator1);
843+
vm.prank(operator2); // operator2 is not authorized to call on behalf of operator1
832844
vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector);
833845
keyRegistrar.deregisterKey(operator1, operatorSet);
834846
}
847+
848+
function testDeregisterKey_OperatorNotSlashable() public {
849+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
850+
851+
vm.prank(avs1);
852+
keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA);
853+
854+
bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1);
855+
856+
vm.prank(operator1);
857+
keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature);
858+
859+
// Set operator as not slashable
860+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet, false);
861+
862+
// Operator should be able to deregister their key when not slashable
863+
vm.prank(operator1);
864+
vm.expectEmit(true, true, true, true);
865+
emit KeyDeregistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.ECDSA);
866+
keyRegistrar.deregisterKey(operator1, operatorSet);
867+
868+
assertFalse(keyRegistrar.isRegistered(operatorSet, operator1));
869+
}
870+
871+
function testDeregisterKey_RevertOperatorStillSlashable() public {
872+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
873+
874+
vm.prank(avs1);
875+
keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA);
876+
877+
bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1);
878+
879+
vm.prank(operator1);
880+
keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature);
881+
882+
// Set operator as slashable
883+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet, true);
884+
885+
// Operator should not be able to deregister their key when still slashable
886+
vm.prank(operator1);
887+
vm.expectRevert(abi.encodeWithSelector(IKeyRegistrarErrors.OperatorStillSlashable.selector, operatorSet, operator1));
888+
keyRegistrar.deregisterKey(operator1, operatorSet);
889+
890+
assertTrue(keyRegistrar.isRegistered(operatorSet, operator1));
891+
}
835892
}

0 commit comments

Comments
 (0)