diff --git a/docs/core/accounting/SharesAccounting.md b/docs/core/accounting/SharesAccounting.md index 940dfab0a..01ccecc9d 100644 --- a/docs/core/accounting/SharesAccounting.md +++ b/docs/core/accounting/SharesAccounting.md @@ -162,6 +162,67 @@ See implementation in: * [`StrategyManager.depositIntoStrategy`](../../../src/contracts/core/StrategyManager.sol) * [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](../../../src/contracts/pods/EigenPodManager.sol) + +--- + +### Delegation + +Suppose we have an undelegated staker who decides to delegate to an operator. +We have the following properties that should be preserved. + +#### Operator Level + +Operator shares should be increased by the amount of delegatable shares the staker has, this is synonymous to their withdrawable shares $a_n$. Therefore, + +$$ +op_{n+1} = op_{n} + a_n +$$ + +$$ += op_{n} + s_n k_n l_n m_n +$$ + + +#### Staker Level + +withdrawable shares should remain unchanged + +$$ +a_{n+1} = a_n +$$ + +deposit shares should remain unchanged + +$$ +s_{n+1} = s_n +$$ + +beaconChainSlashingFactor and maxMagnitude should also remain unchanged. In this case, since the staker is not delegated, then their maxMagnitude should by default be equal to 1. + +$$ +l_{n+1} = l_n +$$ + +Now the question is what is the new depositScalingFactor equal to? + +$$ +a_{n+1} = a_n +$$ + +$$ +=> s_{n+1} k_{n+1} l_{n+1} m_{n+1} = s_n k_n l_n m_n +$$ + +$$ +=> s_{n} k_{n+1} l_{n} m_{n+1} = s_n k_n l_n m_n +$$ + +$$ +=> k_{n+1} = \frac {k_n m_n} { m_{n+1} } +$$ + +Notice how the staker variables that update $k_{n+1}$ and $m_{n+1}$ do not affect previously queued withdrawals and shares received upon withdrawal completion. This is because the maxMagnitude that is looked up is dependent on the operator at the time of the queued withdrawal and the $k_n$ is effectively stored in the scaled shares field. + --- ### Slashing diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index b99a88a2b..61fd514b0 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -241,7 +241,8 @@ contract DelegationManager is strategy: strategy, prevDepositShares: prevDepositShares, addedShares: addedShares, - slashingFactor: slashingFactor + slashingFactor: slashingFactor, + newDelegation: false }); } @@ -340,15 +341,16 @@ contract DelegationManager is * 1) new delegations are not paused (PAUSED_NEW_DELEGATION) */ function _delegate(address staker, address operator) internal onlyWhenNotPaused(PAUSED_NEW_DELEGATION) { + // read staker's withdrawable shares and strategies to add to operator's shares + // also update the staker depositScalingFactor for each strategy + (IStrategy[] memory strategies,) = getDepositedShares(staker); + (uint256[] memory withdrawableShares,) = getWithdrawableShares(staker, strategies); + //Ignoring beaconChainSlashingFactor here because Beacon Chain ETH DSF already reflects BCSF + uint64[] memory slashingFactors = allocationManager.getMaxMagnitudes(operator, strategies); // record the delegation relation between the staker and operator, and emit an event delegatedTo[staker] = operator; emit StakerDelegated(staker, operator); - // read staker's deposited shares and strategies to add to operator's shares - // and also update the staker depositScalingFactor for each strategy - (IStrategy[] memory strategies, uint256[] memory depositedShares) = getDepositedShares(staker); - uint256[] memory slashingFactors = _getSlashingFactors(staker, operator, strategies); - for (uint256 i = 0; i < strategies.length; ++i) { // forgefmt: disable-next-item _increaseDelegation({ @@ -356,8 +358,9 @@ contract DelegationManager is staker: staker, strategy: strategies[i], prevDepositShares: uint256(0), - addedShares: depositedShares[i], - slashingFactor: slashingFactors[i] + addedShares: withdrawableShares[i], + slashingFactor: slashingFactors[i], + newDelegation: true }); } } @@ -409,6 +412,10 @@ contract DelegationManager is depositSharesToWithdraw: singleDepositShares, slashingFactors: singleSlashingFactor }); + + //Reset DepositScalingFactor + DepositScalingFactor storage dsf = _depositScalingFactor[staker][strategies[i]]; + dsf.reset(); } return withdrawalRoots; @@ -592,7 +599,8 @@ contract DelegationManager is strategy: withdrawal.strategies[i], prevDepositShares: prevDepositShares, addedShares: addedShares, - slashingFactor: newSlashingFactors[i] + slashingFactor: newSlashingFactors[i], + newDelegation: false }); } } @@ -607,6 +615,7 @@ contract DelegationManager is * @param prevDepositShares The number of delegated deposit shares the staker had in the strategy prior to the increase * @param addedShares The shares added to the staker in the StrategyManager/EigenPodManager * @param slashingFactor The current slashing factor for the staker/operator/strategy + * @param newDelegation Boolean flag which signifies whether this is a new delegation */ function _increaseDelegation( address operator, @@ -614,7 +623,8 @@ contract DelegationManager is IStrategy strategy, uint256 prevDepositShares, uint256 addedShares, - uint256 slashingFactor + uint256 slashingFactor, + bool newDelegation ) internal { // Ensure that the operator has not been fully slashed for a strategy // and that the staker has not been fully slashed if it is the beaconChainStrategy @@ -622,9 +632,10 @@ contract DelegationManager is require(slashingFactor != 0, FullySlashed()); // Update the staker's depositScalingFactor. This only results in an update - // if the slashing factor has changed for this strategy. + // if the slashing factor has changed for this strategy. Dsf update formula + // is different in the on delegation case. DepositScalingFactor storage dsf = _depositScalingFactor[staker][strategy]; - dsf.update(prevDepositShares, addedShares, slashingFactor); + dsf.update(prevDepositShares, addedShares, slashingFactor, newDelegation); emit DepositScalingFactorUpdated(staker, strategy, dsf.scalingFactor()); // If the staker is delegated to an operator, update the operator's shares diff --git a/src/contracts/libraries/SlashingLib.sol b/src/contracts/libraries/SlashingLib.sol index 7063a6d3e..c9c2593c1 100644 --- a/src/contracts/libraries/SlashingLib.sol +++ b/src/contracts/libraries/SlashingLib.sol @@ -92,11 +92,20 @@ library SlashingLib { DepositScalingFactor storage dsf, uint256 prevDepositShares, uint256 addedShares, - uint256 slashingFactor + uint256 slashingFactor, + bool newDelegation ) internal { - // If this is the staker's first deposit, set the scaling factor to - // the inverse of slashingFactor - if (prevDepositShares == 0) { + if (newDelegation) { + // In the case of a new delegation, slashingFactor is just maxMagnitude, because the + // BCSF is accounted for in _delegate by adjusting the shares added to the operator + // + // We preserve the old DSF in case it is non-WAD, as (for the beaconChainETH strategy) + // the DSF can be updated if the staker is slashed on the beacon chain. + dsf._scalingFactor = dsf.scalingFactor().divWad(slashingFactor); + return; + } else if (prevDepositShares == 0) { + // If this is the staker's first deposit, set the scaling factor to + // the inverse of slashingFactor dsf._scalingFactor = uint256(WAD).divWad(slashingFactor); return; } @@ -136,6 +145,12 @@ library SlashingLib { dsf._scalingFactor = newDepositScalingFactor; } + function reset( + DepositScalingFactor storage dsf + ) internal { + dsf._scalingFactor = uint256(WAD); + } + // CONVERSION function calcWithdrawable( diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 95910a00e..fb3fa9999 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -188,6 +188,22 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { return result; } + + /// @dev Choose a random subset of validators (selects AT LEAST ONE but NOT ALL) + function _chooseSubset(uint40[] memory validators) internal returns (uint40[] memory) { + require(validators.length >= 2, "Need at least 2 validators to choose subset"); + + uint40[] memory result = new uint40[](validators.length); + uint newLen; + + uint rand = _randUint({ min: 1, max: validators.length ** 2 }); + for (uint i = 0; i < validators.length; i++) { + if (rand >> i & 1 == 1) { + result[newLen] = validators[i]; + newLen++; + } + } + } function _getTokenName(IERC20 token) internal view returns (string memory) { if (token == NATIVE_ETH) { @@ -1313,7 +1329,7 @@ abstract contract IntegrationBase is IntegrationDeployer, TypeImporter { } } - /// @dev Check that the staker's withdrawable shares have decreased by `removedShares` + /// @dev Check that the staker's withdrawable shares have increased by `addedShares` function assert_Snap_Added_Staker_WithdrawableShares( User staker, IStrategy[] memory strategies, diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol index 6bd9d7b03..ebcbab81f 100644 --- a/src/test/integration/IntegrationChecks.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -231,7 +231,7 @@ contract IntegrationCheckUtils is IntegrationBase { ) internal { /// Undelegate from an operator // - // ... check that the staker is undelegated, all strategies from which the staker is deposited are unqeuued, + // ... 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 assertFalse(delegationManager.isDelegated(address(staker)), @@ -250,6 +250,35 @@ contract IntegrationCheckUtils is IntegrationBase { "check_QueuedWithdrawal_State: failed to remove staker withdrawable shares"); } + function check_Redelegate_State( + User staker, + User operator, + IDelegationManagerTypes.Withdrawal[] memory withdrawals, + bytes32[] memory withdrawalRoots, + IStrategy[] memory strategies, + uint[] memory shares + ) internal { + /// Redelegate to a new operator + // + // ... check that the staker is delegated to new operator, 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 + assertTrue(delegationManager.isDelegated(address(staker)), + "check_Undelegate_State: staker should not be delegated"); + assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, + "check_Undelegate_State: calculated withdrawl should match returned root"); + assert_AllWithdrawalsPending(withdrawalRoots, + "check_Undelegate_State: stakers withdrawal should now be pending"); + assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, + "check_Undelegate_State: staker should have increased nonce by withdrawals.length"); + assert_Snap_Removed_OperatorShares(operator, strategies, shares, + "check_Undelegate_State: failed to remove operator shares"); + assert_Snap_Removed_Staker_DepositShares(staker, strategies, shares, + "check_Undelegate_State: failed to remove staker shares"); + assert_Snap_Removed_Staker_WithdrawableShares(staker, strategies, shares, + "check_QueuedWithdrawal_State: failed to remove staker withdrawable shares"); + } + /** * @notice Overloaded function to check the state after a withdrawal as tokens, accepting a non-user type for the operator. * @param staker The staker who completed the withdrawal. @@ -329,6 +358,28 @@ contract IntegrationCheckUtils is IntegrationBase { assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); } + function check_Withdrawal_AsShares_Redelegated_State( + User staker, + User operator, + User newOperator, + IDelegationManagerTypes.Withdrawal memory withdrawal, + IStrategy[] memory strategies, + uint[] memory shares + ) internal { + /// Complete withdrawal(s): + // The staker will complete the withdrawal as shares + // + // ... check that the withdrawal is not pending, that the token balances of the staker and operator are unchanged, + // that the withdrawer received the expected shares, and that that the total shares of each o + // strategy withdrawn remains unchanged + 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_Unchanged_TokenBalances(operator, "operator should not have any change in underlying token balances"); + assert_Snap_Added_Staker_DepositShares(staker, strategies, shares, "staker should have received expected shares"); + assert_Snap_Unchanged_OperatorShares(operator, "operator should have shares unchanged"); + assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); + } + /******************************************************************************* ALM - BASIC INVARIANTS *******************************************************************************/ diff --git a/src/test/integration/tests/Slashed_Eigenpod.t.sol b/src/test/integration/tests/Slashed_Eigenpod.t.sol new file mode 100644 index 000000000..d573bded1 --- /dev/null +++ b/src/test/integration/tests/Slashed_Eigenpod.t.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "src/test/integration/IntegrationChecks.t.sol"; + +contract SlashedEigenpod is IntegrationCheckUtils { + using ArrayLib for *; + + AVS avs; + OperatorSet operatorSet; + + User operator; + IAllocationManagerTypes.AllocateParams allocateParams; + + User staker; + IStrategy[] strategies; + uint[] initTokenBalances; + uint64 slashedGwei; + + function _init() internal override { + _configAssetTypes(HOLDS_ETH); + (staker, strategies, initTokenBalances) = _newRandomStaker(); + (operator,,) = _newRandomOperator(); + (avs,) = _newRandomAVS(); + + cheats.assume(initTokenBalances[0] >= 64 ether); + + //Slash on Beacon chain + (uint40[] memory validators,) = staker.startValidators(); + beaconChain.advanceEpoch_NoRewards(); + staker.verifyWithdrawalCredentials(validators); + + uint[] memory shares = _calculateExpectedShares(strategies, initTokenBalances); + check_Deposit_State(staker, strategies, shares); + + uint40[] memory slashedValidators = _chooseSubset(validators); + slashedGwei = beaconChain.slashValidators(slashedValidators); + beaconChain.advanceEpoch_NoRewards(); + + staker.startCheckpoint(); + staker.completeCheckpoint(); + check_CompleteCheckpoint_WithSlashing_State(staker, slashedValidators, slashedGwei); + } + + function testFuzz_delegateSlashedStaker_dsfWad(uint24 _random) public rand(_random) { + + uint256[] memory initDepositShares = _getStakerDepositShares(staker, strategies); + + // Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, initDepositShares); + + // Create an operator set and register an operator. + operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); + check_Registration_State_NoAllocation(operator, operatorSet, strategies); + + // Allocate to operator set + allocateParams = _genAllocation_AllAvailable(operator, operatorSet, strategies); + operator.modifyAllocations(allocateParams); + check_IncrAlloc_State_Slashable(operator, allocateParams); + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + + // Undelegate from an operator + IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, initDepositShares); + + // Complete withdrawal as shares + // 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_Undelegated_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares); + } + + (uint256[] memory withdrawableSharesAfter, uint256[] memory depositSharesAfter) = delegationManager.getWithdrawableShares(address(staker), strategies); + assertEq(depositSharesAfter[0], initDepositShares[0] - slashedGwei, "Deposit shares should reset to reflect slash(es)"); + assertEq(withdrawableSharesAfter[0], depositSharesAfter[0], "Withdrawable shares should equal deposit shares after withdrawal"); + } + + function testFuzz_delegateSlashedStaker_dsfNonWad(uint24 _random) public rand(_random) { + + //Generate rewards on beacon chain so dsf is nonWad + beaconChain.advanceEpoch(); + + staker.startCheckpoint(); + staker.completeCheckpoint(); + + + uint256[] memory initDepositShares = _getStakerDepositShares(staker, strategies); + + // Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, initDepositShares); + + // Create an operator set and register an operator. + operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); + check_Registration_State_NoAllocation(operator, operatorSet, strategies); + + // Allocate to operator set + allocateParams = _genAllocation_AllAvailable(operator, operatorSet, strategies); + operator.modifyAllocations(allocateParams); + check_IncrAlloc_State_Slashable(operator, allocateParams); + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + // Undelegate from an operator + IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, initDepositShares); + + // Complete withdrawal as shares + // 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_Undelegated_State(staker, operator, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares); + } + + (uint256[] memory withdrawableSharesAfter, uint256[] memory depositSharesAfter) = delegationManager.getWithdrawableShares(address(staker), strategies); + assertEq(depositSharesAfter[0], initDepositShares[0] - slashedGwei, "Deposit shares should reset to reflect slash(es)"); + assertEq(withdrawableSharesAfter[0], depositSharesAfter[0], "Withdrawable shares should equal deposit shares after withdrawal"); + } + + function testFuzz_delegateSlashedStaker_slashedOperator(uint24 _random) public rand(_random) { + //randomize additional deposit to eigenpod + if(_randBool()){ + beaconChain.advanceEpoch(); + + staker.startCheckpoint(); + staker.completeCheckpoint(); + } + + // Create an operator set and register an operator. + operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); + check_Registration_State_NoAllocation(operator, operatorSet, strategies); + + //Slash operator before delegation + IAllocationManagerTypes.SlashingParams memory slashingParams; + uint wadToSlash = _randWadToSlash(); + slashingParams = avs.slashOperator(operator, operatorSet.id, strategies, wadToSlash.toArrayU256()); + assert_Snap_Allocations_Slashed(slashingParams, operatorSet, true, "operator allocations should be slashed"); + + uint256[] memory initDepositShares = _getStakerDepositShares(staker, strategies); + + // Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, initDepositShares); + + // Allocate to operator set + allocateParams = _genAllocation_AllAvailable(operator, operatorSet, strategies); + operator.modifyAllocations(allocateParams); + check_IncrAlloc_State_Slashable(operator, allocateParams); + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + // Undelegate from an operator + IDelegationManagerTypes.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, initDepositShares); + + // Complete withdrawal as shares + // 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); + } + + (uint256[] memory withdrawableSharesAfter, uint256[] memory depositSharesAfter) = delegationManager.getWithdrawableShares(address(staker), strategies); + assertEq(depositSharesAfter[0], initDepositShares[0] - slashedGwei, "Deposit shares should reset to reflect slash(es)"); + assertEq(withdrawableSharesAfter[0], depositSharesAfter[0], "Withdrawable shares should equal deposit shares after withdrawal"); + } + + function testFuzz_delegateSlashedStaker_redelegate_complete(uint24 _random) public rand(_random){ + + (User operator2, ,) = _newRandomOperator(); + + //Generate rewards on beacon chain so dsf is nonWad + beaconChain.advanceEpoch(); + + staker.startCheckpoint(); + staker.completeCheckpoint(); + + + uint256[] memory initDepositShares = _getStakerDepositShares(staker, strategies); + + // Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, initDepositShares); + + // Create an operator set and register an operator. + operatorSet = avs.createOperatorSet(strategies); + operator.registerForOperatorSet(operatorSet); + check_Registration_State_NoAllocation(operator, operatorSet, strategies); + + // Allocate to operator set + allocateParams = _genAllocation_AllAvailable(operator, operatorSet, strategies); + operator.modifyAllocations(allocateParams); + check_IncrAlloc_State_Slashable(operator, allocateParams); + _rollBlocksForCompleteAllocation(operator, operatorSet, strategies); + + // 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); + + // Complete withdrawal as shares + // 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_Redelegated_State(staker, operator, operator2, withdrawals[i], withdrawals[i].strategies, withdrawals[i].scaledShares); + } + + (uint256[] memory withdrawableSharesAfter, uint256[] memory depositSharesAfter) = delegationManager.getWithdrawableShares(address(staker), strategies); + assertEq(depositSharesAfter[0], initDepositShares[0] - slashedGwei, "Deposit shares should reset to reflect slash(es)"); + assertEq(withdrawableSharesAfter[0], depositSharesAfter[0], "Withdrawable shares should equal deposit shares after withdrawal"); + } +} \ No newline at end of file diff --git a/src/test/integration/users/User.t.sol b/src/test/integration/users/User.t.sol index 370ad2461..bba74c193 100644 --- a/src/test/integration/users/User.t.sol +++ b/src/test/integration/users/User.t.sol @@ -237,6 +237,34 @@ contract User is Logger, IDelegationManagerTypes, IAllocationManagerTypes { return expectedWithdrawals; } + /// @dev Redelegate to a new operator + function redelegate( + User newOperator + ) public virtual createSnapshot returns (Withdrawal[] memory) { + print.method("redelegate", newOperator.NAME_COLORED()); + Withdrawal[] memory expectedWithdrawals = _getExpectedWithdrawalStructsForStaker(address(this)); + ISignatureUtils.SignatureWithExpiry memory emptySig; + _tryPrankAppointee_DelegationManager(IDelegationManager.redelegate.selector); + delegationManager.redelegate(address(newOperator), emptySig, bytes32(0)); + print.gasUsed(); + + for (uint256 i = 0; i < expectedWithdrawals.length; i++) { + IStrategy strat = expectedWithdrawals[i].strategies[0]; + + string memory name = strat == beaconChainETHStrategy + ? "Native ETH" + : IERC20Metadata(address(strat.underlyingToken())).name(); + + console.log( + " Expecting withdrawal with nonce %s of %s for %s scaled shares.", + expectedWithdrawals[i].nonce, + name, + expectedWithdrawals[i].scaledShares[0] + ); + } + return expectedWithdrawals; + } + /// @dev Force undelegate staker function forceUndelegate( User staker diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol index cf74b4287..5a532a173 100644 --- a/src/test/unit/DelegationUnit.t.sol +++ b/src/test/unit/DelegationUnit.t.sol @@ -58,6 +58,8 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag uint256 delegationSignerPrivateKey = uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); address defaultApprover = cheats.addr(delegationSignerPrivateKey); uint256 stakerPrivateKey = uint256(123_456_789); + uint256 staker2PrivateKey = uint256(234_567_891); + address defaultStaker2 = cheats.addr(staker2PrivateKey); address defaultStaker = cheats.addr(stakerPrivateKey); address defaultOperator = address(this); address defaultOperator2 = address(0x123); @@ -970,6 +972,63 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag ); } + /// @notice Asserts for depositShares, withdrawableShares, and depositScalingFactor after a delegation + function _assertDelegation( + address staker, + address operator, + IStrategy strategy, + uint256 operatorSharesBefore, + uint256 withdrawableSharesBefore, + uint256 depositSharesBefore, + uint256 prevDsf + ) internal view { + (uint256[] memory withdrawableShares, uint256[] memory depositShares) = + delegationManager.getWithdrawableShares(staker, strategy.toArray()); + // Check deposit shares don't change + assertEq( + depositShares[0], + depositSharesBefore, + "depositShares should be equal to depositSharesBefore" + ); + // Check withdrawable shares don't change + assertApproxEqRel( + withdrawableShares[0], + withdrawableSharesBefore, + APPROX_REL_DIFF, + "withdrawableShares should be equal to withdrawableSharesBefore" + ); + // Check the new dsf is accurate + uint256 expectedWithdrawableShares; + uint256 expectedDsf; + { + uint64 maxMagnitude = allocationManagerMock.getMaxMagnitude(operator, strategy); + expectedDsf = prevDsf.divWad(maxMagnitude); + uint256 slashingFactor = _getSlashingFactor(staker, strategy, maxMagnitude); + expectedWithdrawableShares = + _calcWithdrawableShares(depositSharesBefore, expectedDsf, slashingFactor); + } + // Check the new dsf is accurate + assertEq( + expectedDsf, + delegationManager.depositScalingFactor(staker, strategy), + "depositScalingFactor should be equal to expectedDsf" + ); + // Check new operatorShares increased correctly + if (operator != address(0)) { + assertEq( + operatorSharesBefore + withdrawableSharesBefore, + delegationManager.operatorShares(operator, strategy), + "OperatorShares not increased correctly" + ); + } + // Check the newly calculated withdrawable shares are correct + assertEq( + withdrawableShares[0], + expectedWithdrawableShares, + "withdrawableShares should be equal to expectedWithdrawableShares" + ); + } + /// @notice Asserts for depositShares, and operatorShares decremented properly after a withdrawal function _assertWithdrawal( address staker, @@ -1293,10 +1352,6 @@ contract DelegationManagerUnitTests is EigenLayerUnitTestSetup, IDelegationManag (Withdrawal[] memory withdrawals, ) = delegationManager.getQueuedWithdrawals(staker); for (uint256 i = 0; i < withdrawals.length; ++i) { - assertEq( - withdrawals[i].staker, - withdrawal.staker - ); assertEq( withdrawals[i].withdrawer, withdrawal.withdrawer @@ -2095,7 +2150,7 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests { * - depositShares incremented for staker correctly * - withdrawableShares are correct * - depositScalingFactor is updated correctly - * - operatorShares increase by depositShares amount + * - operatorShares increase by withdrawableShares amount * - defaultOperator is an operator, staker is delegated to defaultOperator, staker is not an operator * - That the staker withdrawableShares is <= operatorShares (less due to rounding from non-WAD maxMagnitude) */ @@ -2113,6 +2168,7 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests { eigenPodManagerMock.setBeaconChainSlashingFactor(staker, beaconChainSlashingFactor); // Set staker shares in BeaconChainStrategy eigenPodManagerMock.setPodOwnerShares(staker, beaconShares); + (uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(staker, beaconChainETHStrategy.toArray()); // delegate from the `staker` to the operator, check for events emitted cheats.startPrank(staker); @@ -2121,21 +2177,20 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests { staker: staker, operator: defaultOperator, strategy: beaconChainETHStrategy, - depositShares: beaconShares > 0 ? uint256(beaconShares) : 0, - depositScalingFactor: uint256(WAD).divWad(maxMagnitude.mulWad(beaconChainSlashingFactor)) + depositShares: beaconShares > 0 ? withdrawableShares[0] : 0, + depositScalingFactor: uint256(WAD).divWad(maxMagnitude) }) ); delegationManager.delegateTo(defaultOperator, emptyApproverSignatureAndExpiry, emptySalt); - _assertDeposit({ + _assertDelegation({ staker: staker, operator: defaultOperator, strategy: beaconChainETHStrategy, operatorSharesBefore: 0, - withdrawableSharesBefore: 0, - depositSharesBefore: 0, - prevDsf: WAD, - depositAmount: uint256(beaconShares) + withdrawableSharesBefore: withdrawableShares[0], + depositSharesBefore: uint256(beaconShares), + prevDsf: WAD }); assertTrue(delegationManager.isOperator(defaultOperator), "staker not registered as operator"); assertEq(delegationManager.delegatedTo(staker), defaultOperator, "staker delegated to the wrong address"); @@ -2150,10 +2205,10 @@ contract DelegationManagerUnitTests_delegateTo is DelegationManagerUnitTests { ); ( - uint256[] memory withdrawableShares, + uint256[] memory withdrawableSharesAfter, ) = delegationManager.getWithdrawableShares(staker, beaconChainETHStrategy.toArray()); _assertWithdrawableAndOperatorShares( - withdrawableShares[0], + withdrawableSharesAfter[0], delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy), "withdrawableShares not set correctly" ); @@ -3483,7 +3538,7 @@ contract DelegationManagerUnitTests_increaseDelegatedShares is DelegationManager // deposit and increaseDelegatedShares strategyManagerMock.addDeposit(staker, strategy, shares); uint256 slashingFactor = _getSlashingFactor(staker, strategy, magnitude); - dsf.update(0, shares, slashingFactor); + dsf.update(0, shares, slashingFactor, false); _increaseDelegatedShares_expectEmit( IncreaseDelegatedSharesEmitStruct({ staker: staker, @@ -3603,7 +3658,9 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager * Ensures that after the decrease, the staker's withdrawableShares <= operatorShares, * preventing any underflow for the operator's shares if they were all to be withdrawn. */ - function testFuzz_decreaseDelegatedShares_nonSlashedOperator(Randomness r) public rand(r) { + function testFuzz_decreaseDelegatedShares_nonSlashedOperator( + Randomness r + ) public rand(r) { int256 beaconShares = int256(r.Uint256(1, MAX_ETH_SUPPLY)); uint256 sharesDecrease = r.Uint256(0, uint256(beaconShares) - 1); uint64 beaconChainSlashingFactor = r.Uint64(1, WAD); @@ -3612,30 +3669,24 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager _registerOperatorWithBaseDetails(defaultOperator); eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares); eigenPodManagerMock.setBeaconChainSlashingFactor(defaultStaker, beaconChainSlashingFactor); + (uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator); - _assertDeposit({ + _assertDelegation({ staker: defaultStaker, operator: defaultOperator, strategy: beaconChainETHStrategy, operatorSharesBefore: 0, - withdrawableSharesBefore: 0, - depositSharesBefore: 0, - prevDsf: WAD, - depositAmount: uint256(beaconShares) + withdrawableSharesBefore: withdrawableShares[0], + depositSharesBefore: uint256(beaconShares), + prevDsf: WAD }); // 2. Perform beaconChain slash + decreaseDelegatedShares() - ( - uint64 prevBeaconSlashingFactor, - uint64 newBeaconSlashingFactor - ) = _setNewBeaconChainSlashingFactor(defaultStaker, beaconShares, sharesDecrease); + (uint64 prevBeaconSlashingFactor, uint64 newBeaconSlashingFactor) = + _setNewBeaconChainSlashingFactor(defaultStaker, beaconShares, sharesDecrease); uint64 beaconChainSlashingFactorDecrease = prevBeaconSlashingFactor - newBeaconSlashingFactor; - assertEq( - beaconChainSlashingFactor, - prevBeaconSlashingFactor, - "Bad test setup" - ); - uint256 depositScalingFactor = uint256(WAD).divWad(beaconChainSlashingFactor); + assertEq(beaconChainSlashingFactor, prevBeaconSlashingFactor, "Bad test setup"); + uint256 depositScalingFactor = uint256(WAD); // expected operatorShares decreased for event uint256 operatorSharesToDecrease = _calcWithdrawableShares({ depositShares: uint256(beaconShares), @@ -3651,7 +3702,9 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager }) ); cheats.prank(address(eigenPodManagerMock)); - delegationManager.decreaseDelegatedShares(defaultStaker, uint256(beaconShares), beaconChainSlashingFactorDecrease); + delegationManager.decreaseDelegatedShares( + defaultStaker, uint256(beaconShares), beaconChainSlashingFactorDecrease + ); // 3. Assert correct values uint256 expectedWithdrawableShares = _calcWithdrawableShares({ @@ -3661,16 +3714,17 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager }); _assertSharesAfterBeaconSlash({ staker: defaultStaker, - withdrawableSharesBefore: uint256(beaconShares), + withdrawableSharesBefore: withdrawableShares[0], expectedWithdrawableShares: expectedWithdrawableShares, prevBeaconSlashingFactor: prevBeaconSlashingFactor }); // Assert correct end state values - (uint256[] memory withdrawableSharesAfter, ) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (uint256[] memory withdrawableSharesAfter,) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); assertEq( delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy) + operatorSharesToDecrease, - uint256(beaconShares), + withdrawableShares[0], "operator shares not decreased correctly" ); @@ -3689,7 +3743,9 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager * Ensures that after the decrease, the staker's withdrawableShares <= operatorShares, * preventing any underflow for the operator's shares if they were all to be withdrawn. */ - function testFuzz_decreaseDelegatedShares_slashedOperator(Randomness r) public rand(r) { + function testFuzz_decreaseDelegatedShares_slashedOperator( + Randomness r + ) public rand(r) { int256 beaconShares = int256(r.Uint256(1, MAX_ETH_SUPPLY)); uint256 sharesDecrease = r.Uint256(0, uint256(beaconShares) - 1); uint64 maxMagnitude = r.Uint64(1, WAD - 1); @@ -3700,30 +3756,24 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager _setOperatorMagnitude(defaultOperator, beaconChainETHStrategy, maxMagnitude); eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares); eigenPodManagerMock.setBeaconChainSlashingFactor(defaultStaker, beaconChainSlashingFactor); + (uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator); - _assertDeposit({ + _assertDelegation({ staker: defaultStaker, operator: defaultOperator, strategy: beaconChainETHStrategy, operatorSharesBefore: 0, - withdrawableSharesBefore: 0, - depositSharesBefore: 0, - prevDsf: WAD, - depositAmount: uint256(beaconShares) + withdrawableSharesBefore: withdrawableShares[0], + depositSharesBefore: uint256(beaconShares), + prevDsf: WAD }); // 2. Perform beaconChain slash + decreaseDelegatedShares() - ( - uint64 prevBeaconSlashingFactor, - uint64 newBeaconSlashingFactor - ) = _setNewBeaconChainSlashingFactor(defaultStaker, beaconShares, sharesDecrease); + (uint64 prevBeaconSlashingFactor, uint64 newBeaconSlashingFactor) = + _setNewBeaconChainSlashingFactor(defaultStaker, beaconShares, sharesDecrease); uint64 beaconChainSlashingFactorDecrease = prevBeaconSlashingFactor - newBeaconSlashingFactor; - assertEq( - beaconChainSlashingFactor, - prevBeaconSlashingFactor, - "Bad test setup" - ); - uint256 depositScalingFactor = uint256(WAD).divWad(maxMagnitude.mulWad(beaconChainSlashingFactor)); + assertEq(beaconChainSlashingFactor, prevBeaconSlashingFactor, "Bad test setup"); + uint256 depositScalingFactor = uint256(WAD).divWad(maxMagnitude); // expected operatorShares decreased for event uint256 operatorSharesToDecrease = _calcWithdrawableShares({ depositShares: uint256(beaconShares), @@ -3739,7 +3789,9 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager }) ); cheats.prank(address(eigenPodManagerMock)); - delegationManager.decreaseDelegatedShares(defaultStaker, uint256(beaconShares), beaconChainSlashingFactorDecrease); + delegationManager.decreaseDelegatedShares( + defaultStaker, uint256(beaconShares), beaconChainSlashingFactorDecrease + ); // 3. Assert correct values uint256 expectedWithdrawableShares = _calcWithdrawableShares({ @@ -3749,16 +3801,17 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager }); _assertSharesAfterBeaconSlash({ staker: defaultStaker, - withdrawableSharesBefore: uint256(beaconShares), + withdrawableSharesBefore: withdrawableShares[0], expectedWithdrawableShares: expectedWithdrawableShares, prevBeaconSlashingFactor: prevBeaconSlashingFactor }); // Assert correct end state values - (uint256[] memory withdrawableSharesAfter, ) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (uint256[] memory withdrawableSharesAfter,) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); assertEq( delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy) + operatorSharesToDecrease, - uint256(beaconShares), + withdrawableShares[0], "operator shares not decreased correctly" ); @@ -3774,7 +3827,9 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager * is slashed. Their withdrawable shares should be 0 afterwards and decreasing operatorShares should * not underflow and revert either. */ - function testFuzz_decreaseDelegatedShares_entireBalance(Randomness r) public rand(r) { + function testFuzz_decreaseDelegatedShares_entireBalance( + Randomness r + ) public rand(r) { int256 beaconShares = int256(r.Uint256(1, MAX_ETH_SUPPLY)); uint64 maxMagnitude = r.Uint64(1, WAD); uint64 beaconChainSlashingFactor = r.Uint64(1, WAD); @@ -3784,30 +3839,24 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager _setOperatorMagnitude(defaultOperator, beaconChainETHStrategy, maxMagnitude); eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares); eigenPodManagerMock.setBeaconChainSlashingFactor(defaultStaker, beaconChainSlashingFactor); + (uint256[] memory withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator); - _assertDeposit({ + _assertDelegation({ staker: defaultStaker, operator: defaultOperator, strategy: beaconChainETHStrategy, operatorSharesBefore: 0, - withdrawableSharesBefore: 0, - depositSharesBefore: 0, - prevDsf: WAD, - depositAmount: uint256(beaconShares) + withdrawableSharesBefore: withdrawableShares[0], + depositSharesBefore: uint256(beaconShares), + prevDsf: WAD }); // 2. Perform beaconChain slash + decreaseDelegatedShares() - ( - uint64 prevBeaconSlashingFactor, - uint64 newBeaconSlashingFactor - ) = _setNewBeaconChainSlashingFactor(defaultStaker, beaconShares, uint256(beaconShares)); - assertEq( - beaconChainSlashingFactor, - prevBeaconSlashingFactor, - "Bad test setup" - ); + (uint64 prevBeaconSlashingFactor, uint64 newBeaconSlashingFactor) = + _setNewBeaconChainSlashingFactor(defaultStaker, beaconShares, uint256(beaconShares)); + assertEq(beaconChainSlashingFactor, prevBeaconSlashingFactor, "Bad test setup"); uint64 beaconChainSlashingFactorDecrease = prevBeaconSlashingFactor - newBeaconSlashingFactor; - uint256 depositScalingFactor = uint256(WAD).divWad(maxMagnitude.mulWad(beaconChainSlashingFactor)); + uint256 depositScalingFactor = uint256(WAD).divWad(maxMagnitude); // expected operatorShares decreased for event uint256 operatorSharesToDecrease = _calcWithdrawableShares({ depositShares: uint256(beaconShares), @@ -3831,25 +3880,19 @@ contract DelegationManagerUnitTests_decreaseDelegatedShares is DelegationManager depositScalingFactor: depositScalingFactor, slashingFactor: maxMagnitude.mulWad(newBeaconSlashingFactor) }); + assertEq(expectedWithdrawableShares, 0, "All shares should be slashed"); assertEq( - expectedWithdrawableShares, - 0, - "All shares should be slashed" - ); - assertEq( - eigenPodManagerMock.beaconChainSlashingFactor(defaultStaker), - 0, - "beaconChainSlashingFactor should be 0" + eigenPodManagerMock.beaconChainSlashingFactor(defaultStaker), 0, "beaconChainSlashingFactor should be 0" ); _assertSharesAfterBeaconSlash({ staker: defaultStaker, - withdrawableSharesBefore: uint256(beaconShares), + withdrawableSharesBefore: withdrawableShares[0], expectedWithdrawableShares: expectedWithdrawableShares, prevBeaconSlashingFactor: prevBeaconSlashingFactor }); assertEq( delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy) + operatorSharesToDecrease, - uint256(beaconShares), + withdrawableShares[0], "operator shares not decreased correctly" ); } @@ -6277,11 +6320,8 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator); // Queue withdrawal - ( - QueuedWithdrawalParams[] memory queuedWithdrawalParams, - Withdrawal memory withdrawal, - bytes32 withdrawalRoot - ) = _setUpQueueWithdrawalsSingleStrat({ + (QueuedWithdrawalParams[] memory queuedWithdrawalParams, Withdrawal memory withdrawal, bytes32 withdrawalRoot) = + _setUpQueueWithdrawalsSingleStrat({ staker: defaultStaker, strategy: beaconChainETHStrategy, depositSharesToWithdraw: withdrawalAmount @@ -6291,19 +6331,25 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage { uint256 sharesToDecrement = _calcWithdrawableShares({ depositShares: withdrawalAmount, - depositScalingFactor: uint256(WAD).divWad(initialBCSF), + depositScalingFactor: uint256(WAD), slashingFactor: uint256(initialBCSF) }); - uint256 operatorSharesBeforeQueue = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy); + uint256 operatorSharesBeforeQueue = + delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy); cheats.prank(defaultStaker); delegationManager.queueWithdrawals(queuedWithdrawalParams); assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), "withdrawalRoot should be pending"); uint256 operatorSharesAfterQueue = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy); - assertEq(operatorSharesAfterQueue, operatorSharesBeforeQueue - sharesToDecrement, "operator shares should be decreased after queue"); + assertEq( + operatorSharesAfterQueue, + operatorSharesBeforeQueue - sharesToDecrement, + "operator shares should be decreased after queue" + ); // Slash the staker for beacon chain shares while it has queued a withdrawal // simulate the operations done in EigenPodManager._reduceSlashingFactor - (uint256[] memory withdrawableSharesBefore, ) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (uint256[] memory withdrawableSharesBefore,) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); uint256 currentPodShares = uint256(depositAmount) - withdrawalAmount; (prevBeaconSlashingFactor, newBeaconSlashingFactor) = _decreaseBeaconChainShares({ @@ -6314,17 +6360,17 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage uint256 expectedWithdrawableShares = _calcWithdrawableShares({ depositShares: uint256(currentPodShares), - depositScalingFactor: uint256(WAD).divWad(prevBeaconSlashingFactor), + depositScalingFactor: uint256(WAD), slashingFactor: uint256(newBeaconSlashingFactor) }); - _assertSharesAfterBeaconSlash(defaultStaker, withdrawableSharesBefore[0], expectedWithdrawableShares, prevBeaconSlashingFactor); + _assertSharesAfterBeaconSlash( + defaultStaker, withdrawableSharesBefore[0], expectedWithdrawableShares, prevBeaconSlashingFactor + ); } // Complete queue withdrawal - ( - uint256[] memory withdrawableShares, - uint256[] memory depositShares - ) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (uint256[] memory withdrawableShares, uint256[] memory depositShares) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); uint256 operatorSharesBefore = delegationManager.operatorShares(defaultOperator, beaconChainETHStrategy); { @@ -6338,7 +6384,7 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage }) ); cheats.prank(defaultStaker); - delegationManager.completeQueuedWithdrawal(withdrawal, tokens, receiveAsTokens); + delegationManager.completeQueuedWithdrawal(withdrawal, tokens, receiveAsTokens); } _assertCompletedWithdrawal( @@ -6350,7 +6396,7 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage operatorSharesBefore: operatorSharesBefore.toArrayU256(), withdrawableSharesBefore: withdrawableShares, depositSharesBefore: depositShares, - prevDepositScalingFactors: uint256(WAD).divWad(initialBCSF).toArrayU256(), + prevDepositScalingFactors: uint256(WAD).toArrayU256(), slashingFactors: uint256(WAD).toArrayU256(), // beaconChainSlashingFactor is separate from slashingFactors input beaconChainSlashingFactor: newBeaconSlashingFactor }) @@ -7545,7 +7591,9 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests * and also on the beaconChain. This test ensures that the order of slashing does not matter and nets * the same withdrawableShares for the staker whether slashing occurred on the beaconChain, or on EigenLayer first. */ - function testFuzz_beaconSlashAndAVSSlash(Randomness r) public rand(r) { + function testFuzz_beaconSlashAndAVSSlash( + Randomness r + ) public rand(r) { uint64 initialMagnitude = r.Uint64(2, WAD); uint64 newMaxMagnitude = r.Uint64(1, initialMagnitude); // note: beaconShares only goes negative when performing withdrawal -- and this will change post-migration @@ -7593,15 +7641,13 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests { // Slash beaconChain first { - (withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (withdrawableShares,) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); uint256 beaconSharesBeforeSlash = withdrawableShares[0]; uint64 prevBeaconChainSlashingFactor; - (prevBeaconChainSlashingFactor, newBeaconSlashingFactor) = _decreaseBeaconChainShares( - defaultStaker, - beaconShares, - sharesDecrease - ); + (prevBeaconChainSlashingFactor, newBeaconSlashingFactor) = + _decreaseBeaconChainShares(defaultStaker, beaconShares, sharesDecrease); uint256 expectedWithdrawableShares = _calcWithdrawableShares({ depositShares: uint256(beaconShares), @@ -7617,19 +7663,23 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests } // Slash on EigenLayer second { - (withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (withdrawableShares,) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); uint256 beaconSharesBeforeSlash = withdrawableShares[0]; // do a slash via an AVS _setOperatorMagnitude(defaultOperator, beaconChainETHStrategy, newMaxMagnitude); cheats.prank(address(allocationManagerMock)); - delegationManager.slashOperatorShares(defaultOperator, beaconChainETHStrategy, initialMagnitude, newMaxMagnitude); + delegationManager.slashOperatorShares( + defaultOperator, beaconChainETHStrategy, initialMagnitude, newMaxMagnitude + ); // save the outcome - (withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (withdrawableShares,) = + delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); uint256 beaconSharesAfterSecondSlash = withdrawableShares[0]; uint256 expectedWithdrawable = _calcWithdrawableShares( - uint256(beaconShares), + uint256(beaconShares), uint256(WAD).divWad(initialMagnitude), _getSlashingFactor(defaultStaker, beaconChainETHStrategy, newMaxMagnitude) ); @@ -7651,16 +7701,15 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests // 2. do AVS slash then beacon chain slash //////////////////////////// - // restore the staker and operator to their original state - // Reset operator's magnitude, beaconChainSlashingFactor + // initialize new staker and operator with same initial conditions delegationManager.undelegate(defaultStaker); _registerOperatorWithBaseDetails(defaultOperator2); _setOperatorMagnitude(defaultOperator2, beaconChainETHStrategy, initialMagnitude); - eigenPodManagerMock.setPodOwnerShares(defaultStaker, beaconShares); - eigenPodManagerMock.setBeaconChainSlashingFactor(defaultStaker, WAD); - _delegateToOperatorWhoAcceptsAllStakers(defaultStaker, defaultOperator2); + eigenPodManagerMock.setPodOwnerShares(defaultStaker2, beaconShares); + eigenPodManagerMock.setBeaconChainSlashingFactor(defaultStaker2, WAD); + _delegateToOperatorWhoAcceptsAllStakers(defaultStaker2, defaultOperator2); _assertDeposit({ - staker: defaultStaker, + staker: defaultStaker2, operator: defaultOperator2, strategy: beaconChainETHStrategy, operatorSharesBefore: 0, @@ -7673,21 +7722,24 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests { // Slash on EigenLayer first { - (withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (withdrawableShares,) = + delegationManager.getWithdrawableShares(defaultStaker2, beaconChainETHStrategy.toArray()); uint256 beaconSharesBeforeSlash = withdrawableShares[0]; _setOperatorMagnitude(defaultOperator2, beaconChainETHStrategy, newMaxMagnitude); cheats.prank(address(allocationManagerMock)); - delegationManager.slashOperatorShares(defaultOperator2, beaconChainETHStrategy, initialMagnitude, newMaxMagnitude); + delegationManager.slashOperatorShares( + defaultOperator2, beaconChainETHStrategy, initialMagnitude, newMaxMagnitude + ); uint256 expectedWithdrawable = _calcWithdrawableShares( - uint256(beaconShares), + uint256(beaconShares), uint256(WAD).divWad(initialMagnitude), - _getSlashingFactor(defaultStaker, beaconChainETHStrategy, newMaxMagnitude) + _getSlashingFactor(defaultStaker2, beaconChainETHStrategy, newMaxMagnitude) ); _assertSharesAfterSlash({ - staker: defaultStaker, + staker: defaultStaker2, strategy: beaconChainETHStrategy, withdrawableSharesBefore: beaconSharesBeforeSlash, expectedWithdrawableShares: expectedWithdrawable, @@ -7698,15 +7750,13 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests // Slash beaconChain second { - (withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); + (withdrawableShares,) = + delegationManager.getWithdrawableShares(defaultStaker2, beaconChainETHStrategy.toArray()); uint256 beaconSharesBeforeSlash = withdrawableShares[0]; uint64 prevBeaconChainSlashingFactor; - (prevBeaconChainSlashingFactor, newBeaconSlashingFactor) = _decreaseBeaconChainShares( - defaultStaker, - beaconShares, - sharesDecrease - ); + (prevBeaconChainSlashingFactor, newBeaconSlashingFactor) = + _decreaseBeaconChainShares(defaultStaker2, beaconShares, sharesDecrease); uint256 expectedWithdrawableShares = _calcWithdrawableShares({ depositShares: uint256(beaconShares), @@ -7714,7 +7764,7 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests slashingFactor: newMaxMagnitude.mulWad(newBeaconSlashingFactor) }); _assertSharesAfterBeaconSlash({ - staker: defaultStaker, + staker: defaultStaker2, withdrawableSharesBefore: beaconSharesBeforeSlash, expectedWithdrawableShares: expectedWithdrawableShares, prevBeaconSlashingFactor: prevBeaconChainSlashingFactor @@ -7725,12 +7775,9 @@ contract DelegationManagerUnitTests_slashingShares is DelegationManagerUnitTests //////////////////////////// // 3. Confirm withdrawable shares are the same regardless of order of operations in Test 1 or Test 2 //////////////////////////// - (withdrawableShares,) = delegationManager.getWithdrawableShares(defaultStaker, beaconChainETHStrategy.toArray()); - assertEq( - withdrawableShares[0], - sharesAfterAllSlashing, - "shares after all slashing should be the same" - ); + (withdrawableShares,) = + delegationManager.getWithdrawableShares(defaultStaker2, beaconChainETHStrategy.toArray()); + assertEq(withdrawableShares[0], sharesAfterAllSlashing, "shares after all slashing should be the same"); } }