From c5f72cca85d291849c0a91477007d0a595fa0479 Mon Sep 17 00:00:00 2001 From: Aleksandr Tarelkin Date: Wed, 14 Feb 2024 14:42:52 +0300 Subject: [PATCH] claim user requests separately --- contracts/Escrow.sol | 63 +++++++++++++++++++++++++------ test/scenario/escrow.t.sol | 77 +++++++++++++++++++++++++++----------- test/utils/interfaces.sol | 3 ++ 3 files changed, 111 insertions(+), 32 deletions(-) diff --git a/contracts/Escrow.sol b/contracts/Escrow.sol index c48ca5dc..344a05c4 100644 --- a/contracts/Escrow.sol +++ b/contracts/Escrow.sol @@ -78,6 +78,8 @@ contract Escrow { error FinalizedRequest(uint256); error RequestNotFound(uint256 id); error SenderIsNotAllowed(); + error RequestIsNotFromBatch(uint256 id); + error RequestFromBatch(uint256 id); event RageQuitAccumulationStarted(); event RageQuitStarted(); @@ -95,6 +97,15 @@ contract Escrow { uint256 wstEthInEthShares; uint256 wqRequestsBalance; uint256 finalizedWqRequestsBalance; + // uint256[] wqRequestIds; + uint256 eth; + } + + struct WithdrawalRequest { + uint256 stEthInEthShares; + uint256 wstEthInEthShares; + uint256 wqRequestsBalance; + uint256 finalizedWqRequestsBalance; } struct Balance { @@ -102,6 +113,8 @@ contract Escrow { uint256 wstEth; uint256 wqRequestsBalance; uint256 finalizedWqRequestsBalance; + // WithdrawalReqsuest[] wqRequests; + uint256 eth; } Configuration internal immutable CONFIG; @@ -117,6 +130,7 @@ contract Escrow { uint256 internal _totalWstEthInEthLocked; uint256 internal _totalWithdrawalNftsAmountLocked; uint256 internal _totalFinalizedWithdrawalNftsAmountLocked; + uint256 internal _totalClaimedEthLocked; uint256 internal _totalEscrowShares; uint256 internal _claimedWQRequestsAmount; @@ -157,6 +171,7 @@ contract Escrow { balance.wstEth = IStETH(ST_ETH).getSharesByPooledEth(_getETHByShares(state.wstEthInEthShares)); balance.wqRequestsBalance = state.wqRequestsBalance; balance.finalizedWqRequestsBalance = state.finalizedWqRequestsBalance; + balance.eth = state.eth; } function lockStEth(uint256 amount) external { @@ -308,13 +323,17 @@ contract Escrow { address sender = msg.sender; HolderState memory state = _balances[sender]; - uint256 ethToClaim = - (state.stEthInEthShares + state.wqRequestsBalance) * _rageQuitAmountTotal / _totalEscrowShares; + uint256 ethToClaim = _getETHByShares(state.stEthInEthShares); + ethToClaim += _getETHByShares(state.wstEthInEthShares); + ethToClaim += _balances[sender].eth; _balances[sender].stEthInEthShares = 0; _balances[sender].wstEthInEthShares = 0; + _balances[sender].eth = 0; - payable(sender).transfer(ethToClaim); + if (ethToClaim > 0) { + payable(sender).transfer(ethToClaim); + } } /// @@ -381,7 +400,8 @@ contract Escrow { _totalStEthInEthLocked + _totalWstEthInEthLocked + _totalWithdrawalNftsAmountLocked; rageQuitSupport = (totalRageQuitStEthLocked * 10 ** 18) / stEthTotalSupply; - uint256 totalStakedEthLocked = totalRageQuitStEthLocked + _totalFinalizedWithdrawalNftsAmountLocked; + uint256 totalStakedEthLocked = + totalRageQuitStEthLocked + _totalFinalizedWithdrawalNftsAmountLocked + _totalClaimedEthLocked; totalSupport = (totalStakedEthLocked * 10 ** 18) / stEthTotalSupply; } @@ -490,15 +510,36 @@ contract Escrow { for (uint256 i = 0; i < requestIds.length; ++i) { uint256 id = requestIds[i]; address owner = _wqRequests[id].owner; - if (owner == address(this)) { - _claimedWQRequestsAmount += wqRequestStatuses[i].amountOfStETH; - } else { - _balances[owner].finalizedWqRequestsBalance += wqRequestStatuses[i].amountOfStETH; - _balances[owner].wqRequestsBalance -= _wqRequests[id].amountOfStETH; + if (owner != address(this)) { + revert RequestIsNotFromBatch(id); + } + _claimedWQRequestsAmount += wqRequestStatuses[i].amountOfStETH; + } + } - _totalFinalizedWithdrawalNftsAmountLocked += wqRequestStatuses[i].amountOfStETH; - _totalWithdrawalNftsAmountLocked -= _wqRequests[id].amountOfStETH; + function claimWithdrawalRequests(uint256[] calldata requestIds, uint256[] calldata hints) external { + IWithdrawalQueue(WITHDRAWAL_QUEUE).claimWithdrawals(requestIds, hints); + + WithdrawalRequestStatus[] memory wqRequestStatuses = + IWithdrawalQueue(WITHDRAWAL_QUEUE).getWithdrawalStatus(requestIds); + + for (uint256 i = 0; i < requestIds.length; ++i) { + uint256 id = requestIds[i]; + WithdrawalRequestStatus memory request = _wqRequests[id]; + address owner = request.owner; + if (owner == address(this) || owner == address(0)) { + revert RequestFromBatch(id); + } + + if (request.isFinalized) { + _balances[owner].finalizedWqRequestsBalance -= request.amountOfStETH; + _totalFinalizedWithdrawalNftsAmountLocked -= request.amountOfStETH; + } else { + _balances[owner].wqRequestsBalance -= request.amountOfStETH; + _totalWithdrawalNftsAmountLocked -= request.amountOfStETH; } + _balances[owner].eth += wqRequestStatuses[i].amountOfStETH; + _totalClaimedEthLocked += wqRequestStatuses[i].amountOfStETH; } } diff --git a/test/scenario/escrow.t.sol b/test/scenario/escrow.t.sol index 4c7e825c..73ca435e 100644 --- a/test/scenario/escrow.t.sol +++ b/test/scenario/escrow.t.sol @@ -32,16 +32,17 @@ contract TestHelpers is DualGovernanceSetup { ); } - function setLastFinalizedId(uint256 newLastFinalizedId) public { - bytes32 LAST_FINALIZED_REQUEST_ID_POSITION = 0x992f2e0c24ce59a21f2dab8bba13b25c2f872129df7f4d45372155e717db0c48; // keccak256("lido.WithdrawalQueue.lastFinalizedRequestId"); - - uint256 currentLastFinalizedId = uint256(vm.load(WITHDRAWAL_QUEUE, LAST_FINALIZED_REQUEST_ID_POSITION)); - - assertEq(newLastFinalizedId > currentLastFinalizedId, true); - vm.store(WITHDRAWAL_QUEUE, LAST_FINALIZED_REQUEST_ID_POSITION, bytes32(newLastFinalizedId)); + function finalizeWQ() public { + uint256 lastRequestId = IWithdrawalQueue(WITHDRAWAL_QUEUE).getLastRequestId(); + finalizeWQ(lastRequestId); } - function updateLockedEtherAmount() public { + function finalizeWQ(uint256 id) public { + uint256 finalizationShareRate = IStEth(ST_ETH).getPooledEthByShares(1e27) + 1e9; // TODO check finalization rate + address lido = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; + vm.prank(lido); + IWithdrawalQueue(WITHDRAWAL_QUEUE).finalize(id, finalizationShareRate); + bytes32 LOCKED_ETHER_AMOUNT_POSITION = 0x0e27eaa2e71c8572ab988fef0b54cd45bbd1740de1e22343fb6cda7536edc12f; // keccak256("lido.WithdrawalQueue.lockedEtherAmount"); vm.store(WITHDRAWAL_QUEUE, LOCKED_ETHER_AMOUNT_POSITION, bytes32(address(WITHDRAWAL_QUEUE).balance)); @@ -207,15 +208,14 @@ contract EscrowHappyPath is TestHelpers { amounts[i] = 1e18; } - vm.startPrank(stEthHolder1); + vm.prank(stEthHolder1); uint256[] memory ids = IWithdrawalQueue(WITHDRAWAL_QUEUE).requestWithdrawals(amounts, stEthHolder1); - setLastFinalizedId(ids[1]); + finalizeWQ(); + vm.prank(stEthHolder1); vm.expectRevert(); escrow.lockWithdrawalNFT(ids); - - vm.stopPrank(); } function test_check_finalization() public { @@ -233,7 +233,7 @@ contract EscrowHappyPath is TestHelpers { assertEq(balance.wqRequestsBalance, 2 * 1e18); assertEq(balance.finalizedWqRequestsBalance, 0); - setLastFinalizedId(ids[0]); + finalizeWQ(ids[0]); escrow.checkForFinalization(ids); balance = escrow.balanceOf(stEthHolder1); @@ -262,7 +262,7 @@ contract EscrowHappyPath is TestHelpers { assertEq(totalSupport, 4 * 1e18 * 1e18 / totalSupply); assertEq(rageQuitSupport, 4 * 1e18 * 1e18 / totalSupply); - setLastFinalizedId(ids[0]); + finalizeWQ(ids[0]); escrow.checkForFinalization(ids); balance = escrow.balanceOf(stEthHolder1); @@ -305,7 +305,8 @@ contract EscrowHappyPath is TestHelpers { assertEq(IWithdrawalQueue(WITHDRAWAL_QUEUE).balanceOf(address(escrow)), 50); assertEq(escrow.isRageQuitFinalized(), false); - setLastFinalizedId(IWithdrawalQueue(WITHDRAWAL_QUEUE).getLastRequestId()); + vm.deal(WITHDRAWAL_QUEUE, 1000 * requestAmount); + finalizeWQ(); uint256[] memory hints = IWithdrawalQueue(WITHDRAWAL_QUEUE).findCheckpointHints( ids, @@ -313,7 +314,7 @@ contract EscrowHappyPath is TestHelpers { IWithdrawalQueue(WITHDRAWAL_QUEUE).getLastCheckpointIndex() ); - escrow.claimNextETHBatch(ids, hints); + escrow.claimWithdrawalRequests(ids, hints); assertEq(escrow.isRageQuitFinalized(), true); @@ -332,9 +333,6 @@ contract EscrowHappyPath is TestHelpers { IWithdrawalQueue(WITHDRAWAL_QUEUE).getLastCheckpointIndex() ); - vm.deal(WITHDRAWAL_QUEUE, 100 * requestAmount); - updateLockedEtherAmount(); - vm.expectRevert(); vm.prank(stEthHolder1); escrow.claimETH(); @@ -345,6 +343,39 @@ contract EscrowHappyPath is TestHelpers { escrow.claimETH(); } + function test_wq_requests_only_happy_path() public { + uint256 requestAmount = 10 * 1e18; + uint256 requestsCount = 10; + uint256[] memory amounts = new uint256[](requestsCount); + for (uint256 i = 0; i < requestsCount; ++i) { + amounts[i] = requestAmount; + } + + vm.prank(stEthHolder1); + uint256[] memory ids = IWithdrawalQueue(WITHDRAWAL_QUEUE).requestWithdrawals(amounts, stEthHolder1); + + lockAssets(stEthHolder1, 0, 0, ids); + + vm.prank(address(govState)); + escrow.startRageQuit(); + + vm.deal(WITHDRAWAL_QUEUE, 100 * requestAmount); + finalizeWQ(); + + uint256[] memory hints = IWithdrawalQueue(WITHDRAWAL_QUEUE).findCheckpointHints( + ids, + IWithdrawalQueue(WITHDRAWAL_QUEUE).getLastCheckpointIndex() - 2, + IWithdrawalQueue(WITHDRAWAL_QUEUE).getLastCheckpointIndex() + ); + + escrow.claimWithdrawalRequests(ids, hints); + + assertEq(escrow.isRageQuitFinalized(), true); + + vm.prank(stEthHolder1); + escrow.claimETH(); + } + function lockAssets( address owner, uint256 stEthAmountToLock, @@ -382,9 +413,11 @@ contract EscrowHappyPath is TestHelpers { balanceBefore.stEth + stEthAmountToLock, balanceBefore.wstEth + wstEthAmountToLock, balanceBefore.wqRequestsBalance + wqRequestsAmount, - balanceBefore.finalizedWqRequestsBalance + balanceBefore.finalizedWqRequestsBalance, + 0 ) ); + // new uint256[](0) assertApproxEqAbs(IERC20(ST_ETH).balanceOf(owner), stEthBalanceBefore - stEthAmountToLock, 3); assertEq(IERC20(WST_ETH).balanceOf(owner), wstEthBalanceBefore - wstEthAmountToLock); @@ -435,9 +468,11 @@ contract EscrowHappyPath is TestHelpers { unlockStEth ? 0 : balanceBefore.stEth, unlockWstEth ? 0 : balanceBefore.wstEth, balanceBefore.wqRequestsBalance - wqRequestsAmount, - balanceBefore.finalizedWqRequestsBalance + balanceBefore.finalizedWqRequestsBalance, + 0 ) ); + // new uint256[](0) uint256 expectedStEthAmount = uint256(int256(balanceBefore.stEth) * (10000 + rebaseBP) / 10000); uint256 expectedWstEthAmount = uint256(int256(balanceBefore.wstEth) * (10000 + rebaseBP) / 10000); diff --git a/test/utils/interfaces.sol b/test/utils/interfaces.sol index 9958ace3..625c8e3d 100644 --- a/test/utils/interfaces.sol +++ b/test/utils/interfaces.sol @@ -72,4 +72,7 @@ interface IWithdrawalQueue { uint256 _lastIndex ) external view returns (uint256[] memory hintIds); function getLastCheckpointIndex() external view returns (uint256); + function claimWithdrawals(uint256[] calldata requestIds, uint256[] calldata hints) external; + function getLastFinalizedRequestId() external view returns (uint256); + function finalize(uint256 _lastRequestIdToBeFinalized, uint256 _maxShareRate) external payable; }