Skip to content

Commit 26741b4

Browse files
committed
wip
1 parent a32a2d5 commit 26741b4

File tree

4 files changed

+128
-316
lines changed

4 files changed

+128
-316
lines changed

src/test/integration/IntegrationBase.t.sol

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,22 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
671671
}
672672
}
673673

674+
function assert_Snap_Removed_AllocatedStrats(
675+
User operator,
676+
OperatorSet memory operatorSet,
677+
IStrategy[] memory removedStrats,
678+
string memory err
679+
) internal {
680+
IStrategy[] memory curAllocatedStrats = _getAllocatedStrats(operator, operatorSet);
681+
IStrategy[] memory prevAllocatedStrats = _getPrevAllocatedStrats(operator, operatorSet);
682+
683+
assertEq(curAllocatedStrats.length + removedStrats.length, prevAllocatedStrats.length, err);
684+
685+
for (uint i = 0; i < removedStrats.length; i++) {
686+
assertFalse(curAllocatedStrats.contains(removedStrats[i]), err);
687+
}
688+
}
689+
674690
function assert_Snap_Unchanged_StrategyAllocations(
675691
User operator,
676692
OperatorSet memory operatorSet,
@@ -1141,14 +1157,17 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
11411157

11421158
function assert_Snap_Slashed_MaxMagnitude(
11431159
User operator,
1160+
OperatorSet memory operatorSet,
11441161
SlashingParams memory params,
11451162
string memory err
11461163
) internal {
1164+
Allocation[] memory prevAllocations = _getPrevAllocations(operator, operatorSet, params.strategies);
1165+
11471166
Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, params.strategies);
11481167
Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies);
11491168

11501169
for (uint i = 0; i < params.strategies.length; i++) {
1151-
uint expectedSlashed = prevMagnitudes[i].max.mulWadRoundUp(params.wadsToSlash[i]);
1170+
uint expectedSlashed = prevAllocations[i].currentMagnitude.mulWadRoundUp(params.wadsToSlash[i]);
11521171
assertEq(curMagnitudes[i].max, prevMagnitudes[i].max - expectedSlashed, err);
11531172
}
11541173
}
@@ -1236,33 +1255,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
12361255
}
12371256
}
12381257

1239-
function assert_HasUnderlyingTokenBalances_AfterSlash(
1240-
User staker,
1241-
AllocateParams memory allocateParams,
1242-
SlashingParams memory slashingParams,
1243-
uint[] memory expectedBalances,
1244-
string memory err
1245-
) internal view {
1246-
for (uint i; i < allocateParams.strategies.length; ++i) {
1247-
IStrategy strat = allocateParams.strategies[i];
1248-
1249-
uint balance = strat == BEACONCHAIN_ETH_STRAT
1250-
? address(staker).balance
1251-
: strat.underlyingToken().balanceOf(address(staker));
1252-
1253-
uint256 maxDelta = strat == BEACONCHAIN_ETH_STRAT ? 1 gwei : 3;
1254-
1255-
if (slashingParams.strategies.contains(strat)) {
1256-
uint256 wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
1257-
1258-
expectedBalances[i] -= expectedBalances[i]
1259-
.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
1260-
}
1261-
1262-
assertApproxEqAbs(expectedBalances[i], balance, maxDelta, err);
1263-
}
1264-
}
1265-
12661258
function assert_Snap_StakerWithdrawableShares_AfterSlash(
12671259
User staker,
12681260
AllocateParams memory allocateParams,
@@ -2045,7 +2037,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
20452037
) internal returns (SlashingParams memory params) {
20462038
params.operator = address(operator);
20472039
params.operatorSetId = operatorSet.id;
2048-
params.description = "genSlashing_Half";
2040+
params.description = "genSlashing_Rand";
20492041
params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort();
20502042
params.wadsToSlash = new uint[](params.strategies.length);
20512043

@@ -2067,37 +2059,30 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
20672059
params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort();
20682060
params.wadsToSlash = new uint[](params.strategies.length);
20692061

2062+
// slash 50%
20702063
for (uint i = 0; i < params.wadsToSlash.length; i++) {
2071-
params.wadsToSlash[i] = 1e17;
2064+
params.wadsToSlash[i] = 5e17;
20722065
}
20732066
}
20742067

2075-
function _randWadToSlash() internal returns (uint) {
2076-
return _randUint({ min: 0.01 ether, max: 1 ether });
2077-
}
2078-
2079-
function _randStrategiesAndWadsToSlash(
2068+
function _genSlashing_Full(
2069+
User operator,
20802070
OperatorSet memory operatorSet
2081-
) internal returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) {
2082-
// Get list of all strategies in an operator set.
2083-
strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
2084-
2085-
// Randomly select a subset of strategies to slash.
2086-
uint len = _randUint({ min: 1, max: strategies.length });
2071+
) internal view returns (SlashingParams memory params) {
2072+
params.operator = address(operator);
2073+
params.operatorSetId = operatorSet.id;
2074+
params.description = "_genSlashing_Full";
2075+
params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort();
2076+
params.wadsToSlash = new uint[](params.strategies.length);
20872077

2088-
// Update length of strategies array.
2089-
assembly {
2090-
mstore(strategies, len)
2091-
}
2092-
2093-
wadsToSlash = new uint[](len);
2094-
2095-
// Randomly select a `wadToSlash` for each strategy.
2096-
for (uint i; i < len; ++i) {
2097-
wadsToSlash[i] = _randWadToSlash();
2078+
// slash 100%
2079+
for (uint i = 0; i < params.wadsToSlash.length; i++) {
2080+
params.wadsToSlash[i] = 1e18;
20982081
}
2082+
}
20992083

2100-
return (strategies.sort(), wadsToSlash);
2084+
function _randWadToSlash() internal returns (uint) {
2085+
return _randUint({ min: 0.01 ether, max: 1 ether });
21012086
}
21022087

21032088
function _strategiesAndWadsForFullSlash(
@@ -2288,6 +2273,13 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
22882273
}
22892274
}
22902275

2276+
function _calculateExpectedShares(Withdrawal memory withdrawal) internal returns (uint[] memory) {
2277+
bytes32 root = delegationManager.calculateWithdrawalRoot(withdrawal);
2278+
2279+
(, uint[] memory shares) = delegationManager.getQueuedWithdrawalFromRoot(root);
2280+
return shares;
2281+
}
2282+
22912283
/// @dev For some strategies/underlying token balances, calculate the expected shares received
22922284
/// from depositing all tokens
22932285
function _calculateExpectedShares(IStrategy[] memory strategies, uint[] memory tokenBalances) internal returns (uint[] memory) {

src/test/integration/IntegrationChecks.t.sol

Lines changed: 11 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ contract IntegrationCheckUtils is IntegrationBase {
237237
// ... check that the staker is undelegated, all strategies from which the staker is deposited are unqueued,
238238
// that the returned root matches the hashes for each strategy and share amounts, and that the staker
239239
// and operator have reduced shares
240+
assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares");
240241
assertFalse(delegationManager.isDelegated(address(staker)),
241242
"check_Undelegate_State: staker should not be delegated");
242243
assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots,
@@ -338,7 +339,7 @@ contract IntegrationCheckUtils is IntegrationBase {
338339
if (operator != staker) {
339340
assert_Snap_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances");
340341
}
341-
assert_Snap_Added_OperatorShares(operator, withdrawal.strategies, withdrawal.scaledShares, "operator should have received shares");
342+
assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares");
342343
}
343344
}
344345

@@ -882,7 +883,7 @@ contract IntegrationCheckUtils is IntegrationBase {
882883
check_IsSlashable_State(operator, operatorSet, allocateParams.strategies);
883884

884885
// Slashing SHOULD change max magnitude and current allocation
885-
assert_Snap_Slashed_MaxMagnitude(operator, slashParams, "slash should lower max magnitude");
886+
assert_Snap_Slashed_MaxMagnitude(operator, operatorSet, slashParams, "slash should lower max magnitude");
886887
assert_Snap_Slashed_EncumberedMagnitude(operator, slashParams, "slash should lower max magnitude");
887888
assert_Snap_Slashed_AllocatedStake(operator, operatorSet, slashParams, "slash should lower allocated stake");
888889
assert_Snap_Slashed_SlashableStake(operator, operatorSet, slashParams, "slash should lower slashable stake");
@@ -894,97 +895,19 @@ contract IntegrationCheckUtils is IntegrationBase {
894895
assert_Snap_Unchanged_AllocatableMagnitude(operator, allStrats, "slashing should not change allocatable magnitude");
895896
assert_Snap_Unchanged_Registration(operator, operatorSet, "slash should not change registration status");
896897
assert_Snap_Unchanged_Slashability(operator, operatorSet, "slash should not change slashability status");
897-
assert_Snap_Unchanged_AllocatedSets(operator, "should not have updated allocated sets");
898-
assert_Snap_Unchanged_AllocatedStrats(operator, operatorSet, "should not have updated allocated strategies");
898+
// assert_Snap_Unchanged_AllocatedSets(operator, "should not have updated allocated sets");
899+
// assert_Snap_Unchanged_AllocatedStrats(operator, operatorSet, "should not have updated allocated strategies");
899900
}
900901

901-
// TODO: improvement needed
902-
903-
function check_Withdrawal_AsTokens_State_AfterSlash(
904-
User staker,
902+
/// Slashing invariants when the operator has been fully slashed for every strategy in the operator set
903+
function check_FullySlashed_State(
905904
User operator,
906-
Withdrawal memory withdrawal,
907905
AllocateParams memory allocateParams,
908-
SlashingParams memory slashingParams,
909-
uint[] memory expectedTokens
910-
) internal {
911-
IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length);
912-
913-
for (uint i; i < withdrawal.strategies.length; i++) {
914-
IStrategy strat = withdrawal.strategies[i];
915-
916-
bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy;
917-
918-
tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken();
919-
920-
if (slashingParams.strategies.contains(strat)) {
921-
uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)];
922-
923-
expectedTokens[i] -= expectedTokens[i]
924-
.mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash));
925-
926-
uint256 max = allocationManager.getMaxMagnitude(address(operator), strat);
927-
928-
withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max);
929-
930-
// Round down to the nearest gwei for beaconchain ETH strategy.
931-
if (isBeaconChainETHStrategy) {
932-
expectedTokens[i] -= expectedTokens[i] % 1 gwei;
933-
}
934-
}
935-
}
936-
937-
// Common checks
938-
assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
939-
940-
// TODO FIXME
941-
// assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens");
942-
assert_Snap_Unchanged_Staker_DepositShares(staker, "staker shares should not have changed");
943-
assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.scaledShares, "strategies should have total shares decremented");
944-
945-
// Checks specific to an operator that the Staker has delegated to
946-
if (operator != User(payable(0))) {
947-
if (operator != staker) {
948-
assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed");
949-
}
950-
assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed");
951-
}
952-
}
953-
954-
function check_Withdrawal_AsShares_State_AfterSlash(
955-
User staker,
956-
User operator,
957-
Withdrawal memory withdrawal,
958-
AllocateParams memory allocateParams, // TODO - was this needed?
959-
SlashingParams memory slashingParams
906+
SlashingParams memory slashParams
960907
) internal {
961-
IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length);
962-
963-
for (uint i; i < withdrawal.strategies.length; i++) {
964-
IStrategy strat = withdrawal.strategies[i];
908+
check_Base_Slashing_State(operator, allocateParams, slashParams);
965909

966-
bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy;
967-
968-
tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken();
969-
970-
if (slashingParams.strategies.contains(strat)) {
971-
uint256 max = allocationManager.getMaxMagnitude(address(operator), strat);
972-
973-
withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max);
974-
}
975-
}
976-
977-
// Common checks applicable to both user and non-user operator types
978-
assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending");
979-
assert_Snap_Unchanged_TokenBalances(staker, "staker should not have any change in underlying token balances");
980-
assert_Snap_Added_Staker_DepositShares(staker, withdrawal.strategies, withdrawal.scaledShares, "staker should have received expected shares");
981-
assert_Snap_Unchanged_StrategyShares(withdrawal.strategies, "strategies should have total shares unchanged");
982-
983-
// Additional checks or handling for the non-user operator scenario
984-
if (operator != User(User(payable(0)))) {
985-
if (operator != staker) {
986-
assert_Snap_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances");
987-
}
988-
}
910+
assert_Snap_Removed_AllocatedSet(operator, allocateParams.operatorSet, "should not have updated allocated sets");
911+
assert_Snap_Removed_AllocatedStrats(operator, allocateParams.operatorSet, slashParams.strategies, "should not have updated allocated strategies");
989912
}
990913
}

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

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat
1515

1616
User staker;
1717
IStrategy[] strategies;
18+
IERC20[] tokens;
1819
uint[] initTokenBalances;
1920
uint[] initDepositShares;
2021

@@ -29,6 +30,7 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat
2930
(staker, strategies, initTokenBalances) = _newRandomStaker();
3031
(operator,,) = _newRandomOperator();
3132
(avs,) = _newRandomAVS();
33+
tokens = _getUnderlyingTokens(strategies);
3234

3335
uint256[] memory tokensToDeposit = new uint256[](initTokenBalances.length);
3436
numTokensRemaining = new uint256[](initTokenBalances.length);
@@ -65,32 +67,26 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat
6567
_rollBlocksForCompleteAllocation(operator, operatorSet, strategies);
6668
}
6769

68-
function testFuzz_fullSlash_queue_complete_redeposit(
70+
function testFuzz_fullSlash_undelegate_complete_redeposit(
6971
uint24 _random
7072
) public rand(_random) {
7173
// 4. Fully slash operator
72-
SlashingParams memory slashingParams;
73-
{
74-
(IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) =
75-
_strategiesAndWadsForFullSlash(operatorSet);
76-
77-
slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash);
78-
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");
80-
assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed");
81-
}
74+
SlashingParams memory slashParams = _genSlashing_Full(operator, operatorSet);
75+
avs.slashOperator(slashParams);
76+
check_FullySlashed_State(operator, allocateParams, slashParams);
8277

8378
// 5. Undelegate from an operator
8479
Withdrawal[] memory withdrawals = staker.undelegate();
8580
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
8681

87-
// 6. Complete withdrawal
82+
// 6. Complete withdrawal. Staker should receive 0 shares/tokens after a full slash
8883
_rollBlocksForCompleteWithdrawals(withdrawals);
84+
uint[] memory expectedShares = new uint[](strategies.length);
85+
uint[] memory expectedTokens = new uint[](strategies.length);
86+
8987
for (uint256 i = 0; i < withdrawals.length; ++i) {
90-
uint256[] memory expectedTokens =
91-
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
9288
staker.completeWithdrawalAsTokens(withdrawals[i]);
93-
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
89+
check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, expectedShares, tokens, expectedTokens);
9490
}
9591

9692
// 7. Redeposit
@@ -111,24 +107,18 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat
111107
bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals);
112108

113109
// 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-
}
110+
SlashingParams memory slashParams = _genSlashing_Full(operator, operatorSet);
111+
avs.slashOperator(slashParams);
112+
check_FullySlashed_State(operator, allocateParams, slashParams);
124113

125-
// 6. Complete withdrawal
114+
// 6. Complete withdrawal. Staker should receive 0 shares/tokens after a full slash
126115
_rollBlocksForCompleteWithdrawals(withdrawals);
116+
uint[] memory expectedShares = new uint[](strategies.length);
117+
uint[] memory expectedTokens = new uint[](strategies.length);
118+
127119
for (uint256 i = 0; i < withdrawals.length; ++i) {
128-
uint256[] memory expectedTokens =
129-
_calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares);
130120
staker.completeWithdrawalAsTokens(withdrawals[i]);
131-
check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens);
121+
check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, expectedShares, tokens, expectedTokens);
132122
}
133123

134124
// 7. Redeposit
@@ -168,8 +158,9 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat
168158
_rollBlocksForCompleteWithdrawals(withdrawals);
169159

170160
for (uint256 i = 0; i < withdrawals.length; ++i) {
161+
uint[] memory expectedShares = _calculateExpectedShares(withdrawals[i]);
171162
staker.completeWithdrawalAsShares(withdrawals[i]);
172-
check_Withdrawal_AsShares_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams);
163+
check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[i], withdrawals[i].strategies, expectedShares);
173164
}
174165

175166
// Check final state:

0 commit comments

Comments
 (0)