diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2366b1bd1d..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "solidity.packageDefaultDependenciesContractsDirectory": "src", - "solidity.packageDefaultDependenciesDirectory": "lib", - "editor.formatOnSave": true, - "[solidity]": { - "editor.defaultFormatter": "JuanBlanco.solidity" - }, - "solidity.formatter": "forge", - "solidity.compileUsingRemoteVersion": "latest" -} \ No newline at end of file diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index d8f4884018..f140467a19 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -45,133 +45,94 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { * This user is ready to deposit into some strategies and has some underlying token balances */ function _newRandomStaker() internal returns (User, IStrategy[] memory, uint[] memory) { - string memory stakerName; + (User staker, IStrategy[] memory strategies, uint[] memory tokenBalances) + = _randUser(_getStakerName()); - User staker; - IStrategy[] memory strategies; - uint[] memory tokenBalances; - - if (!isUpgraded) { - stakerName = string.concat("M2Staker", cheats.toString(numStakers)); - - (staker, strategies, tokenBalances) = _randUser(stakerName); - - stakersToMigrate.push(staker); - } else { - stakerName = string.concat("staker", cheats.toString(numStakers)); - - (staker, strategies, tokenBalances) = _randUser(stakerName); - } + if (!isUpgraded) stakersToMigrate.push(staker); assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "_newRandomStaker: failed to award token balances"); - - numStakers++; return (staker, strategies, tokenBalances); } - function _newBasicStaker() internal returns (User, IStrategy[] memory, uint[] memory) { - string memory stakerName; - - User staker; - IStrategy[] memory strategies; - uint[] memory tokenBalances; - - if (!isUpgraded) { - stakerName = string.concat("M2Staker", cheats.toString(numStakers)); - - (staker, strategies, tokenBalances) = _randUser(stakerName); - - stakersToMigrate.push(staker); - } else { - stakerName = string.concat("staker", cheats.toString(numStakers)); - - (staker, strategies, tokenBalances) = _randUser(stakerName); - } + /// Given a list of strategies, creates a new user with random token balances in each underlying token + function _newStaker(IStrategy[] memory strategies) internal returns (User, uint[] memory) { + (User staker, uint[] memory tokenBalances) + = _randUser(_getStakerName(), strategies); - assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "_newRandomStaker: failed to award token balances"); + if (!isUpgraded) stakersToMigrate.push(staker); - numStakers++; - assembly { // TODO HACK - mstore(strategies, 1) - mstore(tokenBalances, 1) - } - return (staker, strategies, tokenBalances); + assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "_newStaker: failed to award token balances"); + return (staker, tokenBalances); } /** * @dev Create a new operator according to configured random variants. * This user will immediately deposit their randomized assets into eigenlayer. - * @notice If forktype is mainnet and not upgraded, then the operator will only randomize LSTs assets and deposit them - * as ETH podowner shares are not available yet. */ function _newRandomOperator() internal returns (User, IStrategy[] memory, uint[] memory) { - User operator; - IStrategy[] memory strategies; - uint[] memory tokenBalances; - uint[] memory addedShares; - - if (!isUpgraded) { - string memory operatorName = string.concat("M2Operator", numOperators.toString()); + /// TODO: Allow operators to have ETH + (User operator, IStrategy[] memory strategies, uint[] memory tokenBalances) + = _randUser_NoETH(_getOperatorName()); - // Create an operator for M2. - // TODO: Allow this operator to have ETH - (operator, strategies, tokenBalances) = _randUser_NoETH(operatorName); + /// Operators are created with all assets already deposited + uint[] memory addedShares = _calculateExpectedShares(strategies, tokenBalances); + operator.depositIntoEigenlayer(strategies, tokenBalances); - addedShares = _calculateExpectedShares(strategies, tokenBalances); - + /// Registration flow differs for M2 vs Slashing release + if (!isUpgraded) { User_M2(payable(operator)).registerAsOperator_M2(); - operator.depositIntoEigenlayer(strategies, tokenBalances); // Deposits interface doesn't change between M2 and slashing operatorsToMigrate.push(operator); } else { - string memory operatorName = string.concat("operator", numOperators.toString()); - - (operator, strategies, tokenBalances) = _randUser_NoETH(operatorName); - - addedShares = _calculateExpectedShares(strategies, tokenBalances); - operator.registerAsOperator(); - operator.depositIntoEigenlayer(strategies, tokenBalances); - // Roll past the allocation configuration delay rollForward({blocks: ALLOCATION_CONFIGURATION_DELAY + 1}); - - assert_Snap_Added_Staker_DepositShares(operator, strategies, addedShares, "_newRandomOperator: failed to add delegatable shares"); - } + } assert_Snap_Added_OperatorShares(operator, strategies, addedShares, "_newRandomOperator: failed to award shares to operator"); assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered"); - - numOperators++; + assertEq(delegationManager.delegatedTo(address(operator)), address(operator), "_newRandomOperator: should be self-delegated"); return (operator, strategies, tokenBalances); } /// @dev Creates a new operator with no assets function _newRandomOperator_NoAssets() internal returns (User) { - User operator; + User operator = _randUser_NoAssets(_getOperatorName()); + /// Registration flow differs for M2 vs Slashing release if (!isUpgraded) { - string memory operatorName = string.concat("M2Operator", numOperators.toString()); - - // Create an operator for M2. - operator = _randUser_NoAssets(operatorName); User_M2(payable(operator)).registerAsOperator_M2(); operatorsToMigrate.push(operator); } else { - string memory operatorName = string.concat("operator", numOperators.toString()); - - operator = _randUser_NoAssets(operatorName); operator.registerAsOperator(); - // Roll past the allocation configuration delay rollForward({blocks: ALLOCATION_CONFIGURATION_DELAY + 1}); - } + } assertTrue(delegationManager.isOperator(address(operator)), "_newRandomOperator: operator should be registered"); + assertEq(delegationManager.delegatedTo(address(operator)), address(operator), "_newRandomOperator: should be self-delegated"); + return operator; + } + /// @dev Name a newly-created staker ("staker1", "staker2", ...) + function _getStakerName() private returns (string memory) { + numStakers++; + + string memory stakerNum = cheats.toString(numStakers); + string memory namePrefix = isUpgraded ? "staker" : "m2-staker"; + + return string.concat(namePrefix, stakerNum); + } + + /// @dev Name a newly-created operator ("operator1", "operator2", ...) + function _getOperatorName() private returns (string memory) { numOperators++; - return operator; + + string memory operatorNum = cheats.toString(numOperators); + string memory namePrefix = isUpgraded ? "operator" : "m2-operator"; + + return string.concat(namePrefix, operatorNum); } function _newRandomAVS() internal returns (AVS avs, OperatorSet[] memory operatorSets) { @@ -570,7 +531,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { User staker, IStrategy[] memory strategies, string memory err - ) internal { + ) internal view { uint[] memory depositScalingFactors = _getDepositScalingFactors(staker, strategies); for (uint i = 0; i < strategies.length; i++) { assertEq(depositScalingFactors[i], WAD, err); @@ -671,6 +632,22 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Removed_AllocatedStrats( + User operator, + OperatorSet memory operatorSet, + IStrategy[] memory removedStrats, + string memory err + ) internal { + IStrategy[] memory curAllocatedStrats = _getAllocatedStrats(operator, operatorSet); + IStrategy[] memory prevAllocatedStrats = _getPrevAllocatedStrats(operator, operatorSet); + + assertEq(curAllocatedStrats.length + removedStrats.length, prevAllocatedStrats.length, err); + + for (uint i = 0; i < removedStrats.length; i++) { + assertFalse(curAllocatedStrats.contains(removedStrats[i]), err); + } + } + function assert_Snap_Unchanged_StrategyAllocations( User operator, OperatorSet memory operatorSet, @@ -890,7 +867,9 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); for (uint i = 0; i < params.strategies.length; i++) { - uint expectedSlashed = SlashingLib.calcSlashedAmount({ + // Slashing doesn't occur if the operator has no slashable magnitude + // This prevents a div by 0 when calculating expected slashed + uint expectedSlashed = prevMagnitudes[i].max == 0 ? 0 : SlashingLib.calcSlashedAmount({ operatorShares: prevSlashableStake[i], prevMaxMagnitude: prevMagnitudes[i].max, newMaxMagnitude: curMagnitudes[i].max @@ -955,7 +934,9 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); for (uint i = 0; i < curAllocatedStake.length; i++) { - uint expectedSlashed = SlashingLib.calcSlashedAmount({ + // Slashing doesn't occur if the operator has no slashable magnitude + // This prevents a div by 0 when calculating expected slashed + uint expectedSlashed = prevMagnitudes[i].max == 0 ? 0 : SlashingLib.calcSlashedAmount({ operatorShares: prevAllocatedStake[i], prevMaxMagnitude: prevMagnitudes[i].max, newMaxMagnitude: curMagnitudes[i].max @@ -1141,14 +1122,17 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { function assert_Snap_Slashed_MaxMagnitude( User operator, + OperatorSet memory operatorSet, SlashingParams memory params, string memory err ) internal { + Allocation[] memory prevAllocations = _getPrevAllocations(operator, operatorSet, params.strategies); + Magnitudes[] memory curMagnitudes = _getMagnitudes(operator, params.strategies); Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); for (uint i = 0; i < params.strategies.length; i++) { - uint expectedSlashed = prevMagnitudes[i].max.mulWadRoundUp(params.wadsToSlash[i]); + uint expectedSlashed = prevAllocations[i].currentMagnitude.mulWadRoundUp(params.wadsToSlash[i]); assertEq(curMagnitudes[i].max, prevMagnitudes[i].max - expectedSlashed, err); } } @@ -1236,33 +1220,6 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } - function assert_HasUnderlyingTokenBalances_AfterSlash( - User staker, - AllocateParams memory allocateParams, - SlashingParams memory slashingParams, - uint[] memory expectedBalances, - string memory err - ) internal view { - for (uint i; i < allocateParams.strategies.length; ++i) { - IStrategy strat = allocateParams.strategies[i]; - - uint balance = strat == BEACONCHAIN_ETH_STRAT - ? address(staker).balance - : strat.underlyingToken().balanceOf(address(staker)); - - uint256 maxDelta = strat == BEACONCHAIN_ETH_STRAT ? 1 gwei : 3; - - if (slashingParams.strategies.contains(strat)) { - uint256 wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; - - expectedBalances[i] -= expectedBalances[i] - .mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); - } - - assertApproxEqAbs(expectedBalances[i], balance, maxDelta, err); - } - } - function assert_Snap_StakerWithdrawableShares_AfterSlash( User staker, AllocateParams memory allocateParams, @@ -1380,7 +1337,9 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { Magnitudes[] memory prevMagnitudes = _getPrevMagnitudes(operator, params.strategies); for (uint i = 0; i < params.strategies.length; i++) { - uint expectedSlashed = SlashingLib.calcSlashedAmount({ + // Slashing doesn't occur if the operator has no slashable magnitude + // This prevents a div by 0 when calculating expected slashed + uint expectedSlashed = prevMagnitudes[i].max == 0 ? 0 : SlashingLib.calcSlashedAmount({ operatorShares: prevShares[i], prevMaxMagnitude: prevMagnitudes[i].max, newMaxMagnitude: curMagnitudes[i].max @@ -1520,11 +1479,12 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } /// @dev Check that all the staker's withdrawable shares have been removed + /// TODO - this is not a snap assertion, investigate usage function assert_Snap_RemovedAll_Staker_WithdrawableShares( User staker, IStrategy[] memory strategies, string memory err - ) internal { + ) internal view { uint[] memory curShares = _getStakerWithdrawableShares(staker, strategies); // For each strategy, check all shares have been withdrawn for (uint i = 0; i < strategies.length; i++) { @@ -2020,16 +1980,16 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { User operator, OperatorSet memory operatorSet ) internal view returns (AllocateParams memory params) { - return _genDeallocation_Full({ - operator: operator, - operatorSet: operatorSet, - strategies: allocationManager.getStrategiesInOperatorSet(operatorSet) - }); + return _genDeallocation_Full( + operator, + operatorSet, + allocationManager.getStrategiesInOperatorSet(operatorSet) + ); } /// @dev Generates params for a full deallocation from all strategies the operator is allocated to in the operator set function _genDeallocation_Full( - User operator, + User, OperatorSet memory operatorSet, IStrategy[] memory strategies ) internal pure returns (AllocateParams memory params) { @@ -2045,7 +2005,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { ) internal returns (SlashingParams memory params) { params.operator = address(operator); params.operatorSetId = operatorSet.id; - params.description = "genSlashing_Half"; + params.description = "genSlashing_Rand"; params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort(); params.wadsToSlash = new uint[](params.strategies.length); @@ -2067,37 +2027,30 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort(); params.wadsToSlash = new uint[](params.strategies.length); + // slash 50% for (uint i = 0; i < params.wadsToSlash.length; i++) { - params.wadsToSlash[i] = 1e17; + params.wadsToSlash[i] = 5e17; } } - function _randWadToSlash() internal returns (uint) { - return _randUint({ min: 0.01 ether, max: 1 ether }); - } - - function _randStrategiesAndWadsToSlash( + function _genSlashing_Full( + User operator, OperatorSet memory operatorSet - ) internal returns (IStrategy[] memory strategies, uint[] memory wadsToSlash) { - // Get list of all strategies in an operator set. - strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); - - // Randomly select a subset of strategies to slash. - uint len = _randUint({ min: 1, max: strategies.length }); + ) internal view returns (SlashingParams memory params) { + params.operator = address(operator); + params.operatorSetId = operatorSet.id; + params.description = "_genSlashing_Full"; + params.strategies = allocationManager.getStrategiesInOperatorSet(operatorSet).sort(); + params.wadsToSlash = new uint[](params.strategies.length); - // Update length of strategies array. - assembly { - mstore(strategies, len) - } - - wadsToSlash = new uint[](len); - - // Randomly select a `wadToSlash` for each strategy. - for (uint i; i < len; ++i) { - wadsToSlash[i] = _randWadToSlash(); + // slash 100% + for (uint i = 0; i < params.wadsToSlash.length; i++) { + params.wadsToSlash[i] = 1e18; } + } - return (strategies.sort(), wadsToSlash); + function _randWadToSlash() internal returns (uint) { + return _randUint({ min: 0.01 ether, max: 1 ether }); } function _strategiesAndWadsForFullSlash( @@ -2288,6 +2241,13 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function _calculateExpectedShares(Withdrawal memory withdrawal) internal view returns (uint[] memory) { + bytes32 root = delegationManager.calculateWithdrawalRoot(withdrawal); + + (, uint[] memory shares) = delegationManager.getQueuedWithdrawal(root); + return shares; + } + /// @dev For some strategies/underlying token balances, calculate the expected shares received /// from depositing all tokens function _calculateExpectedShares(IStrategy[] memory strategies, uint[] memory tokenBalances) internal returns (uint[] memory) { diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 129e02a995..0174f653bd 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -210,7 +210,8 @@ contract IntegrationCheckUtils is IntegrationBase { User staker, User operator, IStrategy[] memory strategies, - uint[] memory shares, + uint[] memory depositShares, + uint[] memory withdrawableShares, Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots ) internal { @@ -226,11 +227,11 @@ contract IntegrationCheckUtils is IntegrationBase { "check_QueuedWithdrawal_State: calculated withdrawals should match returned roots"); assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "check_QueuedWithdrawal_State: staker should have increased nonce by withdrawals.length"); - assert_Snap_Removed_OperatorShares(operator, strategies, shares, + assert_Snap_Removed_OperatorShares(operator, strategies, withdrawableShares, "check_QueuedWithdrawal_State: failed to remove operator shares"); - assert_Snap_Removed_Staker_DepositShares(staker, strategies, shares, + assert_Snap_Removed_Staker_DepositShares(staker, strategies, depositShares, "check_QueuedWithdrawal_State: failed to remove staker shares"); - assert_Snap_Removed_Staker_WithdrawableShares(staker, strategies, shares, + assert_Snap_Removed_Staker_WithdrawableShares(staker, strategies, withdrawableShares, "check_QueuedWithdrawal_State: failed to remove staker withdrawable shares"); } @@ -248,6 +249,7 @@ contract IntegrationCheckUtils is IntegrationBase { // ... check that the staker is undelegated, all strategies from which the staker is deposited are unqueued, // that the returned root matches the hashes for each strategy and share amounts, and that the staker // and operator have reduced shares + assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); assertFalse(delegationManager.isDelegated(address(staker)), "check_Undelegate_State: staker should not be delegated"); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, @@ -268,7 +270,8 @@ contract IntegrationCheckUtils is IntegrationBase { function check_Redelegate_State( User staker, - User operator, + User prevOperator, + User newOperator, IDelegationManagerTypes.Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots, IStrategy[] memory strategies, @@ -282,13 +285,16 @@ contract IntegrationCheckUtils is IntegrationBase { // and operator have reduced shares assertTrue(delegationManager.isDelegated(address(staker)), "check_Redelegate_State: staker should not be delegated"); + assertEq(address(newOperator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator"); + assert_HasExpectedShares(staker, strategies, new uint[](strategies.length), "staker should not have deposit shares after redelegation"); + assert_Snap_Unchanged_OperatorShares(newOperator, "new operator should not have received any shares"); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "check_Redelegate_State: calculated withdrawal should match returned root"); assert_AllWithdrawalsPending(withdrawalRoots, "check_Redelegate_State: stakers withdrawal should now be pending"); assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "check_Redelegate_State: staker should have increased nonce by withdrawals.length"); - assert_Snap_Removed_OperatorShares(operator, strategies, stakerDelegatedShares, + assert_Snap_Removed_OperatorShares(prevOperator, strategies, stakerDelegatedShares, "check_Redelegate_State: failed to remove operator shares"); assert_Snap_Removed_Staker_DepositShares(staker, strategies, stakerDepositShares, "check_Redelegate_State: failed to remove staker shares"); @@ -349,7 +355,7 @@ contract IntegrationCheckUtils is IntegrationBase { if (operator != staker) { assert_Snap_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances"); } - assert_Snap_Added_OperatorShares(operator, withdrawal.strategies, withdrawal.scaledShares, "operator should have received shares"); + assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); } } @@ -438,7 +444,7 @@ contract IntegrationCheckUtils is IntegrationBase { /// @dev Check global max magnitude invariants - these should ALWAYS hold function check_MaxMag_Invariants( User operator - ) internal { + ) internal view { assert_MaxMagsEqualMaxMagsAtCurrentBlock(operator, allStrats, "max magnitudes should equal upperlookup at current block"); assert_MaxEqualsAllocatablePlusEncumbered(operator, "max magnitude should equal encumbered plus allocatable"); } @@ -447,7 +453,7 @@ contract IntegrationCheckUtils is IntegrationBase { function check_ActiveModification_State( User operator, AllocateParams memory params - ) internal { + ) internal view { OperatorSet memory operatorSet = params.operatorSet; IStrategy[] memory strategies = params.strategies; @@ -459,7 +465,7 @@ contract IntegrationCheckUtils is IntegrationBase { User operator, OperatorSet memory operatorSet, IStrategy[] memory strategies - ) internal { + ) internal view { assert_IsSlashable(operator, operatorSet, "operator should be slashable for operator set"); assert_CurMinSlashableEqualsMinAllocated(operator, operatorSet, strategies, "minimum slashable stake should equal allocated stake at current block"); } @@ -467,7 +473,7 @@ contract IntegrationCheckUtils is IntegrationBase { function check_NotSlashable_State( User operator, OperatorSet memory operatorSet - ) internal { + ) internal view { assert_NotSlashable(operator, operatorSet, "operator should not be slashable for operator set"); assert_NoSlashableStake(operator, operatorSet, "operator should not have any slashable stake"); } @@ -893,8 +899,8 @@ contract IntegrationCheckUtils is IntegrationBase { check_IsSlashable_State(operator, operatorSet, allocateParams.strategies); // Slashing SHOULD change max magnitude and current allocation - assert_Snap_Slashed_MaxMagnitude(operator, slashParams, "slash should lower max magnitude"); - assert_Snap_Slashed_EncumberedMagnitude(operator, slashParams, "slash should lower max magnitude"); + assert_Snap_Slashed_MaxMagnitude(operator, operatorSet, slashParams, "slash should lower max magnitude"); + assert_Snap_Slashed_EncumberedMagnitude(operator, slashParams, "slash should lower encumbered magnitude"); assert_Snap_Slashed_AllocatedStake(operator, operatorSet, slashParams, "slash should lower allocated stake"); assert_Snap_Slashed_SlashableStake(operator, operatorSet, slashParams, "slash should lower slashable stake"); assert_Snap_Slashed_OperatorShares(operator, slashParams, "slash should remove operator shares"); @@ -905,97 +911,19 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Unchanged_AllocatableMagnitude(operator, allStrats, "slashing should not change allocatable magnitude"); assert_Snap_Unchanged_Registration(operator, operatorSet, "slash should not change registration status"); assert_Snap_Unchanged_Slashability(operator, operatorSet, "slash should not change slashability status"); - assert_Snap_Unchanged_AllocatedSets(operator, "should not have updated allocated sets"); - assert_Snap_Unchanged_AllocatedStrats(operator, operatorSet, "should not have updated allocated strategies"); + // assert_Snap_Unchanged_AllocatedSets(operator, "should not have updated allocated sets"); + // assert_Snap_Unchanged_AllocatedStrats(operator, operatorSet, "should not have updated allocated strategies"); } - // TODO: improvement needed - - function check_Withdrawal_AsTokens_State_AfterSlash( - User staker, + /// Slashing invariants when the operator has been fully slashed for every strategy in the operator set + function check_FullySlashed_State( User operator, - Withdrawal memory withdrawal, AllocateParams memory allocateParams, - SlashingParams memory slashingParams, - uint[] memory expectedTokens - ) internal { - IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length); - - for (uint i; i < withdrawal.strategies.length; i++) { - IStrategy strat = withdrawal.strategies[i]; - - bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy; - - tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken(); - - if (slashingParams.strategies.contains(strat)) { - uint wadToSlash = slashingParams.wadsToSlash[slashingParams.strategies.indexOf(strat)]; - - expectedTokens[i] -= expectedTokens[i] - .mulWadRoundUp(allocateParams.newMagnitudes[i].mulWadRoundUp(wadToSlash)); - - uint256 max = allocationManager.getMaxMagnitude(address(operator), strat); - - withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max); - - // Round down to the nearest gwei for beaconchain ETH strategy. - if (isBeaconChainETHStrategy) { - expectedTokens[i] -= expectedTokens[i] % 1 gwei; - } - } - } - - // Common checks - assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); - - // TODO FIXME - // assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker shares should not have changed"); - assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.scaledShares, "strategies should have total shares decremented"); - - // Checks specific to an operator that the Staker has delegated to - if (operator != User(payable(0))) { - if (operator != staker) { - assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); - } - assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); - } - } - - function check_Withdrawal_AsShares_State_AfterSlash( - User staker, - User operator, - Withdrawal memory withdrawal, - AllocateParams memory allocateParams, // TODO - was this needed? - SlashingParams memory slashingParams + SlashingParams memory slashParams ) internal { - IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length); - - for (uint i; i < withdrawal.strategies.length; i++) { - IStrategy strat = withdrawal.strategies[i]; - - bool isBeaconChainETHStrategy = strat == beaconChainETHStrategy; + check_Base_Slashing_State(operator, allocateParams, slashParams); - tokens[i] = isBeaconChainETHStrategy ? NATIVE_ETH : withdrawal.strategies[i].underlyingToken(); - - if (slashingParams.strategies.contains(strat)) { - uint256 max = allocationManager.getMaxMagnitude(address(operator), strat); - - withdrawal.scaledShares[i] -= withdrawal.scaledShares[i].calcSlashedAmount(WAD, max); - } - } - - // Common checks applicable to both user and non-user operator types - assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); - assert_Snap_Unchanged_TokenBalances(staker, "staker should not have any change in underlying token balances"); - assert_Snap_Added_Staker_DepositShares(staker, withdrawal.strategies, withdrawal.scaledShares, "staker should have received expected shares"); - assert_Snap_Unchanged_StrategyShares(withdrawal.strategies, "strategies should have total shares unchanged"); - - // Additional checks or handling for the non-user operator scenario - if (operator != User(User(payable(0)))) { - if (operator != staker) { - assert_Snap_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances"); - } - } + assert_Snap_Removed_AllocatedSet(operator, allocateParams.operatorSet, "should not have updated allocated sets"); + assert_Snap_Removed_AllocatedStrats(operator, allocateParams.operatorSet, slashParams.strategies, "should not have updated allocated strategies"); } } diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 90811262cd..e3a0e4d06b 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -35,6 +35,7 @@ IStrategy constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeE abstract contract IntegrationDeployer is ExistingDeploymentParser { using StdStyle for *; + using ArrayLib for *; // Fork ids for specific fork tests bool isUpgraded; @@ -193,6 +194,13 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { _upgradeProxies(); _initializeProxies(); + // Place native ETH first in `allStrats` + // This ensures when we select a nonzero number of strategies from this array, we always + // have beacon chain ETH + ethStrats.push(BEACONCHAIN_ETH_STRAT); + allStrats.push(BEACONCHAIN_ETH_STRAT); + allTokens.push(NATIVE_ETH); + // Deploy and configure strategies and tokens for (uint i = 1; i < NUM_LST_STRATS + 1; ++i) { string memory name = string.concat("LST-Strat", cheats.toString(i), " token"); @@ -201,9 +209,6 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { _newStrategyAndToken(name, symbol, 10e50, address(this), i % 2 == 0); } - ethStrats.push(BEACONCHAIN_ETH_STRAT); - allStrats.push(BEACONCHAIN_ETH_STRAT); - allTokens.push(NATIVE_ETH); maxUniqueAssetsHeld = allStrats.length; // Create time machine and beacon chain. Set block time to beacon chain genesis time @@ -226,6 +231,13 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { string memory existingDeploymentParams = "script/configs/mainnet.json"; _parseParamsForIntegrationUpgrade(existingDeploymentParams); + // Place native ETH first in `allStrats` + // This ensures when we select a nonzero number of strategies from this array, we always + // have beacon chain ETH + ethStrats.push(BEACONCHAIN_ETH_STRAT); + allStrats.push(BEACONCHAIN_ETH_STRAT); + allTokens.push(NATIVE_ETH); + // Add deployed strategies to lstStrats and allStrats for (uint i; i < deployedStrategyArray.length; i++) { IStrategy strategy = IStrategy(deployedStrategyArray[i]); @@ -281,11 +293,6 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { }); cheats.stopPrank(); - - ethStrats.push(BEACONCHAIN_ETH_STRAT); - allStrats.push(BEACONCHAIN_ETH_STRAT); - allTokens.push(NATIVE_ETH); - maxUniqueAssetsHeld = allStrats.length; } function _deployProxies() public { @@ -547,49 +554,56 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { function _randUser( string memory name ) internal noTracing returns (User, IStrategy[] memory, uint[] memory) { - // For the new user, select what type of assets they'll have and whether - // they'll use `xWithSignature` methods. - // - // The values selected here are in the ranges configured via `_configRand` - uint assetType = _randAssetType(); - uint userType = _randUserType(); - // Deploy new User contract + uint userType = _randUserType(); User user = _genRandUser(name, userType); - // For the specific asset selection we made, get a random assortment of - // strategies and deal the user some corresponding underlying token balances - (IStrategy[] memory strategies, uint[] memory tokenBalances) = _dealRandAssets(user, assetType); + // For the specific asset selection we made, get a random assortment of strategies + // and deal the user some corresponding underlying token balances + uint assetType = _randAssetType(); + IStrategy[] memory strategies = _selectRandAssets(assetType); + uint[] memory tokenBalances = _dealRandAmounts(user, strategies); print.user(name, assetType, userType, strategies, tokenBalances); return (user, strategies, tokenBalances); } + function _randUser( + string memory name, + IStrategy[] memory strategies + ) internal noTracing returns (User, uint[] memory) { + // Deploy new User contract + uint userType = _randUserType(); + User user = _genRandUser(name, userType); + + // Deal the user some corresponding underlying token balances + uint[] memory tokenBalances = _dealRandAmounts(user, strategies); + + print.user(name, HOLDS_ALL, userType, strategies, tokenBalances); + return (user, tokenBalances); + } + /// @dev Create a new user without native ETH. See _randUser above for standard usage function _randUser_NoETH( string memory name ) internal noTracing returns (User, IStrategy[] memory, uint[] memory) { - // For the new user, select what type of assets they'll have and whether - // they'll use `xWithSignature` methods. - // - // The values selected here are in the ranges configured via `_configRand` + // Deploy new User contract uint userType = _randUserType(); + User user = _genRandUser(name, userType); // Pick the user's asset distribution, removing "native ETH" as an option // I'm sorry if this eventually leads to a bug that's really hard to track down uint assetType = _randAssetType(); if (assetType == HOLDS_ETH) { assetType = NO_ASSETS; - } else if (assetType == HOLDS_ALL) { + } else if (assetType == HOLDS_ALL || assetType == HOLDS_MAX) { assetType = HOLDS_LST; } - // Deploy new User contract - User user = _genRandUser(name, userType); - - // For the specific asset selection we made, get a random assortment of - // strategies and deal the user some corresponding underlying token balances - (IStrategy[] memory strategies, uint[] memory tokenBalances) = _dealRandAssets(user, assetType); + // For the specific asset selection we made, get a random assortment of strategies + // and deal the user some corresponding underlying token balances + IStrategy[] memory strategies = _selectRandAssets(assetType); + uint[] memory tokenBalances = _dealRandAmounts(user, strategies); print.user(name, assetType, userType, strategies, tokenBalances); return (user, strategies, tokenBalances); @@ -599,13 +613,8 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { function _randUser_NoAssets( string memory name ) internal noTracing returns (User) { - // For the new user, select what type of assets they'll have and whether - // they'll use `xWithSignature` methods. - // - // The values selected here are in the ranges configured via `_configRand` - uint userType = _randUserType(); - // Deploy new User contract + uint userType = _randUserType(); User user = _genRandUser(name, userType); print.user(name, NO_ASSETS, userType, new IStrategy[](0), new uint[](0)); @@ -651,83 +660,64 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser { } } - /// @dev For a given `assetType`, select a random assortment of strategies and assets - /// NO_ASSETS - return will be empty - /// HOLDS_LST - `strategies` will be a random subset of initialized strategies - /// `tokenBalances` will be the user's balances in each token - /// HOLDS_ETH - `strategies` will only contain BEACONCHAIN_ETH_STRAT, and - /// `tokenBalances` will contain the user's eth balance - /// HOLDS_ALL - `strategies` will contain ALL initialized strategies AND BEACONCHAIN_ETH_STRAT, and - /// `tokenBalances` will contain random token/eth balances accordingly - function _dealRandAssets(User user, uint assetType) internal noTracing returns (IStrategy[] memory, uint[] memory) { - IStrategy[] memory strategies; - uint[] memory tokenBalances; + /// Given an assetType, select strategies the user will be dealt assets in + function _selectRandAssets(uint assetType) internal noTracing returns (IStrategy[] memory) { if (assetType == NO_ASSETS) { - strategies = new IStrategy[](0); - tokenBalances = new uint[](0); - } else if (assetType == HOLDS_LST) { - assetType = HOLDS_LST; - // Select a random number of assets - uint max = lstStrats.length; - if (max > maxUniqueAssetsHeld) { - max = maxUniqueAssetsHeld; - } - uint numAssets = _randUint({min: 1, max: max}); + return new IStrategy[](0); + } + + /// Select only ETH + if (assetType == HOLDS_ETH) { + return beaconChainETHStrategy.toArray(); + } - strategies = new IStrategy[](numAssets); - tokenBalances = new uint[](numAssets); + /// Select multiple LSTs, and maybe add ETH: - // For each asset, award the user a random balance of the underlying token - for (uint i = 0; i < numAssets; i++) { - IStrategy strat = lstStrats[i]; - IERC20 underlyingToken = strat.underlyingToken(); - uint balance = _randUint({min: MIN_BALANCE, max: MAX_BALANCE}); + // Select number of assets: + // HOLDS_LST can hold at most all LSTs. HOLDS_ALL and HOLDS_MAX also hold ETH. + // Clamp number of assets to maxUniqueAssetsHeld (guaranteed to be at least 1) + uint assetPoolSize = assetType == HOLDS_LST ? lstStrats.length : allStrats.length; + uint maxAssets = assetPoolSize > maxUniqueAssetsHeld ? maxUniqueAssetsHeld : assetPoolSize; - StdCheats.deal(address(underlyingToken), address(user), balance); - tokenBalances[i] = balance; - strategies[i] = strat; + uint numAssets = assetType == HOLDS_MAX ? maxAssets : _randUint(1, maxAssets); + + IStrategy[] memory strategies = new IStrategy[](numAssets); + for (uint i = 0; i < strategies.length; i++) { + if (assetType == HOLDS_LST) { + strategies[i] = lstStrats[i]; + } else { + // allStrats[0] is the beaconChainETHStrategy + strategies[i] = allStrats[i]; } - } else if (assetType == HOLDS_ETH) { - strategies = new IStrategy[](1); - tokenBalances = new uint[](1); + } - // Award the user with a random amount of ETH - // This guarantees a multiple of 32 ETH (at least 1, up to/incl 5) - uint amount = 32 ether * _randUint({min: 1, max: 5}); - cheats.deal(address(user), amount); + return strategies; + } - strategies[0] = BEACONCHAIN_ETH_STRAT; - tokenBalances[0] = amount; - } else if (assetType == HOLDS_ALL || assetType == HOLDS_MAX) { - uint randHeld = _randUint({min: 1, max: maxUniqueAssetsHeld-1}); - uint numLSTs = assetType == HOLDS_MAX ? lstStrats.length : randHeld; - strategies = new IStrategy[](numLSTs + 1); - tokenBalances = new uint[](numLSTs + 1); + /// Given an input list of strategies, deal random underlying token amounts to a user + function _dealRandAmounts(User user, IStrategy[] memory strategies) internal noTracing returns (uint[] memory) { + uint[] memory tokenBalances = new uint[](strategies.length); + + for (uint i = 0; i < tokenBalances.length; i++) { + IStrategy strategy = strategies[i]; + uint balance; - // For each LST, award the user a random balance of the underlying token - for (uint i = 0; i < numLSTs; i++) { - IStrategy strat = lstStrats[i]; - IERC20 underlyingToken = strat.underlyingToken(); - uint balance = _randUint({min: MIN_BALANCE, max: MAX_BALANCE}); + if (strategy == BEACONCHAIN_ETH_STRAT) { + // Award the user with a random amount of ETH + // This guarantees a multiple of 32 ETH (at least 1, up to/incl 5) + balance = 32 ether * _randUint({min: 1, max: 5}); + cheats.deal(address(user), balance); + } else { + IERC20 underlyingToken = strategy.underlyingToken(); + balance = _randUint({min: MIN_BALANCE, max: MAX_BALANCE}); StdCheats.deal(address(underlyingToken), address(user), balance); - tokenBalances[i] = balance; - strategies[i] = strat; } - // Award the user with a random amount of ETH - // This guarantees a multiple of 32 ETH (at least 1, up to/incl 5) - uint amount = 32 ether * _randUint({min: 1, max: 5}); - cheats.deal(address(user), amount); - - // Add BEACONCHAIN_ETH_STRAT and eth balance - strategies[numLSTs] = BEACONCHAIN_ETH_STRAT; - tokenBalances[numLSTs] = amount; - } else { - revert("_dealRandAssets: assetType unimplemented"); + tokenBalances[i] = balance; } - return (strategies, tokenBalances); + return tokenBalances; } /// @dev Uses `random` to return a random uint, with a range given by `min` and `max` (inclusive) diff --git a/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol index b58269e028..9c57581eae 100644 --- a/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol +++ b/src/test/integration/tests/Delegate_Deposit_Queue_Complete.t.sol @@ -24,9 +24,10 @@ contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils { assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); // 3. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 4. Complete Queued Withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); @@ -55,9 +56,10 @@ contract Integration_Delegate_Deposit_Queue_Complete is IntegrationCheckUtils { assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); // 3. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 4. Complete Queued Withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); diff --git a/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol b/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol index e38bc43ecb..8819edba37 100644 --- a/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Allocate_Slash_Queue_Redeposit.t.sol @@ -15,6 +15,7 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat User staker; IStrategy[] strategies; + IERC20[] tokens; uint[] initTokenBalances; uint[] initDepositShares; @@ -29,6 +30,7 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat (staker, strategies, initTokenBalances) = _newRandomStaker(); (operator,,) = _newRandomOperator(); (avs,) = _newRandomAVS(); + tokens = _getUnderlyingTokens(strategies); uint256[] memory tokensToDeposit = new uint256[](initTokenBalances.length); numTokensRemaining = new uint256[](initTokenBalances.length); @@ -65,32 +67,26 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); } - function testFuzz_fullSlash_queue_complete_redeposit( + function testFuzz_fullSlash_undelegate_complete_redeposit( uint24 _random ) public rand(_random) { // 4. Fully slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _strategiesAndWadsForFullSlash(operatorSet); - - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); - } + SlashingParams memory slashParams = _genSlashing_Full(operator, operatorSet); + avs.slashOperator(slashParams); + check_FullySlashed_State(operator, allocateParams, slashParams); // 5. Undelegate from an operator Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - // 6. Complete withdrawal + // 6. Complete withdrawal. Staker should receive 0 shares/tokens after a full slash _rollBlocksForCompleteWithdrawals(withdrawals); + uint[] memory expectedShares = new uint[](strategies.length); + uint[] memory expectedTokens = new uint[](strategies.length); + for (uint256 i = 0; i < withdrawals.length; ++i) { - uint256[] memory expectedTokens = - _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, expectedShares, tokens, expectedTokens); } // 7. Redeposit @@ -111,24 +107,18 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); // 5. Fully slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _strategiesAndWadsForFullSlash(operatorSet); - - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); - } + SlashingParams memory slashParams = _genSlashing_Full(operator, operatorSet); + avs.slashOperator(slashParams); + check_FullySlashed_State(operator, allocateParams, slashParams); - // 6. Complete withdrawal + // 6. Complete withdrawal. Staker should receive 0 shares/tokens after a full slash _rollBlocksForCompleteWithdrawals(withdrawals); + uint[] memory expectedShares = new uint[](strategies.length); + uint[] memory expectedTokens = new uint[](strategies.length); + for (uint256 i = 0; i < withdrawals.length; ++i) { - uint256[] memory expectedTokens = - _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, expectedShares, tokens, expectedTokens); } // 7. Redeposit @@ -168,8 +158,9 @@ contract Integration_Deposit_Delegate_Allocate_Slash_Queue_Redeposit is Integrat _rollBlocksForCompleteWithdrawals(withdrawals); for (uint256 i = 0; i < withdrawals.length; ++i) { + uint[] memory expectedShares = _calculateExpectedShares(withdrawals[i]); staker.completeWithdrawalAsShares(withdrawals[i]); - check_Withdrawal_AsShares_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams); + check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[i], withdrawals[i].strategies, expectedShares); } // Check final state: diff --git a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol index 2245f53a90..d4cea402ef 100644 --- a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol @@ -43,9 +43,10 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { check_Delegation_State(staker, operator, strategies, shares); // 3. Queue Withdrawals + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -96,9 +97,10 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { check_Delegation_State(staker, operator, strategies, shares); // 3. Queue Withdrawals + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -160,7 +162,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { Withdrawal[] memory withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawShares, withdrawals, withdrawalRoots); // 4. Complete withdrawals // Fast forward to when we can complete the withdrawal @@ -216,7 +218,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { Withdrawal[] memory withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawShares, withdrawals, withdrawalRoots); // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol index 48b37cad8b..130a184e1d 100644 --- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -64,9 +64,10 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); // 6. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); withdrawals = staker.queueWithdrawals(strategies, shares); withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 7. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -129,9 +130,10 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); // 6. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); withdrawals = staker.queueWithdrawals(strategies, shares); withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 7. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -230,7 +232,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti shares = _calculateExpectedShares(strategies, tokenBalances); Withdrawal[] memory newWithdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory newWithdrawalRoots = _getWithdrawalHashes(newWithdrawals); - check_QueuedWithdrawal_State(staker, operator2, strategies, shares, newWithdrawals, newWithdrawalRoots); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, shares, newWithdrawals, newWithdrawalRoots); // 8. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -323,7 +325,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti totalShares = _calculateExpectedShares(strategies, tokenBalances); Withdrawal[] memory newWithdrawals = staker.queueWithdrawals(strategies, totalShares); bytes32[] memory newWithdrawalRoots = _getWithdrawalHashes(newWithdrawals); - check_QueuedWithdrawal_State(staker, operator2, strategies, totalShares, newWithdrawals, newWithdrawalRoots); + check_QueuedWithdrawal_State(staker, operator2, strategies, totalShares, totalShares, newWithdrawals, newWithdrawalRoots); // 8. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -388,10 +390,13 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti check_Delegation_State(staker, operator2, strategies, shares); assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); - // 7. Queue Withdrawal - withdrawals = staker.queueWithdrawals(strategies, shares); - withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); + { + // 7. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); + withdrawals = staker.queueWithdrawals(strategies, shares); + withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); + } // 8. Complete withdrawal as shares // Fast forward to when we can complete the withdrawal @@ -459,7 +464,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUti shares = _calculateExpectedShares(strategies, withdrawnTokenBalances); withdrawals = staker.queueWithdrawals(strategies, shares); withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, shares, withdrawals, withdrawalRoots); // 8. Complete withdrawal as shares // Fast forward to when we can complete the withdrawal diff --git a/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol index 20c4ccbcaf..a0b3f58f23 100644 --- a/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Register_QueueWithdrawal_Complete.t.sol @@ -19,9 +19,10 @@ contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationChe assertTrue(delegationManager.isOperator(address(staker)), "Staker should be registered as an operator"); // 3. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 4. Complete Queued Withdrawal as Shares _rollBlocksForCompleteWithdrawals(withdrawals); @@ -44,9 +45,10 @@ contract Integration_Deposit_Register_QueueWithdrawal_Complete is IntegrationChe assertTrue(delegationManager.isOperator(address(staker)), "Staker should be registered as an operator"); // 3. Queue Withdrawal + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, staker, strategies, shares, withdrawableShares, withdrawals, withdrawalRoots); // 4. Complete Queued Withdrawal as Tokens _rollBlocksForCompleteWithdrawals(withdrawals); diff --git a/src/test/integration/tests/Slashed_Eigenpod.t.sol b/src/test/integration/tests/Slashed_Eigenpod.t.sol index c98990ab19..af338d948b 100644 --- a/src/test/integration/tests/Slashed_Eigenpod.t.sol +++ b/src/test/integration/tests/Slashed_Eigenpod.t.sol @@ -237,7 +237,7 @@ contract Integration_SlashedEigenpod is IntegrationCheckUtils { // Undelegate from an operator IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.redelegate(operator2); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - check_Redelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, initDepositShares, delegatedShares); + check_Redelegate_State(staker, operator, operator2, withdrawals, withdrawalRoots, strategies, initDepositShares, delegatedShares); // Complete withdrawal as shares // Fast forward to when we can complete the withdrawal diff --git a/src/test/integration/tests/SlashingWithdrawals.t.sol b/src/test/integration/tests/SlashingWithdrawals.t.sol index e0349aefcb..7077a4a97b 100644 --- a/src/test/integration/tests/SlashingWithdrawals.t.sol +++ b/src/test/integration/tests/SlashingWithdrawals.t.sol @@ -10,9 +10,11 @@ contract Integration_ALMSlashBase is IntegrationCheckUtils { User operator; AllocateParams allocateParams; + SlashingParams slashParams; User staker; IStrategy[] strategies; + IERC20[] tokens; // underlying token for each strategy uint[] initTokenBalances; uint[] initDepositShares; @@ -26,13 +28,10 @@ contract Integration_ALMSlashBase is IntegrationCheckUtils { /// NOTE: Steps 4 and 5 are done in random order, as these should not have an outcome on the test function _init() internal virtual override { _configAssetTypes(HOLDS_LST); - // (staker, strategies, initTokenBalances) = _newRandomStaker(); - // operator = _newRandomOperator_NoAssets(); - // (avs,) = _newRandomAVS(); - - (staker, strategies, initTokenBalances) = _newBasicStaker(); + (staker, strategies, initTokenBalances) = _newRandomStaker(); operator = _newRandomOperator_NoAssets(); (avs,) = _newRandomAVS(); + tokens = _getUnderlyingTokens(strategies); // 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, initTokenBalances); @@ -70,24 +69,86 @@ contract Integration_ALMSlashBase is IntegrationCheckUtils { } } -contract Integration_InitSlash is Integration_ALMSlashBase { - - SlashingParams slashParams; +contract Integration_SlashThenWithdraw is Integration_ALMSlashBase { + + User stakerB; + uint[] initTokenBalancesB; + uint[] initTokenSharesB; + + User operatorB; + OperatorSet operatorSetB; + AllocateParams allocateParamsB; + SlashingParams slashParamsB; + + /// Init: Registered operator gets slashed one or more times for a random magnitude + /// - Second operator (for redelegation) may or may not be slashed + /// + /// Tests: Operator is slashed one or more times, then staker withdraws in different ways + function _init() internal override { + super._init(); + + /// Create second operator set with the same strategies as the first + /// Create a second operator. Register and allocate to operatorSetB + /// Also create a second staker with the same assets as the first, + /// delegated to operatorB. This is to give operatorB initial assets that can + /// be checked via invariants. + { + // Create operatorB + operatorB = _newRandomOperator_NoAssets(); + + // Create stakerB, deposit, and delegate to operatorB + (stakerB, initTokenBalancesB) = _newStaker(strategies); + + stakerB.depositIntoEigenlayer(strategies, initTokenBalancesB); + initTokenSharesB = _calculateExpectedShares(strategies, initTokenBalancesB); + check_Deposit_State(stakerB, strategies, initTokenSharesB); + + stakerB.delegateTo(operatorB); + check_Delegation_State(stakerB, operatorB, strategies, initTokenSharesB); + + // Create operatorSetB + operatorSetB = avs.createOperatorSet(strategies); + + // Register and allocate fully to operatorSetB + operatorB.registerForOperatorSet(operatorSetB); + check_Registration_State_NoAllocation(operatorB, operatorSetB, allStrats); + + allocateParamsB = _genAllocation_AllAvailable(operatorB, operatorSetB); + operatorB.modifyAllocations(allocateParamsB); + check_IncrAlloc_State_Slashable(operatorB, allocateParamsB); + + _rollBlocksForCompleteAllocation(operatorB, operatorSetB, strategies); + } - function testFuzz_slashSingle(uint24 _r) public rand(_r) { - slashParams = _genSlashing_Rand(operator, operatorSet); - avs.slashOperator(slashParams); - check_Base_Slashing_State(operator, allocateParams, slashParams); - } + /// Slash first operator one or more times + /// Each slash is for 1 to 99% + { + uint numSlashes = _randUint(1, 10); + for (uint i = 0; i < numSlashes; i++) { + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + + // TODO - staker variant? + // assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); + // assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashParams, "staker deposit shares should be slashed"); + } + } - function testFuzz_slashMulti_WithdrawTokens(uint24 _r) public rand(_r) { - for (uint i = 0; i < 25; i++) { - slashParams = _genSlashing_Rand(operator, operatorSet); - avs.slashOperator(slashParams); - check_Base_Slashing_State(operator, allocateParams, slashParams); + /// Optionally do a single slash for the second operator + /// This is to test redelegation where the new operator has already been slashed + { + bool slashSecondOperator = _randBool(); + if (slashSecondOperator) { + slashParamsB = _genSlashing_Half(operatorB, operatorSetB); + avs.slashOperator(slashParamsB); + check_Base_Slashing_State(operatorB, allocateParamsB, slashParamsB); + } } + } - // undelegate + function testFuzz_undelegate_completeAsTokens(uint24 _r) public rand(_r) { + /// Undelegate from operatorA uint[] memory shares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory roots = _getWithdrawalHashes(withdrawals); @@ -95,190 +156,160 @@ contract Integration_InitSlash is Integration_ALMSlashBase { _rollBlocksForCompleteWithdrawals(withdrawals); - // try withdrawing as tokens: - IERC20[] memory tokens = _getUnderlyingTokens(strategies); + /// Complete withdrawal as tokens uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares); - staker.completeWithdrawalsAsTokens(withdrawals); for (uint i = 0; i < withdrawals.length; i++) { check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens); } } - function testFuzz_slashMulti_WithdrawShares(uint24 _r) public rand(_r) { - for (uint i = 0; i < 25; i++) { - slashParams = _genSlashing_Rand(operator, operatorSet); - avs.slashOperator(slashParams); - check_Base_Slashing_State(operator, allocateParams, slashParams); - } - - // undelegate + function testFuzz_redelegate_completeAsTokens(uint24 _r) public rand(_r) { + /// Redelegate to operatorB uint[] memory shares = _getStakerWithdrawableShares(staker, strategies); - Withdrawal[] memory withdrawals = staker.undelegate(); + Withdrawal[] memory withdrawals = staker.redelegate(operatorB); bytes32[] memory roots = _getWithdrawalHashes(withdrawals); - check_Undelegate_State(staker, operator, withdrawals, roots, strategies, initDepositShares, shares); + check_Redelegate_State(staker, operator, operatorB, withdrawals, roots, strategies, initDepositShares, shares); _rollBlocksForCompleteWithdrawals(withdrawals); - // try withdrawing as shares - staker.completeWithdrawalsAsShares(withdrawals); + /// Complete withdrawal as tokens + uint[] memory expectedTokens = _calculateExpectedTokens(strategies, shares); + staker.completeWithdrawalsAsTokens(withdrawals); for (uint i = 0; i < withdrawals.length; i++) { - check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[i], strategies, shares); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens); } } -} -contract Integration_SlashingWithdrawals is Integration_ALMSlashBase { + function testFuzz_queueFull_completeAsTokens(uint24 _r) public rand(_r) { + // Queue a withdrawal for all shares + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, initDepositShares); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, initDepositShares, withdrawableShares, withdrawals, withdrawalRoots); + + _rollBlocksForCompleteWithdrawals(withdrawals); - function testFuzz_slash_undelegate_completeAsTokens( - uint24 _random - ) public rand(_random) { - // 4. Slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); + // Complete withdrawal as tokens + for (uint i = 0; i < withdrawals.length; i++) { + uint[] memory expectedShares = _calculateExpectedShares(withdrawals[i]); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, expectedShares); + staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawals[i].strategies, expectedShares, tokens, expectedTokens); } + } - // 5. Undelegate from an operator + function testFuzz_undelegate_completeAsShares(uint24 _r) public rand(_r) { + // Undelegate from operatorA + uint[] memory shares = _getStakerWithdrawableShares(staker, strategies); Withdrawal[] memory withdrawals = staker.undelegate(); - bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + bytes32[] memory roots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, roots, strategies, initDepositShares, shares); - // 6. Complete withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); - for (uint256 i = 0; i < withdrawals.length; ++i) { - uint256[] memory expectedTokens = - _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); - staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens); - } - // Check Final State - assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); - assert_HasUnderlyingTokenBalances_AfterSlash( - staker, - allocateParams, - slashingParams, - initTokenBalances, - "staker should once again have original token initTokenBalances minus slashed" - ); - assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + // Complete withdrawal as shares + staker.completeWithdrawalsAsShares(withdrawals); + for (uint i = 0; i < withdrawals.length; i++) { + check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[i], strategies, shares); + } } + + function testFuzz_redelegate_completeAsShares(uint24 _r) public rand(_r) { + // Redelegate to operatorB + uint[] memory shares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.redelegate(operatorB); + bytes32[] memory roots = _getWithdrawalHashes(withdrawals); + check_Redelegate_State(staker, operator, operatorB, withdrawals, roots, strategies, initDepositShares, shares); - function testFuzz_slash_undelegate_completeAsShares( - uint24 _random - ) public rand(_random) { - // 4. Slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); + _rollBlocksForCompleteWithdrawals(withdrawals); + + // Complete withdrawal as shares + staker.completeWithdrawalsAsShares(withdrawals); + for (uint i = 0; i < withdrawals.length; i++) { + check_Withdrawal_AsShares_Redelegated_State(staker, operator, operatorB, withdrawals[i], strategies, shares); } + } - // 5. Undelegate from an operator - Withdrawal[] memory withdrawals = staker.undelegate(); + function testFuzz_queueFull_completeAsShares(uint24 _r) public rand(_r) { + // Queue a withdrawal for all shares + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, initDepositShares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, initDepositShares, withdrawableShares, withdrawals, withdrawalRoots); - // 4. Complete withdrawal - // Fast forward to when we can complete the withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); - for (uint256 i = 0; i < withdrawals.length; ++i) { - staker.completeWithdrawalAsShares(withdrawals[i]); - check_Withdrawal_AsShares_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams); + // Complete withdrawal as shares + staker.completeWithdrawalsAsShares(withdrawals); + for (uint i = 0; i < withdrawals.length; i++) { + check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, withdrawableShares); } - - // Check final state: - assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens"); - assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } +} - function testFuzz_queue_slash_completeAsTokens( - uint24 _random - ) public rand(_random) { +contract Integration_QueueWithdrawalThenSlash is Integration_ALMSlashBase { + + function testFuzz_queue_slash_completeAsTokens(uint24 _r) public rand(_r) { // 4. Queue withdrawal - Withdrawal[] memory withdrawals = - staker.queueWithdrawals(strategies, _calculateExpectedShares(strategies, initTokenBalances)); + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, initDepositShares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, initDepositShares, withdrawableShares, withdrawals, withdrawalRoots); // 5. Slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); - } + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + // TODO - staker variants? // 6. Complete withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); for (uint256 i = 0; i < withdrawals.length; ++i) { - uint256[] memory expectedTokens = - _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); + uint[] memory expectedShares = _calculateExpectedShares(withdrawals[i]); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, expectedShares); staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash( - staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens - ); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, expectedShares, tokens, expectedTokens); } // Check Final State + // check_FullyWithdrawn_State(staker, ..., ); TODO assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); - assert_HasUnderlyingTokenBalances_AfterSlash( - staker, - allocateParams, - slashingParams, - initTokenBalances, - "staker should once again have original token initTokenBalances minus slashed" - ); assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } - function testFuzz_queue_slash_completeAsShares( - uint24 _random - ) public rand(_random) { + function testFuzz_queue_slash_completeAsShares(uint24 _r) public rand(_r) { // 4. Queue withdrawal - Withdrawal[] memory withdrawals = - staker.queueWithdrawals(strategies, _calculateExpectedShares(strategies, initTokenBalances)); + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, initDepositShares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, initDepositShares, withdrawableShares, withdrawals, withdrawalRoots); // 5. Slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); - } + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + // TODO - staker variants? // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); for (uint256 i = 0; i < withdrawals.length; ++i) { + uint[] memory expectedShares = _calculateExpectedShares(withdrawals[i]); staker.completeWithdrawalAsShares(withdrawals[i]); - check_Withdrawal_AsShares_State_AfterSlash(staker, operator, withdrawals[i], allocateParams, slashingParams); + check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, expectedShares); } // Check final state: assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens"); assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } +} + +contract Integration_DeallocateThenSlash is Integration_ALMSlashBase { - function testFuzz_deallocate_slash_queue_completeAsTokens( - uint24 _random - ) public rand(_random) { + function testFuzz_deallocate_slash_queue_completeAsTokens(uint24 _r) public rand(_r) { // 4. Deallocate all. AllocateParams memory deallocateParams = _genDeallocation_Full(operator, operatorSet); operator.modifyAllocations(deallocateParams); @@ -287,20 +318,16 @@ contract Integration_SlashingWithdrawals is Integration_ALMSlashBase { _rollForward_DeallocationDelay(); // 5. Slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, deallocateParams, slashingParams, "staker deposit shares should be slashed"); - } + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, deallocateParams, slashParams); + // TODO - staker variants? // 6. Queue withdrawals - Withdrawal[] memory withdrawals = - staker.queueWithdrawals(strategies, _calculateExpectedShares(strategies, initTokenBalances)); + uint[] memory withdrawableShares = _getStakerWithdrawableShares(staker, strategies); + Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, initDepositShares); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, initDepositShares, withdrawableShares, withdrawals, withdrawalRoots); // 7. Complete withdrawal _rollBlocksForCompleteWithdrawals(withdrawals); @@ -308,9 +335,7 @@ contract Integration_SlashingWithdrawals is Integration_ALMSlashBase { uint256[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].scaledShares); staker.completeWithdrawalAsTokens(withdrawals[i]); - check_Withdrawal_AsTokens_State_AfterSlash( - staker, operator, withdrawals[i], allocateParams, slashingParams, expectedTokens - ); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, initDepositShares, tokens, expectedTokens); } // Check Final State @@ -323,22 +348,15 @@ contract Integration_SlashingWithdrawals is Integration_ALMSlashBase { assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); } - function testFuzz_deregister_slash( - uint24 _random - ) public rand(_random) { + function testFuzz_deregister_slash(uint24 _r) public rand(_r) { // 4. Deregister. operator.deregisterFromOperatorSet(operatorSet); check_Deregistration_State_PendingAllocation(operator, operatorSet); // 5. Slash operator - SlashingParams memory slashingParams; - { - (IStrategy[] memory strategiesToSlash, uint256[] memory wadsToSlash) = - _randStrategiesAndWadsToSlash(operatorSet); - slashingParams = avs.slashOperator(operator, operatorSet.id, strategiesToSlash, wadsToSlash); - assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); - assert_Snap_Unchanged_Staker_DepositShares(staker, "staker deposit shares should be unchanged after slashing"); - assert_Snap_StakerWithdrawableShares_AfterSlash(staker, allocateParams, slashingParams, "staker deposit shares should be slashed"); - } + slashParams = _genSlashing_Rand(operator, operatorSet); + avs.slashOperator(slashParams); + check_Base_Slashing_State(operator, allocateParams, slashParams); + // TODO - staker variants? } } \ No newline at end of file diff --git a/src/test/utils/Logger.t.sol b/src/test/utils/Logger.t.sol index 7a5aab8f99..6f1415e6f4 100644 --- a/src/test/utils/Logger.t.sol +++ b/src/test/utils/Logger.t.sol @@ -97,7 +97,7 @@ abstract contract Logger is Test { /// @dev Returns `name` colored based logging its role. function colorByRole( string memory name - ) public view returns (string memory colored) { + ) public pure returns (string memory colored) { bool isOperator = _contains(name, "operator"); bool isStaker = _contains(name, "staker"); bool isAVS = _contains(name, "avs");