Skip to content

Commit cbb859e

Browse files
ypatil12claude
andcommitted
fix: update slasher field immediately when instantEffectBlock=true
Addresses audit finding L-01: Instant slasher setting leaves stale slasher field in storage. When a slasher is set with immediate effect (via createOperatorSets or migrateSlashers), _updateSlasher now also updates the params.slasher field, not just pendingSlasher. This ensures storage consistency where the raw SlasherParams struct reflects the effective slasher immediately. Changes: - AllocationManager._updateSlasher(): Set params.slasher when instantEffectBlock=true - IAllocationManager: Updated NatSpec for SlasherParams struct - AllocationManagerHarness: Added getSlasherParams() for testing raw storage - Unit tests: Added tests verifying storage consistency - Docs: Updated createOperatorSets and migrateSlashers effects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e4bf888 commit cbb859e

File tree

5 files changed

+63
-8
lines changed

5 files changed

+63
-8
lines changed

docs/core/AllocationManager.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ Optionally, the `avs` can provide a list of `strategies`, specifying which strat
337337
* For each `params.strategies` element:
338338
* Add `strategy` to `_operatorSetStrategies[operatorSetKey]`
339339
* Emits `StrategyAddedToOperatorSet` event
340-
* Sets the slasher in `_slashers[operatorSetKey]`, with an `effectBlock = uint32(block.number)`, allowing the slasher to be active immediately
340+
* Sets the slasher in `_slashers[operatorSetKey]`, with an `effectBlock = uint32(block.number)`, allowing the slasher to be active immediately. Both `slasher` and `pendingSlasher` fields are set to ensure storage consistency.
341341
* Emits a `SlasherUpdated` event
342342

343343

@@ -1119,9 +1119,8 @@ Only 1 slasher can be slash an operatorSet on behalf of an AVS; however, multipl
11191119
**The slasher can only be migrated once**. After, an operatorSet must use [`updateSlasher`](#updateslasher) to set a new address.
11201120

11211121
*Effects*:
1122-
* For each operatorSet:
1123-
* Sets the operatorSet's `pendingSlasher` to the proposed `slasher`, and save the `effectBlock` at which the `pendingSlasher` can be activated
1124-
* `effectBlock = uint32(block.number)`, allowing the slasher to slash immediately
1122+
* For each operatorSet:
1123+
* Sets the operatorSet's `slasher` and `pendingSlasher` to the proposed slasher, with `effectBlock = uint32(block.number)`, ensuring immediate effect with consistent storage.
11251124
* If the operatorSet has a `pendingDelay`, and if the `effectBlock` has passed, sets the operatorSet's slasher to the pendingSlasher
11261125
* Emits a `SlasherMigrated` event
11271126
* Emits an `SlasherUpdated` event

src/contracts/core/AllocationManager.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,8 @@ contract AllocationManager is
754754
// Set the pending parameters
755755
params.pendingSlasher = slasher;
756756
if (instantEffectBlock) {
757+
// Update slasher field immediately for storage consistency
758+
params.slasher = slasher;
757759
params.effectBlock = uint32(block.number);
758760
} else {
759761
params.effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY + 1;

src/contracts/interfaces/IAllocationManager.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ interface IAllocationManagerTypes {
8181
uint32 effectBlock;
8282
}
8383

84-
/// @notice Parameters for addresses that can slash operatorSets
85-
/// @param slasher the address that can slash the operator set
86-
/// @param pendingSlasher the address that will become the slasher for the operator set after a delay
87-
/// @param effectBlock the block at which the pending slasher will take effect
84+
/// @notice Slasher configuration for an operator set.
85+
/// @param slasher The current effective slasher address. Updated immediately when instantEffectBlock=true
86+
/// (e.g., during operator set creation or migration).
87+
/// @param pendingSlasher The slasher address that will become effective at effectBlock.
88+
/// @param effectBlock The block number at which pendingSlasher becomes the effective slasher.
8889
/// @dev It is not possible for the slasher to be the 0 address, which is used to denote if the slasher is not set
8990
struct SlasherParams {
9091
address slasher;

src/test/harnesses/AllocationManagerHarness.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,12 @@ contract AllocationManagerHarness is AllocationManager {
3535
function setSlasherToZero(OperatorSet memory operatorSet) external {
3636
_slashers[operatorSet.key()] = SlasherParams(address(0), address(0), 0);
3737
}
38+
39+
/// @notice Returns the raw SlasherParams struct from storage for testing purposes.
40+
/// @dev This bypasses the in-memory application of pending slasher that getSlasher() does.
41+
function getSlasherParams(
42+
OperatorSet memory operatorSet
43+
) external view returns (SlasherParams memory) {
44+
return _slashers[operatorSet.key()];
45+
}
3846
}

src/test/unit/AllocationManagerUnit.t.sol

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4181,6 +4181,8 @@ contract AllocationManagerUnitTests_createRedistributingOperatorSetsV2 is Alloca
41814181
}
41824182

41834183
contract AllocationManagerUnitTests_updateSlasher is AllocationManagerUnitTests, IPermissionControllerErrors {
4184+
using ArrayLib for *;
4185+
41844186
/// -----------------------------------------------------------------------
41854187
/// updateSlasher() + getSlasher() + getPendingSlasher()
41864188
/// -----------------------------------------------------------------------
@@ -4332,6 +4334,30 @@ contract AllocationManagerUnitTests_updateSlasher is AllocationManagerUnitTests,
43324334
(returnedSlasher) = allocationManager.getSlasher(defaultOperatorSet);
43334335
assertEq(returnedSlasher, secondSlasher, "slasher not set");
43344336
}
4337+
4338+
/// @notice Tests that createOperatorSets with instantEffectBlock=true sets the slasher field in raw storage
4339+
function test_createOperatorSets_instantEffectBlock_setsSlasherFieldInStorage() public {
4340+
address avs = address(0x123);
4341+
address slasher = address(0x456);
4342+
4343+
cheats.prank(avs);
4344+
allocationManager.updateAVSMetadataURI(avs, "https://example.com");
4345+
4346+
// Create operator set with slasher (uses instantEffectBlock=true)
4347+
cheats.prank(avs);
4348+
allocationManager.createOperatorSets(
4349+
avs,
4350+
CreateSetParamsV2({operatorSetId: 0, strategies: defaultStrategies, slasher: slasher}).toArray()
4351+
);
4352+
4353+
OperatorSet memory operatorSet = OperatorSet(avs, 0);
4354+
4355+
// Verify raw storage: slasher field should be set, not just pendingSlasher
4356+
SlasherParams memory params = AllocationManagerHarness(address(allocationManager)).getSlasherParams(operatorSet);
4357+
assertEq(params.slasher, slasher, "slasher field should be set immediately in storage");
4358+
assertEq(params.pendingSlasher, slasher, "pendingSlasher should also be set");
4359+
assertEq(params.effectBlock, uint32(block.number), "effectBlock should be current block");
4360+
}
43354361
}
43364362

43374363
contract AllocationManagerUnitTests_migrateSlashers is AllocationManagerUnitTests {
@@ -4522,6 +4548,25 @@ contract AllocationManagerUnitTests_migrateSlashers is AllocationManagerUnitTest
45224548
_assertNothingPending(operatorSets[i]);
45234549
}
45244550
}
4551+
4552+
/// @notice Tests that migrateSlashers sets the slasher field immediately in raw storage
4553+
function test_migrateSlashers_setsSlasherFieldImmediatelyInStorage() public {
4554+
// Zero out the slasher (simulating a legacy operator set)
4555+
AllocationManagerHarness(address(allocationManager)).setSlasherToZero(defaultOperatorSet);
4556+
4557+
// Verify slasher is zeroed in raw storage
4558+
SlasherParams memory paramsBefore = AllocationManagerHarness(address(allocationManager)).getSlasherParams(defaultOperatorSet);
4559+
assertEq(paramsBefore.slasher, address(0), "slasher should be zeroed before migration");
4560+
4561+
// Migrate the slasher
4562+
allocationManager.migrateSlashers(defaultOperatorSet.toArray());
4563+
4564+
// Verify raw storage: slasher field should be set, not just pendingSlasher
4565+
SlasherParams memory paramsAfter = AllocationManagerHarness(address(allocationManager)).getSlasherParams(defaultOperatorSet);
4566+
assertEq(paramsAfter.slasher, defaultAVS, "slasher field should be set immediately in storage");
4567+
assertEq(paramsAfter.pendingSlasher, defaultAVS, "pendingSlasher should also be set");
4568+
assertEq(paramsAfter.effectBlock, uint32(block.number), "effectBlock should be current block");
4569+
}
45254570
}
45264571

45274572
contract AllocationManagerUnitTests_setAVSRegistrar is AllocationManagerUnitTests {

0 commit comments

Comments
 (0)