Skip to content

Commit ab0f4d2

Browse files
committed
test: add slasher tests
1 parent 78c1646 commit ab0f4d2

File tree

4 files changed

+210
-7
lines changed

4 files changed

+210
-7
lines changed

src/contracts/core/AllocationManager.sol

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ contract AllocationManager is
344344
}
345345

346346
/// @inheritdoc IAllocationManager
347-
function migrateSlasher(
347+
function migrateSlashers(
348348
OperatorSet[] memory operatorSets
349349
) external {
350350
for (uint256 i = 0; i < operatorSets.length; i++) {
@@ -353,9 +353,8 @@ contract AllocationManager is
353353
continue;
354354
}
355355

356-
// If the operatorSet slasher is already migrated, continue
357-
// We know if an operatorSet slasher is migrated if the slasher is not the 0 address
358-
if (_slashers[operatorSets[i].key()].slasher == address(0)) {
356+
// If the slasher is already set, continue
357+
if (getSlasher(operatorSets[i]) != address(0)) {
359358
continue;
360359
}
361360

@@ -773,7 +772,7 @@ contract AllocationManager is
773772
* @param operatorSet the operator set to update the slasher for
774773
* @param slasher the new slasher
775774
* @param instantEffectBlock Whether the new slasher will take effect immediately. Instant if on operatorSet creation or migration function.
776-
* The new slasher will take `ALLOCATION_CONFIGURATION_DELAY` blocks to take effect if called by the `updateSlasher` function.
775+
* The new slasher will take `ALLOCATION_CONFIGURATION_DELAY` blocks to take effect if called by the `setSlasher` function.
777776
*/
778777
function _setSlasher(OperatorSet memory operatorSet, address slasher, bool instantEffectBlock) internal {
779778
// Ensure that the slasher address is not the 0 address, which is used to denote if the slasher is not set
@@ -1136,7 +1135,7 @@ contract AllocationManager is
11361135
/// @inheritdoc IAllocationManager
11371136
function getPendingSlasher(
11381137
OperatorSet memory operatorSet
1139-
) public view returns (address, uint32) {
1138+
) external view returns (address, uint32) {
11401139
// Initialize the pending slasher and effect block to the address(0) and 0 respectively
11411140
address pendingSlasher;
11421141
uint32 effectBlock;

src/contracts/interfaces/IAllocationManager.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ interface IAllocationManager is IAllocationManagerErrors, IAllocationManagerEven
455455
* - The operator set does not exist
456456
* - The operator set has already been migrated
457457
*/
458-
function migrateSlasher(
458+
function migrateSlashers(
459459
OperatorSet[] memory operatorSets
460460
) external;
461461

src/test/harnesses/AllocationManagerHarness.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ contract AllocationManagerHarness is AllocationManager {
3030
function deallocationQueueAtIndex(address operator, IStrategy strategy, uint index) external view returns (bytes32) {
3131
return deallocationQueue[operator][strategy].at(index);
3232
}
33+
34+
function setSlasherZero(OperatorSet memory operatorSet) external {
35+
_slashers[operatorSet.key()] = SlasherParams(address(0), address(0), 0);
36+
}
3337
}

src/test/unit/AllocationManagerUnit.t.sol

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4299,6 +4299,206 @@ contract AllocationManagerUnitTests_SetSlasher is AllocationManagerUnitTests, IP
42994299
}
43004300
}
43014301

4302+
contract AllocationManagerUnitTests_migrateSlashers is AllocationManagerUnitTests {
4303+
using ArrayLib for *;
4304+
4305+
// Test appointees
4306+
address appointee1 = address(0x1);
4307+
address appointee2 = address(0x2);
4308+
4309+
function _assertNothingPending(OperatorSet memory operatorSet) internal view {
4310+
(address returnedPendingSlasher, uint32 returnedEffectBlock) = allocationManager.getPendingSlasher(operatorSet);
4311+
assertEq(returnedPendingSlasher, address(0), "pending slasher should be the 0 address");
4312+
assertEq(returnedEffectBlock, 0, "effect block should be 0");
4313+
}
4314+
4315+
function setUp() public override {
4316+
AllocationManagerUnitTests.setUp();
4317+
4318+
// Manually set the slasher of the defaultAVS to be address(0)
4319+
// Given that the slasher is already set to the defaultAVS, we need to manually update so that the `migrateSlashers` function will not noop
4320+
allocationManager.setSlasherZero(defaultOperatorSet);
4321+
}
4322+
4323+
function test_noop_invalidOperatorSet() public {
4324+
OperatorSet memory operatorSet = OperatorSet(defaultAVS, 1);
4325+
4326+
// Start recording
4327+
vm.record();
4328+
allocationManager.migrateSlashers(operatorSet.toArray());
4329+
4330+
(bytes32[] memory reads,) = vm.accesses(address(allocationManager));
4331+
assertEq(reads.length, 3, "should have 3 reads");
4332+
}
4333+
4334+
function test_noop_slasherAlreadySet() public {
4335+
// Register the operatorSet
4336+
OperatorSet memory operatorSet = OperatorSet(defaultAVS, 1);
4337+
CreateSetParams[] memory createSetParams = new CreateSetParams[](1);
4338+
createSetParams[0] = CreateSetParams(operatorSet.id, new IStrategy[](0));
4339+
cheats.prank(defaultAVS);
4340+
allocationManager.createOperatorSets(defaultAVS, createSetParams);
4341+
4342+
// Start recording - in this case the slasher is already set, so we noop after
4343+
vm.record();
4344+
allocationManager.migrateSlashers(operatorSet.toArray());
4345+
4346+
(bytes32[] memory reads,) = vm.accesses(address(allocationManager));
4347+
assertEq(reads.length, 5, "should have 5 reads");
4348+
}
4349+
4350+
function test_noSlasherInPC() public {
4351+
// Migrate the slasher
4352+
cheats.expectEmit(true, true, true, true, address(allocationManager));
4353+
emit SlasherUpdated(defaultOperatorSet, defaultAVS, uint32(block.number));
4354+
vm.record();
4355+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4356+
4357+
// Sanity check on number of reads (greater than previous test)
4358+
(bytes32[] memory reads,) = vm.accesses(address(allocationManager));
4359+
assertGt(reads.length, 5, "should have greater than 5 reads");
4360+
4361+
// Check that the slasher is set to the defaultAVS
4362+
assertEq(allocationManager.getSlasher(defaultOperatorSet), defaultAVS, "slasher should be the defaultAVS");
4363+
_assertNothingPending(defaultOperatorSet);
4364+
}
4365+
4366+
function test_zeroAddressInPC() public {
4367+
// Add an appointee for the zero address
4368+
cheats.prank(defaultAVS);
4369+
permissionController.setAppointee(defaultAVS, address(0), address(allocationManager), allocationManager.slashOperator.selector);
4370+
4371+
// Migrate the slasher
4372+
cheats.expectEmit(true, true, true, true, address(allocationManager));
4373+
emit SlasherUpdated(defaultOperatorSet, defaultAVS, uint32(block.number));
4374+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4375+
4376+
// Check that the slasher is set to the defaultAVS
4377+
assertEq(allocationManager.getSlasher(defaultOperatorSet), defaultAVS, "slasher should be the defaultAVS");
4378+
_assertNothingPending(defaultOperatorSet);
4379+
}
4380+
4381+
function test_multipleAppointees() public {
4382+
// Add two appointees
4383+
cheats.startPrank(defaultAVS);
4384+
permissionController.setAppointee(defaultAVS, appointee1, address(allocationManager), allocationManager.slashOperator.selector);
4385+
permissionController.setAppointee(defaultAVS, appointee2, address(allocationManager), allocationManager.slashOperator.selector);
4386+
cheats.stopPrank();
4387+
4388+
// Migrate the slasher - only the first appointee should be set
4389+
cheats.expectEmit(true, true, true, true, address(allocationManager));
4390+
emit SlasherUpdated(defaultOperatorSet, appointee1, uint32(block.number));
4391+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4392+
4393+
// Check that the slasher is set to the first appointee
4394+
assertEq(allocationManager.getSlasher(defaultOperatorSet), appointee1, "slasher should be the first appointee");
4395+
_assertNothingPending(defaultOperatorSet);
4396+
}
4397+
4398+
/// @notice Same as previous test, bus since appointee2 is added first, the slasher should be the second appointee
4399+
function test_multipleAppointees_differentOrder() public {
4400+
// Add two appointees
4401+
cheats.startPrank(defaultAVS);
4402+
permissionController.setAppointee(defaultAVS, appointee2, address(allocationManager), allocationManager.slashOperator.selector);
4403+
permissionController.setAppointee(defaultAVS, appointee1, address(allocationManager), allocationManager.slashOperator.selector);
4404+
cheats.stopPrank();
4405+
4406+
// Migrate the slasher - only the second appointee should be set
4407+
cheats.expectEmit(true, true, true, true, address(allocationManager));
4408+
emit SlasherUpdated(defaultOperatorSet, appointee2, uint32(block.number));
4409+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4410+
4411+
// Check that the slasher is set to the second appointee
4412+
assertEq(allocationManager.getSlasher(defaultOperatorSet), appointee2, "slasher should be the second appointee");
4413+
_assertNothingPending(defaultOperatorSet);
4414+
}
4415+
4416+
function test_cannotMigrateMultipleTimes() public {
4417+
// Migrate the slasher
4418+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4419+
assertEq(allocationManager.getSlasher(defaultOperatorSet), defaultAVS, "slasher should be the defaultAVS");
4420+
4421+
// Set an appointee for the slasher
4422+
cheats.prank(defaultAVS);
4423+
permissionController.setAppointee(defaultAVS, appointee1, address(allocationManager), allocationManager.slashOperator.selector);
4424+
4425+
// Migrate the slasher again - should noop
4426+
vm.record();
4427+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4428+
4429+
// Sanity check on number of reads (should be 5)
4430+
(bytes32[] memory reads,) = vm.accesses(address(allocationManager));
4431+
assertEq(reads.length, 5, "should have 5 reads");
4432+
4433+
// Check that the slasher is still set to the defaultAVS
4434+
assertEq(allocationManager.getSlasher(defaultOperatorSet), defaultAVS, "slasher should be the defaultAVS");
4435+
_assertNothingPending(defaultOperatorSet);
4436+
}
4437+
4438+
/**
4439+
* @notice Test for when an AVS sets the slasher first and then a migration occurs
4440+
* @dev This is a known race condition. Migration will take precedence over setting a slasher
4441+
* via `setSlasher`. The operatorSet should wait until the migration is complete before setting a slasher.
4442+
*/
4443+
function test_setSlasher_migrate() public {
4444+
// Set the slasher
4445+
cheats.prank(defaultAVS);
4446+
allocationManager.setSlasher(defaultOperatorSet, appointee1);
4447+
4448+
// Set the slasher in the permission controller
4449+
cheats.prank(defaultAVS);
4450+
permissionController.setAppointee(defaultAVS, appointee2, address(allocationManager), allocationManager.slashOperator.selector);
4451+
4452+
// Migrate the slasher
4453+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4454+
4455+
// The slasher should be set to the second appointee
4456+
assertEq(allocationManager.getSlasher(defaultOperatorSet), appointee2, "slasher should be the second appointee");
4457+
_assertNothingPending(defaultOperatorSet);
4458+
}
4459+
4460+
function testFuzz_migrateSlashers_Correctness(Randomness r) public rand(r) {
4461+
address avs = r.Address();
4462+
uint numOpSets = r.Uint256(1, FUZZ_MAX_OP_SETS);
4463+
4464+
cheats.prank(avs);
4465+
allocationManager.updateAVSMetadataURI(avs, "https://example.com");
4466+
4467+
CreateSetParamsV2[] memory createSetParams = new CreateSetParamsV2[](numOpSets);
4468+
OperatorSet[] memory operatorSets = new OperatorSet[](numOpSets);
4469+
4470+
for (uint i = 0; i < numOpSets; ++i) {
4471+
createSetParams[i].operatorSetId = r.Uint32(1, type(uint32).max);
4472+
createSetParams[i].strategies = r.StrategyArray(0);
4473+
createSetParams[i].slasher = r.Address();
4474+
operatorSets[i] = OperatorSet(avs, createSetParams[i].operatorSetId);
4475+
}
4476+
4477+
cheats.prank(avs);
4478+
allocationManager.createOperatorSets(avs, createSetParams);
4479+
4480+
// Set slashers to zero address on all previously create opSets so we can migrate them
4481+
for (uint i = 0; i < numOpSets; ++i) {
4482+
allocationManager.setSlasherZero(operatorSets[i]);
4483+
}
4484+
4485+
// Expect event emits
4486+
for (uint i = 0; i < numOpSets; ++i) {
4487+
cheats.expectEmit(true, true, true, true, address(allocationManager));
4488+
emit SlasherUpdated(operatorSets[i], avs, uint32(block.number));
4489+
}
4490+
4491+
// Migrate the slashers
4492+
allocationManager.migrateSlashers(operatorSets);
4493+
4494+
// Check that the slashers are set to the AVS
4495+
for (uint i = 0; i < numOpSets; ++i) {
4496+
assertEq(allocationManager.getSlasher(operatorSets[i]), avs, "slasher should be the AVS");
4497+
_assertNothingPending(operatorSets[i]);
4498+
}
4499+
}
4500+
}
4501+
43024502
contract AllocationManagerUnitTests_setAVSRegistrar is AllocationManagerUnitTests {
43034503
function test_getAVSRegistrar() public {
43044504
address randomAVS = random().Address();

0 commit comments

Comments
 (0)