Skip to content

Commit 701c569

Browse files
feat(draft): AllocationManager redistribution support (#1346)
**Motivation:** Add support for redistributing slashed funds to specified recipients instead of burning them, while optimizing code size and improving test coverage. **Modifications:** Added a new redistribution system that allows AVSs to specify recipients for slashed funds. Code optimizations were made to reduce codesize, including consolidating repeated logic and a small `PermissionControllerMixin` refactor. **Result:** Slashed funds can now be redirected to specified recipients instead of being burned, with improved code efficiency and test coverage.
1 parent 5ffb1b8 commit 701c569

File tree

6 files changed

+330
-52
lines changed

6 files changed

+330
-52
lines changed

src/contracts/core/AllocationManager.sol

+117-37
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,25 @@ contract AllocationManager is
6464
function slashOperator(
6565
address avs,
6666
SlashingParams calldata params
67-
) external onlyWhenNotPaused(PAUSED_OPERATOR_SLASHING) checkCanCall(avs) {
67+
)
68+
external
69+
onlyWhenNotPaused(PAUSED_OPERATOR_SLASHING)
70+
checkCanCall(avs)
71+
returns (uint256 slashId, uint256[] memory shares)
72+
{
6873
// Check that the operator set exists and the operator is registered to it
6974
OperatorSet memory operatorSet = OperatorSet(avs, params.operatorSetId);
70-
require(params.strategies.length == params.wadsToSlash.length, InputArrayLengthMismatch());
71-
require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet());
75+
_checkArrayLengthsMatch(params.strategies.length, params.wadsToSlash.length);
76+
_checkIsOperatorSet(operatorSet);
7277
require(isOperatorSlashable(params.operator, operatorSet), OperatorNotSlashable());
7378

7479
uint256[] memory wadSlashed = new uint256[](params.strategies.length);
7580

81+
// Increment the slash count for the operator set, first `slashId` equals 1.
82+
slashId = ++_slashCount[operatorSet.key()];
83+
// Initialize the shares array.
84+
shares = new uint256[](params.strategies.length);
85+
7686
// For each strategy in the operator set, slash any existing allocation
7787
for (uint256 i = 0; i < params.strategies.length; i++) {
7888
// Check that `strategies` is in ascending order.
@@ -155,7 +165,7 @@ contract AllocationManager is
155165
) external onlyWhenNotPaused(PAUSED_MODIFY_ALLOCATIONS) {
156166
// Check that the caller is allowed to modify allocations on behalf of the operator
157167
// We do not use a modifier to avoid `stack too deep` errors
158-
require(_checkCanCall(operator), InvalidCaller());
168+
_checkCanCall(operator);
159169

160170
// Check that the operator exists and has configured an allocation delay
161171
uint32 operatorAllocationDelay;
@@ -166,15 +176,15 @@ contract AllocationManager is
166176
}
167177

168178
for (uint256 i = 0; i < params.length; i++) {
169-
require(params[i].strategies.length == params[i].newMagnitudes.length, InputArrayLengthMismatch());
179+
_checkArrayLengthsMatch(params[i].strategies.length, params[i].newMagnitudes.length);
170180

171181
// Check that the operator set exists and get the operator's registration status
172182
// Operators do not need to be registered for an operator set in order to allocate
173183
// slashable magnitude to the set. In fact, it is expected that operators will
174184
// allocate magnitude before registering, as AVS's will likely only accept
175185
// registrations from operators that are already slashable.
176186
OperatorSet memory operatorSet = params[i].operatorSet;
177-
require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet());
187+
_checkIsOperatorSet(operatorSet);
178188

179189
bool _isOperatorSlashable = isOperatorSlashable(operator, operatorSet);
180190

@@ -246,8 +256,7 @@ contract AllocationManager is
246256
IStrategy[] calldata strategies,
247257
uint16[] calldata numToClear
248258
) external onlyWhenNotPaused(PAUSED_MODIFY_ALLOCATIONS) {
249-
require(strategies.length == numToClear.length, InputArrayLengthMismatch());
250-
259+
_checkArrayLengthsMatch(strategies.length, numToClear.length);
251260
for (uint256 i = 0; i < strategies.length; ++i) {
252261
_clearDeallocationQueue({operator: operator, strategy: strategies[i], numToClear: numToClear[i]});
253262
}
@@ -258,13 +267,13 @@ contract AllocationManager is
258267
address operator,
259268
RegisterParams calldata params
260269
) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) checkCanCall(operator) {
261-
// Check that the operator exists
262-
require(delegation.isOperator(operator), InvalidOperator());
270+
// Check if the operator has registered.
271+
_checkIsOperator(operator);
263272

264273
for (uint256 i = 0; i < params.operatorSetIds.length; i++) {
265274
// Check the operator set exists and the operator is not currently registered to it
266275
OperatorSet memory operatorSet = OperatorSet(params.avs, params.operatorSetIds[i]);
267-
require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet());
276+
_checkIsOperatorSet(operatorSet);
268277
require(!isOperatorSlashable(operator, operatorSet), AlreadyMemberOfSet());
269278

270279
// Add operator to operator set
@@ -285,12 +294,12 @@ contract AllocationManager is
285294
DeregisterParams calldata params
286295
) external onlyWhenNotPaused(PAUSED_OPERATOR_SET_REGISTRATION_AND_DEREGISTRATION) {
287296
// Check that the caller is either authorized on behalf of the operator or AVS
288-
require(_checkCanCall(params.operator) || _checkCanCall(params.avs), InvalidCaller());
297+
require(_canCall(params.operator) || _canCall(params.avs), InvalidPermissions());
289298

290299
for (uint256 i = 0; i < params.operatorSetIds.length; i++) {
291300
// Check the operator set exists and the operator is registered to it
292301
OperatorSet memory operatorSet = OperatorSet(params.avs, params.operatorSetIds[i]);
293-
require(_operatorSets[params.avs].contains(operatorSet.id), InvalidOperatorSet());
302+
_checkIsOperatorSet(operatorSet);
294303
require(registrationStatus[params.operator][operatorSet.key()].registered, NotMemberOfSet());
295304

296305
// Remove operator from operator set
@@ -313,8 +322,8 @@ contract AllocationManager is
313322
/// @inheritdoc IAllocationManager
314323
function setAllocationDelay(address operator, uint32 delay) external {
315324
if (msg.sender != address(delegation)) {
316-
require(_checkCanCall(operator), InvalidCaller());
317-
require(delegation.isOperator(operator), InvalidOperator());
325+
_checkCanCall(operator);
326+
_checkIsOperator(operator);
318327
}
319328
_setAllocationDelay(operator, delay);
320329
}
@@ -330,31 +339,28 @@ contract AllocationManager is
330339

331340
/// @inheritdoc IAllocationManager
332341
function updateAVSMetadataURI(address avs, string calldata metadataURI) external checkCanCall(avs) {
333-
if (!_avsRegisteredMetadata[avs]) {
334-
_avsRegisteredMetadata[avs] = true;
335-
}
336-
342+
if (!_avsRegisteredMetadata[avs]) _avsRegisteredMetadata[avs] = true;
337343
emit AVSMetadataURIUpdated(avs, metadataURI);
338344
}
339345

340346
/// @inheritdoc IAllocationManager
341347
function createOperatorSets(address avs, CreateSetParams[] calldata params) external checkCanCall(avs) {
342-
// Check that the AVS exists and has registered metadata
343348
require(_avsRegisteredMetadata[avs], NonexistentAVSMetadata());
344-
345349
for (uint256 i = 0; i < params.length; i++) {
346-
OperatorSet memory operatorSet = OperatorSet(avs, params[i].operatorSetId);
347-
348-
// Create the operator set, ensuring it does not already exist
349-
require(_operatorSets[avs].add(operatorSet.id), InvalidOperatorSet());
350-
emit OperatorSetCreated(OperatorSet(avs, operatorSet.id));
350+
_createOperatorSet(avs, params[i], DEFAULT_BURN_ADDRESS);
351+
}
352+
}
351353

352-
// Add strategies to the operator set
353-
bytes32 operatorSetKey = operatorSet.key();
354-
for (uint256 j = 0; j < params[i].strategies.length; j++) {
355-
_operatorSetStrategies[operatorSetKey].add(address(params[i].strategies[j]));
356-
emit StrategyAddedToOperatorSet(operatorSet, params[i].strategies[j]);
357-
}
354+
/// @inheritdoc IAllocationManager
355+
function createRedistributingOperatorSets(
356+
address avs,
357+
CreateSetParams[] calldata params,
358+
address[] calldata redistributionRecipients
359+
) external checkCanCall(avs) {
360+
_checkArrayLengthsMatch(params.length, redistributionRecipients.length);
361+
require(_avsRegisteredMetadata[avs], NonexistentAVSMetadata());
362+
for (uint256 i = 0; i < params.length; i++) {
363+
_createOperatorSet(avs, params[i], redistributionRecipients[i]);
358364
}
359365
}
360366

@@ -365,10 +371,8 @@ contract AllocationManager is
365371
IStrategy[] calldata strategies
366372
) external checkCanCall(avs) {
367373
OperatorSet memory operatorSet = OperatorSet(avs, operatorSetId);
374+
_checkIsOperatorSet(operatorSet);
368375
bytes32 operatorSetKey = operatorSet.key();
369-
370-
require(_operatorSets[avs].contains(operatorSet.id), InvalidOperatorSet());
371-
372376
for (uint256 i = 0; i < strategies.length; i++) {
373377
require(_operatorSetStrategies[operatorSetKey].add(address(strategies[i])), StrategyAlreadyInOperatorSet());
374378
emit StrategyAddedToOperatorSet(operatorSet, strategies[i]);
@@ -382,8 +386,7 @@ contract AllocationManager is
382386
IStrategy[] calldata strategies
383387
) external checkCanCall(avs) {
384388
OperatorSet memory operatorSet = OperatorSet(avs, operatorSetId);
385-
require(_operatorSets[avs].contains(operatorSet.id), InvalidOperatorSet());
386-
389+
_checkIsOperatorSet(operatorSet);
387390
bytes32 operatorSetKey = operatorSet.key();
388391
for (uint256 i = 0; i < strategies.length; i++) {
389392
require(_operatorSetStrategies[operatorSetKey].remove(address(strategies[i])), StrategyNotInOperatorSet());
@@ -397,6 +400,41 @@ contract AllocationManager is
397400
*
398401
*/
399402

403+
/**
404+
* @notice Creates a new operator set for an AVS.
405+
* @param avs The AVS address that owns the operator set.
406+
* @param params The parameters for creating the operator set.
407+
* @param redistributionRecipient Address to receive redistributed funds when operators are slashed.
408+
* @dev If `redistributionRecipient` is address(0), the operator set is considered non-redistributing
409+
* and slashed funds are sent to the `DEFAULT_BURN_ADDRESS`.
410+
* @dev Providing `BEACONCHAIN_ETH_STRAT` as a strategy will revert since it's not currently supported.
411+
*/
412+
function _createOperatorSet(
413+
address avs,
414+
CreateSetParams calldata params,
415+
address redistributionRecipient
416+
) internal {
417+
OperatorSet memory operatorSet = OperatorSet(avs, params.operatorSetId);
418+
419+
// Create the operator set, ensuring it does not already exist.
420+
require(_operatorSets[avs].add(operatorSet.id), InvalidOperatorSet());
421+
emit OperatorSetCreated(operatorSet);
422+
423+
bool isRedistributing = redistributionRecipient != DEFAULT_BURN_ADDRESS;
424+
425+
if (isRedistributing) {
426+
_redistributionRecipients[operatorSet.key()] = redistributionRecipient;
427+
emit RedistributionAddressSet(operatorSet, redistributionRecipient);
428+
}
429+
430+
// Add strategies to the operator set
431+
for (uint256 j = 0; j < params.strategies.length; j++) {
432+
if (isRedistributing && params.strategies[j] == BEACONCHAIN_ETH_STRAT) revert InvalidStrategy();
433+
_operatorSetStrategies[operatorSet.key()].add(address(params.strategies[j]));
434+
emit StrategyAddedToOperatorSet(operatorSet, params.strategies[j]);
435+
}
436+
}
437+
400438
/**
401439
* @dev Clear one or more pending deallocations to a strategy's allocated magnitude
402440
* @param operator the operator whose pending deallocations will be cleared
@@ -605,6 +643,25 @@ contract AllocationManager is
605643
return uint256(int256(int128(uint128(a)) + b)).toUint64();
606644
}
607645

646+
/// @dev Reverts if the operator set does not exist.
647+
function _checkIsOperatorSet(
648+
OperatorSet memory operatorSet
649+
) internal view {
650+
require(_operatorSets[operatorSet.avs].contains(operatorSet.id), InvalidOperatorSet());
651+
}
652+
653+
/// @dev Reverts if the provided arrays have different lengths.
654+
function _checkArrayLengthsMatch(uint256 left, uint256 right) internal pure {
655+
require(left == right, InputArrayLengthMismatch());
656+
}
657+
658+
/// @dev Reverts if the operator is not registered.
659+
function _checkIsOperator(
660+
address operator
661+
) internal view {
662+
require(delegation.isOperator(operator), InvalidOperator());
663+
}
664+
608665
/**
609666
*
610667
* VIEW FUNCTIONS
@@ -905,4 +962,27 @@ contract AllocationManager is
905962
// less than or equal to
906963
return status.registered || block.number <= status.slashableUntil;
907964
}
965+
966+
/// @inheritdoc IAllocationManager
967+
function getRedistributionRecipient(
968+
OperatorSet memory operatorSet
969+
) external view returns (address) {
970+
// Load the redistribution recipient and return it if set, otherwise return the default burn address.
971+
address redistributionRecipient = _redistributionRecipients[operatorSet.key()];
972+
return redistributionRecipient == address(0) ? DEFAULT_BURN_ADDRESS : redistributionRecipient;
973+
}
974+
975+
/// @inheritdoc IAllocationManager
976+
function isRedistributingOperatorSet(
977+
OperatorSet memory operatorSet
978+
) external view returns (bool) {
979+
return _redistributionRecipients[operatorSet.key()] != address(0);
980+
}
981+
982+
/// @inheritdoc IAllocationManager
983+
function getSlashCount(
984+
OperatorSet memory operatorSet
985+
) external view returns (uint256) {
986+
return _slashCount[operatorSet.key()];
987+
}
908988
}

src/contracts/core/AllocationManagerStorage.sol

+17-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ abstract contract AllocationManagerStorage is IAllocationManager {
1414

1515
// Constants
1616

17+
/// @dev The default burn address for slashed funds.
18+
address internal constant DEFAULT_BURN_ADDRESS = 0x00000000000000000000000000000000000E16E4;
19+
20+
/// @dev The beacon chain ETH strategy.
21+
IStrategy internal constant BEACONCHAIN_ETH_STRAT = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);
22+
1723
/// @dev Index for flag that pauses operator allocations/deallocations when set.
1824
uint8 internal constant PAUSED_MODIFY_ALLOCATIONS = 0;
1925

@@ -93,6 +99,16 @@ abstract contract AllocationManagerStorage is IAllocationManager {
9399
/// @notice bool is not used and is always true if the avs has registered metadata
94100
mapping(address avs => bool) internal _avsRegisteredMetadata;
95101

102+
/// @notice Returns the number of slashes for a given operator set.
103+
/// @dev This is also used as a unique slash identifier.
104+
/// @dev This tracks the number of slashes after the redistribution release.
105+
mapping(bytes32 operatorSetKey => uint256 slashId) public _slashCount;
106+
107+
/// @notice Returns the address where slashed funds will be sent for a given operator set.
108+
/// @dev For redistributing Operator Sets, returns the configured redistribution address set during Operator Set creation.
109+
/// For non-redistributing or non-existing operator sets, returns `address(0)`.
110+
mapping(bytes32 operatorSetKey => address redistributionAddr) internal _redistributionRecipients;
111+
96112
// Construction
97113

98114
constructor(IDelegationManager _delegation, uint32 _DEALLOCATION_DELAY, uint32 _ALLOCATION_CONFIGURATION_DELAY) {
@@ -106,5 +122,5 @@ abstract contract AllocationManagerStorage is IAllocationManager {
106122
* variables without shifting down storage in the inheritance chain.
107123
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
108124
*/
109-
uint256[36] private __gap;
125+
uint256[34] private __gap;
110126
}

src/contracts/core/DelegationManager.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ contract DelegationManager is
158158
if (msg.sender != staker) {
159159
address operator = delegatedTo[staker];
160160

161-
require(_checkCanCall(operator) || msg.sender == delegationApprover(operator), CallerCannotUndelegate());
161+
require(_canCall(operator) || msg.sender == delegationApprover(operator), CallerCannotUndelegate());
162162
emit StakerForceUndelegated(staker, operator);
163163
}
164164

0 commit comments

Comments
 (0)