From c439468474fd8e76ed7bd689c2feb659a5da1c2d Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:06:10 -0500 Subject: [PATCH] Integration Tests: Scenarios 2-3 (#350) --- src/test/integration/IntegrationBase.t.sol | 94 ++++- src/test/integration/IntegrationChecks.t.sol | 130 +++++++ .../integration/IntegrationDeployer.t.sol | 2 +- src/test/integration/User.t.sol | 44 ++- .../Deposit_Delegate_Queue_Complete.t.sol | 332 ++++-------------- ...Deposit_Delegate_Redelegate_Complete.t.sol | 87 +++++ ...Deposit_Delegate_Undelegate_Complete.t.sol | 223 ++++++++++++ 7 files changed, 645 insertions(+), 267 deletions(-) create mode 100644 src/test/integration/IntegrationChecks.t.sol create mode 100644 src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol create mode 100644 src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index e9e2b2aa8..75f0ddfc0 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -123,18 +123,26 @@ abstract contract IntegrationBase is IntegrationDeployer { /// @dev Asserts that ALL of the `withdrawalRoots` is in `delegationManager.pendingWithdrawals` function assert_AllWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal { for (uint i = 0; i < withdrawalRoots.length; i++) { - assertTrue(delegationManager.pendingWithdrawals(withdrawalRoots[i]), err); + assert_WithdrawalPending(withdrawalRoots[i], err); } } /// @dev Asserts that NONE of the `withdrawalRoots` is in `delegationManager.pendingWithdrawals` function assert_NoWithdrawalsPending(bytes32[] memory withdrawalRoots, string memory err) internal { for (uint i = 0; i < withdrawalRoots.length; i++) { - assertFalse(delegationManager.pendingWithdrawals(withdrawalRoots[i]), err); + assert_WithdrawalNotPending(withdrawalRoots[i], err); } } /// @dev Asserts that the hash of each withdrawal corresponds to the provided withdrawal root + function assert_WithdrawalPending(bytes32 withdrawalRoot, string memory err) internal { + assertTrue(delegationManager.pendingWithdrawals(withdrawalRoot), err); + } + + function assert_WithdrawalNotPending(bytes32 withdrawalRoot, string memory err) internal { + assertFalse(delegationManager.pendingWithdrawals(withdrawalRoot), err); + } + function assert_ValidWithdrawalHashes( IDelegationManager.Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots, @@ -143,9 +151,17 @@ abstract contract IntegrationBase is IntegrationDeployer { bytes32[] memory expectedRoots = _getWithdrawalHashes(withdrawals); for (uint i = 0; i < withdrawals.length; i++) { - assertEq(withdrawalRoots[i], expectedRoots[i], err); + assert_ValidWithdrawalHash(withdrawals[i], withdrawalRoots[i], err); } } + + function assert_ValidWithdrawalHash( + IDelegationManager.Withdrawal memory withdrawal, + bytes32 withdrawalRoot, + string memory err + ) internal { + assertEq(withdrawalRoot, delegationManager.calculateWithdrawalRoot(withdrawal), err); + } /******************************************************************************* SNAPSHOT ASSERTIONS @@ -264,6 +280,45 @@ abstract contract IntegrationBase is IntegrationDeployer { } } + function assert_Snap_Removed_StrategyShares( + IStrategy[] memory strategies, + uint[] memory removedShares, + string memory err + ) internal { + uint[] memory curShares = _getTotalStrategyShares(strategies); + + // Use timewarp to get previous strategy shares + uint[] memory prevShares = _getPrevTotalStrategyShares(strategies); + + for (uint i = 0; i < strategies.length; i++) { + // Ignore BeaconChainETH strategy since it doesn't keep track of global strategy shares + if (strategies[i] == BEACONCHAIN_ETH_STRAT) { + continue; + } + uint prevShare = prevShares[i]; + uint curShare = curShares[i]; + + assertEq(prevShare - removedShares[i], curShare, err); + } + } + + function assert_Snap_Unchanged_StrategyShares( + IStrategy[] memory strategies, + string memory err + ) internal { + uint[] memory curShares = _getTotalStrategyShares(strategies); + + // Use timewarp to get previous strategy shares + uint[] memory prevShares = _getPrevTotalStrategyShares(strategies); + + for (uint i = 0; i < strategies.length; i++) { + uint prevShare = prevShares[i]; + uint curShare = curShares[i]; + + assertEq(prevShare, curShare, err); + } + } + /// Snapshot assertions for underlying token balances: /// @dev Check that the staker has `addedTokens` additional underlying tokens @@ -339,6 +394,18 @@ abstract contract IntegrationBase is IntegrationDeployer { assertEq(prevQueuedWithdrawals + withdrawals.length, curQueuedWithdrawals, err); } + function assert_Snap_Added_QueuedWithdrawal( + User staker, + IDelegationManager.Withdrawal memory withdrawal, + string memory err + ) internal { + uint curQueuedWithdrawal = _getCumulativeWithdrawals(staker); + // Use timewarp to get previous cumulative withdrawals + uint prevQueuedWithdrawal = _getPrevCumulativeWithdrawals(staker); + + assertEq(prevQueuedWithdrawal + 1, curQueuedWithdrawal, err); + } + /******************************************************************************* UTILITY METHODS *******************************************************************************/ @@ -375,6 +442,10 @@ abstract contract IntegrationBase is IntegrationDeployer { return (withdrawStrats, withdrawShares); } + /** + * Helpful getters: + */ + /// @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) { @@ -524,4 +595,21 @@ abstract contract IntegrationBase is IntegrationDeployer { return balances; } + + function _getPrevTotalStrategyShares(IStrategy[] memory strategies) internal timewarp() returns (uint[] memory) { + return _getTotalStrategyShares(strategies); + } + + function _getTotalStrategyShares(IStrategy[] memory strategies) internal view returns (uint[] memory) { + uint[] memory shares = new uint[](strategies.length); + + for (uint i = 0; i < strategies.length; i++) { + if (strategies[i] != BEACONCHAIN_ETH_STRAT) { + shares[i] = strategies[i].totalShares(); + } + // BeaconChainETH strategy doesn't keep track of global strategy shares, so we ignore + } + + return shares; + } } \ No newline at end of file diff --git a/src/test/integration/IntegrationChecks.t.sol b/src/test/integration/IntegrationChecks.t.sol new file mode 100644 index 000000000..edb6548e2 --- /dev/null +++ b/src/test/integration/IntegrationChecks.t.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "src/test/integration/IntegrationBase.t.sol"; +import "src/test/integration/User.t.sol"; + +/// @notice Contract that provides utility functions to reuse common test blocks & checks +contract IntegrationCheckUtils is IntegrationBase { + + function check_Deposit_State(User staker, IStrategy[] memory strategies, uint[] memory shares) internal { + /// Deposit into strategies: + // For each of the assets held by the staker (either StrategyManager or EigenPodManager), + // the staker calls the relevant deposit function, depositing all held assets. + // + // ... check that all underlying tokens were transferred to the correct destination + // and that the staker now has the expected amount of delegated shares in each strategy + assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens"); + assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); + } + + function check_Delegation_State(User staker, User operator, IStrategy[] memory strategies, uint[] memory shares) internal { + /// Delegate to an operator: + // + // ... check that the staker is now delegated to the operator, and that the operator + // was awarded the staker shares + assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); + assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator"); + assert_HasExpectedShares(staker, strategies, shares, "staker should still have expected shares after delegating"); + assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); + } + + function check_QueuedWithdrawal_State(User staker, User operator, IStrategy[] memory strategies, uint[] memory shares, IDelegationManager.Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots) internal { + // The staker will queue one or more withdrawals for the selected strategies and shares + // + // ... check that each withdrawal was successfully enqueued, that the returned roots + // match the hashes of each withdrawal, and that the staker and operator have + // reduced shares. + assert_AllWithdrawalsPending(withdrawalRoots, "staker withdrawals should now be pending"); + assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); + assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); + assert_Snap_Removed_OperatorShares(operator, strategies, shares, "failed to remove operator shares"); + assert_Snap_Removed_StakerShares(staker, strategies, shares, "failed to remove staker shares"); + } + + function check_Undelegate_State( + User staker, + User operator, + IDelegationManager.Withdrawal[] memory withdrawals, + bytes32[] memory withdrawalRoots, + IStrategy[] memory strategies, + uint[] memory shares + ) internal { + /// Undelegate from an operator + // + // ... check that the staker is undelegated, all strategies from which the staker is deposited are unqeuued, + // that the returned root matches the hashes for each strategy and share amounts, and that the staker + // and operator have reduced shares + assertEq(withdrawalRoots.length, 1, "should only be one withdrawal root"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawl should match returned root"); + assert_AllWithdrawalsPending(withdrawalRoots, "stakers withdrawal should now be pending"); + assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by 1"); + assert_Snap_Removed_OperatorShares(operator, strategies, shares, "failed to remove operator shares"); + assert_Snap_Removed_StakerShares(staker, strategies, shares, "failed to remove staker shares"); + } + + function check_Withdrawal_AsTokens_State( + User staker, + User operator, + IDelegationManager.Withdrawal memory withdrawal, + IStrategy[] memory strategies, + uint[] memory shares, + IERC20[] memory tokens, + uint[] memory expectedTokens + ) internal { + /// Complete withdrawal(s): + // The staker will complete the withdrawal as tokens + // + // ... check that the withdrawal is not pending, that the withdrawer received the expected tokens, and that the total shares of each + // strategy withdrawn decreases + assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker withdrawal should no longer be pending"); + assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); + assert_Snap_Unchanged_StakerShares(staker, "staker shares should not have changed"); + assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); + assert_Snap_Removed_StrategyShares(strategies, shares, "strategies should have total shares decremented"); + } + + function check_Withdrawal_AsShares_State( + User staker, + User operator, + IDelegationManager.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 withdrawer received the expected shares, and that the total shares of each + // 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_StakerShares(staker, strategies, shares, "staker should have received expected shares"); + assert_Snap_Added_OperatorShares(operator, withdrawal.strategies, withdrawal.shares, "operator should have received shares"); + assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); + } + + /// @notice Difference from above is that operator shares do not increase since staker is not delegated + function check_Withdrawal_AsShares_Undelegated_State( + User staker, + User operator, + IDelegationManager.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_StakerShares(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"); + } +} \ No newline at end of file diff --git a/src/test/integration/IntegrationDeployer.t.sol b/src/test/integration/IntegrationDeployer.t.sol index 657aaf255..0774df734 100644 --- a/src/test/integration/IntegrationDeployer.t.sol +++ b/src/test/integration/IntegrationDeployer.t.sol @@ -231,7 +231,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address(eigenPodManagerImplementation), abi.encodeWithSelector( EigenPodManager.initialize.selector, - type(uint256).max, // maxPods + type(uint).max, // maxPods address(beaconChainOracle), eigenLayerReputedMultisig, // initialOwner pauserRegistry, diff --git a/src/test/integration/User.t.sol b/src/test/integration/User.t.sol index c3aa5a4fa..50dae0e5a 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/User.t.sol @@ -123,6 +123,22 @@ contract User is Test { delegationManager.delegateTo(address(operator), emptySig, bytes32(0)); } + /// @dev Undelegate from operator + function undelegate() public createSnapshot virtual returns(IDelegationManager.Withdrawal[] memory){ + IDelegationManager.Withdrawal[] memory withdrawal = new IDelegationManager.Withdrawal[](1); + withdrawal[0] = _getExpectedWithdrawalStructForStaker(address(this)); + delegationManager.undelegate(address(this)); + return withdrawal; + } + + /// @dev Force undelegate staker + function forceUndelegate(User staker) public createSnapshot virtual returns(IDelegationManager.Withdrawal[] memory){ + IDelegationManager.Withdrawal[] memory withdrawal = new IDelegationManager.Withdrawal[](1); + withdrawal[0] = _getExpectedWithdrawalStructForStaker(address(staker)); + delegationManager.undelegate(address(staker)); + return withdrawal; + } + /// @dev Queues a single withdrawal for every share and strategy pair function queueWithdrawals( IStrategy[] memory strategies, @@ -160,11 +176,19 @@ contract User is Test { return (withdrawals); } + + function completeWithdrawalAsTokens(IDelegationManager.Withdrawal memory withdrawal) public createSnapshot virtual returns (IERC20[] memory) { + return _completeQueuedWithdrawal(withdrawal, true); + } + + function completeWithdrawalAsShares(IDelegationManager.Withdrawal memory withdrawal) public createSnapshot virtual returns (IERC20[] memory) { + return _completeQueuedWithdrawal(withdrawal, false); + } - function completeQueuedWithdrawal( + function _completeQueuedWithdrawal( IDelegationManager.Withdrawal memory withdrawal, bool receiveAsTokens - ) public createSnapshot virtual returns (IERC20[] memory) { + ) internal virtual returns (IERC20[] memory) { IERC20[] memory tokens = new IERC20[](withdrawal.strategies.length); for (uint i = 0; i < tokens.length; i++) { @@ -215,6 +239,22 @@ contract User is Test { function _podWithdrawalCredentials() internal view returns (bytes memory) { return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(pod)); } + + /// @notice Assumes staker and withdrawer are the same and that all strategies and shares are withdrawn + function _getExpectedWithdrawalStructForStaker(address staker) internal view returns (IDelegationManager.Withdrawal memory) { + (IStrategy[] memory strategies, uint[] memory shares) + = delegationManager.getDelegatableShares(staker); + + return IDelegationManager.Withdrawal({ + staker: staker, + delegatedTo: delegationManager.delegatedTo(staker), + withdrawer: staker, + nonce: delegationManager.cumulativeWithdrawalsQueued(staker), + startBlock: uint32(block.number), + strategies: strategies, + shares: shares + }); + } } /// @notice A user contract that calls nonstandard methods (like xBySignature methods) 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 7bd3559a2..fe73309b2 100644 --- a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "src/test/integration/IntegrationBase.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; import "src/test/integration/User.t.sol"; -contract Deposit_Delegate_Queue_Complete is IntegrationBase { +contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { /******************************************************************************* FULL WITHDRAWALS @@ -39,71 +39,27 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - { - /// 1. Deposit into strategies: - // For each of the assets held by the staker (either StrategyManager or EigenPodManager), - // the staker calls the relevant deposit function, depositing all held assets. - // - // ... check that all underlying tokens were transferred to the correct destination - // and that the staker now has the expected amount of delegated shares in each strategy - staker.depositIntoEigenlayer(strategies, tokenBalances); - - assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens"); - assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); - } + // 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); - { - /// 2. Delegate to an operator: - // - // ... check that the staker is now delegated to the operator, and that the operator - // was awarded the staker's shares - staker.delegateTo(operator); - - assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); - assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator"); - assert_Snap_Unchanged_StakerShares(staker, "staker shares should be unchanged after delegating"); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - } + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); - IDelegationManager.Withdrawal[] memory withdrawals; - bytes32[] memory withdrawalRoots; - - { - /// 3. Queue withdrawal(s): - // The staker will queue one or more withdrawals for all strategies and shares - // - // ... check that each withdrawal was successfully enqueued, that the returned withdrawals - // match now-pending withdrawal roots, and that the staker and operator have - // reduced shares. - withdrawals = staker.queueWithdrawals(strategies, shares); - withdrawalRoots = _getWithdrawalHashes(withdrawals); - - assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); - assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); - assert_Snap_Removed_OperatorShares(operator, strategies, shares, "failed to remove operator shares"); - assert_Snap_Removed_StakerShares(staker, strategies, shares, "failed to remove staker shares"); - } + // 3. Queue Withdrawals + IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots); + // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - { - /// 4. Complete withdrawal(s): - // The staker will complete each withdrawal as tokens - // - // ... check that the staker received their tokens - for (uint i = 0; i < withdrawals.length; i++) { - IDelegationManager.Withdrawal memory withdrawal = withdrawals[i]; - - uint[] memory expectedTokens = _calculateExpectedTokens(withdrawal.strategies, withdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawal, true); - - assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); - assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); - assert_Snap_Unchanged_StakerShares(staker, "staker shares should not have changed"); - assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); - } + for (uint i = 0; i < withdrawals.length; i++) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens); } // Check final state: @@ -142,71 +98,26 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - { - /// 1. Deposit into strategies: - // For each of the assets held by the staker (either StrategyManager or EigenPodManager), - // the staker calls the relevant deposit function, depositing all held assets. - // - // ... check that all underlying tokens were transferred to the correct destination - // and that the staker now has the expected amount of delegated shares in each strategy - staker.depositIntoEigenlayer(strategies, tokenBalances); - - assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens"); - assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); - } + // 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); - { - /// 2. Delegate to an operator: - // - // ... check that the staker is now delegated to the operator, and that the operator - // was awarded the staker's shares - staker.delegateTo(operator); - - assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); - assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator"); - assert_Snap_Unchanged_StakerShares(staker, "staker shares should be unchanged after delegating"); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - } + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); - IDelegationManager.Withdrawal[] memory withdrawals; - bytes32[] memory withdrawalRoots; - - { - /// 3. Queue withdrawal(s): - // The staker will queue one or more withdrawals for all strategies and shares - // - // ... check that each withdrawal was successfully enqueued, that the returned withdrawals - // match now-pending withdrawal roots, and that the staker and operator have - // reduced shares. - withdrawals = staker.queueWithdrawals(strategies, shares); - withdrawalRoots = _getWithdrawalHashes(withdrawals); - - assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); - assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); - assert_Snap_Removed_OperatorShares(operator, strategies, shares, "failed to remove operator shares"); - assert_Snap_Removed_StakerShares(staker, strategies, shares, "failed to remove staker shares"); - } + // 3. Queue Withdrawals + IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(strategies, shares); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, strategies, shares, withdrawals, withdrawalRoots); + // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - { - /// 4. Complete withdrawal(s): - // The staker will complete each withdrawal as tokens - // - // ... check that the staker and operator received their shares and that neither - // have any change in token balances - for (uint i = 0; i < withdrawals.length; i++) { - IDelegationManager.Withdrawal memory withdrawal = withdrawals[i]; - - staker.completeQueuedWithdrawal(withdrawal, false); - - 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_StakerShares(staker, withdrawal.strategies, withdrawal.shares, "staker should have received shares"); - assert_Snap_Added_OperatorShares(operator, withdrawal.strategies, withdrawal.shares, "operator should have received shares"); - } + for (uint i = 0; i < withdrawals.length; i++) { + staker.completeWithdrawalAsShares(withdrawals[i]); + check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, shares); } // Check final state: @@ -249,78 +160,32 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - { - /// 1. Deposit into strategies: - // For each of the assets held by the staker (either StrategyManager or EigenPodManager), - // the staker calls the relevant deposit function, depositing all held assets. - // - // ... check that all underlying tokens were transferred to the correct destination - // and that the staker now has the expected amount of delegated shares in each strategy - staker.depositIntoEigenlayer(strategies, tokenBalances); - - assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens"); - assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); - } + // 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); - { - /// 2. Delegate to an operator: - // - // ... check that the staker is now delegated to the operator, and that the operator - // was awarded the staker's shares - staker.delegateTo(operator); - - assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); - assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator"); - assert_Snap_Unchanged_StakerShares(staker, "staker shares should be unchanged after delegating"); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - } + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + // 3. Queue Withdrawals // Randomly select one or more assets to withdraw ( IStrategy[] memory withdrawStrats, uint[] memory withdrawShares ) = _randWithdrawal(strategies, shares); - IDelegationManager.Withdrawal[] memory withdrawals; - bytes32[] memory withdrawalRoots; - - { - /// 3. Queue withdrawal(s): - // The staker will queue one or more withdrawals for the selected strategies and shares - // - // ... check that each withdrawal was successfully enqueued, that the returned roots - // match the hashes of each withdrawal, and that the staker and operator have - // reduced shares. - withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares); - withdrawalRoots = _getWithdrawalHashes(withdrawals); - - assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); - assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); - assert_Snap_Removed_OperatorShares(operator, withdrawStrats, withdrawShares, "failed to remove operator shares"); - assert_Snap_Removed_StakerShares(staker, withdrawStrats, withdrawShares, "failed to remove staker shares"); - } + IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawals, withdrawalRoots); + // 4. Complete withdrawals // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - - { - /// 4. Complete withdrawal(s): - // The staker will complete each withdrawal as tokens - // - // ... check that the staker received their tokens and that the staker/operator - // have unchanged share amounts - for (uint i = 0; i < withdrawals.length; i++) { - IDelegationManager.Withdrawal memory withdrawal = withdrawals[i]; - - uint[] memory expectedTokens = _calculateExpectedTokens(withdrawal.strategies, withdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawal, true); - - assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); - assert_Snap_Unchanged_TokenBalances(operator, "operator token balances should not have changed"); - assert_Snap_Unchanged_StakerShares(staker, "staker shares should not have changed"); - assert_Snap_Unchanged_OperatorShares(operator, "operator shares should not have changed"); - } + for (uint i = 0; i < withdrawals.length; i++) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares, tokens, expectedTokens); } // Check final state: @@ -334,7 +199,7 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { /// 3. queues a withdrawal for a random subset of shares /// 4. completes the queued withdrawal as shares function testFuzz_deposit_delegate_queueRand_completeAsShares(uint24 _random) public { - // When new Users are created, they will choose a random configuration from these params: + // When new Users are created, they will choose a random configuration from these params: _configRand({ _randomSeed: _random, _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, @@ -357,77 +222,32 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - { - /// 1. Deposit into strategies: - // For each of the assets held by the staker (either StrategyManager or EigenPodManager), - // the staker calls the relevant deposit function, depositing all held assets. - // - // ... check that all underlying tokens were transferred to the correct destination - // and that the staker now has the expected amount of delegated shares in each strategy - staker.depositIntoEigenlayer(strategies, tokenBalances); - - assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens"); - assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); - } + // 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); - { - /// 2. Delegate to an operator: - // - // ... check that the staker is now delegated to the operator, and that the operator - // was awarded the staker's shares - staker.delegateTo(operator); - - assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); - assertEq(address(operator), delegationManager.delegatedTo(address(staker)), "staker should be delegated to operator"); - assert_Snap_Unchanged_StakerShares(staker, "staker shares should be unchanged after delegating"); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - } + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + // 3. Queue Withdrawals // Randomly select one or more assets to withdraw ( IStrategy[] memory withdrawStrats, uint[] memory withdrawShares ) = _randWithdrawal(strategies, shares); - IDelegationManager.Withdrawal[] memory withdrawals; - bytes32[] memory withdrawalRoots; - - { - /// 3. Queue withdrawal(s): - // The staker will queue one or more withdrawals for the selected strategies and shares - // - // ... check that each withdrawal was successfully enqueued, that the returned roots - // match the hashes of each withdrawal, and that the staker and operator have - // reduced shares. - withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares); - withdrawalRoots = _getWithdrawalHashes(withdrawals); - - assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); - assert_Snap_Added_QueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); - assert_Snap_Removed_OperatorShares(operator, withdrawStrats, withdrawShares, "failed to remove operator shares"); - assert_Snap_Removed_StakerShares(staker, withdrawStrats, withdrawShares, "failed to remove staker shares"); - } + IDelegationManager.Withdrawal[] memory withdrawals = staker.queueWithdrawals(withdrawStrats, withdrawShares); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator, withdrawStrats, withdrawShares, withdrawals, withdrawalRoots); + // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - { - /// 4. Complete withdrawal(s): - // The staker will complete each withdrawal as tokens - // - // ... check that the staker received their tokens and that the staker/operator - // have unchanged share amounts - for (uint i = 0; i < withdrawals.length; i++) { - IDelegationManager.Withdrawal memory withdrawal = withdrawals[i]; - - staker.completeQueuedWithdrawal(withdrawal, false); - - 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_StakerShares(staker, withdrawal.strategies, withdrawal.shares, "staker should have received shares"); - assert_Snap_Added_OperatorShares(operator, withdrawal.strategies, withdrawal.shares, "operator should have received shares"); - } + for (uint i = 0; i < withdrawals.length; i++) { + staker.completeWithdrawalAsShares(withdrawals[i]); + check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares); } // Check final state: @@ -466,27 +286,17 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - { - /// 1. Deposit into strategies: - // For each of the assets held by the staker (either StrategyManager or EigenPodManager), - // the staker calls the relevant deposit function, depositing all held assets. - // - // ... check that all underlying tokens were transferred to the correct destination - // and that the staker now has the expected amount of delegated shares in each strategy - staker.depositIntoEigenlayer(strategies, tokenBalances); - - assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker should have transferred all underlying tokens"); - assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); - } + // 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); - { - /// 2. Register the staker as an operator, then attempt to delegate to an operator. - /// This should fail as the staker is already delegated to themselves. - staker.registerAsOperator(); - assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); + // 2. Register staker as an operator + staker.registerAsOperator(); + assertTrue(delegationManager.isDelegated(address(staker)), "staker should be delegated"); - cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated"); - staker.delegateTo(operator); - } + // 3. Attempt to delegate to an operator + // This should fail as the staker is already delegated to themselves. + cheats.expectRevert("DelegationManager._delegate: staker is already actively delegated"); + staker.delegateTo(operator); } } \ No newline at end of file diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol new file mode 100644 index 000000000..eddbd5a04 --- /dev/null +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "src/test/integration/User.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; + +contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationCheckUtils { + /// Randomly generates a user with different held assets. Then: + /// 1. deposit into strategy + /// 2. delegate to an operator + /// 3. undelegates from the operator + /// 4. complete queued withdrawal as shares + /// 5. delegate to a new operator + /// 5. queueWithdrawal + /// 7. complete their queued withdrawal as tokens + function testFuzz_deposit_delegate_reDelegate_completeAsTokens(uint24 _random) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS + }); + + /// 0. Create an operator and a staker with: + // - some nonzero underlying token balances + // - corresponding to a random number of strategies + // + // ... check that the staker has no deleagatable shares and isn't delegated + + ( + User staker, + IStrategy[] memory strategies, + uint[] memory tokenBalances + ) = _newRandomStaker(); + (User operator1, ,) = _newRandomOperator(); + (User operator2, ,) = _newRandomOperator(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator1); + check_Delegation_State(staker, operator1, strategies, shares); + + // 3. Undelegate from an operator + IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator1, withdrawals, withdrawalRoots, strategies, shares); + + // 4. Complete withdrawal as shares + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + staker.completeWithdrawalAsShares(withdrawals[0]); + check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[0], strategies, shares); + + // 5. Delegate to a new operator + staker.delegateTo(operator2); + check_Delegation_State(staker, operator2, strategies, shares); + assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); + + // 6. Queue Withdrawal + withdrawals = staker.queueWithdrawals(strategies, shares); + withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); + + // 7. Complete withdrawal + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + + // Complete withdrawals + for (uint i = 0; i < withdrawals.length; i++) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); + check_Withdrawal_AsTokens_State(staker, operator2, withdrawals[i], strategies, shares, tokens, expectedTokens); + } + } + + //TODO: add complete last withdrawal as shares + //TODO: add complete middle withdrawal as tokens and then restake and redelegate + //TODO: additional deposit before delegating to new operator + //TODO: additional deposit after delegating to new operator +} \ No newline at end of file diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol new file mode 100644 index 000000000..1d25902b4 --- /dev/null +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "src/test/integration/User.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; + +contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUtils { + + /// Randomly generates a user with different held assets. Then: + /// 1. deposit into strategy + /// 2. delegate to an operator + /// 3. undelegates from the operator + /// 4. complete their queued withdrawal as tokens + function testFuzz_deposit_undelegate_completeAsTokens(uint24 _random) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS + }); + + /// 0. Create an operator and a staker with: + // - some nonzero underlying token balances + // - corresponding to a random number of strategies + // + // ... check that the staker has no deleagatable shares and isn't delegated + + ( + User staker, + IStrategy[] memory strategies, + uint[] memory tokenBalances + ) = _newRandomStaker(); + (User operator, ,) = _newRandomOperator(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // 3. Undelegate from an operator + IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + + // 4. Complete withdrawal + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + + // Complete withdrawal + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[0].strategies, withdrawals[0].shares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[0]); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[0], strategies, shares, tokens, expectedTokens); + + // Check Final State + assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); + assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token balances"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } + + /// Randomly generates a user with different held assets. Then: + /// 1. deposit into strategy + /// 2. delegate to an operator + /// 3. undelegates from the operator + /// 4. complete their queued withdrawal as shares + function testFuzz_deposit_undelegate_completeAsShares(uint24 _random) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS + }); + + /// 0. Create an operator and a staker with: + // - some nonzero underlying token balances + // - corresponding to a random number of strategies + // + // ... check that the staker has no deleagatable shares and isn't delegated + + ( + User staker, + IStrategy[] memory strategies, + uint[] memory tokenBalances + ) = _newRandomStaker(); + (User operator, ,) = _newRandomOperator(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // 3. Undelegate from an operator + IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + + // 4. Complete withdrawal + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + staker.completeWithdrawalAsShares(withdrawals[0]); + check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[0], strategies, shares); + + // Check final state: + assert_HasExpectedShares(staker, strategies, shares, "staker should have all original shares"); + assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } + + function testFuzz_deposit_delegate_forceUndelegate_completeAsTokens(uint24 _random) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS + }); + + /// 0. Create an operator and a staker with: + // - some nonzero underlying token balances + // - corresponding to a random number of strategies + // + // ... check that the staker has no deleagatable shares and isn't delegated + + ( + User staker, + IStrategy[] memory strategies, + uint[] memory tokenBalances + ) = _newRandomStaker(); + (User operator, ,) = _newRandomOperator(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // 3. Force undelegate + IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + + // 4. Complete withdrawal + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[0].strategies, withdrawals[0].shares); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[0]); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[0], strategies, shares, tokens, expectedTokens); + + // Check Final State + assert_HasNoDelegatableShares(staker, "staker should have withdrawn all shares"); + assert_HasUnderlyingTokenBalances(staker, strategies, tokenBalances, "staker should once again have original token balances"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } + + function testFuzz_deposit_delegate_forceUndelegate_completeAsShares(uint24 _random) public { + // When new Users are created, they will choose a random configuration from these params: + _configRand({ + _randomSeed: _random, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS + }); + + /// 0. Create an operator and a staker with: + // - some nonzero underlying token balances + // - corresponding to a random number of strategies + // + // ... check that the staker has no deleagatable shares and isn't delegated + + ( + User staker, + IStrategy[] memory strategies, + uint[] memory tokenBalances + ) = _newRandomStaker(); + (User operator, ,) = _newRandomOperator(); + uint[] memory shares = _calculateExpectedShares(strategies, tokenBalances); + + assert_HasNoDelegatableShares(staker, "staker should not have delegatable shares before depositing"); + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + check_Deposit_State(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + check_Delegation_State(staker, operator, strategies, shares); + + // 3. Force undelegate + IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + check_Undelegate_State(staker, operator, withdrawals, withdrawalRoots, strategies, shares); + + // 4. Complete withdrawal + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + staker.completeWithdrawalAsShares(withdrawals[0]); + check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[0], strategies, shares); + + // Check final state: + assert_HasExpectedShares(staker, strategies, shares, "staker should have all original shares"); + assert_HasNoUnderlyingTokenBalance(staker, strategies, "staker not have any underlying tokens"); + assert_NoWithdrawalsPending(withdrawalRoots, "all withdrawals should be removed from pending"); + } +} \ No newline at end of file