Skip to content

Commit a61f5de

Browse files
eigenmikemMichael
andauthored
Slashing integration tests (#1138)
Co-authored-by: Michael <[email protected]>
1 parent 184a361 commit a61f5de

File tree

5 files changed

+435
-157
lines changed

5 files changed

+435
-157
lines changed

src/test/integration/IntegrationBase.t.sol

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,36 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
219219

220220
return result;
221221
}
222+
223+
/// @dev Choose a random subset of validators (selects AT LEAST ONE but NOT ALL)
224+
function _chooseSubset(uint40[] memory validators) internal returns (uint40[] memory) {
225+
require(validators.length >= 2, "Need at least 2 validators to choose subset");
226+
227+
uint40[] memory result = new uint40[](validators.length);
228+
uint newLen;
229+
230+
uint rand = _randUint({ min: 1, max: validators.length ** 2 });
231+
for (uint i = 0; i < validators.length; i++) {
232+
if (rand >> i & 1 == 1) {
233+
result[newLen] = validators[i];
234+
newLen++;
235+
}
236+
}
237+
238+
// If we picked all, remove one random validator
239+
if (newLen == validators.length) {
240+
uint indexToRemove = _randUint({ min: 0, max: validators.length - 1 });
241+
for (uint i = indexToRemove; i < newLen - 1; i++) {
242+
result[i] = result[i + 1];
243+
}
244+
newLen--;
245+
}
246+
247+
// Update array length
248+
assembly { mstore(result, newLen) }
249+
250+
return result;
251+
}
222252

223253
function _getTokenName(IERC20 token) internal view returns (string memory) {
224254
if (token == NATIVE_ETH) {
@@ -1281,8 +1311,11 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
12811311
uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
12821312
slashedShares = prevShares[i].mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
12831313
}
1314+
console.log(prevShares[i]);
1315+
console.log(slashedShares);
1316+
console.log(curShares[i]);
12841317

1285-
assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1, err);
1318+
assertApproxEqAbs(prevShares[i] - slashedShares, curShares[i], 1000, err);
12861319
}
12871320
}
12881321

@@ -2115,42 +2148,18 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
21152148

21162149
function _strategiesAndWadsForFullSlash(
21172150
OperatorSet memory operatorSet
2118-
) internal view returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
2119-
// Get list of all strategies in an operator set.
2120-
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
2121-
2122-
wadsToSlash = new uint[](strategies.length);
2123-
2124-
for (uint i; i < strategies.length; ++i) {
2125-
wadsToSlash[i] = 1 ether;
2126-
}
2151+
) internal view returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
2152+
// Get list of all strategies in an operator set.
2153+
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
2154+
2155+
wadsToSlash = new uint[](strategies.length);
2156+
2157+
for (uint i; i < strategies.length; ++i) {
2158+
wadsToSlash[i] = 1 ether;
2159+
}
21272160

2128-
return(strategies.sort(), wadsToSlash);
2161+
return (strategies.sort(), wadsToSlash);
21292162
}
2130-
2131-
function _strategiesAndWadsForRandFullSlash(
2132-
OperatorSet memory operatorSet
2133-
) internal returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
2134-
// Get list of all strategies in an operator set.
2135-
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
2136-
2137-
// Randomly select a subset of strategies to slash.
2138-
uint len = _randUint({ min: 1, max: strategies.length });
2139-
2140-
// Update length of strategies array.
2141-
assembly {
2142-
mstore(strategies, len)
2143-
}
2144-
2145-
wadsToSlash = new uint[](len);
2146-
2147-
// Fully slash each selected strategy
2148-
for (uint i; i < len; ++i) {
2149-
wadsToSlash[i] = 1 ether;
2150-
}
2151-
2152-
return (strategies.sort(), wadsToSlash);
2153-
}
21542163

21552164
function _randMagnitudes(uint64 sum, uint256 len) internal returns (uint64[] memory magnitudes) {
21562165
magnitudes = new uint64[](len);

src/test/integration/IntegrationChecks.t.sol

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,56 @@ contract IntegrationCheckUtils is IntegrationBase {
971971
}
972972
}
973973

974+
function check_Withdrawal_AsTokens_State_AfterBeaconSlash(
975+
User staker,
976+
User operator,
977+
IDelegationManagerTypes.Withdrawal memory withdrawal,
978+
IAllocationManagerTypes.AllocateParams memory allocateParams,
979+
IAllocationManagerTypes.SlashingParams memory slashingParams,
980+
uint[] memory expectedTokens
981+
) internal {
982+
IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length);
983+
984+
for (uint i; i < withdrawal.strategies.length; i++) {
985+
IStrategy strat = withdrawal.strategies[i];
986+
987+
bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy;
988+
989+
tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken();
990+
991+
if (slashingParams.strategies.contains(strat)) {
992+
uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
993+
994+
expectedTokens[i] -= expectedTokens[i]
995+
.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
996+
997+
uint256 max = allocationManager.getMaxMagnitude(address(operator), strat);
998+
999+
withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max);
1000+
1001+
// Round down to the nearest gwei for beaconchain ETH strategy.
1002+
if (isBeaconChainETHStrategy) {
1003+
expectedTokens[i] -= expectedTokens[i] % 1 gwei;
1004+
}
1005+
}
1006+
}
1007+
1008+
// Common checks
1009+
assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
1010+
1011+
// assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens");
1012+
assert_Snap_Unchanged_StakerDepositShares(staker, "staker shares should not have changed");
1013+
assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.scaledShares, "strategies should have total shares decremented");
1014+
1015+
// Checks specific to an operator that the Staker has delegated to
1016+
if (operator != User(payable(0))) {
1017+
if (operator != staker) {
1018+
assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed");
1019+
}
1020+
assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed");
1021+
}
1022+
}
1023+
9741024
function check_Withdrawal_AsShares_State_AfterSlash(
9751025
User staker,
9761026
User operator,

src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol

Lines changed: 40 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,23 @@ import "src/test/integration/IntegrationChecks.t.sol";
55
import "src/test/integration/users/User.t.sol";
66
import {console} from "forge-std/console.sol";
77

8-
contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is IntegrationCheckUtils {
8+
contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is IntegrationCheckUtils, IDelegationManagerTypes {
99

10-
AVS avs;
11-
OperatorSet operatorSet;
12-
13-
User operator;
14-
AllocateParams allocateParams;
15-
16-
User staker;
17-
IStrategy[] strategies;
18-
uint[] initTokenBalances;
19-
uint[] initDepositShares;
20-
21-
uint[] numTokensRemaining;
22-
23-
function _init() internal override {
24-
// TODO: Partial deposits don't work when beacon chain eth balance is initialized to < 64 ETH, need to write _newRandomStaker variant that ensures beacon chain ETH balance
25-
// greater than or equal to 64
26-
_configAssetTypes(HOLDS_LST);
27-
_configUserTypes(DEFAULT);
28-
29-
(staker, strategies, initTokenBalances) = _newRandomStaker();
30-
(operator,,) = _newRandomOperator();
31-
(avs,) = _newRandomAVS();
32-
33-
uint256[] memory tokensToDeposit = new uint256[](initTokenBalances.length);
34-
numTokensRemaining = new uint256[](initTokenBalances.length);
35-
for (uint256 i = 0; i < initTokenBalances.length; i++) {
36-
if (strategies[i] == BEACONCHAIN_ETH_STRAT) {
37-
tokensToDeposit[i] = initTokenBalances[i];
38-
continue;
39-
}
40-
41-
tokensToDeposit[i] = initTokenBalances[i]/2;
42-
numTokensRemaining[i] = initTokenBalances[i] - tokensToDeposit[i];
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];
4325
}
4426

4527
uint256[] memory shares = _calculateExpectedShares(strategies, tokensToDeposit);
@@ -53,127 +35,63 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat
5335
check_Delegation_State(staker, operator, strategies, shares);
5436

5537
// Create operator set and register operator
56-
operatorSet = avs.createOperatorSet(strategies);
38+
OperatorSet memory operatorSet = avs.createOperatorSet(strategies);
5739
operator.registerForOperatorSet(operatorSet);
58-
check_Registration_State_NoAllocation(operator, operatorSet, allStrats);
5940

6041
// 3. Allocate to operator set
61-
allocateParams = _genAllocation_AllAvailable(operator, operatorSet);
62-
operator.modifyAllocations(allocateParams);
63-
check_IncrAlloc_State_Slashable(operator, allocateParams);
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+
);
6451

6552
_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);
66-
}
67-
68-
function testFuzz_fullSlash_queue_complete_redeposit(
69-
uint24 _random
70-
) public rand(_random) {
53+
54+
assert_Snap_Allocations_Modified(
55+
operator,
56+
allocateParams,
57+
true,
58+
"operator allocations should be updated after delay"
59+
);
60+
7161
// 4. Fully slash operator
72-
SlashingParams memory slashingParams;
62+
IAllocationManagerTypes.SlashingParams memory slashingParams;
7363
{
7464
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
7565
_strategiesAndWadsForFullSlash(operatorSet);
7666

7767
slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
7868
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
79-
assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing");
69+
assert_Snap_Unchanged_StakerDepositShares(staker, "staker deposit shares should be unchanged after slashing");
8070
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
8171
}
8272

8373
// 5. Undelegate from an operator
84-
Withdrawal[] memory withdrawals = staker.undelegate();
85-
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
86-
87-
// 6. Complete withdrawal
88-
_rollBlocksForCompleteWithdrawals(withdrawals);
89-
for (uint256 i = 0; i < withdrawals.length; ++i) {
90-
uint256[] memory expectedTokens =
91-
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
92-
staker.completeWithdrawalAsTokens(withdrawals[i]);
93-
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
94-
}
95-
96-
// 7. Redeposit
97-
uint[] memory shares = _calculateExpectedShares(strategies, numTokensRemaining);
98-
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
99-
check_Deposit_State(staker, strategies, shares);
100-
101-
// Final state checks
102-
assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit");
103-
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
104-
}
105-
106-
function testFuzz_undelegate_fullSlash_complete_redeposit(
107-
uint24 _random
108-
) public rand(_random) {
109-
// 4. Undelegate from an operator
110-
Withdrawal[] memory withdrawals = staker.undelegate();
74+
IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate();
11175
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
11276

113-
// 5. Fully slash operator
114-
SlashingParams memory slashingParams;
115-
{
116-
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
117-
_strategiesAndWadsForFullSlash(operatorSet);
118-
119-
slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
120-
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
121-
assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing");
122-
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
123-
}
124-
12577
// 6. Complete withdrawal
12678
_rollBlocksForCompleteWithdrawals(withdrawals);
12779
for (uint256 i = 0; i < withdrawals.length; ++i) {
12880
uint256[] memory expectedTokens =
12981
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
82+
for (uint256 i = 0; i < expectedTokens.length; i++) {
83+
}
13084
staker.completeWithdrawalAsTokens(withdrawals[i]);
13185
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
13286
}
13387

13488
// 7. Redeposit
135-
uint[] memory shares = _calculateExpectedShares(strategies, numTokensRemaining);
89+
shares = _calculateExpectedShares(strategies, numTokensRemaining);
13690
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
13791
check_Deposit_State(staker, strategies, shares);
13892

13993
// Final state checks
14094
assert_HasExpectedShares(staker, strategies, shares, "staker should have expected shares after redeposit");
14195
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
14296
}
143-
144-
function testFuzz_depositFull_fullSlash_undelegate_completeAsShares(
145-
uint24 _random
146-
) public rand(_random) {
147-
uint[] memory shares = _calculateExpectedShares(strategies, numTokensRemaining);
148-
staker.depositIntoEigenlayer(strategies, numTokensRemaining);
149-
check_Deposit_State(staker, strategies, shares);
150-
151-
// 4. Fully slash random proper subset of operators strategies
152-
SlashingParams memory slashingParams;
153-
{
154-
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
155-
_strategiesAndWadsForRandFullSlash(operatorSet);
156-
slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
157-
assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed");
158-
assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing");
159-
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
160-
}
161-
162-
// 5. Undelegate from an operator
163-
Withdrawal[] memory withdrawals = staker.undelegate();
164-
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
165-
166-
// 6. Complete withdrawal as shares
167-
// Fast forward to when we can complete the withdrawal
168-
_rollBlocksForCompleteWithdrawals(withdrawals);
169-
170-
for (uint256 i = 0; i < withdrawals.length; ++i) {
171-
staker.completeWithdrawalAsShares(withdrawals[i]);
172-
check_Withdrawal_AsShares_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams);
173-
}
174-
175-
// Check final state:
176-
assert_HasNoUnderlyingTokenBalance(staker, slashingParams.strategies, "staker not have any underlying tokens");
177-
assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending");
178-
}
17997
}

0 commit comments

Comments
 (0)