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
7 changes: 3 additions & 4 deletions docs/core/AllocationManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ Optionally, the `avs` can provide a list of `strategies`, specifying which strat
* For each `params.strategies` element:
* Add `strategy` to `_operatorSetStrategies[operatorSetKey]`
* Emits `StrategyAddedToOperatorSet` event
* Sets the slasher in `_slashers[operatorSetKey]`, with an `effectBlock = uint32(block.number)`, allowing the slasher to be active immediately
* 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.
* Emits a `SlasherUpdated` event


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

*Effects*:
* For each operatorSet:
* Sets the operatorSet's `pendingSlasher` to the proposed `slasher`, and save the `effectBlock` at which the `pendingSlasher` can be activated
* `effectBlock = uint32(block.number)`, allowing the slasher to slash immediately
* For each operatorSet:
* Sets the operatorSet's `slasher` and `pendingSlasher` to the proposed slasher, with `effectBlock = uint32(block.number)`, ensuring immediate effect with consistent storage.
* If the operatorSet has a `pendingDelay`, and if the `effectBlock` has passed, sets the operatorSet's slasher to the pendingSlasher
* Emits a `SlasherMigrated` event
* Emits an `SlasherUpdated` event
Expand Down
2 changes: 1 addition & 1 deletion pkg/bindings/AllocationManager/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/AllocationManagerView/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/CrossChainRegistry/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/DelegationManager/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/KeyRegistrar/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/RewardsCoordinator/binding.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bindings/StrategyManager/binding.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/contracts/core/AllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,8 @@ contract AllocationManager is
// Set the pending parameters
params.pendingSlasher = slasher;
if (instantEffectBlock) {
// Update slasher field immediately for storage consistency
params.slasher = slasher;
params.effectBlock = uint32(block.number);
} else {
params.effectBlock = uint32(block.number) + ALLOCATION_CONFIGURATION_DELAY + 1;
Expand Down
9 changes: 5 additions & 4 deletions src/contracts/interfaces/IAllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ interface IAllocationManagerTypes {
uint32 effectBlock;
}

/// @notice Parameters for addresses that can slash operatorSets
/// @param slasher the address that can slash the operator set
/// @param pendingSlasher the address that will become the slasher for the operator set after a delay
/// @param effectBlock the block at which the pending slasher will take effect
/// @notice Slasher configuration for an operator set.
/// @param slasher The current effective slasher address. Updated immediately when instantEffectBlock=true
/// (e.g., during operator set creation or migration).
/// @param pendingSlasher The slasher address that will become effective at effectBlock.
/// @param effectBlock The block number at which pendingSlasher becomes the effective slasher.
/// @dev It is not possible for the slasher to be the 0 address, which is used to denote if the slasher is not set
struct SlasherParams {
address slasher;
Expand Down
6 changes: 6 additions & 0 deletions src/test/harnesses/AllocationManagerHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@ contract AllocationManagerHarness is AllocationManager {
function setSlasherToZero(OperatorSet memory operatorSet) external {
_slashers[operatorSet.key()] = SlasherParams(address(0), address(0), 0);
}

/// @notice Returns the raw SlasherParams struct from storage for testing purposes.
/// @dev This bypasses the in-memory application of pending slasher that getSlasher() does.
function getSlasherParams(OperatorSet memory operatorSet) external view returns (SlasherParams memory) {
return _slashers[operatorSet.key()];
}
}
44 changes: 44 additions & 0 deletions src/test/unit/AllocationManagerUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4181,6 +4181,8 @@ contract AllocationManagerUnitTests_createRedistributingOperatorSetsV2 is Alloca
}

contract AllocationManagerUnitTests_updateSlasher is AllocationManagerUnitTests, IPermissionControllerErrors {
using ArrayLib for *;

/// -----------------------------------------------------------------------
/// updateSlasher() + getSlasher() + getPendingSlasher()
/// -----------------------------------------------------------------------
Expand Down Expand Up @@ -4332,6 +4334,29 @@ contract AllocationManagerUnitTests_updateSlasher is AllocationManagerUnitTests,
(returnedSlasher) = allocationManager.getSlasher(defaultOperatorSet);
assertEq(returnedSlasher, secondSlasher, "slasher not set");
}

/// @notice Tests that createOperatorSets with instantEffectBlock=true sets the slasher field in raw storage
function test_createOperatorSets_instantEffectBlock_setsSlasherFieldInStorage() public {
address avs = address(0x123);
address slasher = address(0x456);

cheats.prank(avs);
allocationManager.updateAVSMetadataURI(avs, "https://example.com");

// Create operator set with slasher (uses instantEffectBlock=true)
cheats.prank(avs);
allocationManager.createOperatorSets(
avs, CreateSetParamsV2({operatorSetId: 0, strategies: defaultStrategies, slasher: slasher}).toArray()
);

OperatorSet memory operatorSet = OperatorSet(avs, 0);

// Verify raw storage: slasher field should be set, not just pendingSlasher
SlasherParams memory params = AllocationManagerHarness(address(allocationManager)).getSlasherParams(operatorSet);
assertEq(params.slasher, slasher, "slasher field should be set immediately in storage");
assertEq(params.pendingSlasher, slasher, "pendingSlasher should also be set");
assertEq(params.effectBlock, uint32(block.number), "effectBlock should be current block");
}
}

contract AllocationManagerUnitTests_migrateSlashers is AllocationManagerUnitTests {
Expand Down Expand Up @@ -4522,6 +4547,25 @@ contract AllocationManagerUnitTests_migrateSlashers is AllocationManagerUnitTest
_assertNothingPending(operatorSets[i]);
}
}

/// @notice Tests that migrateSlashers sets the slasher field immediately in raw storage
function test_migrateSlashers_setsSlasherFieldImmediatelyInStorage() public {
// Zero out the slasher (simulating a legacy operator set)
AllocationManagerHarness(address(allocationManager)).setSlasherToZero(defaultOperatorSet);

// Verify slasher is zeroed in raw storage
SlasherParams memory paramsBefore = AllocationManagerHarness(address(allocationManager)).getSlasherParams(defaultOperatorSet);
assertEq(paramsBefore.slasher, address(0), "slasher should be zeroed before migration");

// Migrate the slasher
allocationManager.migrateSlashers(defaultOperatorSet.toArray());

// Verify raw storage: slasher field should be set, not just pendingSlasher
SlasherParams memory paramsAfter = AllocationManagerHarness(address(allocationManager)).getSlasherParams(defaultOperatorSet);
assertEq(paramsAfter.slasher, defaultAVS, "slasher field should be set immediately in storage");
assertEq(paramsAfter.pendingSlasher, defaultAVS, "pendingSlasher should also be set");
assertEq(paramsAfter.effectBlock, uint32(block.number), "effectBlock should be current block");
}
}

contract AllocationManagerUnitTests_setAVSRegistrar is AllocationManagerUnitTests {
Expand Down