From 6c9187128d6ceaa3e9448f7e4e92ac68714a74ad Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Mon, 20 Nov 2023 16:13:45 -0500 Subject: [PATCH 1/9] test: add scenario 2 tests --- src/test/integration/IntegrationBase.t.sol | 86 ++++++- src/test/integration/User.t.sol | 5 + .../Deposit_Delegate_Queue_Complete.t.sol | 22 +- ...Deposit_Delegate_Undelegate_Complete.t.sol | 213 ++++++++++++++++++ src/test/integration/tests/utils.t.sol | 10 + 5 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol create mode 100644 src/test/integration/tests/utils.t.sol diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index e9e2b2aa8..6bd3b389b 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 @@ -266,6 +282,18 @@ abstract contract IntegrationBase is IntegrationDeployer { /// Snapshot assertions for underlying token balances: + function assert_Snap_IncrementQueuedWithdrawals( + User staker, + string memory err + ) internal { + uint curQueuedWithdrawals = _getCumulativeWithdrawals(staker); + // Use timewarp to get previous cumulative withdrawals + uint prevQueuedWithdrawals = _getPrevCumulativeWithdrawals(staker); + + assertEq(prevQueuedWithdrawals + 1, curQueuedWithdrawals, err); + } + + /// @dev Check that the staker has `addedTokens` additional underlying tokens // since the last snapshot function assert_Snap_Added_TokenBalances( @@ -374,6 +402,44 @@ abstract contract IntegrationBase is IntegrationDeployer { return (withdrawStrats, withdrawShares); } + function assert_Snap_Removed_StrategyShares( + IStrategy[] memory strategies, + uint[] memory removedShares, + string memory err + ) internal { + uint[] memory curShares = _getStrategyShares(strategies); + + // Use timewarp to get previous strategy shares + uint[] memory prevShares = _getPrevStrategyShares(strategies); + + for (uint i = 0; i < strategies.length; i++) { + 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 = _getStrategyShares(strategies); + + // Use timewarp to get previous strategy shares + uint[] memory prevShares = _getPrevStrategyShares(strategies); + + for (uint i = 0; i < strategies.length; i++) { + uint prevShare = prevShares[i]; + uint curShare = curShares[i]; + + assertEq(prevShare, curShare, err); + } + } + + /** + * Helpful getters: + */ /// @dev For some strategies/underlying token balances, calculate the expected shares received /// from depositing all tokens @@ -524,4 +590,18 @@ abstract contract IntegrationBase is IntegrationDeployer { return balances; } + + function _getPrevStrategyShares(IStrategy[] memory strategies) internal timewarp() returns (uint[] memory) { + return _getStrategyShares(strategies); + } + + function _getStrategyShares(IStrategy[] memory strategies) internal view returns (uint[] memory) { + uint[] memory shares = new uint[](strategies.length); + + for (uint i = 0; i < strategies.length; i++) { + shares[i] = strategies[i].totalShares(); + } + + return shares; + } } \ No newline at end of file diff --git a/src/test/integration/User.t.sol b/src/test/integration/User.t.sol index c3aa5a4fa..c465d24b8 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/User.t.sol @@ -123,6 +123,11 @@ contract User is Test { delegationManager.delegateTo(address(operator), emptySig, bytes32(0)); } + /// @dev Undelegate from operator + function undelegate() public createSnapshot virtual returns(bytes32){ + return delegationManager.undelegate(address(this)); + } + /// @dev Queues a single withdrawal for every share and strategy pair function queueWithdrawals( IStrategy[] memory strategies, 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..f0bb261a9 100644 --- a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.12; import "src/test/integration/IntegrationBase.t.sol"; import "src/test/integration/User.t.sol"; -contract Deposit_Delegate_Queue_Complete is IntegrationBase { +contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { /******************************************************************************* FULL WITHDRAWALS @@ -92,17 +92,20 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { /// 4. Complete withdrawal(s): // The staker will complete each withdrawal as tokens // - // ... check that the staker received their tokens + // ... check that the withdrawal is not pending, that the staker received the expected tokens, and that the total shares of each + // strategy withdrawn from decreased 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_IncreasedTokenBalances(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_DecreasedStrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); + assert_NoWithdrawalsPending(withdrawalRoots, "staker's withdrawals should no longer be pending"); } } @@ -308,6 +311,7 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { /// 4. Complete withdrawal(s): // The staker will complete each withdrawal as tokens // +<<<<<<< HEAD // ... check that the staker received their tokens and that the staker/operator // have unchanged share amounts for (uint i = 0; i < withdrawals.length; i++) { @@ -316,7 +320,7 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { 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_IncreasedTokenBalances(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"); @@ -418,15 +422,25 @@ contract Deposit_Delegate_Queue_Complete is IntegrationBase { // // ... check that the staker received their tokens and that the staker/operator // have unchanged share amounts +======= + // ... 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 +>>>>>>> 6ed48d00 (test: add scenario 2 tests) for (uint i = 0; i < withdrawals.length; i++) { IDelegationManager.Withdrawal memory withdrawal = withdrawals[i]; staker.completeQueuedWithdrawal(withdrawal, false); +<<<<<<< HEAD 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"); +======= + assert_NoWithdrawalsPending(withdrawalRoots, "staker's withdrawals should no longer be pending"); + assert_Snap_AddedStakerShares(staker, withdrawal.strategies, withdrawal.shares, "staker should have received expected tokens"); + assert_Snap_UnchangedStrategyShares(withdrawal.strategies, "strategies should have total shares unchanged"); +>>>>>>> 6ed48d00 (test: add scenario 2 tests) } } 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..61b5e545c --- /dev/null +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -0,0 +1,213 @@ +// 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"; + +contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationBase { + + /// 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, + _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: + // 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"); + } + + { + /// 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_HasExpectedShares(staker, strategies, shares, "staker should still have expected shares after delegating"); + assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); + } + + IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); + { + /// 3. 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 + + bytes32 withdrawalRoot = staker.undelegate(); + + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + assert_ValidWithdrawalHash(expectedWithdrawal, withdrawalRoot, "calculated withdrawl should match returned root"); + assert_WithdrawalPending(withdrawalRoot, "staker's withdrawal should now be pending"); + assert_Snap_IncrementQueuedWithdrawals(staker, "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"); + } + + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + + { + /// 4. Complete withdrawal(s): + // The staker will complete the withdrawal as tokens + // + // ... check that the withdrawal is not pending, that the staker received the expected tokens, and that the total shares of each + // strategy withdrawn from decreased + uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, true); + + assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(expectedWithdrawal), "staker's withdrawal should no longer be pending"); + assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + assert_Snap_Removed_StrategyShares(strategies, shares, "strategies should have total shares decremented"); + } + } + + /// 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, + _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: + // 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"); + } + + { + /// 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_HasExpectedShares(staker, strategies, shares, "staker should still have expected shares after delegating"); + assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); + } + + IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); + { + /// 3. 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 + + bytes32 withdrawalRoot = staker.undelegate(); + + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + assert_ValidWithdrawalHash(expectedWithdrawal, withdrawalRoot, "calculated withdrawl should match returned root"); + assert_WithdrawalPending(withdrawalRoot, "staker's withdrawal should now be pending"); + assert_Snap_IncrementQueuedWithdrawals(staker, "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"); + } + + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + + { + /// 4. Complete withdrawal(s): + // The staker will complete the withdrawal as tokens + // + // ... 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 + staker.completeQueuedWithdrawal(expectedWithdrawal, false); + + assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(expectedWithdrawal), "staker's withdrawal should no longer be pending"); + assert_Snap_Added_StakerShares(staker, expectedWithdrawal.strategies, expectedWithdrawal.shares, "staker should have received expected tokens"); + assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); + } + } + + /// @notice Assumes staker and withdrawer are the same + function _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { + (IStrategy[] memory strategies, uint[] memory shares) + = delegationManager.getDelegatableShares(address(staker)); + + return IDelegationManager.Withdrawal({ + staker: address(staker), + delegatedTo: delegationManager.delegatedTo(address(staker)), + withdrawer: address(staker), + nonce: delegationManager.cumulativeWithdrawalsQueued(address(staker)), + startBlock: uint32(block.number), + strategies: strategies, + shares: shares + }); + } + + function _getExpectedWithdrawalStruct_diffWithdrawer(User staker, address withdrawer) internal view returns (IDelegationManager.Withdrawal memory) { + IDelegationManager.Withdrawal memory withdrawal = _getExpectedWithdrawalStruct(staker); + withdrawal.withdrawer = withdrawer; + return withdrawal; + } +} \ No newline at end of file diff --git a/src/test/integration/tests/utils.t.sol b/src/test/integration/tests/utils.t.sol new file mode 100644 index 000000000..feecf65af --- /dev/null +++ b/src/test/integration/tests/utils.t.sol @@ -0,0 +1,10 @@ +// 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 +contract IntegrationTestUtils { + +} \ No newline at end of file From 3bf209f6e774e2374b7adae5dfafa25e6b1edc81 Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Mon, 20 Nov 2023 17:46:48 -0500 Subject: [PATCH 2/9] test: move assertions into common functions --- ...Deposit_Delegate_Undelegate_Complete.t.sol | 141 ++++-------------- src/test/integration/tests/utils.t.sol | 80 +++++++++- 2 files changed, 111 insertions(+), 110 deletions(-) diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol index 61b5e545c..2c1873d59 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -3,8 +3,9 @@ pragma solidity =0.8.12; import "src/test/integration/IntegrationBase.t.sol"; import "src/test/integration/User.t.sol"; +import "src/test/integration/tests/utils.t.sol"; -contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationBase { +contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtils { /// Randomly generates a user with different held assets. Then: /// 1. deposit into strategy @@ -36,66 +37,28 @@ contract Integration_Deposit_Delegate_Undelegate_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"); - } - - { - /// 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_HasExpectedShares(staker, strategies, shares, "staker should still have expected shares after delegating"); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - } + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + assertDepositState(staker, strategies, shares); + // 2. Delegate to an operator + staker.delegateTo(operator); + assertDelegationState(staker, operator, strategies, shares); + + // 3. Undelegate from an operator IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - { - /// 3. 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 - - bytes32 withdrawalRoot = staker.undelegate(); - - assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - assert_ValidWithdrawalHash(expectedWithdrawal, withdrawalRoot, "calculated withdrawl should match returned root"); - assert_WithdrawalPending(withdrawalRoot, "staker's withdrawal should now be pending"); - assert_Snap_IncrementQueuedWithdrawals(staker, "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"); - } + bytes32 withdrawalRoot = staker.undelegate(); + assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + // 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 the withdrawal as tokens - // - // ... check that the withdrawal is not pending, that the staker received the expected tokens, and that the total shares of each - // strategy withdrawn from decreased - uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, true); - - assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(expectedWithdrawal), "staker's withdrawal should no longer be pending"); - assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); - assert_Snap_Removed_StrategyShares(strategies, shares, "strategies should have total shares decremented"); - } + // Complete withdrawal + uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, true); + + assertWithdrawalAsTokensState(staker, expectedWithdrawal, strategies, shares, tokens, expectedTokens); } /// Randomly generates a user with different held assets. Then: @@ -128,65 +91,27 @@ contract Integration_Deposit_Delegate_Undelegate_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"); - } - - { - /// 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_HasExpectedShares(staker, strategies, shares, "staker should still have expected shares after delegating"); - assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); - } + /// 1. Deposit Into Strategies + staker.depositIntoEigenlayer(strategies, tokenBalances); + assertDepositState(staker, strategies, shares); + // 2. Delegate to an operator + staker.delegateTo(operator); + assertDelegationState(staker, operator, strategies, shares); + + // 3. Undelegate from an operator IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - { - /// 3. 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 - - bytes32 withdrawalRoot = staker.undelegate(); - - assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - assert_ValidWithdrawalHash(expectedWithdrawal, withdrawalRoot, "calculated withdrawl should match returned root"); - assert_WithdrawalPending(withdrawalRoot, "staker's withdrawal should now be pending"); - assert_Snap_IncrementQueuedWithdrawals(staker, "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"); - } + bytes32 withdrawalRoot = staker.undelegate(); + assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + // 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 the withdrawal as tokens - // - // ... 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 - staker.completeQueuedWithdrawal(expectedWithdrawal, false); - - assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(expectedWithdrawal), "staker's withdrawal should no longer be pending"); - assert_Snap_Added_StakerShares(staker, expectedWithdrawal.strategies, expectedWithdrawal.shares, "staker should have received expected tokens"); - assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); - } + uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, false); + + assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); } /// @notice Assumes staker and withdrawer are the same diff --git a/src/test/integration/tests/utils.t.sol b/src/test/integration/tests/utils.t.sol index feecf65af..5e431989a 100644 --- a/src/test/integration/tests/utils.t.sol +++ b/src/test/integration/tests/utils.t.sol @@ -4,7 +4,83 @@ 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 -contract IntegrationTestUtils { +/// @notice Contract that provides utility functions to reuse common test blocks & checks +contract IntegrationTestUtils is IntegrationBase { + function assertDepositState(User staker, IStrategy[] memory strategies, uint256[] 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_AddedStakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); + } + + function assertDelegationState(User staker, User operator, IStrategy[] memory strategies, uint256[] 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's 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_AddedOperatorShares(operator, strategies, shares, "operator should have received shares"); + } + + function assertUndelegateState( + User staker, + User operator, + IDelegationManager.Withdrawal memory withdrawal, + bytes32 withdrawalRoot, + IStrategy[] memory strategies, + uint256[] 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 + assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); + assert_ValidWithdrawalHash(withdrawal, withdrawalRoot, "calculated withdrawl should match returned root"); + assert_withdrawalPending(withdrawalRoot, "staker's withdrawal should now be pending"); + assert_Snap_IncrementQueuedWithdrawals(staker, "staker should have increased nonce by 1"); + assert_Snap_RemovedOperatorShares(operator, strategies, shares, "failed to remove operator shares"); + assert_Snap_RemovedStakerShares(staker, strategies, shares, "failed to remove staker shares"); + } + + function assertWithdrawalAsTokensState( + User staker, + IDelegationManager.Withdrawal memory withdrawal, + IStrategy[] memory strategies, + uint256[] memory shares, + IERC20[] memory tokens, + uint256[] 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 shares, and that the total shares of each + // strategy withdrawn remains unchanged + assert_withdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker's withdrawal should no longer be pending"); + assert_Snap_IncreasedTokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + assert_Snap_DecreasedStrategyShares(strategies, shares, "strategies should have total shares decremented"); + } + + function assertWithdrawalAsSharesState( + User staker, + IDelegationManager.Withdrawal memory withdrawal, + IStrategy[] memory strategies, + uint256[] 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's withdrawal should no longer be pending"); + assert_Snap_AddedStakerShares(staker, strategies, shares, "staker should have received expected shares"); + assert_Snap_UnchangedStrategyShares(strategies, "strategies should have total shares unchanged"); + } } \ No newline at end of file From 6ffbc5849a52935b35b44dabd08f09eaf79cf738 Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Wed, 22 Nov 2023 11:37:33 -0500 Subject: [PATCH 3/9] test: add scenario 3 --- src/test/integration/User.t.sol | 5 + ...Deposit_Delegate_Undelegate_Complete.t.sol | 99 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/test/integration/User.t.sol b/src/test/integration/User.t.sol index c465d24b8..1391f70db 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/User.t.sol @@ -128,6 +128,11 @@ contract User is Test { return delegationManager.undelegate(address(this)); } + /// @dev Force undelegate staker + function undelegate(User staker) public createSnapshot virtual returns(bytes32){ + return delegationManager.undelegate(address(staker)); + } + /// @dev Queues a single withdrawal for every share and strategy pair function queueWithdrawals( IStrategy[] memory strategies, diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol index 2c1873d59..07fdeb90d 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -114,6 +114,105 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); } + 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, + _userTypes: DEFAULT | SIGNED_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); + assertDepositState(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + assertDelegationState(staker, operator, strategies, shares); + + // 3. Force undelegate + IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); + bytes32 withdrawalRoot = operator.undelegate(staker); + assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, 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(expectedWithdrawal.strategies, expectedWithdrawal.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, true); + + assertWithdrawalAsTokensState(staker, expectedWithdrawal, strategies, shares, tokens, expectedTokens); + } + + 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, + _userTypes: DEFAULT | SIGNED_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); + assertDepositState(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator); + assertDelegationState(staker, operator, strategies, shares); + + // 3. Force undelegate + IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); + bytes32 withdrawalRoot = operator.undelegate(staker); + assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + + // 4. Complete withdrawal + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + + uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, false); + + assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); + } + + + /// @notice Assumes staker and withdrawer are the same function _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { (IStrategy[] memory strategies, uint[] memory shares) From afc00bcbfb90c8ab9c61a332de9f4fcf5ca3ace3 Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Wed, 29 Nov 2023 12:55:03 -0500 Subject: [PATCH 4/9] test: scaffold for scenario 4 --- .../tests/Deposit_Delegate_Redelegate_Complete.t.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol 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..0b080a65d --- /dev/null +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -0,0 +1,10 @@ +// 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"; +import "src/test/integration/tests/utils.t.sol"; + +contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtils { + +} \ No newline at end of file From 1ef9ec8eb31dde1c3938dbee5314170100ba8227 Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Wed, 29 Nov 2023 19:56:21 -0500 Subject: [PATCH 5/9] test: add ETH support; add single test for scenario 4 --- src/test/integration/IntegrationBase.t.sol | 13 ++- ...Deposit_Delegate_Redelegate_Complete.t.sol | 104 +++++++++++++++++- ...Deposit_Delegate_Undelegate_Complete.t.sol | 22 ++-- src/test/integration/tests/utils.t.sol | 17 ++- 4 files changed, 136 insertions(+), 20 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 6bd3b389b..c08c044e4 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -413,8 +413,12 @@ abstract contract IntegrationBase is IntegrationDeployer { uint[] memory prevShares = _getPrevStrategyShares(strategies); for (uint i = 0; i < strategies.length; i++) { - uint prevShare = prevShares[i]; - uint curShare = curShares[i]; + // Ignore BeaconChainETH strategy since it doesn't keep track of global strategy shares + if (strategies[i] == BEACONCHAIN_ETH_STRAT) { + continue; + } + uint256 prevShare = prevShares[i]; + uint256 curShare = curShares[i]; assertEq(prevShare - removedShares[i], curShare, err); } @@ -599,7 +603,10 @@ abstract contract IntegrationBase is IntegrationDeployer { uint[] memory shares = new uint[](strategies.length); for (uint i = 0; i < strategies.length; i++) { - shares[i] = strategies[i].totalShares(); + 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; diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol index 0b080a65d..28ddeeb26 100644 --- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -6,5 +6,107 @@ import "src/test/integration/User.t.sol"; import "src/test/integration/tests/utils.t.sol"; contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtils { - + /// 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); + assertDepositState(staker, strategies, shares); + + // 2. Delegate to an operator + staker.delegateTo(operator1); + assertDelegationState(staker, operator1, strategies, shares); + + // 3. Undelegate from an operator + IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); + bytes32 withdrawalRoot = staker.undelegate(); + assertUndelegateState(staker, operator1, expectedWithdrawal, withdrawalRoot, strategies, shares); + + // 4. Complete withdrawal as shares + // Fast forward to when we can complete the withdrawal + cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); + staker.completeQueuedWithdrawal(expectedWithdrawal, false); + + assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); + + // 5. Delegate to a new operator + staker.delegateTo(operator2); + assertDelegationState(staker, operator2, strategies, shares); + assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); + + // 6. Queue Withdrawal + IDelegationManager.Withdrawal[] memory withdrawals; + bytes32[] memory withdrawalRoots; + (withdrawals, withdrawalRoots) = staker.queueWithdrawals(strategies, shares); + assertQueuedWithdrawalState(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++) { + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); + assertWithdrawalAsTokensState(staker, withdrawals[i], strategies, shares, tokens, expectedTokens); + } + } + + /// @notice Assumes staker and withdrawer are the same + function _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { + (IStrategy[] memory strategies, uint256[] memory shares) + = delegationManager.getDelegatableShares(address(staker)); + + return IDelegationManager.Withdrawal({ + staker: address(staker), + delegatedTo: delegationManager.delegatedTo(address(staker)), + withdrawer: address(staker), + nonce: delegationManager.cumulativeWithdrawalsQueued(address(staker)), + startBlock: uint32(block.number), + strategies: strategies, + shares: shares + }); + } + + //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 + + function _getExpectedWithdrawalStruct_diffWithdrawer(User staker, address withdrawer) internal view returns (IDelegationManager.Withdrawal memory) { + IDelegationManager.Withdrawal memory withdrawal = _getExpectedWithdrawalStruct(staker); + withdrawal.withdrawer = withdrawer; + return withdrawal; + } } \ 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 index 07fdeb90d..49fb061e6 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -16,7 +16,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // When new Users are created, they will choose a random configuration from these params: _configRand({ _randomSeed: _random, - _assetTypes: HOLDS_LST, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, _userTypes: DEFAULT | ALT_METHODS }); @@ -70,7 +70,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // When new Users are created, they will choose a random configuration from these params: _configRand({ _randomSeed: _random, - _assetTypes: HOLDS_LST, + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, _userTypes: DEFAULT | ALT_METHODS }); @@ -107,10 +107,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - - uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, false); - + staker.completeQueuedWithdrawal(expectedWithdrawal, false); assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); } @@ -118,8 +115,8 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // When new Users are created, they will choose a random configuration from these params: _configRand({ _randomSeed: _random, - _assetTypes: HOLDS_LST, - _userTypes: DEFAULT | SIGNED_METHODS + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS }); /// 0. Create an operator and a staker with: @@ -167,8 +164,8 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // When new Users are created, they will choose a random configuration from these params: _configRand({ _randomSeed: _random, - _assetTypes: HOLDS_LST, - _userTypes: DEFAULT | SIGNED_METHODS + _assetTypes: HOLDS_LST | HOLDS_ETH | HOLDS_ALL, + _userTypes: DEFAULT | ALT_METHODS }); /// 0. Create an operator and a staker with: @@ -204,10 +201,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - - uint[] memory expectedTokens = _calculateExpectedTokens(expectedWithdrawal.strategies, expectedWithdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, false); - + staker.completeQueuedWithdrawal(expectedWithdrawal, false); assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); } diff --git a/src/test/integration/tests/utils.t.sol b/src/test/integration/tests/utils.t.sol index 5e431989a..a67140b69 100644 --- a/src/test/integration/tests/utils.t.sol +++ b/src/test/integration/tests/utils.t.sol @@ -29,6 +29,19 @@ contract IntegrationTestUtils is IntegrationBase { assert_Snap_AddedOperatorShares(operator, strategies, shares, "operator should have received shares"); } + function assertQueuedWithdrawalState(User staker, User operator, IStrategy[] memory strategies, uint256[] 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's withdrawals should now be pending"); + assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); + assert_Snap_IncreasedQueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); + assert_Snap_RemovedOperatorShares(operator, strategies, shares, "failed to remove operator shares"); + assert_Snap_RemovedStakerShares(staker, strategies, shares, "failed to remove staker shares"); + } + function assertUndelegateState( User staker, User operator, @@ -61,8 +74,8 @@ contract IntegrationTestUtils is IntegrationBase { /// Complete withdrawal(s): // The staker will complete the withdrawal as tokens // - // ... 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 + // ... 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's withdrawal should no longer be pending"); assert_Snap_IncreasedTokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); assert_Snap_DecreasedStrategyShares(strategies, shares, "strategies should have total shares decremented"); From 34d1257967bf0003c19d690cf6d0d0c79bbfe28f Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Fri, 1 Dec 2023 12:50:06 -0500 Subject: [PATCH 6/9] test: address some comments --- src/test/integration/IntegrationBase.t.sol | 91 ++++++++++--------- .../integration/IntegrationDeployer.t.sol | 2 +- src/test/integration/User.t.sol | 26 +++++- .../Deposit_Delegate_Queue_Complete.t.sol | 29 ++---- ...Deposit_Delegate_Redelegate_Complete.t.sol | 33 +------ ...Deposit_Delegate_Undelegate_Complete.t.sol | 68 +++++--------- src/test/integration/tests/utils.t.sol | 52 ++++++----- 7 files changed, 134 insertions(+), 167 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index c08c044e4..78d9264ec 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -280,19 +280,46 @@ abstract contract IntegrationBase is IntegrationDeployer { } } - /// Snapshot assertions for underlying token balances: + function assert_Snap_Removed_StrategyShares( + IStrategy[] memory strategies, + uint[] memory removedShares, + string memory err + ) internal { + uint[] memory curShares = _getStrategyShares(strategies); - function assert_Snap_IncrementQueuedWithdrawals( - User staker, + // Use timewarp to get previous strategy shares + uint[] memory prevShares = _getPrevStrategyShares(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 curQueuedWithdrawals = _getCumulativeWithdrawals(staker); - // Use timewarp to get previous cumulative withdrawals - uint prevQueuedWithdrawals = _getPrevCumulativeWithdrawals(staker); + uint[] memory curShares = _getStrategyShares(strategies); + + // Use timewarp to get previous strategy shares + uint[] memory prevShares = _getPrevStrategyShares(strategies); - assertEq(prevQueuedWithdrawals + 1, curQueuedWithdrawals, err); + 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 // since the last snapshot @@ -367,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 *******************************************************************************/ @@ -402,44 +441,6 @@ abstract contract IntegrationBase is IntegrationDeployer { return (withdrawStrats, withdrawShares); } - function assert_Snap_Removed_StrategyShares( - IStrategy[] memory strategies, - uint[] memory removedShares, - string memory err - ) internal { - uint[] memory curShares = _getStrategyShares(strategies); - - // Use timewarp to get previous strategy shares - uint[] memory prevShares = _getPrevStrategyShares(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; - } - uint256 prevShare = prevShares[i]; - uint256 curShare = curShares[i]; - - assertEq(prevShare - removedShares[i], curShare, err); - } - } - - function assert_Snap_Unchanged_StrategyShares( - IStrategy[] memory strategies, - string memory err - ) internal { - uint[] memory curShares = _getStrategyShares(strategies); - - // Use timewarp to get previous strategy shares - uint[] memory prevShares = _getPrevStrategyShares(strategies); - - for (uint i = 0; i < strategies.length; i++) { - uint prevShare = prevShares[i]; - uint curShare = curShares[i]; - - assertEq(prevShare, curShare, err); - } - } /** * Helpful getters: 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 1391f70db..a6ced2627 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/User.t.sol @@ -124,13 +124,17 @@ contract User is Test { } /// @dev Undelegate from operator - function undelegate() public createSnapshot virtual returns(bytes32){ - return delegationManager.undelegate(address(this)); + function undelegate() public createSnapshot virtual returns(Withdrawal[] memory){ + IDelegationManager.Withdrawal[] memory withdrawal = [_getExpectedWithdrawalStruct()]; + delegationManager.undelegate(address(this)); + return withdrawals; } /// @dev Force undelegate staker - function undelegate(User staker) public createSnapshot virtual returns(bytes32){ + function forceUndelegate(User staker) public createSnapshot virtual returns(Withdrawal[] memory){ + IDelegationManager.Withdrawal[] memory withdrawal = [_getExpectedWithdrawalStruct()]; return delegationManager.undelegate(address(staker)); + return withdrawl; } /// @dev Queues a single withdrawal for every share and strategy pair @@ -225,6 +229,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 _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { + (IStrategy[] memory strategies, uint[] memory shares) + = delegationManager.getDelegatableShares(address(staker)); + + return IDelegationManager.Withdrawal({ + staker: address(staker), + delegatedTo: delegationManager.delegatedTo(address(staker)), + withdrawer: address(staker), + nonce: delegationManager.cumulativeWithdrawalsQueued(address(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 f0bb261a9..b8226028c 100644 --- a/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Queue_Complete.t.sol @@ -79,7 +79,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { withdrawalRoots = _getWithdrawalHashes(withdrawals); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); + assert_AllWithdrawalsPending(withdrawalRoots, "staker 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"); @@ -100,12 +100,11 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { uint[] memory expectedTokens = _calculateExpectedTokens(withdrawal.strategies, withdrawal.shares); IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawal, true); - assert_Snap_IncreasedTokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + 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_DecreasedStrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); - assert_NoWithdrawalsPending(withdrawalRoots, "staker's withdrawals should no longer be pending"); + assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); } } @@ -185,7 +184,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { withdrawalRoots = _getWithdrawalHashes(withdrawals); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); + assert_AllWithdrawalsPending(withdrawalRoots, "staker 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"); @@ -209,6 +208,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { 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"); + assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); } } @@ -298,7 +298,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { withdrawalRoots = _getWithdrawalHashes(withdrawals); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); + assert_AllWithdrawalsPending(withdrawalRoots, "staker 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"); @@ -311,7 +311,6 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { /// 4. Complete withdrawal(s): // The staker will complete each withdrawal as tokens // -<<<<<<< HEAD // ... check that the staker received their tokens and that the staker/operator // have unchanged share amounts for (uint i = 0; i < withdrawals.length; i++) { @@ -320,10 +319,11 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { uint[] memory expectedTokens = _calculateExpectedTokens(withdrawal.strategies, withdrawal.shares); IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawal, true); - assert_Snap_IncreasedTokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + 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(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); } } @@ -407,7 +407,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { withdrawalRoots = _getWithdrawalHashes(withdrawals); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); + assert_AllWithdrawalsPending(withdrawalRoots, "staker 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"); @@ -422,25 +422,16 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { // // ... check that the staker received their tokens and that the staker/operator // have unchanged share amounts -======= - // ... 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 ->>>>>>> 6ed48d00 (test: add scenario 2 tests) for (uint i = 0; i < withdrawals.length; i++) { IDelegationManager.Withdrawal memory withdrawal = withdrawals[i]; staker.completeQueuedWithdrawal(withdrawal, false); -<<<<<<< HEAD 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"); -======= - assert_NoWithdrawalsPending(withdrawalRoots, "staker's withdrawals should no longer be pending"); - assert_Snap_AddedStakerShares(staker, withdrawal.strategies, withdrawal.shares, "staker should have received expected tokens"); - assert_Snap_UnchangedStrategyShares(withdrawal.strategies, "strategies should have total shares unchanged"); ->>>>>>> 6ed48d00 (test: add scenario 2 tests) + assert_Snap_Unchanged_StrategyShares(withdrawal.strategies, "strategies should have total shares unchanged"); } } diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol index 28ddeeb26..641c42bec 100644 --- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -49,9 +49,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil assertDelegationState(staker, operator1, strategies, shares); // 3. Undelegate from an operator - IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - bytes32 withdrawalRoot = staker.undelegate(); - assertUndelegateState(staker, operator1, expectedWithdrawal, withdrawalRoot, strategies, shares); + IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); // 4. Complete withdrawal as shares // Fast forward to when we can complete the withdrawal @@ -66,9 +66,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil assertNotEq(address(operator1), delegationManager.delegatedTo(address(staker)), "staker should not be delegated to operator1"); // 6. Queue Withdrawal - IDelegationManager.Withdrawal[] memory withdrawals; - bytes32[] memory withdrawalRoots; - (withdrawals, withdrawalRoots) = staker.queueWithdrawals(strategies, shares); + withdrawals = staker.queueWithdrawals(strategies, shares); + withdrawalRoots = _getWithdrawalHashes(withdrawals); assertQueuedWithdrawalState(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); // 7. Complete withdrawal @@ -83,30 +82,8 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil } } - /// @notice Assumes staker and withdrawer are the same - function _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { - (IStrategy[] memory strategies, uint256[] memory shares) - = delegationManager.getDelegatableShares(address(staker)); - - return IDelegationManager.Withdrawal({ - staker: address(staker), - delegatedTo: delegationManager.delegatedTo(address(staker)), - withdrawer: address(staker), - nonce: delegationManager.cumulativeWithdrawalsQueued(address(staker)), - startBlock: uint32(block.number), - strategies: strategies, - shares: shares - }); - } - //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 - - function _getExpectedWithdrawalStruct_diffWithdrawer(User staker, address withdrawer) internal view returns (IDelegationManager.Withdrawal memory) { - IDelegationManager.Withdrawal memory withdrawal = _getExpectedWithdrawalStruct(staker); - withdrawal.withdrawer = withdrawer; - return withdrawal; - } } \ 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 index 49fb061e6..1baa9634b 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -46,19 +46,19 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil assertDelegationState(staker, operator, strategies, shares); // 3. Undelegate from an operator - IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - bytes32 withdrawalRoot = staker.undelegate(); - assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, 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(expectedWithdrawal.strategies, expectedWithdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, true); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals.strategies, withdrawals.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals, true); - assertWithdrawalAsTokensState(staker, expectedWithdrawal, strategies, shares, tokens, expectedTokens); + assertWithdrawalAsTokensState(staker, withdrawals, strategies, shares, tokens, expectedTokens); } /// Randomly generates a user with different held assets. Then: @@ -100,15 +100,15 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil assertDelegationState(staker, operator, strategies, shares); // 3. Undelegate from an operator - IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - bytes32 withdrawalRoot = staker.undelegate(); - assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - staker.completeQueuedWithdrawal(expectedWithdrawal, false); - assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); + staker.completeQueuedWithdrawal(withdrawals, false); + assertWithdrawalAsSharesState(staker, withdrawals, strategies, shares); } function testFuzz_deposit_delegate_forceUndelegate_completeAsTokens(uint24 _random) public { @@ -145,19 +145,19 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil assertDelegationState(staker, operator, strategies, shares); // 3. Force undelegate - IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - bytes32 withdrawalRoot = operator.undelegate(staker); - assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, 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(expectedWithdrawal.strategies, expectedWithdrawal.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(expectedWithdrawal, true); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals.strategies, withdrawals.shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals, true); - assertWithdrawalAsTokensState(staker, expectedWithdrawal, strategies, shares, tokens, expectedTokens); + assertWithdrawalAsTokensState(staker, withdrawals, strategies, shares, tokens, expectedTokens); } function testFuzz_deposit_delegate_forceUndelegate_completeAsShares(uint24 _random) public { @@ -194,38 +194,14 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil assertDelegationState(staker, operator, strategies, shares); // 3. Force undelegate - IDelegationManager.Withdrawal memory expectedWithdrawal = _getExpectedWithdrawalStruct(staker); - bytes32 withdrawalRoot = operator.undelegate(staker); - assertUndelegateState(staker, operator, expectedWithdrawal, withdrawalRoot, strategies, shares); + IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker); + bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); + assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - staker.completeQueuedWithdrawal(expectedWithdrawal, false); - assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); - } - - - - /// @notice Assumes staker and withdrawer are the same - function _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { - (IStrategy[] memory strategies, uint[] memory shares) - = delegationManager.getDelegatableShares(address(staker)); - - return IDelegationManager.Withdrawal({ - staker: address(staker), - delegatedTo: delegationManager.delegatedTo(address(staker)), - withdrawer: address(staker), - nonce: delegationManager.cumulativeWithdrawalsQueued(address(staker)), - startBlock: uint32(block.number), - strategies: strategies, - shares: shares - }); - } - - function _getExpectedWithdrawalStruct_diffWithdrawer(User staker, address withdrawer) internal view returns (IDelegationManager.Withdrawal memory) { - IDelegationManager.Withdrawal memory withdrawal = _getExpectedWithdrawalStruct(staker); - withdrawal.withdrawer = withdrawer; - return withdrawal; + staker.completeQueuedWithdrawal(withdrawals, false); + assertWithdrawalAsSharesState(staker, withdrawals, strategies, shares); } } \ No newline at end of file diff --git a/src/test/integration/tests/utils.t.sol b/src/test/integration/tests/utils.t.sol index a67140b69..25266a1e6 100644 --- a/src/test/integration/tests/utils.t.sol +++ b/src/test/integration/tests/utils.t.sol @@ -7,7 +7,7 @@ import "src/test/integration/User.t.sol"; /// @notice Contract that provides utility functions to reuse common test blocks & checks contract IntegrationTestUtils is IntegrationBase { - function assertDepositState(User staker, IStrategy[] memory strategies, uint256[] memory shares) internal { + function assertDepositState(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. @@ -15,10 +15,10 @@ contract IntegrationTestUtils is IntegrationBase { // ... 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_AddedStakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); + assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); } - function assertDelegationState(User staker, User operator, IStrategy[] memory strategies, uint256[] memory shares) internal { + function assertDelegationState(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 @@ -26,10 +26,10 @@ contract IntegrationTestUtils is IntegrationBase { 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_AddedOperatorShares(operator, strategies, shares, "operator should have received shares"); + assert_Snap_Added_OperatorShares(operator, strategies, shares, "operator should have received shares"); } - function assertQueuedWithdrawalState(User staker, User operator, IStrategy[] memory strategies, uint256[] memory shares, IDelegationManager.Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots) internal { + function assertQueuedWithdrawalState(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 @@ -37,63 +37,65 @@ contract IntegrationTestUtils is IntegrationBase { // reduced shares. assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawals should now be pending"); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawals should match returned roots"); - assert_Snap_IncreasedQueuedWithdrawals(staker, withdrawals, "staker should have increased nonce by withdrawals.length"); - assert_Snap_RemovedOperatorShares(operator, strategies, shares, "failed to remove operator shares"); - assert_Snap_RemovedStakerShares(staker, strategies, shares, "failed to remove staker shares"); + 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 assertUndelegateState( User staker, User operator, - IDelegationManager.Withdrawal memory withdrawal, - bytes32 withdrawalRoot, + IDelegationManager.Withdrawal[] memory withdrawals, + bytes32[] memory withdrawalRoots, IStrategy[] memory strategies, - uint256[] memory shares + 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"); + assertEq(withdrawals.length, withdrawalRoots.length, "withdrawals and roots should have same length"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); - assert_ValidWithdrawalHash(withdrawal, withdrawalRoot, "calculated withdrawl should match returned root"); - assert_withdrawalPending(withdrawalRoot, "staker's withdrawal should now be pending"); - assert_Snap_IncrementQueuedWithdrawals(staker, "staker should have increased nonce by 1"); - assert_Snap_RemovedOperatorShares(operator, strategies, shares, "failed to remove operator shares"); - assert_Snap_RemovedStakerShares(staker, strategies, shares, "failed to remove staker shares"); + assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawl should match returned root"); + assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawal should now be pending"); + assert_Snap_Added_QueuedWithdrawal(staker, withdrawal, "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 assertWithdrawalAsTokensState( User staker, IDelegationManager.Withdrawal memory withdrawal, IStrategy[] memory strategies, - uint256[] memory shares, + uint[] memory shares, IERC20[] memory tokens, - uint256[] memory expectedTokens + 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's withdrawal should no longer be pending"); - assert_Snap_IncreasedTokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); - assert_Snap_DecreasedStrategyShares(strategies, shares, "strategies should have total shares decremented"); + assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker's withdrawal should no longer be pending"); + assert_Snap_Added_TokenBalances(staker, tokens, expectedTokens, "staker should have received expected tokens"); + assert_Snap_Removed_StrategyShares(strategies, shares, "strategies should have total shares decremented"); } function assertWithdrawalAsSharesState( User staker, IDelegationManager.Withdrawal memory withdrawal, IStrategy[] memory strategies, - uint256[] memory shares + 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's withdrawal should no longer be pending"); - assert_Snap_AddedStakerShares(staker, strategies, shares, "staker should have received expected shares"); - assert_Snap_UnchangedStrategyShares(strategies, "strategies should have total shares unchanged"); + assert_WithdrawalNotPending(delegationManager.calculateWithdrawalRoot(withdrawal), "staker's withdrawal should no longer be pending"); + assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should have received expected shares"); + assert_Snap_Unchanged_StrategyShares(strategies, "strategies should have total shares unchanged"); } } \ No newline at end of file From e3a437af9b04063ea3194f1141beab4d95a54bf0 Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Fri, 1 Dec 2023 15:41:52 -0500 Subject: [PATCH 7/9] test: address additional comments; all tests passing --- src/test/integration/User.t.sol | 28 +- .../Deposit_Delegate_Queue_Complete.t.sol | 337 ++++-------------- ...Deposit_Delegate_Redelegate_Complete.t.sol | 19 +- ...Deposit_Delegate_Undelegate_Complete.t.sol | 66 ++-- src/test/integration/tests/utils.t.sol | 55 ++- 5 files changed, 178 insertions(+), 327 deletions(-) diff --git a/src/test/integration/User.t.sol b/src/test/integration/User.t.sol index a6ced2627..3652b8b0d 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/User.t.sol @@ -124,17 +124,19 @@ contract User is Test { } /// @dev Undelegate from operator - function undelegate() public createSnapshot virtual returns(Withdrawal[] memory){ - IDelegationManager.Withdrawal[] memory withdrawal = [_getExpectedWithdrawalStruct()]; + 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 withdrawals; + return withdrawal; } /// @dev Force undelegate staker - function forceUndelegate(User staker) public createSnapshot virtual returns(Withdrawal[] memory){ - IDelegationManager.Withdrawal[] memory withdrawal = [_getExpectedWithdrawalStruct()]; - return delegationManager.undelegate(address(staker)); - return withdrawl; + 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 @@ -231,15 +233,15 @@ contract User is Test { } /// @notice Assumes staker and withdrawer are the same and that all strategies and shares are withdrawn - function _getExpectedWithdrawalStruct(User staker) internal view returns (IDelegationManager.Withdrawal memory) { + function _getExpectedWithdrawalStructForStaker(address staker) internal view returns (IDelegationManager.Withdrawal memory) { (IStrategy[] memory strategies, uint[] memory shares) - = delegationManager.getDelegatableShares(address(staker)); + = delegationManager.getDelegatableShares(staker); return IDelegationManager.Withdrawal({ - staker: address(staker), - delegatedTo: delegationManager.delegatedTo(address(staker)), - withdrawer: address(staker), - nonce: delegationManager.cumulativeWithdrawalsQueued(address(staker)), + staker: staker, + delegatedTo: delegationManager.delegatedTo(staker), + withdrawer: staker, + nonce: delegationManager.cumulativeWithdrawalsQueued(staker), startBlock: uint32(block.number), strategies: strategies, shares: shares 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 b8226028c..a5a2df8d0 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/tests/utils.t.sol"; import "src/test/integration/User.t.sol"; -contract Integration_Deposit_Delegate_Queue_Complete is IntegrationBase { +contract Integration_Deposit_Delegate_Queue_Complete is IntegrationTestUtils { /******************************************************************************* FULL WITHDRAWALS @@ -39,73 +39,27 @@ contract Integration_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 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 withdrawal is not pending, that the staker received the expected tokens, and that the total shares of each - // strategy withdrawn from decreased - 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"); - assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); - } + for (uint i = 0; i < withdrawals.length; i++) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens); } // Check final state: @@ -144,72 +98,26 @@ contract Integration_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 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"); - assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); - } + for (uint i = 0; i < withdrawals.length; i++) { + staker.completeQueuedWithdrawal(withdrawals[i], false); + check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, shares); } // Check final state: @@ -252,79 +160,32 @@ contract Integration_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 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"); - assert_Snap_Removed_StrategyShares(withdrawal.strategies, withdrawal.shares, "strategies should have total shares decremented"); - } + for (uint i = 0; i < withdrawals.length; i++) { + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares, tokens, expectedTokens); } // Check final state: @@ -338,7 +199,7 @@ contract Integration_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, @@ -361,78 +222,32 @@ contract Integration_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 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"); - assert_Snap_Unchanged_StrategyShares(withdrawal.strategies, "strategies should have total shares unchanged"); - } + for (uint i = 0; i < withdrawals.length; i++) { + staker.completeQueuedWithdrawal(withdrawals[i], false); + check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares); } // Check final state: @@ -471,27 +286,17 @@ contract Integration_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 index 641c42bec..db0f5d064 100644 --- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -42,33 +42,32 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - assertDepositState(staker, strategies, shares); + check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator staker.delegateTo(operator1); - assertDelegationState(staker, operator1, strategies, shares); + check_Delegation_State(staker, operator1, strategies, shares); // 3. Undelegate from an operator IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); + 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.completeQueuedWithdrawal(expectedWithdrawal, false); - - assertWithdrawalAsSharesState(staker, expectedWithdrawal, strategies, shares); + staker.completeQueuedWithdrawal(withdrawals[0], false); + check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[0], strategies, shares); // 5. Delegate to a new operator staker.delegateTo(operator2); - assertDelegationState(staker, operator2, strategies, shares); + 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); - assertQueuedWithdrawalState(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); + check_QueuedWithdrawal_State(staker, operator2, strategies, shares, withdrawals, withdrawalRoots); // 7. Complete withdrawal // Fast forward to when we can complete the withdrawal @@ -76,9 +75,9 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil // Complete withdrawals for (uint i = 0; i < withdrawals.length; i++) { - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); - assertWithdrawalAsTokensState(staker, withdrawals[i], strategies, shares, tokens, expectedTokens); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + check_Withdrawal_AsTokens_State(staker, operator2, withdrawals[i], strategies, shares, tokens, expectedTokens); } } diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol index 1baa9634b..84584fc77 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -1,7 +1,6 @@ // 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"; import "src/test/integration/tests/utils.t.sol"; @@ -39,26 +38,30 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - assertDepositState(staker, strategies, shares); + check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator staker.delegateTo(operator); - assertDelegationState(staker, operator, strategies, shares); + check_Delegation_State(staker, operator, strategies, shares); // 3. Undelegate from an operator IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); + 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.strategies, withdrawals.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals, true); - - assertWithdrawalAsTokensState(staker, withdrawals, strategies, shares, tokens, expectedTokens); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[0].strategies, withdrawals[0].shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[0], true); + 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: @@ -93,22 +96,27 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - assertDepositState(staker, strategies, shares); + check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator staker.delegateTo(operator); - assertDelegationState(staker, operator, strategies, shares); + check_Delegation_State(staker, operator, strategies, shares); // 3. Undelegate from an operator IDelegationManager.Withdrawal[] memory withdrawals = staker.undelegate(); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); + 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.completeQueuedWithdrawal(withdrawals, false); - assertWithdrawalAsSharesState(staker, withdrawals, strategies, shares); + staker.completeQueuedWithdrawal(withdrawals[0], false); + 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 { @@ -138,26 +146,29 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - assertDepositState(staker, strategies, shares); + check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator staker.delegateTo(operator); - assertDelegationState(staker, operator, strategies, shares); + check_Delegation_State(staker, operator, strategies, shares); // 3. Force undelegate IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); + 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.strategies, withdrawals.shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals, true); + uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[0].strategies, withdrawals[0].shares); + IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[0], true); + check_Withdrawal_AsTokens_State(staker, operator, withdrawals[0], strategies, shares, tokens, expectedTokens); - assertWithdrawalAsTokensState(staker, withdrawals, 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 { @@ -187,21 +198,26 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil /// 1. Deposit Into Strategies staker.depositIntoEigenlayer(strategies, tokenBalances); - assertDepositState(staker, strategies, shares); + check_Deposit_State(staker, strategies, shares); // 2. Delegate to an operator staker.delegateTo(operator); - assertDelegationState(staker, operator, strategies, shares); + check_Delegation_State(staker, operator, strategies, shares); // 3. Force undelegate IDelegationManager.Withdrawal[] memory withdrawals = operator.forceUndelegate(staker); bytes32[] memory withdrawalRoots = _getWithdrawalHashes(withdrawals); - assertUndelegateState(staker, operator, withdrawals, withdrawalRoot, strategies, shares); + 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.completeQueuedWithdrawal(withdrawals, false); - assertWithdrawalAsSharesState(staker, withdrawals, strategies, shares); + staker.completeQueuedWithdrawal(withdrawals[0], false); + 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 diff --git a/src/test/integration/tests/utils.t.sol b/src/test/integration/tests/utils.t.sol index 25266a1e6..94871ace4 100644 --- a/src/test/integration/tests/utils.t.sol +++ b/src/test/integration/tests/utils.t.sol @@ -7,7 +7,7 @@ import "src/test/integration/User.t.sol"; /// @notice Contract that provides utility functions to reuse common test blocks & checks contract IntegrationTestUtils is IntegrationBase { - function assertDepositState(User staker, IStrategy[] memory strategies, uint[] memory shares) internal { + 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. @@ -18,31 +18,31 @@ contract IntegrationTestUtils is IntegrationBase { assert_Snap_Added_StakerShares(staker, strategies, shares, "staker should expected shares in each strategy after depositing"); } - function assertDelegationState(User staker, User operator, IStrategy[] memory strategies, uint[] memory shares) internal { + 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's shares + // 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 assertQueuedWithdrawalState(User staker, User operator, IStrategy[] memory strategies, uint[] memory shares, IDelegationManager.Withdrawal[] memory withdrawals, bytes32[] memory withdrawalRoots) internal { + 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's withdrawals should now be pending"); + 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 assertUndelegateState( + function check_Undelegate_State( User staker, User operator, IDelegationManager.Withdrawal[] memory withdrawals, @@ -56,17 +56,17 @@ contract IntegrationTestUtils is IntegrationBase { // 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"); - assertEq(withdrawals.length, withdrawalRoots.length, "withdrawals and roots should have same length"); assertFalse(delegationManager.isDelegated(address(staker)), "staker should not be delegated"); assert_ValidWithdrawalHashes(withdrawals, withdrawalRoots, "calculated withdrawl should match returned root"); - assert_AllWithdrawalsPending(withdrawalRoots, "staker's withdrawal should now be pending"); - assert_Snap_Added_QueuedWithdrawal(staker, withdrawal, "staker should have increased nonce by 1"); + 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 assertWithdrawalAsTokensState( + function check_Withdrawal_AsTokens_State( User staker, + User operator, IDelegationManager.Withdrawal memory withdrawal, IStrategy[] memory strategies, uint[] memory shares, @@ -78,13 +78,17 @@ contract IntegrationTestUtils is IntegrationBase { // // ... 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's withdrawal should no longer be pending"); + 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 assertWithdrawalAsSharesState( + function check_Withdrawal_AsShares_State( User staker, + User operator, IDelegationManager.Withdrawal memory withdrawal, IStrategy[] memory strategies, uint[] memory shares @@ -94,8 +98,33 @@ contract IntegrationTestUtils is IntegrationBase { // // ... 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's withdrawal should no longer be pending"); + 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 From 06f635c85de60e349de1107fbdbc8909b0612aeb Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Fri, 1 Dec 2023 15:50:17 -0500 Subject: [PATCH 8/9] test: add separate withdrawal as tokens and shares methods --- .../{tests/utils.t.sol => IntegrationChecks.t.sol} | 2 +- src/test/integration/User.t.sol | 12 ++++++++++-- .../tests/Deposit_Delegate_Queue_Complete.t.sol | 12 ++++++------ .../tests/Deposit_Delegate_Redelegate_Complete.t.sol | 9 ++++----- .../tests/Deposit_Delegate_Undelegate_Complete.t.sol | 12 ++++++------ 5 files changed, 27 insertions(+), 20 deletions(-) rename src/test/integration/{tests/utils.t.sol => IntegrationChecks.t.sol} (99%) diff --git a/src/test/integration/tests/utils.t.sol b/src/test/integration/IntegrationChecks.t.sol similarity index 99% rename from src/test/integration/tests/utils.t.sol rename to src/test/integration/IntegrationChecks.t.sol index 94871ace4..edb6548e2 100644 --- a/src/test/integration/tests/utils.t.sol +++ b/src/test/integration/IntegrationChecks.t.sol @@ -5,7 +5,7 @@ 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 IntegrationTestUtils is IntegrationBase { +contract IntegrationCheckUtils is IntegrationBase { function check_Deposit_State(User staker, IStrategy[] memory strategies, uint[] memory shares) internal { /// Deposit into strategies: diff --git a/src/test/integration/User.t.sol b/src/test/integration/User.t.sol index 3652b8b0d..50dae0e5a 100644 --- a/src/test/integration/User.t.sol +++ b/src/test/integration/User.t.sol @@ -176,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++) { 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 a5a2df8d0..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/tests/utils.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; import "src/test/integration/User.t.sol"; -contract Integration_Deposit_Delegate_Queue_Complete is IntegrationTestUtils { +contract Integration_Deposit_Delegate_Queue_Complete is IntegrationCheckUtils { /******************************************************************************* FULL WITHDRAWALS @@ -58,7 +58,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationTestUtils { for (uint i = 0; i < withdrawals.length; i++) { uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], strategies, shares, tokens, expectedTokens); } @@ -116,7 +116,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationTestUtils { cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); for (uint i = 0; i < withdrawals.length; i++) { - staker.completeQueuedWithdrawal(withdrawals[i], false); + staker.completeWithdrawalAsShares(withdrawals[i]); check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], strategies, shares); } @@ -184,7 +184,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationTestUtils { cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); for (uint i = 0; i < withdrawals.length; i++) { uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); check_Withdrawal_AsTokens_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares, tokens, expectedTokens); } @@ -246,7 +246,7 @@ contract Integration_Deposit_Delegate_Queue_Complete is IntegrationTestUtils { cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); for (uint i = 0; i < withdrawals.length; i++) { - staker.completeQueuedWithdrawal(withdrawals[i], false); + staker.completeWithdrawalAsShares(withdrawals[i]); check_Withdrawal_AsShares_State(staker, operator, withdrawals[i], withdrawStrats, withdrawShares); } diff --git a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol index db0f5d064..eddbd5a04 100644 --- a/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Redelegate_Complete.t.sol @@ -1,11 +1,10 @@ // 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"; -import "src/test/integration/tests/utils.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; -contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtils { +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 @@ -56,7 +55,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil // 4. Complete withdrawal as shares // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - staker.completeQueuedWithdrawal(withdrawals[0], false); + staker.completeWithdrawalAsShares(withdrawals[0]); check_Withdrawal_AsShares_Undelegated_State(staker, operator1, withdrawals[0], strategies, shares); // 5. Delegate to a new operator @@ -76,7 +75,7 @@ contract Integration_Deposit_Delegate_Redelegate_Complete is IntegrationTestUtil // Complete withdrawals for (uint i = 0; i < withdrawals.length; i++) { uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[i].strategies, withdrawals[i].shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[i], true); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[i]); check_Withdrawal_AsTokens_State(staker, operator2, withdrawals[i], strategies, shares, tokens, expectedTokens); } } diff --git a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol index 84584fc77..1d25902b4 100644 --- a/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol +++ b/src/test/integration/tests/Deposit_Delegate_Undelegate_Complete.t.sol @@ -2,9 +2,9 @@ pragma solidity =0.8.12; import "src/test/integration/User.t.sol"; -import "src/test/integration/tests/utils.t.sol"; +import "src/test/integration/IntegrationChecks.t.sol"; -contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtils { +contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationCheckUtils { /// Randomly generates a user with different held assets. Then: /// 1. deposit into strategy @@ -55,7 +55,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // Complete withdrawal uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[0].strategies, withdrawals[0].shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[0], true); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[0]); check_Withdrawal_AsTokens_State(staker, operator, withdrawals[0], strategies, shares, tokens, expectedTokens); // Check Final State @@ -110,7 +110,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - staker.completeQueuedWithdrawal(withdrawals[0], false); + staker.completeWithdrawalAsShares(withdrawals[0]); check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[0], strategies, shares); // Check final state: @@ -162,7 +162,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); uint[] memory expectedTokens = _calculateExpectedTokens(withdrawals[0].strategies, withdrawals[0].shares); - IERC20[] memory tokens = staker.completeQueuedWithdrawal(withdrawals[0], true); + IERC20[] memory tokens = staker.completeWithdrawalAsTokens(withdrawals[0]); check_Withdrawal_AsTokens_State(staker, operator, withdrawals[0], strategies, shares, tokens, expectedTokens); // Check Final State @@ -212,7 +212,7 @@ contract Integration_Deposit_Delegate_Undelegate_Complete is IntegrationTestUtil // 4. Complete withdrawal // Fast forward to when we can complete the withdrawal cheats.roll(block.number + delegationManager.withdrawalDelayBlocks()); - staker.completeQueuedWithdrawal(withdrawals[0], false); + staker.completeWithdrawalAsShares(withdrawals[0]); check_Withdrawal_AsShares_Undelegated_State(staker, operator, withdrawals[0], strategies, shares); // Check final state: From 396c9bb94fb7a37eb3605aff6a37a1e1067f2b39 Mon Sep 17 00:00:00 2001 From: Yash Patil Date: Fri, 1 Dec 2023 15:51:38 -0500 Subject: [PATCH 9/9] fix: total strategy shares method naming --- src/test/integration/IntegrationBase.t.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/integration/IntegrationBase.t.sol b/src/test/integration/IntegrationBase.t.sol index 78d9264ec..75f0ddfc0 100644 --- a/src/test/integration/IntegrationBase.t.sol +++ b/src/test/integration/IntegrationBase.t.sol @@ -285,10 +285,10 @@ abstract contract IntegrationBase is IntegrationDeployer { uint[] memory removedShares, string memory err ) internal { - uint[] memory curShares = _getStrategyShares(strategies); + uint[] memory curShares = _getTotalStrategyShares(strategies); // Use timewarp to get previous strategy shares - uint[] memory prevShares = _getPrevStrategyShares(strategies); + 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 @@ -306,10 +306,10 @@ abstract contract IntegrationBase is IntegrationDeployer { IStrategy[] memory strategies, string memory err ) internal { - uint[] memory curShares = _getStrategyShares(strategies); + uint[] memory curShares = _getTotalStrategyShares(strategies); // Use timewarp to get previous strategy shares - uint[] memory prevShares = _getPrevStrategyShares(strategies); + uint[] memory prevShares = _getPrevTotalStrategyShares(strategies); for (uint i = 0; i < strategies.length; i++) { uint prevShare = prevShares[i]; @@ -596,11 +596,11 @@ abstract contract IntegrationBase is IntegrationDeployer { return balances; } - function _getPrevStrategyShares(IStrategy[] memory strategies) internal timewarp() returns (uint[] memory) { - return _getStrategyShares(strategies); + function _getPrevTotalStrategyShares(IStrategy[] memory strategies) internal timewarp() returns (uint[] memory) { + return _getTotalStrategyShares(strategies); } - function _getStrategyShares(IStrategy[] memory strategies) internal view returns (uint[] memory) { + 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++) {