Skip to content

Commit d8747c6

Browse files
committed
test: redistribution integration (#1415)
**Motivation:** We want integration test coverage for the redistribution relaese. **Modifications:** - Fixed view revert bug (see comment bellow). - Added timing tests (cannot release escrow before delay, can after) **Result:** Integration test coverage.
1 parent e5431a9 commit d8747c6

20 files changed

+750
-158
lines changed

.github/workflows/foundry.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ jobs:
6666
- name: Run ${{ matrix.suite }} tests
6767
run: |
6868
case "${{ matrix.suite }}" in
69-
Unit) forge test --no-match-contract Integration ;;
70-
Integration) forge test --match-contract Integration ;;
71-
Fork) forge test --match-contract Integration ;;
69+
Unit) forge test --no-match-contract Integration -vvv ;;
70+
Integration) forge test --match-contract Integration -vvv ;;
71+
Fork) forge test --match-contract Integration -vvv ;;
7272
esac
7373
env:
7474
FOUNDRY_PROFILE: ${{ matrix.suite == 'Fork' && 'forktest' || 'medium' }}

src/contracts/core/StrategyManager.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ contract StrategyManager is
466466

467467
for (uint256 i = 0; i < keys.length; ++i) {
468468
strategies[i] = IStrategy(keys[i]);
469-
shares[i] = burnOrRedistributableShares.get(keys[i]);
469+
(, shares[i]) = burnOrRedistributableShares.tryGet(keys[i]);
470470
}
471471

472472
return (strategies, shares);
@@ -478,7 +478,8 @@ contract StrategyManager is
478478
uint256 slashId,
479479
IStrategy strategy
480480
) external view returns (uint256) {
481-
return _burnOrRedistributableShares[operatorSet.key()][slashId].get(address(strategy));
481+
(, uint256 shares) = _burnOrRedistributableShares[operatorSet.key()][slashId].tryGet(address(strategy));
482+
return shares;
482483
}
483484

484485
/// @inheritdoc IStrategyManager

src/contracts/interfaces/ISlashEscrowFactory.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ interface ISlashEscrowFactory is ISlashEscrowFactoryErrors, ISlashEscrowFactoryE
272272
) external pure returns (bytes32);
273273

274274
/**
275-
* @notice Returns whether a slash escrow is deployed or still counterfactual.
275+
* @notice Returns whether a slash escrow is deployed or not.
276276
* @param operatorSet The operator set whose slash escrow is being queried.
277277
* @param slashId The slash ID of the slash escrow that is being queried.
278278
* @return Whether the slash escrow is deployed.

src/test/integration/IntegrationBase.t.sol

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -202,15 +202,16 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
202202
view
203203
{
204204
for (uint i = 0; i < strategies.length; i++) {
205-
IStrategy strat = strategies[i];
205+
assert_HasUnderlyingTokenBalance(user, strategies[i], expectedBalances[i], err);
206+
}
207+
}
206208

207-
uint expectedBalance = expectedBalances[i];
208-
uint tokenBalance;
209-
if (strat == BEACONCHAIN_ETH_STRAT) tokenBalance = address(user).balance;
210-
else tokenBalance = strat.underlyingToken().balanceOf(address(user));
209+
function assert_HasUnderlyingTokenBalance(User user, IStrategy strategy, uint expectedBalance, string memory err) internal view {
210+
uint tokenBalance;
211+
if (strategy == BEACONCHAIN_ETH_STRAT) tokenBalance = address(user).balance;
212+
else tokenBalance = strategy.underlyingToken().balanceOf(address(user));
211213

212-
assertApproxEqAbs(expectedBalance, tokenBalance, 1, err);
213-
}
214+
assertApproxEqAbs(expectedBalance, tokenBalance, 1, err);
214215
}
215216

216217
function assert_HasNoUnderlyingTokenBalance(User user, IStrategy[] memory strategies, string memory err) internal view {
@@ -1408,17 +1409,38 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
14081409
OperatorSet memory operatorSet,
14091410
User operator,
14101411
SlashingParams memory params,
1412+
uint slashId,
14111413
string memory err
14121414
) internal {
14131415
uint[] memory curShares = _getOperatorShares(operator, params.strategies);
14141416
uint[] memory prevShares = _getPrevOperatorShares(operator, params.strategies);
14151417

14161418
for (uint i = 0; i < params.strategies.length; i++) {
1417-
uint curBurnable = _getBurnableShares(params.strategies[i]);
1418-
uint prevBurnable = _getPrevBurnableShares(params.strategies[i]);
1419+
uint curBurnable = _getBurnOrRedistributableShares(operatorSet, slashId, params.strategies[i]);
1420+
uint prevBurnable = _getPrevBurnOrRedistributableShares(operatorSet, slashId, params.strategies[i]);
14191421
uint slashedAtLeast = prevShares[i] - curShares[i];
14201422
// Not factoring in slashable shares in queue here, because that gets more complex (TODO)
14211423
assertTrue(curBurnable >= (prevBurnable + slashedAtLeast), err);
1424+
1425+
// TODO: Improve this check in the future, it's not very optimized.
1426+
// In the future, we can simply use a flag to communicate whether the operator set is redistributable.
1427+
if (curShares[i] == prevShares[i]) continue;
1428+
bool flag = false;
1429+
for (uint j = 0; j < params.strategies.length; j++) {
1430+
if (params.strategies[j] == BEACONCHAIN_ETH_STRAT) flag = true;
1431+
}
1432+
if (flag) continue;
1433+
1434+
assertTrue(_getIsPendingOperatorSet(operatorSet), "operator set should be pending");
1435+
1436+
assertTrue(_getIsPendingSlashId(operatorSet, slashId), "slash id should be pending");
1437+
assertFalse(_getPrevIsPendingSlashId(operatorSet, slashId), "slash id should not be pending");
1438+
1439+
assertEq(_getEscrowStartBlock(operatorSet, slashId), block.number, "escrow start block should be current block");
1440+
assertEq(_getPrevEscrowStartBlock(operatorSet, slashId), 0, "escrow start block should be 0");
1441+
1442+
assertTrue(_getIsDeployedSlashEscrow(operatorSet, slashId), "escrow should be deployed");
1443+
assertFalse(_getPrevIsDeployedSlashEscrow(operatorSet, slashId), "escrow should not be deployed");
14221444
}
14231445
}
14241446

@@ -2215,6 +2237,22 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
22152237
}
22162238
}
22172239

2240+
function _genSlashing_SingleStrategy(User operator, OperatorSet memory operatorSet, IStrategy strategy)
2241+
internal
2242+
returns (SlashingParams memory params)
2243+
{
2244+
params.operator = address(operator);
2245+
params.operatorSetId = operatorSet.id;
2246+
params.description = "_genSlashing_SingleStrategy";
2247+
params.strategies = strategy.toArray();
2248+
params.wadsToSlash = new uint[](params.strategies.length);
2249+
2250+
// slash 100%
2251+
for (uint i = 0; i < params.wadsToSlash.length; i++) {
2252+
params.wadsToSlash[i] = 1e18;
2253+
}
2254+
}
2255+
22182256
function _genSlashing_Custom(User operator, OperatorSet memory operatorSet, uint wadsToSlash)
22192257
internal
22202258
returns (SlashingParams memory params)
@@ -2533,6 +2571,10 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
25332571
cheats.roll(latest + 1);
25342572
}
25352573

2574+
function _rollBlocksForCompleteSlashEscrow() internal {
2575+
cheats.roll(block.number + INITIAL_GLOBAL_DELAY_BLOCKS + 1);
2576+
}
2577+
25362578
/// @dev Uses timewarp modifier to get the operator set strategy allocations at the last snapshot.
25372579
function _getPrevAllocations(User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies)
25382580
internal
@@ -2709,12 +2751,54 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter {
27092751
return allocationManager.isMemberOfOperatorSet(address(operator), operatorSet);
27102752
}
27112753

2712-
function _getPrevBurnableShares(IStrategy strategy) internal timewarp returns (uint) {
2713-
return _getBurnableShares(strategy);
2754+
function _getPrevBurnOrRedistributableShares(OperatorSet memory operatorSet, uint slashId, IStrategy strategy)
2755+
internal
2756+
timewarp
2757+
returns (uint)
2758+
{
2759+
return _getBurnOrRedistributableShares(operatorSet, slashId, strategy);
2760+
}
2761+
2762+
function _getBurnOrRedistributableShares(OperatorSet memory operatorSet, uint slashId, IStrategy strategy)
2763+
internal
2764+
view
2765+
returns (uint)
2766+
{
2767+
return strategy == beaconChainETHStrategy
2768+
? eigenPodManager.burnableETHShares()
2769+
: strategyManager.getBurnOrRedistributableShares(operatorSet, slashId, strategy);
2770+
}
2771+
2772+
function _getPrevIsPendingOperatorSet(OperatorSet memory operatorSet) internal timewarp returns (bool) {
2773+
return _getIsPendingOperatorSet(operatorSet);
2774+
}
2775+
2776+
function _getIsPendingOperatorSet(OperatorSet memory operatorSet) internal view returns (bool) {
2777+
return slashEscrowFactory.isPendingOperatorSet(operatorSet);
2778+
}
2779+
2780+
function _getPrevIsPendingSlashId(OperatorSet memory operatorSet, uint slashId) internal timewarp returns (bool) {
2781+
return _getIsPendingSlashId(operatorSet, slashId);
2782+
}
2783+
2784+
function _getIsPendingSlashId(OperatorSet memory operatorSet, uint slashId) internal view returns (bool) {
2785+
return slashEscrowFactory.isPendingSlashId(operatorSet, slashId);
2786+
}
2787+
2788+
function _getPrevEscrowStartBlock(OperatorSet memory operatorSet, uint slashId) internal timewarp returns (uint) {
2789+
return _getEscrowStartBlock(operatorSet, slashId);
2790+
}
2791+
2792+
function _getEscrowStartBlock(OperatorSet memory operatorSet, uint slashId) internal view returns (uint) {
2793+
return slashEscrowFactory.getEscrowStartBlock(operatorSet, slashId);
2794+
}
2795+
2796+
function _getPrevIsDeployedSlashEscrow(OperatorSet memory operatorSet, uint slashId) internal timewarp returns (bool) {
2797+
return _getIsDeployedSlashEscrow(operatorSet, slashId);
27142798
}
27152799

2716-
function _getBurnableShares(IStrategy strategy) internal view returns (uint) {
2717-
return strategy == beaconChainETHStrategy ? eigenPodManager.burnableETHShares() : strategyManager.getBurnableShares(strategy);
2800+
function _getIsDeployedSlashEscrow(OperatorSet memory operatorSet, uint slashId) internal view returns (bool) {
2801+
return slashEscrowFactory.isDeployedSlashEscrow(operatorSet, slashId);
27182802
}
27192803

27202804
function _getPrevSlashableSharesInQueue(User operator, IStrategy[] memory strategies) internal timewarp returns (uint[] memory) {

src/test/integration/IntegrationChecks.t.sol

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,9 @@ contract IntegrationCheckUtils is IntegrationBase {
10521052
* ALM - SLASHING
10531053
*
10541054
*/
1055-
function check_Base_Slashing_State(User operator, AllocateParams memory allocateParams, SlashingParams memory slashParams) internal {
1055+
function check_Base_Slashing_State(User operator, AllocateParams memory allocateParams, SlashingParams memory slashParams, uint slashId)
1056+
internal
1057+
{
10561058
OperatorSet memory operatorSet = allocateParams.operatorSet;
10571059

10581060
check_MaxMag_Invariants(operator);
@@ -1065,7 +1067,7 @@ contract IntegrationCheckUtils is IntegrationBase {
10651067
assert_Snap_Slashed_SlashableStake(operator, operatorSet, slashParams, "slash should lower slashable stake");
10661068
assert_Snap_Slashed_OperatorShares(operator, slashParams, "slash should remove operator shares");
10671069
assert_Snap_Slashed_Allocation(operator, operatorSet, slashParams, "slash should reduce current magnitude");
1068-
// assert_Snap_Increased_BurnableShares(operatorSet, operator, slashParams, "slash should increase burnable shares");
1070+
assert_Snap_Increased_BurnableShares(operatorSet, operator, slashParams, slashId, "slash should increase burnable shares");
10691071

10701072
// Slashing SHOULD NOT change allocatable magnitude, registration, and slashability status
10711073
assert_Snap_Unchanged_AllocatableMagnitude(operator, allStrats, "slashing should not change allocatable magnitude");
@@ -1079,22 +1081,55 @@ contract IntegrationCheckUtils is IntegrationBase {
10791081
User operator,
10801082
AllocateParams memory allocateParams,
10811083
SlashingParams memory slashParams,
1082-
Withdrawal[] memory withdrawals
1084+
Withdrawal[] memory withdrawals,
1085+
uint slashId
10831086
) internal {
1084-
check_Base_Slashing_State(operator, allocateParams, slashParams);
1087+
check_Base_Slashing_State(operator, allocateParams, slashParams, slashId);
10851088
assert_Snap_Decreased_SlashableSharesInQueue(operator, slashParams, withdrawals, "slash should decrease slashable shares in queue");
10861089
}
10871090

10881091
/// Slashing invariants when the operator has been fully slashed for every strategy in the operator set
1089-
function check_FullySlashed_State(User operator, AllocateParams memory allocateParams, SlashingParams memory slashParams) internal {
1090-
check_Base_Slashing_State(operator, allocateParams, slashParams);
1092+
function check_FullySlashed_State(User operator, AllocateParams memory allocateParams, SlashingParams memory slashParams, uint slashId)
1093+
internal
1094+
{
1095+
check_Base_Slashing_State(operator, allocateParams, slashParams, slashId);
10911096

10921097
assert_Snap_Removed_AllocatedSet(operator, allocateParams.operatorSet, "should not have updated allocated sets");
10931098
assert_Snap_Removed_AllocatedStrats(
10941099
operator, allocateParams.operatorSet, slashParams.strategies, "should not have updated allocated strategies"
10951100
);
10961101
}
10971102

1103+
function check_releaseSlashEscrow_State(
1104+
OperatorSet memory operatorSet,
1105+
uint slashId,
1106+
IStrategy[] memory strategies,
1107+
uint[] memory initTokenBalances,
1108+
address redistributionRecipient
1109+
) internal {
1110+
assert_HasUnderlyingTokenBalances(
1111+
User(payable(redistributionRecipient)),
1112+
strategies,
1113+
initTokenBalances,
1114+
"redistribution recipient should have underlying token balances"
1115+
);
1116+
1117+
assertFalse(_getIsPendingSlashId(operatorSet, slashId), "slash id should not be pending");
1118+
assertEq(_getEscrowStartBlock(operatorSet, slashId), 0, "escrow start block should be deleted after");
1119+
assertTrue(_getIsDeployedSlashEscrow(operatorSet, slashId), "escrow should be deployed after");
1120+
}
1121+
1122+
function check_releaseSlashEscrow_State_NoneRemaining(
1123+
OperatorSet memory operatorSet,
1124+
uint slashId,
1125+
IStrategy[] memory strategies,
1126+
uint[] memory initTokenBalances,
1127+
address redistributionRecipient
1128+
) internal {
1129+
check_releaseSlashEscrow_State(operatorSet, slashId, strategies, initTokenBalances, redistributionRecipient);
1130+
assertFalse(_getIsPendingOperatorSet(operatorSet), "operator set should not be pending");
1131+
}
1132+
10981133
/**
10991134
*
11001135
* DUAL SLASHING CHECKS

src/test/integration/IntegrationDeployer.t.sol

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {
5454

5555
uint8 constant NUM_LST_STRATS = 32;
5656

57-
uint32 INITIAL_GLOBAL_DELAY_BLOCKS = 28_800; // 4 days in blocks
57+
uint32 INITIAL_GLOBAL_DELAY_BLOCKS = 4 days / 12 seconds; // 4 days in blocks
5858

5959
// Lists of strategies used in the system
6060
//
@@ -443,6 +443,12 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {
443443
allocationManager.initialize({initialPausedStatus: 0});
444444

445445
strategyFactory.initialize({_initialOwner: executorMultisig, _initialPausedStatus: 0, _strategyBeacon: strategyBeacon});
446+
447+
slashEscrowFactory.initialize({
448+
initialOwner: communityMultisig,
449+
initialPausedStatus: 0,
450+
initialGlobalDelayBlocks: INITIAL_GLOBAL_DELAY_BLOCKS
451+
});
446452
}
447453

448454
/// @dev Deploy a strategy and its underlying token, push to global lists of tokens/strategies, and whitelist
@@ -720,29 +726,13 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {
720726
return userType;
721727
}
722728

723-
function _shuffle(IStrategy[] memory strats) internal returns (IStrategy[] memory) {
724-
uint[] memory casted;
725-
726-
assembly {
727-
casted := strats
728-
}
729-
730-
casted = vm.shuffle(casted);
731-
732-
assembly {
733-
strats := casted
734-
}
735-
736-
return strats;
737-
}
738-
739729
function _randomStrategies() internal returns (IStrategy[][] memory strategies) {
740730
uint numOpSets = _randUint({min: 1, max: 5});
741731

742732
strategies = new IStrategy[][](numOpSets);
743733

744734
for (uint i; i < numOpSets; ++i) {
745-
IStrategy[] memory randomStrategies = _shuffle(allStrats);
735+
IStrategy[] memory randomStrategies = allStrats.shuffle();
746736
uint numStrategies = _randUint({min: 1, max: maxUniqueAssetsHeld});
747737

748738
// Modify the length of the array in memory (thus ignoring remaining elements).

0 commit comments

Comments
 (0)