Skip to content

Commit 217fc48

Browse files
eigenmikemMichael
authored andcommitted
Slashing integration tests (#1003)
* Validate that users who are slashed fully can redeposit once undelegated * removing arraylib use * test for updating eigenpod state -> slash/undelegate * removing unnecessary logging function * fixing strategy set * beacon chain tests in progress --------- Co-authored-by: Michael <[email protected]>
1 parent 8f5d2d6 commit 217fc48

File tree

5 files changed

+509
-1
lines changed

5 files changed

+509
-1
lines changed

src/test/integration/IntegrationBase.t.sol

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,36 @@ abstract contract IntegrationBase is IntegrationDeployer {
186186

187187
return result;
188188
}
189+
190+
/// @dev Choose a random subset of validators (selects AT LEAST ONE but NOT ALL)
191+
function _chooseSubset(uint40[] memory validators) internal returns (uint40[] memory) {
192+
require(validators.length >= 2, "Need at least 2 validators to choose subset");
193+
194+
uint40[] memory result = new uint40[](validators.length);
195+
uint newLen;
196+
197+
uint rand = _randUint({ min: 1, max: validators.length ** 2 });
198+
for (uint i = 0; i < validators.length; i++) {
199+
if (rand >> i & 1 == 1) {
200+
result[newLen] = validators[i];
201+
newLen++;
202+
}
203+
}
204+
205+
// If we picked all, remove one random validator
206+
if (newLen == validators.length) {
207+
uint indexToRemove = _randUint({ min: 0, max: validators.length - 1 });
208+
for (uint i = indexToRemove; i < newLen - 1; i++) {
209+
result[i] = result[i + 1];
210+
}
211+
newLen--;
212+
}
213+
214+
// Update array length
215+
assembly { mstore(result, newLen) }
216+
217+
return result;
218+
}
189219

190220
function _getTokenName(IERC20 token) internal view returns (string memory) {
191221
if (token == NATIVE_ETH) {
@@ -561,8 +591,11 @@ abstract contract IntegrationBase is IntegrationDeployer {
561591
uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
562592
slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
563593
}
594+
console.log(prevShares[i]);
595+
console.log(slashedShares);
596+
console.log(curShares[i]);
564597

565-
assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1, err);
598+
assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1000, err);
566599
}
567600
}
568601

@@ -1133,6 +1166,21 @@ abstract contract IntegrationBase is IntegrationDeployer {
11331166

11341167
return (strategies.sort(), wadsToSlash);
11351168
}
1169+
1170+
function _strategiesAndWadsForFullSlash(
1171+
OperatorSet memory operatorSet
1172+
) internal view returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
1173+
// Get list of all strategies in an operator set.
1174+
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
1175+
1176+
wadsToSlash = new uint[](strategies.length);
1177+
1178+
for (uint i; i < strategies.length; ++i) {
1179+
wadsToSlash[i] = 1 ether;
1180+
}
1181+
1182+
return (strategies.sort(), wadsToSlash);
1183+
}
11361184

11371185
function _randMagnitudes(uint64 sum, uint256 len) internal returns (uint64[] memory magnitudes) {
11381186
magnitudes = new uint64[](len);
@@ -1151,6 +1199,18 @@ abstract contract IntegrationBase is IntegrationDeployer {
11511199
}
11521200
}
11531201

1202+
function _maxMagnitudes(OperatorSet memory operatorSet, User operator) internal view returns (uint64[] memory magnitudes) {
1203+
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
1204+
uint256 len = strategies.length;
1205+
magnitudes = new uint64[](len);
1206+
1207+
if (len == 0) return magnitudes;
1208+
1209+
for (uint256 i; i < len; ++i) {
1210+
magnitudes[i] = allocationManager.getMaxMagnitude(address(operator), strategies[i]);
1211+
}
1212+
}
1213+
11541214
function _randWithdrawal(
11551215
IStrategy[] memory strategies,
11561216
uint[] memory shares

src/test/integration/IntegrationChecks.t.sol

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,56 @@ contract IntegrationCheckUtils is IntegrationBase {
390390
}
391391
}
392392

393+
function check_Withdrawal_AsTokens_State_AfterBeaconSlash(
394+
User staker,
395+
User operator,
396+
IDelegationManagerTypes.Withdrawal memory withdrawal,
397+
IAllocationManagerTypes.AllocateParams memory allocateParams,
398+
IAllocationManagerTypes.SlashingParams memory slashingParams,
399+
uint[] memory expectedTokens
400+
) internal {
401+
IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length);
402+
403+
for (uint i; i < withdrawal.strategies.length; i++) {
404+
IStrategy strat = withdrawal.strategies[i];
405+
406+
bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy;
407+
408+
tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken();
409+
410+
if (slashingParams.strategies.contains(strat)) {
411+
uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
412+
413+
expectedTokens[i] -= expectedTokens[i]
414+
.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
415+
416+
uint256 max = allocationManager.getMaxMagnitude(address(operator), strat);
417+
418+
withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max);
419+
420+
// Round down to the nearest gwei for beaconchain ETH strategy.
421+
if (isBeaconChainETHStrategy) {
422+
expectedTokens[i] -= expectedTokens[i] % 1 gwei;
423+
}
424+
}
425+
}
426+
427+
// Common checks
428+
assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
429+
430+
// assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens");
431+
assert_Snap_Unchanged_StakerDepositShares(staker, "staker shares should not have changed");
432+
assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.scaledShares, "strategies should have total shares decremented");
433+
434+
// Checks specific to an operator that the Staker has delegated to
435+
if (operator != User(payable(0))) {
436+
if (operator != staker) {
437+
assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed");
438+
}
439+
assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed");
440+
}
441+
}
442+
393443
function check_Withdrawal_AsShares_State_AfterSlash(
394444
User staker,
395445
User operator,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "src/test/integration/IntegrationChecks.t.sol";
5+
import "src/test/integration/users/User.t.sol";
6+
import {console} from "forge-std/console.sol";
7+
8+
contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is IntegrationCheckUtils, IDelegationManagerTypes {
9+
10+
function testFuzz_deposit_delegate_allocate_fullSlash_queue_complete_redeposit(
11+
uint24 _random
12+
) public {
13+
_configRand({_randomSeed: _random, _assetTypes: HOLDS_LST, _userTypes: DEFAULT});
14+
_upgradeEigenLayerContracts();
15+
16+
(User staker, IStrategy[] memory strategies, uint256[] memory tokenBalances) = _newRandomStaker();
17+
(User operator,,) = _newRandomOperator();
18+
(AVS avs,) = _newRandomAVS();
19+
20+
uint256[] memory tokensToDeposit = new uint256[](tokenBalances.length);
21+
uint256[] memory numTokensRemaining = new uint256[](tokenBalances.length);
22+
for (uint256 i = 0; i < tokenBalances.length; i++) {
23+
tokensToDeposit[i] = tokenBalances[i]/2;
24+
numTokensRemaining[i] = tokenBalances[i] - tokensToDeposit[i];
25+
}
26+
27+
uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit);
28+
29+
// 1. Deposit Into Strategies
30+
staker.depositIntoEigenlayer(strategies, tokensToDeposit);
31+
check_Deposit_State_PartialDeposit(staker, strategies, shares, numTokensRemaining);
32+
33+
// 2. Delegate to operator
34+
staker.delegateTo(operator);
35+
check_Delegation_State(staker, operator, strategies, shares);
36+
37+
// Create operator set and register operator
38+
OperatorSet memory operatorSet = avs.createOperatorSet(strategies);
39+
operator.registerForOperatorSet(operatorSet);
40+
41+
// 3. Allocate to operator set
42+
IAllocationManagerTypes.AllocateParams memory allocateParams =
43+
operator.modifyAllocations(operatorSet, _maxMagnitudes(operatorSet, operator));
44+
45+
assert_Snap_Allocations_Modified(
46+
operator,
47+
allocateParams,
48+
false,
49+
"operator allocations should be updated before delay"
50+
);
51+
52+
_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);
53+
54+
assert_Snap_Allocations_Modified(
55+
operator,
56+
allocateParams,
57+
true,
58+
"operator allocations should be updated after delay"
59+
);
60+
61+
// 4. Fully slash operator
62+
IAllocationManagerTypes.SlashingParams memory slashingParams;
63+
{
64+
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
65+
_strategiesAndWadsForFullSlash(operatorSet);
66+
67+
slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
68+
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
69+
assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing");
70+
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
71+
}
72+
73+
// 5. Undelegate from an operator
74+
IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate();
75+
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
76+
77+
// 6. Complete withdrawal
78+
_rollBlocksForCompleteWithdrawals(withdrawals);
79+
for (uint256 i = 0; i < withdrawals.length; ++i) {
80+
uint256[] memory expectedTokens =
81+
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
82+
for (uint256 i = 0; i < expectedTokens.length; i++) {
83+
}
84+
staker.completeWithdrawalAsTokens(withdrawals[i]);
85+
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
86+
}
87+
88+
// 7. Redeposit
89+
shares = _calculateExpectedShares(strategies, numTokensRemaining);
90+
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
91+
check_Deposit_State(staker, strategies, shares);
92+
93+
// Final state checks
94+
assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit");
95+
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
96+
}
97+
}

0 commit comments

Comments
 (0)