Skip to content

Commit ebbf4d3

Browse files
committed
feat: add SlashingWithdrawalRouter (#5)
* wip * wip * feat: add `SlashingWithdrawalRouter` * perf: optimize `Pausable` for codesize * chore: renaming * refactor: use `Pauseable` instead of OZ `AccessControl` * perf: reduce `Pauseble` codesize * refactor: arrayified inputs * wip * refactor: make struct params arrays * fix: remove unused import * refactor: `redistributionRecipient` == `releaser` * refactor: cleanup * wip * wip * refactor: cleanup * refactor: remove uneeded code * refactor: remove unused error * wip * refactor: review changes * feat: add `getPendingSlashIdsForOperatorSet` * test: wip * test: wip * test: wip * doc: todos * test: wip * refactor: iterate in reverse order to avoid skipping elements * test: passing * feat: add `getPendingBurnOrRedistributions` nested alias * chore: renaming * fix: update gap * fix: dm mock * feat: full introspection * fix: `pendingOperatorSets` removal * fix: update visibility * feat: add delay logic * fix: enforce minimum delay * refactor: review changes * refactor: remove `_checkBurnOrRedistributionDelay` * fix: gap * nit: natspec style * refactor: update headers * refactor: review changes
1 parent de84319 commit ebbf4d3

24 files changed

+1596
-567
lines changed

script/deploy/devnet/deploy_from_scratch.s.sol

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import "../../../src/contracts/core/AVSDirectory.sol";
1414
import "../../../src/contracts/core/RewardsCoordinator.sol";
1515
import "../../../src/contracts/core/AllocationManager.sol";
1616
import "../../../src/contracts/permissions/PermissionController.sol";
17-
17+
import "../../../src/contracts/core/SlashingWithdrawalRouter.sol";
1818
import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
1919
import "../../../src/contracts/strategies/StrategyFactory.sol";
2020
import "../../../src/contracts/strategies/StrategyBase.sol";
@@ -63,6 +63,8 @@ contract DeployFromScratch is Script, Test {
6363
AllocationManager public allocationManager;
6464
PermissionController public permissionController;
6565
PermissionController public permissionControllerImplementation;
66+
SlashingWithdrawalRouter public slashingWithdrawalRouter;
67+
SlashingWithdrawalRouter public slashingWithdrawalRouterImplementation;
6668

6769
EmptyContract public emptyContract;
6870

@@ -214,6 +216,9 @@ contract DeployFromScratch is Script, Test {
214216
permissionController = PermissionController(
215217
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
216218
);
219+
slashingWithdrawalRouter = SlashingWithdrawalRouter(
220+
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
221+
);
217222

218223
// if on mainnet, use the ETH2 deposit contract address
219224
if (chainId == 1) ethPOSDeposit = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa);
@@ -235,7 +240,8 @@ contract DeployFromScratch is Script, Test {
235240
SEMVER
236241
);
237242

238-
strategyManagerImplementation = new StrategyManager(delegation, eigenLayerPauserReg, SEMVER);
243+
strategyManagerImplementation =
244+
new StrategyManager(delegation, slashingWithdrawalRouter, eigenLayerPauserReg, SEMVER);
239245
avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, SEMVER);
240246
eigenPodManagerImplementation =
241247
new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegation, eigenLayerPauserReg, SEMVER);
@@ -264,6 +270,8 @@ contract DeployFromScratch is Script, Test {
264270
);
265271
permissionControllerImplementation = new PermissionController(SEMVER);
266272
strategyFactoryImplementation = new StrategyFactory(strategyManager, eigenLayerPauserReg, SEMVER);
273+
slashingWithdrawalRouterImplementation =
274+
new SlashingWithdrawalRouter(allocationManager, strategyManager, eigenLayerPauserReg, SEMVER);
267275

268276
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
269277
{

script/deploy/local/deploy_from_scratch.slashing.s.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import "../../../src/contracts/core/AVSDirectory.sol";
1414
import "../../../src/contracts/core/RewardsCoordinator.sol";
1515
import "../../../src/contracts/core/AllocationManager.sol";
1616
import "../../../src/contracts/permissions/PermissionController.sol";
17+
import "../../../src/contracts/core/SlashingWithdrawalRouter.sol";
1718

1819
import "../../../src/contracts/strategies/StrategyBaseTVLLimits.sol";
1920

@@ -66,6 +67,8 @@ contract DeployFromScratch is Script, Test {
6667
AllocationManager public allocationManager;
6768
PermissionController public permissionControllerImplementation;
6869
PermissionController public permissionController;
70+
SlashingWithdrawalRouter public slashingWithdrawalRouter;
71+
SlashingWithdrawalRouter public slashingWithdrawalRouterImplementation;
6972

7073
EmptyContract public emptyContract;
7174

@@ -221,6 +224,9 @@ contract DeployFromScratch is Script, Test {
221224
permissionController = PermissionController(
222225
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
223226
);
227+
slashingWithdrawalRouter = SlashingWithdrawalRouter(
228+
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
229+
);
224230

225231
// if on mainnet, use the ETH2 deposit contract address
226232
if (chainId == 1) ethPOSDeposit = IETHPOSDeposit(0x00000000219ab540356cBB839Cbe05303d7705Fa);
@@ -241,7 +247,8 @@ contract DeployFromScratch is Script, Test {
241247
MIN_WITHDRAWAL_DELAY,
242248
SEMVER
243249
);
244-
strategyManagerImplementation = new StrategyManager(delegation, eigenLayerPauserReg, SEMVER);
250+
strategyManagerImplementation =
251+
new StrategyManager(delegation, slashingWithdrawalRouter, eigenLayerPauserReg, SEMVER);
245252
avsDirectoryImplementation = new AVSDirectory(delegation, eigenLayerPauserReg, SEMVER);
246253
eigenPodManagerImplementation =
247254
new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegation, eigenLayerPauserReg, SEMVER);
@@ -269,6 +276,8 @@ contract DeployFromScratch is Script, Test {
269276
SEMVER
270277
);
271278
permissionControllerImplementation = new PermissionController(SEMVER);
279+
slashingWithdrawalRouterImplementation =
280+
new SlashingWithdrawalRouter(allocationManager, strategyManager, eigenLayerPauserReg, SEMVER);
272281

273282
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
274283
{

script/utils/ExistingDeploymentParser.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import "../../src/contracts/core/AVSDirectory.sol";
1111
import "../../src/contracts/core/RewardsCoordinator.sol";
1212
import "../../src/contracts/core/AllocationManager.sol";
1313
import "../../src/contracts/permissions/PermissionController.sol";
14+
import "../../src/contracts/core/SlashingWithdrawalRouter.sol";
1415

1516
import "../../src/contracts/strategies/StrategyFactory.sol";
1617
import "../../src/contracts/strategies/StrategyBase.sol";
@@ -136,6 +137,10 @@ contract ExistingDeploymentParser is Script, Logger {
136137
StrategyBase public baseStrategyImplementation;
137138
StrategyBase public strategyFactoryBeaconImplementation;
138139

140+
/// @dev SlashingWithdrawalRouter
141+
SlashingWithdrawalRouter public slashingWithdrawalRouter;
142+
SlashingWithdrawalRouter public slashingWithdrawalRouterImplementation;
143+
139144
// Token
140145
ProxyAdmin public tokenProxyAdmin;
141146
IEigen public EIGEN;

src/contracts/core/AllocationManager.sol

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ contract AllocationManager is
2121
SemVerMixin
2222
{
2323
using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque;
24-
using EnumerableSet for *;
25-
using SafeCast for *;
26-
2724
using Snapshots for Snapshots.DefaultWadHistory;
2825
using OperatorSetLib for OperatorSet;
2926
using SlashingLib for uint256;
27+
using EnumerableSet for *;
28+
using SafeCast for *;
3029

3130
/**
3231
*
@@ -60,8 +59,6 @@ contract AllocationManager is
6059
_setPausedStatus(initialPausedStatus);
6160
}
6261

63-
// TODO: properly return shares slashed
64-
6562
/// @inheritdoc IAllocationManager
6663
function slashOperator(
6764
address avs,
@@ -331,8 +328,8 @@ contract AllocationManager is
331328
// Increment the slash count for the operator set.
332329
slashId = ++_slashCount[operatorSet.key()];
333330

334-
// Initialize the shares array.
335-
shares = new uint256[](params.strategies.length);
331+
uint64[] memory prevMaxMagnitudes = new uint64[](params.strategies.length);
332+
uint64[] memory newMaxMagnitudes = new uint64[](params.strategies.length);
336333

337334
// For each strategy in the operator set, slash any existing allocation
338335
for (uint256 i = 0; i < params.strategies.length; i++) {
@@ -349,9 +346,6 @@ contract AllocationManager is
349346
StrategyNotInOperatorSet()
350347
);
351348

352-
_burnOrRedistributionBlock[operatorSet.key()][params.strategies[i]][slashId] =
353-
uint32(block.number) + BURN_OR_REDISTRIBUTION_DELAY;
354-
355349
// 1. Get the operator's allocation info for the strategy and operator set
356350
(StrategyInfo memory info, Allocation memory allocation) =
357351
_getUpdatedAllocation(params.operator, operatorSet.key(), params.strategies[i]);
@@ -399,17 +393,20 @@ contract AllocationManager is
399393

400394
_updateMaxMagnitude(params.operator, params.strategies[i], info.maxMagnitude);
401395

402-
// 6. Slash operators shares in the DelegationManager
403-
shares[i] = delegation.slashOperatorShares({
404-
operator: params.operator,
405-
operatorSet: operatorSet,
406-
slashId: slashId,
407-
strategy: params.strategies[i],
408-
prevMaxMagnitude: prevMaxMagnitude,
409-
newMaxMagnitude: info.maxMagnitude
410-
});
396+
prevMaxMagnitudes[i] = prevMaxMagnitude;
397+
newMaxMagnitudes[i] = info.maxMagnitude;
411398
}
412399

400+
// 6. Slash operators shares in the DelegationManager
401+
shares = delegation.slashOperatorShares({
402+
operator: params.operator,
403+
operatorSet: operatorSet,
404+
slashId: slashId,
405+
strategies: params.strategies,
406+
prevMaxMagnitudes: prevMaxMagnitudes,
407+
newMaxMagnitudes: newMaxMagnitudes
408+
});
409+
413410
emit OperatorSlashed(params.operator, operatorSet, params.strategies, wadSlashed, params.description);
414411
}
415412

@@ -998,7 +995,7 @@ contract AllocationManager is
998995
/// @inheritdoc IAllocationManager
999996
function getRedistributionRecipient(
1000997
OperatorSet memory operatorSet
1001-
) external view returns (address) {
998+
) public view returns (address) {
1002999
// Load the redistribution recipient and return it if set, otherwise return the default burn address.
10031000
address redistributionRecipient = _redistributionRecipients[operatorSet.key()];
10041001
return redistributionRecipient == address(0) ? DEFAULT_BURN_ADDRESS : redistributionRecipient;
@@ -1018,15 +1015,6 @@ contract AllocationManager is
10181015
return _slashCount[operatorSet.key()];
10191016
}
10201017

1021-
/// @inheritdoc IAllocationManager
1022-
function getBurnOrRedistributionBlock(
1023-
OperatorSet memory operatorSet,
1024-
IStrategy strategy,
1025-
uint256 slashId
1026-
) external view returns (uint32) {
1027-
return _burnOrRedistributionBlock[operatorSet.key()][strategy][slashId];
1028-
}
1029-
10301018
/// @inheritdoc IAllocationManager
10311019
function isOperatorRedistributable(
10321020
address operator

src/contracts/core/AllocationManagerStorage.sol

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@ import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol";
66

77
import "../interfaces/IAllocationManager.sol";
88
import "../interfaces/IDelegationManager.sol";
9-
109
import {Snapshots} from "../libraries/Snapshots.sol";
1110

1211
abstract contract AllocationManagerStorage is IAllocationManager {
1312
using Snapshots for Snapshots.DefaultWadHistory;
1413

1514
// Constants
1615

17-
/// @dev The delay before a burn or redistribution can occur, denominated in blocks.
18-
uint32 internal constant BURN_OR_REDISTRIBUTION_DELAY = 36_000; // 3.5 days assuming 12s blocks.
19-
2016
/// @dev The default burn address for slashed funds.
2117
address internal constant DEFAULT_BURN_ADDRESS = 0x00000000000000000000000000000000000E16E4;
2218

@@ -112,10 +108,6 @@ abstract contract AllocationManagerStorage is IAllocationManager {
112108
/// For non-redistributing or non-existing operator sets, returns `address(0)`.
113109
mapping(bytes32 operatorSetKey => address redistributionAddr) internal _redistributionRecipients;
114110

115-
/// @notice Returns the block number a burn or redistribution can occur after a given an operator set, strategy, and slash ID.
116-
mapping(bytes32 operatorSetKey => mapping(IStrategy strategy => mapping(uint256 slashId => uint32 blockNumber)))
117-
public _burnOrRedistributionBlock;
118-
119111
// Construction
120112

121113
constructor(IDelegationManager _delegation, uint32 _DEALLOCATION_DELAY, uint32 _ALLOCATION_CONFIGURATION_DELAY) {
@@ -129,5 +121,5 @@ abstract contract AllocationManagerStorage is IAllocationManager {
129121
* variables without shifting down storage in the inheritance chain.
130122
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
131123
*/
132-
uint256[33] private __gap;
124+
uint256[34] private __gap;
133125
}

src/contracts/core/DelegationManager.sol

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -280,62 +280,18 @@ contract DelegationManager is
280280
/// @inheritdoc IDelegationManager
281281
function slashOperatorShares(
282282
address operator,
283-
OperatorSet memory operatorSet,
283+
OperatorSet calldata operatorSet,
284284
uint256 slashId,
285-
IStrategy strategy,
286-
uint64 prevMaxMagnitude,
287-
uint64 newMaxMagnitude
288-
) external onlyAllocationManager nonReentrant returns (uint256) {
289-
/// forgefmt: disable-next-item
290-
uint256 operatorSharesSlashed = SlashingLib.calcSlashedAmount({
291-
operatorShares: operatorShares[operator][strategy],
292-
prevMaxMagnitude: prevMaxMagnitude,
293-
newMaxMagnitude: newMaxMagnitude
294-
});
295-
296-
uint256 scaledSharesSlashedFromQueue = _getSlashableSharesInQueue({
297-
operator: operator,
298-
strategy: strategy,
299-
prevMaxMagnitude: prevMaxMagnitude,
300-
newMaxMagnitude: newMaxMagnitude
301-
});
302-
303-
// Calculate the total deposit shares to burn - slashed operator shares plus still-slashable
304-
// shares sitting in the withdrawal queue.
305-
uint256 totalDepositSharesToBurn = operatorSharesSlashed + scaledSharesSlashedFromQueue;
306-
307-
// Remove shares from operator
308-
_decreaseDelegation({
309-
operator: operator,
310-
staker: address(0), // we treat this as a decrease for the 0-staker (only used for events)
311-
strategy: strategy,
312-
sharesToDecrease: operatorSharesSlashed
313-
});
314-
315-
// Emit event for operator shares being slashed
316-
emit OperatorSharesSlashed(operator, strategy, totalDepositSharesToBurn);
317-
318-
_getShareManager(strategy).increaseBurnableShares(operatorSet, slashId, strategy, totalDepositSharesToBurn);
319-
320-
IStrategy[] memory singleStrategy = new IStrategy[](1);
321-
uint256[] memory singleDepositShares = new uint256[](1);
322-
singleStrategy[0] = strategy;
323-
singleDepositShares[0] = totalDepositSharesToBurn;
324-
325-
Withdrawal memory withdrawal = Withdrawal({
326-
staker: address(0), // TODO: pass in redistribution recipient in call
327-
delegatedTo: address(this),
328-
withdrawer: address(0), // We use address(0) as the withdrawer to special case for allowing anybody to complete the withdrawal
329-
nonce: slashId,
330-
startBlock: uint32(block.number),
331-
strategies: singleStrategy,
332-
scaledShares: singleDepositShares
333-
});
334-
335-
emit RedistributionQueued(calculateWithdrawalRoot(withdrawal), withdrawal);
336-
337-
_addWithdrawalToQueue(withdrawal);
338-
285+
IStrategy[] calldata strategies,
286+
uint64[] calldata prevMaxMagnitudes,
287+
uint64[] calldata newMaxMagnitudes
288+
) external onlyAllocationManager nonReentrant returns (uint256[] memory totalDepositSharesToBurn) {
289+
totalDepositSharesToBurn = new uint256[](strategies.length);
290+
for (uint256 i = 0; i < strategies.length; i++) {
291+
totalDepositSharesToBurn[i] = _slashOperatorShares(
292+
operator, operatorSet, slashId, strategies[i], prevMaxMagnitudes[i], newMaxMagnitudes[i]
293+
);
294+
}
339295
return totalDepositSharesToBurn;
340296
}
341297

@@ -717,6 +673,52 @@ contract DelegationManager is
717673
emit OperatorSharesDecreased(operator, staker, strategy, sharesToDecrease);
718674
}
719675

676+
/// @dev Slashes operator shares and queues a redistribution for the slashable shares in the queue.
677+
/// See `slashOperatorShares` for more details.
678+
function _slashOperatorShares(
679+
address operator,
680+
OperatorSet memory operatorSet,
681+
uint256 slashId,
682+
IStrategy strategy,
683+
uint64 prevMaxMagnitude,
684+
uint64 newMaxMagnitude
685+
) internal returns (uint256 totalDepositSharesToBurn) {
686+
// Avoid emitting events if nothing has changed for sanitization.
687+
if (prevMaxMagnitude != newMaxMagnitude) {
688+
uint256 operatorSharesSlashed = SlashingLib.calcSlashedAmount({
689+
operatorShares: operatorShares[operator][strategy],
690+
prevMaxMagnitude: prevMaxMagnitude,
691+
newMaxMagnitude: newMaxMagnitude
692+
});
693+
694+
uint256 scaledSharesSlashedFromQueue = _getSlashableSharesInQueue({
695+
operator: operator,
696+
strategy: strategy,
697+
prevMaxMagnitude: prevMaxMagnitude,
698+
newMaxMagnitude: newMaxMagnitude
699+
});
700+
701+
// Calculate the total deposit shares to burn - slashed operator shares plus still-slashable
702+
// shares sitting in the withdrawal queue.
703+
totalDepositSharesToBurn = operatorSharesSlashed + scaledSharesSlashedFromQueue;
704+
705+
// Remove shares from operator
706+
_decreaseDelegation({
707+
operator: operator,
708+
staker: address(0), // we treat this as a decrease for the 0-staker (only used for events)
709+
strategy: strategy,
710+
sharesToDecrease: operatorSharesSlashed
711+
});
712+
713+
// Emit event for operator shares being slashed
714+
emit OperatorSharesSlashed(operator, strategy, totalDepositSharesToBurn);
715+
716+
_getShareManager(strategy).increaseBurnableShares(operatorSet, slashId, strategy, totalDepositSharesToBurn);
717+
}
718+
719+
return totalDepositSharesToBurn;
720+
}
721+
720722
/// @dev If `operator` has configured a `delegationApprover`, check that `signature` and `salt`
721723
/// are a valid approval for `staker` delegating to `operator`.
722724
function _checkApproverSignature(

0 commit comments

Comments
 (0)