Skip to content

Commit bc58113

Browse files
0xClandestinebowenli86
authored andcommitted
test(redistribution): add unit tests (#1383)
**Motivation:** We want to ensure `SlashingWithdrawalRouter` has appropriate unit test coverage. **Modifications:** - Add more unit tests. **Result:** 90% coverage.
1 parent 7de25bb commit bc58113

File tree

11 files changed

+773
-181
lines changed

11 files changed

+773
-181
lines changed

.gitmodules

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
[submodule "lib/ds-test"]
22
path = lib/ds-test
33
url = https://github.com/dapphub/ds-test
4-
[submodule "lib/forge-std"]
5-
path = lib/forge-std
6-
url = https://github.com/foundry-rs/forge-std
74
[submodule "lib/openzeppelin-contracts-v4.9.0"]
85
path = lib/openzeppelin-contracts-v4.9.0
96
url = https://github.com/OpenZeppelin/openzeppelin-contracts
@@ -13,3 +10,6 @@
1310
[submodule "lib/zeus-templates"]
1411
path = lib/zeus-templates
1512
url = https://github.com/Layr-Labs/zeus-templates
13+
[submodule "lib/forge-std"]
14+
path = lib/forge-std
15+
url = https://github.com/foundry-rs/forge-std

src/contracts/core/DelegationManager.sol

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -684,37 +684,37 @@ contract DelegationManager is
684684
uint64 newMaxMagnitude
685685
) internal returns (uint256 totalDepositSharesToBurn) {
686686
// 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-
});
687+
if (prevMaxMagnitude == newMaxMagnitude) return 0;
693688

694-
uint256 scaledSharesSlashedFromQueue = _getSlashableSharesInQueue({
695-
operator: operator,
696-
strategy: strategy,
697-
prevMaxMagnitude: prevMaxMagnitude,
698-
newMaxMagnitude: newMaxMagnitude
699-
});
689+
uint256 operatorSharesSlashed = SlashingLib.calcSlashedAmount({
690+
operatorShares: operatorShares[operator][strategy],
691+
prevMaxMagnitude: prevMaxMagnitude,
692+
newMaxMagnitude: newMaxMagnitude
693+
});
700694

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;
695+
uint256 scaledSharesSlashedFromQueue = _getSlashableSharesInQueue({
696+
operator: operator,
697+
strategy: strategy,
698+
prevMaxMagnitude: prevMaxMagnitude,
699+
newMaxMagnitude: newMaxMagnitude
700+
});
704701

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-
});
702+
// Calculate the total deposit shares to burn - slashed operator shares plus still-slashable
703+
// shares sitting in the withdrawal queue.
704+
totalDepositSharesToBurn = operatorSharesSlashed + scaledSharesSlashedFromQueue;
712705

713-
// Emit event for operator shares being slashed
714-
emit OperatorSharesSlashed(operator, strategy, totalDepositSharesToBurn);
706+
// Remove shares from operator
707+
_decreaseDelegation({
708+
operator: operator,
709+
staker: address(0), // we treat this as a decrease for the 0-staker (only used for events)
710+
strategy: strategy,
711+
sharesToDecrease: operatorSharesSlashed
712+
});
715713

716-
_getShareManager(strategy).increaseBurnableShares(operatorSet, slashId, strategy, totalDepositSharesToBurn);
717-
}
714+
// Emit event for operator shares being slashed
715+
emit OperatorSharesSlashed(operator, strategy, totalDepositSharesToBurn);
716+
717+
_getShareManager(strategy).increaseBurnableShares(operatorSet, slashId, strategy, totalDepositSharesToBurn);
718718

719719
return totalDepositSharesToBurn;
720720
}

src/contracts/core/SlashingWithdrawalRouter.sol

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ contract SlashingWithdrawalRouter is
7777
pendingSlashIds.add(slashId);
7878

7979
// Add the operator set to the pending operator sets set.
80-
if (!pendingOperatorSets.contains(operatorSet.key())) {
81-
pendingOperatorSets.add(operatorSet.key());
82-
}
80+
pendingOperatorSets.add(operatorSet.key());
8381

8482
// Add the strategy and underlying amount to the pending burn or redistributions map.
8583
pendingBurnOrRedistributions.set(address(strategy), underlyingAmount);
@@ -288,13 +286,37 @@ contract SlashingWithdrawalRouter is
288286
return operatorSets;
289287
}
290288

289+
/// @inheritdoc ISlashingWithdrawalRouter
290+
function getTotalPendingOperatorSets() external view returns (uint256) {
291+
return _pendingOperatorSets.length();
292+
}
293+
294+
/// @inheritdoc ISlashingWithdrawalRouter
295+
function isPendingOperatorSet(
296+
OperatorSet calldata operatorSet
297+
) external view returns (bool) {
298+
return _pendingOperatorSets.contains(operatorSet.key());
299+
}
300+
291301
/// @inheritdoc ISlashingWithdrawalRouter
292302
function getPendingSlashIds(
293303
OperatorSet calldata operatorSet
294304
) external view returns (uint256[] memory) {
295305
return _pendingSlashIds[operatorSet.key()].values();
296306
}
297307

308+
/// @inheritdoc ISlashingWithdrawalRouter
309+
function getTotalPendingSlashIds(
310+
OperatorSet calldata operatorSet
311+
) external view returns (uint256) {
312+
return _pendingSlashIds[operatorSet.key()].length();
313+
}
314+
315+
/// @inheritdoc ISlashingWithdrawalRouter
316+
function isPendingSlashId(OperatorSet calldata operatorSet, uint256 slashId) external view returns (bool) {
317+
return _pendingSlashIds[operatorSet.key()].contains(slashId);
318+
}
319+
298320
/// @inheritdoc ISlashingWithdrawalRouter
299321
function getPendingBurnOrRedistributions(
300322
OperatorSet memory operatorSet,
@@ -394,6 +416,11 @@ contract SlashingWithdrawalRouter is
394416
uint256 globalDelay = _globalBurnOrRedistributionDelayBlocks;
395417
uint256 strategyDelay = _strategyBurnOrRedistributionDelayBlocks[address(strategy)];
396418

419+
// If the strategy delay is not set, return the global delay.
420+
if (strategyDelay == 0) {
421+
return globalDelay;
422+
}
423+
397424
// If the strategy delay is less than the global delay, return the strategy delay.
398425
// Otherwise, return the global delay.
399426
return strategyDelay < globalDelay ? strategyDelay : globalDelay;

src/contracts/interfaces/ISlashingWithdrawalRouter.sol

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ interface ISlashingWithdrawalRouterEvents {
4444

4545
interface ISlashingWithdrawalRouter is ISlashingWithdrawalRouterErrors, ISlashingWithdrawalRouterEvents {
4646
/**
47-
* @notice Initializes initial admin, pauser, and unpauser roles.
47+
* @notice Initializes the initial owner and paused status.
4848
* @param initialOwner The initial owner of the router.
4949
* @param initialPausedStatus The initial paused status of the router.
5050
*/
@@ -109,6 +109,21 @@ interface ISlashingWithdrawalRouter is ISlashingWithdrawalRouterErrors, ISlashin
109109
*/
110110
function getPendingOperatorSets() external view returns (OperatorSet[] memory operatorSets);
111111

112+
/**
113+
* @notice Returns the total number of operator sets with pending burn or redistributions.
114+
* @return The total number of operator sets with pending burn or redistributions.
115+
*/
116+
function getTotalPendingOperatorSets() external view returns (uint256);
117+
118+
/**
119+
* @notice Returns whether an operator set has pending burn or redistributions.
120+
* @param operatorSet The operator set whose pending burn or redistributions are being queried.
121+
* @return Whether the operator set has pending burn or redistributions.
122+
*/
123+
function isPendingOperatorSet(
124+
OperatorSet calldata operatorSet
125+
) external view returns (bool);
126+
112127
/**
113128
* @notice Returns the pending slash IDs for an operator set.
114129
* @param operatorSet The operator set whose pending slash IDs are being queried.
@@ -117,6 +132,23 @@ interface ISlashingWithdrawalRouter is ISlashingWithdrawalRouterErrors, ISlashin
117132
OperatorSet calldata operatorSet
118133
) external view returns (uint256[] memory);
119134

135+
/**
136+
* @notice Returns the total number of slash IDs for an operator set.
137+
* @param operatorSet The operator set whose total slash IDs are being queried.
138+
* @return The total number of slash IDs for the operator set.
139+
*/
140+
function getTotalPendingSlashIds(
141+
OperatorSet calldata operatorSet
142+
) external view returns (uint256);
143+
144+
/**
145+
* @notice Returns whether a slash ID is pending for an operator set.
146+
* @param operatorSet The operator set whose pending slash IDs are being queried.
147+
* @param slashId The slash ID of the slash that is being queried.
148+
* @return Whether the slash ID is pending for the operator set.
149+
*/
150+
function isPendingSlashId(OperatorSet calldata operatorSet, uint256 slashId) external view returns (bool);
151+
120152
/**
121153
* @notice Returns the pending burn or redistributions for an operator set and slash ID.
122154
* @dev This is a variant that returns the pending burn or redistributions for an operator set and slash ID.

src/test/integration/IntegrationBase.t.sol

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,7 +2189,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
21892189
}
21902190
}
21912191

2192-
function _genSlashing_Half(User operator, OperatorSet memory operatorSet) internal view returns (SlashingParams memory params) {
2192+
function _genSlashing_Half(User operator, OperatorSet memory operatorSet) internal returns (SlashingParams memory params) {
21932193
params.operator = address(operator);
21942194
params.operatorSetId = operatorSet.id;
21952195
params.description = "genSlashing_Half";
@@ -2202,7 +2202,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
22022202
}
22032203
}
22042204

2205-
function _genSlashing_Full(User operator, OperatorSet memory operatorSet) internal view returns (SlashingParams memory params) {
2205+
function _genSlashing_Full(User operator, OperatorSet memory operatorSet) internal returns (SlashingParams memory params) {
22062206
params.operator = address(operator);
22072207
params.operatorSetId = operatorSet.id;
22082208
params.description = "_genSlashing_Full";
@@ -2217,7 +2217,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
22172217

22182218
function _genSlashing_Custom(User operator, OperatorSet memory operatorSet, uint wadsToSlash)
22192219
internal
2220-
view
22212220
returns (SlashingParams memory params)
22222221
{
22232222
params.operator = address(operator);
@@ -2237,7 +2236,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
22372236

22382237
function _strategiesAndWadsForFullSlash(OperatorSet memory operatorSet)
22392238
internal
2240-
view
22412239
returns (IStrategy[] memory strategies, uint[] memory wadsToSlash)
22422240
{
22432241
// Get list of all strategies in an operator set.

src/test/integration/IntegrationDeployer.t.sol

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {
354354
eigenPodManagerImplementation =
355355
new EigenPodManager(DEPOSIT_CONTRACT, eigenPodBeacon, delegationManager, eigenLayerPauserReg, "v9.9.9");
356356
strategyFactoryImplementation = new StrategyFactory(strategyManager, eigenLayerPauserReg, "v9.9.9");
357+
slashingWithdrawalRouterImplementation =
358+
new SlashingWithdrawalRouter(allocationManager, strategyManager, eigenLayerPauserReg, "v9.9.9");
357359

358360
// Beacon implementations
359361
eigenPodImplementation = new EigenPod(DEPOSIT_CONTRACT, eigenPodManager, BEACON_GENESIS_TIME, "v9.9.9");
@@ -402,6 +404,11 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {
402404
ITransparentUpgradeableProxy(payable(address(strategyFactory))), address(strategyFactoryImplementation)
403405
);
404406

407+
// SlashingWithdrawalRouter
408+
eigenLayerProxyAdmin.upgrade(
409+
ITransparentUpgradeableProxy(payable(address(slashingWithdrawalRouter))), address(slashingWithdrawalRouterImplementation)
410+
);
411+
405412
// EigenPod beacon
406413
eigenPodBeacon.upgradeTo(address(eigenPodImplementation));
407414

@@ -711,14 +718,16 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {
711718
}
712719

713720
function _shuffle(IStrategy[] memory strats) internal returns (IStrategy[] memory) {
714-
// Fisher-Yates shuffle algorithm
715-
for (uint i = strats.length - 1; i > 0; i--) {
716-
uint randomIndex = _randUint({min: 0, max: i});
717-
718-
// Swap elements
719-
IStrategy temp = strats[i];
720-
strats[i] = strats[randomIndex];
721-
strats[randomIndex] = temp;
721+
uint[] memory casted;
722+
723+
assembly {
724+
casted := strats
725+
}
726+
727+
casted = vm.shuffle(casted);
728+
729+
assembly {
730+
strats := casted
722731
}
723732

724733
return strats;

src/test/mocks/DelegationManagerMock.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ contract DelegationManagerMock is Test {
3636
uint[] memory amountSlashed = new uint[](strategies.length);
3737

3838
for (uint i = 0; i < strategies.length; i++) {
39+
if (prevMaxMagnitudes[i] == newMaxMagnitudes[i]) continue;
40+
3941
amountSlashed[i] = SlashingLib.calcSlashedAmount({
4042
operatorShares: operatorShares[operator][strategies[i]],
4143
prevMaxMagnitude: prevMaxMagnitudes[i],

0 commit comments

Comments
 (0)