diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index cc569cebfa..a970fc98e5 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -554,7 +554,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } - function assert_DSF_Reset( + function assert_DSF_WAD( User staker, IStrategy[] memory strategies, string memory err @@ -1876,6 +1876,97 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } + function assert_Snap_Increased_DSF( + User staker, + IStrategy[] memory strategies, + string memory err + ) internal { + uint[] memory curDSFs = _getDepositScalingFactors(staker, strategies); + uint[] memory prevDSFs = _getPrevDepositScalingFactors(staker, strategies); + + for (uint i = 0; i < strategies.length; i++) { + assertGt(curDSFs[i], prevDSFs[i], err); + } + } + + /// @dev Used to assert that the DSF is either increased or unchanged, depending on the slashing factor, on a deposit + function assert_Snap_DSF_State_Deposit( + User staker, + IStrategy[] memory strategies, + string memory err + ) internal { + for (uint i = 0; i < strategies.length; i++) { + IStrategy[] memory stratArray = strategies[i].toArray(); + /// @dev We don't need the previous slashing factors as they shouldn't change before/after a deposit + uint curSlashingFactor = _getSlashingFactor(staker, strategies[i]); + + // If there was never a slashing, no need to normalize + if (curSlashingFactor == WAD) { + assert_Snap_Unchanged_DSF(staker, stratArray, err); // No slashing, so DSF is unchanged + assert_DSF_WAD(staker, stratArray, err); // DSF should have always been WAD + } + // If there was a slashing, and we deposit, normalize + else { + assert_Snap_Increased_DSF(staker, stratArray, err); // Slashing, so DSF is increased + } + } + } + + /// @dev When completing withdrawals as shares, we must also handle the case where a staker completes a withdrawal for 0 shares + function assert_Snap_DSF_State_WithdrawalAsShares( + User staker, + IStrategy[] memory strategies, + string memory err + ) internal { + uint[] memory curDepositShares = _getStakerDepositShares(staker, strategies); + for (uint i = 0; i < strategies.length; i++) { + IStrategy[] memory stratArray = strategies[i].toArray(); + /// We don't need the previous slashing factors as they shouldn't change before/after a deposit + uint curSlashingFactor = _getSlashingFactor(staker, strategies[i]); + + // If there was never a slashing, no need to normalize + // If there was a slashing, but we complete a withdrawal for 0 shares, no need to normalize + if (curSlashingFactor == WAD || curDepositShares[i] == 0) { + assert_Snap_Unchanged_DSF(staker, stratArray, err); + assert_DSF_WAD(staker, stratArray, err); + } + // If there was a slashing, and we complete a withdrawal for non-zero shares, normalize + else { + assert_Snap_Increased_DSF(staker, stratArray, err); + } + } + } + + /// @dev On a delegation, the DSF should be increased if the operator magnitude is non-WAD + function assert_Snap_DSF_State_Delegation( + User staker, + IStrategy[] memory strategies, + uint[] memory delegatableShares, + string memory err + ) internal { + uint64[] memory maxMags = _getMaxMagnitudes(staker, strategies); + + for (uint i = 0; i < strategies.length; i++) { + IStrategy[] memory stratArray = strategies[i].toArray(); + + // If you are delegating with 0 shares, no need to normalize + // If there was never an operator slashing, no need to normalize + if (delegatableShares[i] == 0 || maxMags[i] == WAD) { + assert_Snap_Unchanged_DSF(staker, stratArray, err); + + // If we are not a beaconChainStrategy, we should also have a DSF of WAD + // We exclude BEACONCHAIN_ETH_STRAT because it could have had a non-WAD DSF from BC slashings + if (strategies[i] != BEACONCHAIN_ETH_STRAT) { + assert_DSF_WAD(staker, stratArray, err); + } + } + // If there was an operator slashing, and delegating with non-zero shares, normalize + else { + assert_Snap_Increased_DSF(staker, stratArray, err); // Slashing, so DSF is increased + } + } + } + /******************************************************************************* SNAPSHOT ASSERTIONS: STRATEGY SHARES *******************************************************************************/ diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index c88eb071b9..30b8212cbe 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -208,6 +208,7 @@ contract IntegrationCheckUtils is IntegrationBase { } else { assert_Snap_Added_Staker_WithdrawableShares(staker, strategies, shares, "deposit should increase withdrawable shares"); } + assert_Snap_DSF_State_Deposit(staker, strategies, "staker's DSF not updated correctly"); } function check_Deposit_State_PartialDeposit(User staker, IStrategy[] memory strategies, uint[] memory shares, uint[] memory tokenBalances) internal { @@ -226,6 +227,7 @@ contract IntegrationCheckUtils is IntegrationBase { } else { assert_Snap_Added_Staker_WithdrawableShares(staker, strategies, shares, "deposit should increase withdrawable shares"); } + assert_Snap_DSF_State_Deposit(staker, strategies, "staker's DSF not updated correctly"); } function check_Delegation_State( @@ -246,6 +248,7 @@ contract IntegrationCheckUtils is IntegrationBase { uint256[] memory delegatableShares = _getPrevStakerWithdrawableShares(staker, strategies); assert_Snap_Added_OperatorShares(operator, strategies, delegatableShares, "operator should have received shares"); check_Added_SlashableStake(operator, strategies, delegatableShares); + assert_Snap_DSF_State_Delegation(staker, strategies, delegatableShares, "staker's DSF not updated correctly"); } function check_Added_SlashableStake( @@ -297,7 +300,7 @@ contract IntegrationCheckUtils is IntegrationBase { for (uint i = 0; i < strategies.length; i++) { // For a full withdrawal, the dsf should be reset to wad if (_getStakerDepositShares(staker, strategies[i].toArray())[0] == 0) { - assert_DSF_Reset(staker, strategies[i].toArray(), + assert_DSF_WAD(staker, strategies[i].toArray(), "check_QueuedWithdrawal_State: dsf should be reset to wad"); } // For a partial withdrawal, the dsf should not be changed @@ -345,7 +348,7 @@ contract IntegrationCheckUtils is IntegrationBase { "check_Undelegate_State: calculated withdrawal should match returned root"); assert_AllWithdrawalsPending(withdrawalRoots, "check_Undelegate_State: stakers withdrawal should now be pending"); - assert_DSF_Reset(staker, strategies, + assert_DSF_WAD(staker, strategies, "check_Undelegate_State: staker dsfs should be reset to wad"); assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "check_Undelegate_State: staker should have increased nonce by withdrawals.length"); @@ -388,6 +391,8 @@ contract IntegrationCheckUtils is IntegrationBase { "check_QueuedWithdrawal_State: failed to remove staker withdrawable shares"); assert_Snap_Unchanged_OperatorShares(newOperator, "check_Redelegate_State: new operator shares should not have changed"); + assert_DSF_WAD(staker, strategies, + "check_Redelegate_State: staker dsfs should be reset to wad"); } /** @@ -447,6 +452,7 @@ contract IntegrationCheckUtils is IntegrationBase { } assert_Snap_Added_OperatorShares(operator, strategies, withdrawableShares, "operator should have received shares"); } + assert_Snap_DSF_State_WithdrawalAsShares(staker, strategies, "staker's DSF not updated correctly"); } /// @notice Difference from above is that operator shares do not increase since staker is not delegated @@ -471,6 +477,7 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Added_Staker_WithdrawableShares(staker, strategies, expectedWithdrawableShares, "staker should have received expected withdrawable shares"); assert_Snap_Unchanged_OperatorShares(operator, "operator should have shares unchanged"); assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); + assert_Snap_DSF_State_WithdrawalAsShares(staker, strategies, "staker's DSF not updated correctly"); } function check_Withdrawal_AsShares_Redelegated_State( @@ -494,6 +501,7 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Unchanged_OperatorShares(operator, "operator should have shares unchanged"); assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); assert_Snap_Expected_Staker_WithdrawableShares_Deposit(staker, newOperator, strategies, withdrawableShares, "staker should have received expected withdrawable shares"); + assert_Snap_DSF_State_WithdrawalAsShares(staker, strategies, "staker's DSF not updated correctly"); } /*******************************************************************************