Skip to content

Commit 36ac8ec

Browse files
authored
test: partial and full test cases with stake updates (#438)
**Motivation:** It's more intuitive to slash and operator and atomically update their weight **Modifications:** add a call to updateOperators to update the view the AVS has of their weight in the registry coordinator **Result:** More intutive UX
1 parent bbf8c40 commit 36ac8ec

File tree

4 files changed

+522
-34
lines changed

4 files changed

+522
-34
lines changed

src/slashers/InstantSlasher.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@ contract InstantSlasher is IInstantSlasher, SlasherBase {
2424
) external virtual override(IInstantSlasher) onlySlasher {
2525
uint256 requestId = nextRequestId++;
2626
_fulfillSlashingRequest(requestId, _slashingParams);
27+
28+
address[] memory operators = new address[](1);
29+
operators[0] = _slashingParams.operator;
30+
slashingRegistryCoordinator.updateOperators(operators);
2731
}
2832
}

src/slashers/VetoableSlasher.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ contract VetoableSlasher is IVetoableSlasher, SlasherBase {
9494
emit SlashingRequestCancelled(requestId);
9595
}
9696

97-
/// @notice Internal function to fullfill a slashing request and mark it as completed
97+
/// @notice Internal function to fulfill a slashing request and mark it as completed
9898
/// @param requestId The ID of the slashing request to fulfill
9999
function _fulfillSlashingRequestAndMarkAsCompleted(
100100
uint256 requestId
@@ -109,6 +109,10 @@ contract VetoableSlasher is IVetoableSlasher, SlasherBase {
109109
request.status = IVetoableSlasherTypes.SlashingStatus.Completed;
110110

111111
_fulfillSlashingRequest(requestId, request.params);
112+
113+
address[] memory operators = new address[](1);
114+
operators[0] = request.params.operator;
115+
slashingRegistryCoordinator.updateOperators(operators);
112116
}
113117

114118
/// @notice Internal function to verify if an account is the veto committee

test/unit/InstantSlasher.t.sol

Lines changed: 250 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ contract InstantSlasherTest is Test {
200200
slashingRegistryCoordinator =
201201
SlashingRegistryCoordinator(middlewareDeployments.slashingRegistryCoordinator);
202202
instantSlasher = InstantSlasher(middlewareDeployments.instantSlasher);
203+
stakeRegistry = StakeRegistry(middlewareDeployments.stakeRegistry);
203204

204205
PermissionController(coreDeployment.permissionController).setAppointee(
205206
address(serviceManager),
@@ -215,6 +216,13 @@ contract InstantSlasherTest is Test {
215216
AllocationManager.slashOperator.selector
216217
);
217218

219+
PermissionController(coreDeployment.permissionController).setAppointee(
220+
address(serviceManager),
221+
address(slashingRegistryCoordinator),
222+
coreDeployment.allocationManager,
223+
AllocationManager.deregisterFromOperatorSets.selector
224+
);
225+
218226
PermissionController(coreDeployment.permissionController).setAppointee(
219227
address(serviceManager),
220228
proxyAdminOwner,
@@ -262,63 +270,38 @@ contract InstantSlasherTest is Test {
262270
assertEq(instantSlasher.slasher(), slasher);
263271
}
264272

265-
function _createMockSlashingParams()
266-
internal
267-
view
268-
returns (IAllocationManagerTypes.SlashingParams memory)
269-
{
270-
IStrategy[] memory strategies = new IStrategy[](1);
271-
strategies[0] = mockStrategy;
272-
273-
uint256[] memory wadsToSlash = new uint256[](1);
274-
wadsToSlash[0] = 0.5e18; // 50% slash
275-
276-
return IAllocationManagerTypes.SlashingParams({
277-
operator: operatorWallet.key.addr,
278-
operatorSetId: 1,
279-
strategies: strategies,
280-
wadsToSlash: wadsToSlash,
281-
description: "Test slashing"
282-
});
283-
}
284-
285273
function test_fulfillSlashingRequest_revert_notSlasher() public {
286274
IAllocationManagerTypes.SlashingParams memory params = _createMockSlashingParams();
287275
vm.expectRevert(ISlasherErrors.OnlySlasher.selector);
288276
instantSlasher.fulfillSlashingRequest(params);
289277
}
290278

291279
function test_fulfillSlashingRequest() public {
292-
vm.skip(false);
293280
vm.startPrank(operatorWallet.key.addr);
294281
IDelegationManager(coreDeployment.delegationManager).registerAsOperator(
295282
address(0), 1, "metadata"
296283
);
297284

298-
// Mint tokens and deposit into strategy
299285
uint256 depositAmount = 1 ether;
300286
mockToken.mint(operatorWallet.key.addr, depositAmount);
301287
mockToken.approve(address(coreDeployment.strategyManager), depositAmount);
302288
IStrategyManager(coreDeployment.strategyManager).depositIntoStrategy(
303289
mockStrategy, mockToken, depositAmount
304290
);
305291

306-
// Set allocation delay
307292
uint32 minDelay = 1;
308293
IAllocationManager(coreDeployment.allocationManager).setAllocationDelay(
309294
operatorWallet.key.addr, minDelay
310295
);
311296
vm.stopPrank();
312297

313-
// Assert operator has allocation delay set
314298
(bool isSet,) = IAllocationManager(coreDeployment.allocationManager).getAllocationDelay(
315299
operatorWallet.key.addr
316300
);
317301
assertFalse(isSet, "Operator allocation delay not set");
318302

319303
vm.roll(block.number + ALLOCATION_CONFIGURATION_DELAY + 1);
320304

321-
// Set up allocation parameters
322305
IStrategy[] memory allocStrategies = new IStrategy[](1);
323306
allocStrategies[0] = mockStrategy;
324307

@@ -354,7 +337,6 @@ contract InstantSlasherTest is Test {
354337

355338
uint32[] memory operatorSetIds = new uint32[](1);
356339
operatorSetIds[0] = 0;
357-
// Create BLS signing key params
358340
bytes32 messageHash = slashingRegistryCoordinator.calculatePubkeyRegistrationMessageHash(
359341
operatorWallet.key.addr
360342
);
@@ -367,7 +349,6 @@ contract InstantSlasherTest is Test {
367349
pubkeyG2: operatorWallet.signingKey.publicKeyG2
368350
});
369351

370-
// Encode registration data with socket and pubkey params
371352
bytes memory registrationData = abi.encode(
372353
ISlashingRegistryCoordinatorTypes.RegistrationType.NORMAL, "socket", pubkeyParams
373354
);
@@ -385,7 +366,6 @@ contract InstantSlasherTest is Test {
385366

386367
vm.roll(block.number + 100);
387368

388-
// Create slashing params
389369
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
390370
.SlashingParams({
391371
operator: operatorWallet.key.addr,
@@ -395,13 +375,253 @@ contract InstantSlasherTest is Test {
395375
description: "Test slashing"
396376
});
397377

398-
// Set each wad to slash to 1e18 (100% slash)
399378
for (uint256 i = 0; i < params.wadsToSlash.length; i++) {
400379
params.wadsToSlash[i] = 1e18;
401380
}
402381

403-
// Execute slashing
404382
vm.prank(slasher);
405383
instantSlasher.fulfillSlashingRequest(params);
406384
}
385+
386+
function test_partialSlashUpdatesWeight() public {
387+
bytes32 operatorId = _setupOperatorForSlashing();
388+
389+
uint96 initialOperatorStake =
390+
stakeRegistry.weightOfOperatorForQuorum(0, operatorWallet.key.addr);
391+
uint96 initialTotalStake = stakeRegistry.getCurrentTotalStake(0);
392+
393+
assertEq(initialOperatorStake, 2 ether, "Initial operator stake is incorrect");
394+
395+
IAllocationManagerTypes.SlashingParams memory params = _getPartialSlashingParams();
396+
397+
vm.prank(slasher);
398+
instantSlasher.fulfillSlashingRequest(params);
399+
400+
uint96 postSlashingStake =
401+
stakeRegistry.weightOfOperatorForQuorum(0, operatorWallet.key.addr);
402+
uint96 totalStakeAfter = stakeRegistry.getCurrentTotalStake(0);
403+
404+
assertEq(postSlashingStake, 1 ether, "Incorrect post-slash stake");
405+
406+
assertEq(totalStakeAfter, initialTotalStake - 1 ether, "Total stake incorrect");
407+
408+
uint192 bitmap = slashingRegistryCoordinator.getCurrentQuorumBitmap(operatorId);
409+
assertTrue(bitmap & 1 != 0, "Operator removed from quorum 0");
410+
411+
ISlashingRegistryCoordinatorTypes.OperatorStatus status =
412+
slashingRegistryCoordinator.getOperatorStatus(operatorWallet.key.addr);
413+
assertEq(
414+
uint256(status),
415+
uint256(ISlashingRegistryCoordinatorTypes.OperatorStatus.REGISTERED),
416+
"Operator not in REGISTERED status"
417+
);
418+
}
419+
420+
function test_fullySlashRemovesOperator() public {
421+
bytes32 operatorId = _setupOperatorForSlashing();
422+
423+
// Verify operator is registered before slashing
424+
uint96 initialOperatorStake =
425+
stakeRegistry.weightOfOperatorForQuorum(0, operatorWallet.key.addr);
426+
uint96 initialTotalStake = stakeRegistry.getCurrentTotalStake(0);
427+
428+
assertEq(initialOperatorStake, 2 ether, "Initial operator stake is incorrect");
429+
430+
uint192 initialBitmap = slashingRegistryCoordinator.getCurrentQuorumBitmap(operatorId);
431+
assertTrue(initialBitmap & 1 != 0, "Operator should be in quorum 0 before slashing");
432+
433+
ISlashingRegistryCoordinatorTypes.OperatorStatus initialStatus =
434+
slashingRegistryCoordinator.getOperatorStatus(operatorWallet.key.addr);
435+
assertEq(
436+
uint256(initialStatus),
437+
uint256(ISlashingRegistryCoordinatorTypes.OperatorStatus.REGISTERED),
438+
"Operator should be in REGISTERED status before slashing"
439+
);
440+
441+
// Perform full slashing (100%)
442+
IAllocationManagerTypes.SlashingParams memory params = _getSlashingParams();
443+
444+
vm.prank(slasher);
445+
instantSlasher.fulfillSlashingRequest(params);
446+
447+
// Verify operator is removed after slashing
448+
uint96 postSlashingStake =
449+
stakeRegistry.weightOfOperatorForQuorum(0, operatorWallet.key.addr);
450+
uint96 totalStakeAfter = stakeRegistry.getCurrentTotalStake(0);
451+
452+
assertEq(postSlashingStake, 0, "Post-slash stake should be zero");
453+
assertEq(
454+
totalStakeAfter,
455+
initialTotalStake - 2 ether,
456+
"Total stake should be reduced by full amount"
457+
);
458+
459+
uint192 finalBitmap = slashingRegistryCoordinator.getCurrentQuorumBitmap(operatorId);
460+
assertEq(finalBitmap & 1, 0, "Operator should be removed from quorum 0");
461+
462+
ISlashingRegistryCoordinatorTypes.OperatorStatus finalStatus =
463+
slashingRegistryCoordinator.getOperatorStatus(operatorWallet.key.addr);
464+
assertEq(
465+
uint256(finalStatus),
466+
uint256(ISlashingRegistryCoordinatorTypes.OperatorStatus.DEREGISTERED),
467+
"Operator should be in DEREGISTERED status after full slashing"
468+
);
469+
}
470+
471+
function _createMockSlashingParams()
472+
internal
473+
view
474+
returns (IAllocationManagerTypes.SlashingParams memory)
475+
{
476+
IStrategy[] memory strategies = new IStrategy[](1);
477+
strategies[0] = mockStrategy;
478+
479+
uint256[] memory wadsToSlash = new uint256[](1);
480+
wadsToSlash[0] = 0.5e18; // 50% slash
481+
482+
return IAllocationManagerTypes.SlashingParams({
483+
operator: operatorWallet.key.addr,
484+
operatorSetId: 1,
485+
strategies: strategies,
486+
wadsToSlash: wadsToSlash,
487+
description: "Test slashing"
488+
});
489+
}
490+
491+
function _getSlashingParams()
492+
internal
493+
view
494+
returns (IAllocationManagerTypes.SlashingParams memory)
495+
{
496+
IStrategy[] memory allocStrategies = new IStrategy[](1);
497+
allocStrategies[0] = mockStrategy;
498+
499+
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
500+
.SlashingParams({
501+
operator: operatorWallet.key.addr,
502+
operatorSetId: 0,
503+
strategies: allocStrategies,
504+
wadsToSlash: new uint256[](allocStrategies.length),
505+
description: "Full slash test"
506+
});
507+
508+
for (uint256 i = 0; i < params.wadsToSlash.length; i++) {
509+
params.wadsToSlash[i] = 1e18; // 100% slash
510+
}
511+
512+
return params;
513+
}
514+
515+
function _getPartialSlashingParams()
516+
internal
517+
view
518+
returns (IAllocationManagerTypes.SlashingParams memory)
519+
{
520+
IStrategy[] memory allocStrategies = new IStrategy[](1);
521+
allocStrategies[0] = mockStrategy;
522+
523+
IAllocationManagerTypes.SlashingParams memory params = IAllocationManagerTypes
524+
.SlashingParams({
525+
operator: operatorWallet.key.addr,
526+
operatorSetId: 0,
527+
strategies: allocStrategies,
528+
wadsToSlash: new uint256[](allocStrategies.length),
529+
description: "Partial slash test"
530+
});
531+
532+
for (uint256 i = 0; i < params.wadsToSlash.length; i++) {
533+
params.wadsToSlash[i] = 0.5e18; // 50% slash
534+
}
535+
536+
return params;
537+
}
538+
539+
function _setupOperatorForSlashing() internal returns (bytes32) {
540+
vm.startPrank(operatorWallet.key.addr);
541+
IDelegationManager(coreDeployment.delegationManager).registerAsOperator(
542+
address(0), 1, "metadata"
543+
);
544+
545+
uint256 depositAmount = 2 ether;
546+
mockToken.mint(operatorWallet.key.addr, depositAmount);
547+
mockToken.approve(address(coreDeployment.strategyManager), depositAmount);
548+
IStrategyManager(coreDeployment.strategyManager).depositIntoStrategy(
549+
mockStrategy, mockToken, depositAmount
550+
);
551+
552+
uint32 minDelay = 1;
553+
IAllocationManager(coreDeployment.allocationManager).setAllocationDelay(
554+
operatorWallet.key.addr, minDelay
555+
);
556+
vm.stopPrank();
557+
558+
vm.roll(block.number + ALLOCATION_CONFIGURATION_DELAY + 1);
559+
560+
IStrategy[] memory allocStrategies = new IStrategy[](1);
561+
allocStrategies[0] = mockStrategy;
562+
563+
uint64[] memory magnitudes = new uint64[](1);
564+
magnitudes[0] = uint64(1 ether); // Allocate full magnitude (2 ETH)
565+
566+
OperatorSet memory operatorSet = OperatorSet({avs: address(serviceManager), id: 0});
567+
568+
vm.startPrank(serviceManager);
569+
IAllocationManagerTypes.CreateSetParams[] memory createParams =
570+
new IAllocationManagerTypes.CreateSetParams[](1);
571+
createParams[0] =
572+
IAllocationManagerTypes.CreateSetParams({operatorSetId: 0, strategies: allocStrategies});
573+
IAllocationManager(coreDeployment.allocationManager).setAVSRegistrar(
574+
address(serviceManager), IAVSRegistrar(address(slashingRegistryCoordinator))
575+
);
576+
vm.stopPrank();
577+
578+
vm.startPrank(operatorWallet.key.addr);
579+
580+
IAllocationManagerTypes.AllocateParams[] memory allocParams =
581+
new IAllocationManagerTypes.AllocateParams[](1);
582+
allocParams[0] = IAllocationManagerTypes.AllocateParams({
583+
operatorSet: operatorSet,
584+
strategies: allocStrategies,
585+
newMagnitudes: magnitudes
586+
});
587+
588+
IAllocationManager(coreDeployment.allocationManager).modifyAllocations(
589+
operatorWallet.key.addr, allocParams
590+
);
591+
vm.roll(block.number + 100);
592+
593+
uint32[] memory operatorSetIds = new uint32[](1);
594+
operatorSetIds[0] = 0;
595+
bytes32 messageHash = slashingRegistryCoordinator.calculatePubkeyRegistrationMessageHash(
596+
operatorWallet.key.addr
597+
);
598+
IBLSApkRegistryTypes.PubkeyRegistrationParams memory pubkeyParams = IBLSApkRegistryTypes
599+
.PubkeyRegistrationParams({
600+
pubkeyRegistrationSignature: SigningKeyOperationsLib.sign(
601+
operatorWallet.signingKey, messageHash
602+
),
603+
pubkeyG1: operatorWallet.signingKey.publicKeyG1,
604+
pubkeyG2: operatorWallet.signingKey.publicKeyG2
605+
});
606+
607+
bytes memory registrationData = abi.encode(
608+
ISlashingRegistryCoordinatorTypes.RegistrationType.NORMAL, "socket", pubkeyParams
609+
);
610+
611+
IAllocationManagerTypes.RegisterParams memory registerParams = IAllocationManagerTypes
612+
.RegisterParams({
613+
avs: address(serviceManager),
614+
operatorSetIds: operatorSetIds,
615+
data: registrationData
616+
});
617+
IAllocationManager(coreDeployment.allocationManager).registerForOperatorSets(
618+
operatorWallet.key.addr, registerParams
619+
);
620+
vm.stopPrank();
621+
622+
vm.roll(block.number + 100);
623+
624+
bytes32 operatorId = slashingRegistryCoordinator.getOperatorId(operatorWallet.key.addr);
625+
return operatorId;
626+
}
407627
}

0 commit comments

Comments
 (0)