From 4f63ee40c0aecdbc7963bfbcdd6868f57266b2ea Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Fri, 23 May 2025 16:14:16 +0400 Subject: [PATCH 01/21] Update README.md new logo (#28) * Update README.md new logo * fix: fixed ci versions --- .github/workflows/ci.yml | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f99b901..7985434 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: FORGE_GAS_REPORT: true FOUNDRY_PROFILE: ${{ github.event_name == 'push' && 'ci' || '' }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: gas-report path: gas.txt @@ -94,7 +94,7 @@ jobs: FOUNDRY_PROFILE: ${{ github.event_name == 'push' && 'ci' || '' }} - name: Report code coverage - uses: zgosalvez/github-actions-report-lcov@v1 + uses: zgosalvez/github-actions-report-lcov@v4 with: coverage-files: lcov.info.pruned minimum-coverage: 90 @@ -108,7 +108,7 @@ jobs: runs-on: ubuntu-latest needs: [test] steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: gas-report path: gas.txt diff --git a/README.md b/README.md index 54446f8..6814524 100644 --- a/README.md +++ b/README.md @@ -41,5 +41,5 @@ For all information related to the ongoing bug bounty for these contracts run by ---

- +

From ecccf0a1dd588dbdcbf26cbb8cd9389aab0e0778 Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Sun, 29 Jun 2025 15:41:37 +0400 Subject: [PATCH 02/21] feat: Multiple Requests per Owner (SC-20630) (#30) * feat: update shares, refactor, basic test Signed-off-by: calmacfadden * fix: avoid trailing zero array entries Signed-off-by: calmacfadden * chore: formatting and shortcut return if zero array Signed-off-by: calmacfadden * feat: added some unit tests, batch updateShares and fixes. Signed-off-by: calmacfadden * feat: updated tests Signed-off-by: calmacfadden * chore: removed commented code Signed-off-by: calmacfadden * chore: removed console logs Signed-off-by: calmacfadden * feat: pr review changes Signed-off-by: calmacfadden * fix: pr feedback Signed-off-by: calmacfadden * chore: formatting Signed-off-by: calmacfadden * feat: updated tests, added requestsCountByUser mapping Signed-off-by: calmacfadden * feat: max iteration check Signed-off-by: calmacfadden * chore: formatting Signed-off-by: calmacfadden * fix: removed gas limit test as it broke the gas report Signed-off-by: calmacfadden * Feat: Depreciate request ids mapping (Sc-20631) (#31) * feat: updated tests to depreciate requestIds mapping (WIP) Signed-off-by: calmacfadden * feat: fixed tests Signed-off-by: calmacfadden * fix: fixed returning wrong array elements Signed-off-by: calmacfadden --------- Signed-off-by: calmacfadden * fix: pr review fixes Signed-off-by: calmacfadden * fix: pr review fixes Signed-off-by: calmacfadden * feat: updated tests Signed-off-by: calmacfadden * chore: naming conventions Signed-off-by: calmacfadden * chore: Add TODOs and formatting * chore: Add TODOs and formatting * feat: refactored the view functions Signed-off-by: calmacfadden * feat: renamed storage Signed-off-by: calmacfadden * feat: added tests Signed-off-by: calmacfadden * chore: cleanup * chore: cleanup --------- Signed-off-by: calmacfadden Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- .gitignore | 3 + contracts/MapleWithdrawalManager.sol | 196 ++++-- .../interfaces/IMapleWithdrawalManager.sol | 84 ++- .../IMapleWithdrawalManagerStorage.sol | 17 +- .../proxy/MapleWithdrawalManagerStorage.sol | 3 +- tests/fuzz/AddSharesFuzz.t.sol | 22 +- tests/fuzz/RemoveSharesFuzz.t.sol | 21 +- tests/integration/EndToEndTests.t.sol | 32 +- tests/unit/AddShares.t.sol | 68 +- tests/unit/CreateInstance.t.sol | 4 +- tests/unit/ProcessExit.t.sol | 23 +- tests/unit/ProcessRedemptions.t.sol | 168 ++++- tests/unit/RemoveRequest.t.sol | 55 +- tests/unit/RemoveShares.t.sol | 57 +- tests/unit/SetManualWithdrawal.t.sol | 8 - tests/unit/UpdateShares.t.sol | 600 ++++++++++++++++++ tests/unit/ViewFunctions.t.sol | 150 +++++ tests/utils/Harnesses.sol | 10 +- tests/utils/TestBase.sol | 6 + 19 files changed, 1301 insertions(+), 226 deletions(-) create mode 100644 tests/unit/UpdateShares.t.sol diff --git a/.gitignore b/.gitignore index f712bc8..deeea5d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ docs/ # Dotenv file .env + +# Vscode +.vscode* diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index e693114..5ffb820 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -43,6 +43,7 @@ import { MapleWithdrawalManagerStorage } from "./proxy/MapleWithdrawalManagerSto */ +// TODO: Ordering of functions contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManagerStorage , MapleProxiedInternals { /**************************************************************************************************************************************/ @@ -131,22 +132,10 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /*** State-Changing Functions ***/ /**************************************************************************************************************************************/ - function addShares(uint256 shares_, address owner_) external override onlyPoolManager { - require(shares_ > 0, "WM:AS:ZERO_SHARES"); - require(requestIds[owner_] == 0, "WM:AS:IN_QUEUE"); + function addShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint128 lastRequestId_) { + require(shares_ > 0, "WM:AS:ZERO_SHARES"); - uint128 lastRequestId_ = ++queue.lastRequestId; - - queue.requests[lastRequestId_] = WithdrawalRequest(owner_, shares_); - - requestIds[owner_] = lastRequestId_; - - // Increase the number of shares locked. - totalShares += shares_; - - require(ERC20Helper.transferFrom(pool, msg.sender, address(this), shares_), "WM:AS:FAILED_TRANSFER"); - - emit RequestCreated(lastRequestId_, owner_, shares_); + lastRequestId_ = _addRequest(owner_, shares_); } function processExit( @@ -193,61 +182,129 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function removeShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint256 sharesReturned_) { - uint128 requestId_ = requestIds[owner_]; - require(shares_ > 0, "WM:RS:ZERO_SHARES"); - require(requestId_ > 0, "WM:RS:NOT_IN_QUEUE"); - uint256 currentShares_ = queue.requests[requestId_].shares; + uint128 lastRequestId_ = lastRequestIds[owner_]; - require(shares_ <= currentShares_, "WM:RS:INSUFFICIENT_SHARES"); + require(lastRequestId_ > 0, "WM:RS:NO_REQUESTS"); - uint256 sharesRemaining_ = currentShares_ - shares_; + WithdrawalRequest memory request_ = queue.requests[lastRequestId_]; - totalShares -= shares_; + require(request_.shares >= shares_, "WM:RS:INSUFFICIENT_SHARES"); - // If there are no shares remaining, cancel the withdrawal request. - if (sharesRemaining_ == 0) { - _removeRequest(owner_, requestId_); - } else { - queue.requests[requestId_].shares = sharesRemaining_; + sharesReturned_ = _removeShares(lastRequestId_, shares_, request_.owner, request_.shares); + } - emit RequestDecreased(requestId_, shares_); - } + // TODO: To be more gas efficient accumulate shares to be removed and then remove them all at once from totalShares. + function removeRequest( + address owner_, + uint128[] calldata requestIds_ + ) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins + { + require(owner_ != address(0), "WM:RR:ZERO_OWNER"); + require(requestIds_.length > 0, "WM:RR:ZERO_REQUESTS"); - require(ERC20Helper.transfer(pool, owner_, shares_), "WM:RS:TRANSFER_FAIL"); + WithdrawalRequest memory withdrawalRequest_; - sharesReturned_ = shares_; - } + for (uint256 i = 0; i < requestIds_.length; ++i) { + withdrawalRequest_ = queue.requests[requestIds_[i]]; - function removeRequest(address owner_) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { - uint128 requestId_ = requestIds[owner_]; + require(withdrawalRequest_.shares > 0, "WM:RR:NOT_IN_QUEUE"); + require(withdrawalRequest_.owner == owner_, "WM:RR:NOT_OWNER"); - require(requestId_ > 0, "WM:RR:NOT_IN_QUEUE"); + _removeRequest(owner_, requestIds_[i]); - uint256 shares_ = queue.requests[requestId_].shares; + totalShares -= withdrawalRequest_.shares; - totalShares -= shares_; + require(ERC20Helper.transfer(pool, owner_, withdrawalRequest_.shares), "WM:RR:TRANSFER_FAIL"); + } + } - _removeRequest(owner_, requestId_); + function setManualWithdrawal(address owner_, bool isManual_) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { + isManualWithdrawal[owner_] = isManual_; - require(ERC20Helper.transfer(pool, owner_, shares_), "WM:RR:TRANSFER_FAIL"); + emit ManualWithdrawalSet(owner_, isManual_); } - function setManualWithdrawal(address owner_, bool isManual_) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { - uint128 requestId_ = requestIds[owner_]; + // TODO: Do both updateShares need nonReentrant guards? + function updateShares( + uint128 requestId_, + uint256 newSharesTotal_ + ) public override whenProtocolNotPaused returns (uint128 updatedRequestId_) + { + WithdrawalRequest memory request_ = queue.requests[requestId_]; - require(requestId_ == 0, "WM:SMW:IN_QUEUE"); + require(request_.owner != address(0), "WM:US:INVALID_REQUEST"); + require(request_.owner == msg.sender, "WM:US:NOT_OWNER"); + require(request_.shares != newSharesTotal_, "WM:US:NO_CHANGE"); - isManualWithdrawal[owner_] = isManual_; + uint256 sharesToRemove_ = newSharesTotal_ < request_.shares ? request_.shares - newSharesTotal_ : request_.shares; - emit ManualWithdrawalSet(owner_, isManual_); + _removeShares(requestId_, sharesToRemove_, request_.owner, request_.shares); // Removes shares and will cancel the request if there are no shares remaining. + + if (newSharesTotal_ > request_.shares) + updatedRequestId_ = _addRequest(request_.owner, newSharesTotal_); + else + updatedRequestId_ = newSharesTotal_ == 0 ? 0 : requestId_; + } + + function updateSharesBatch( + uint128[] memory requestIds_, + uint256[] calldata newSharesTotals_ + ) external override whenProtocolNotPaused returns (uint128[] memory updatedRequestIds_) + { + require(requestIds_.length == newSharesTotals_.length, "WM:USB:ARRAY_LENGTH_MISMATCH"); + + for (uint256 i = 0; i < requestIds_.length; ++i) { + requestIds_[i] = updateShares(requestIds_[i], newSharesTotals_[i]); + } + + updatedRequestIds_ = requestIds_; } /**************************************************************************************************************************************/ /*** Internal Functions ***/ /**************************************************************************************************************************************/ + function _addRequest(address owner_, uint256 shares_) internal returns (uint128 lastRequestId_) { + lastRequestId_ = ++queue.lastRequestId; + + queue.requests[lastRequestId_] = WithdrawalRequest(owner_, shares_); + lastRequestIds[owner_] = lastRequestId_; + requestCount[owner_]++; + + // Increase the number of shares locked. + totalShares += shares_; + + require(ERC20Helper.transferFrom(pool, msg.sender, address(this), shares_), "WM:AS:FAILED_TRANSFER"); + + emit RequestCreated(lastRequestId_, owner_, shares_); + } + + function _removeShares(uint128 requestId_, + uint256 sharesToRemove_, + address owner_, + uint256 currentShares_ + ) internal returns (uint256 sharesReturned_) + { + uint256 sharesRemaining_ = currentShares_ - sharesToRemove_; + + totalShares -= sharesToRemove_; + + // If there are no shares remaining, cancel the withdrawal request. + if (sharesRemaining_ == 0) { + _removeRequest(owner_, requestId_); + } else { + queue.requests[requestId_].shares = sharesRemaining_; + + emit RequestDecreased(requestId_, sharesToRemove_); + } + + require(ERC20Helper.transfer(pool, owner_, sharesToRemove_), "WM:RS:TRANSFER_FAIL"); + + sharesReturned_ = sharesToRemove_; + } + function _calculateRedemption(uint256 sharesToRedeem_) internal view returns (uint256 redeemableShares_, uint256 resultingAssets_) { IPoolManagerLike poolManager_ = IPoolManagerLike(poolManager); @@ -314,8 +371,10 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag ( processedShares_, resultingAssets_ ) = _calculateRedemption(sharesToProcess_); + uint256 sharesRemaining_ = request_.shares - processedShares_; + // If there are no remaining shares, request has been fully processed. - isProcessed_ = (request_.shares - processedShares_) == 0; + isProcessed_ = sharesRemaining_ == 0; emit RequestProcessed(requestId_, request_.owner, processedShares_, resultingAssets_); @@ -324,7 +383,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(request_.owner, requestId_); } else { // Update the withdrawal request. - queue.requests[requestId_].shares = request_.shares - processedShares_; + queue.requests[requestId_].shares = sharesRemaining_; emit RequestDecreased(requestId_, processedShares_); } @@ -343,8 +402,9 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function _removeRequest(address owner_, uint128 requestId_) internal { - delete requestIds[owner_]; + delete lastRequestIds[owner_]; delete queue.requests[requestId_]; + requestCount[owner_]--; emit RequestRemoved(requestId_); } @@ -424,6 +484,48 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag shares_ = queue.requests[requestId_].shares; } + function requests(address owner_) + external view override returns ( + uint128[] memory requestIds_, + uint256[] memory shares_ + ) + { + ( requestIds_, shares_ ) = requests(owner_, queue.nextRequestId, queue.lastRequestId); + } + + function requests(address owner_, uint128 firstRequestId_, uint128 lastRequestId_) + public view override returns ( + uint128[] memory requestIds_, + uint256[] memory shares_ + ) + { + WithdrawalRequest memory request_; + uint256 count_ = requestCount[owner_]; + uint256 index_; + uint256 requestsAdded_; + + requestIds_ = new uint128[](count_); + shares_ = new uint256[](count_); + + while (firstRequestId_ <= lastRequestId_) { + + if(requestsAdded_ == count_) { + break; + } + + request_ = queue.requests[firstRequestId_]; + + if (request_.owner == owner_) { + requestIds_[index_] = firstRequestId_; + shares_[index_] = request_.shares; + ++index_; + ++requestsAdded_; + } + + ++firstRequestId_; + } + } + function securityAdmin() public view override returns (address securityAdmin_) { securityAdmin_ = IGlobalsLike(globals()).securityAdmin(); } diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index 261ebab..6bca5c9 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -5,6 +5,8 @@ import { IMapleProxied } from "../../modules/maple-proxy-factory/contracts/inter import { IMapleWithdrawalManagerStorage } from "./IMapleWithdrawalManagerStorage.sol"; +// TODO: naming of two functions with requests() +// TODO: Add disclaimer about running out of gas on requests() without pagination interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxied { /**************************************************************************************************************************************/ @@ -71,7 +73,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param shares Amount of shares to add. * @param owner Address of the owner of shares. */ - function addShares(uint256 shares, address owner) external; + function addShares(uint256 shares, address owner) external returns (uint128 lastRequestId); /** * @dev Processes a withdrawal request. @@ -100,11 +102,12 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi function removeShares(uint256 shares, address owner) external returns (uint256 sharesReturned); /** - * @dev Removes a withdrawal request from the queue. + * @dev Removes withdrawal requests from the queue. * Can only be called by the pool delegate. - * @param owner Address of the owner of shares. + * @param owner Address of the owner of shares. + * @param requestIds Array of identifiers of the withdrawal requests to remove. */ - function removeRequest(address owner) external; + function removeRequest(address owner, uint128[] calldata requestIds) external; /** * @dev Defines if an account will withdraw shares manually or automatically. @@ -113,6 +116,30 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function setManualWithdrawal(address account, bool isManual) external; + + /** + * @dev Updates the total amount of shares pending redemption. + * 1. If newSharesTotal is zero then the request will be removed. + * 2. If newSharesTotal is less than the current shares then the request will be decreased. + * 3. If newSharesTotal is greater than the current shares then the current request will be cancelled + * and a new request will be created at the end of the queue. + * @param requestId Identifier of the withdrawal request that is being updated. + * @param newSharesTotal New total amount of shares pending redemption. + * @return currentRequestId Identifier of the withdrawal request that was updated or created. + */ + function updateShares(uint128 requestId, uint256 newSharesTotal) external returns (uint128 currentRequestId); + + /** + * @dev Updates the total amount of shares pending redemption in batch. + * @param requestIds Array of identifiers of the withdrawal requests that are being updated. + * @param newSharesTotals Array of new total amounts of shares pending redemption. + * @return currentRequestIds Array of identifiers of the withdrawal requests that were updated or created. + */ + function updateSharesBatch( + uint128[] calldata requestIds, + uint256[] calldata newSharesTotals + ) external returns (uint128[] memory currentRequestIds); + /**************************************************************************************************************************************/ /*** View Functions ***/ /**************************************************************************************************************************************/ @@ -138,24 +165,24 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi /** * @dev Returns if a user is able to withdraw. Required for compatibility with pool managers. * NOTE: Always returns true to fulfil interface requirements. - * @param owner_ The account to check if it's in withdraw window. - * @return isInExitWindow_ True if the account is in the withdraw window. + * @param owner The account to check if it's in withdraw window. + * @return isInExitWindow True if the account is in the withdraw window. */ - function isInExitWindow(address owner_) external view returns (bool isInExitWindow_); + function isInExitWindow(address owner) external view returns (bool isInExitWindow); /** * @dev Gets the total amount of funds that need to be locked to fulfill exits. * NOTE: Always zero for this implementation. - * @return lockedLiquidity_ The amount of locked liquidity. + * @return lockedLiquidity The amount of locked liquidity. */ - function lockedLiquidity() external view returns (uint256 lockedLiquidity_); + function lockedLiquidity() external view returns (uint256 lockedLiquidity); /** * @dev Gets the amount of locked shares for an account. - * @param owner_ The address to check the exit for. - * @return lockedShares_ The amount of manual shares available. + * @param owner The address to check the exit for. + * @return lockedShares The amount of manual shares available. */ - function lockedShares(address owner_) external view returns (uint256 lockedShares_); + function lockedShares(address owner) external view returns (uint256 lockedShares); /** * @dev Returns the address of the pool delegate. @@ -176,12 +203,12 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi /** * @dev Gets the amount of shares that can be withdrawn. * NOTE: Values just passed through as withdraw is not implemented. - * @param owner_ The address to check the withdrawal for. - * @param assets_ The amount of requested shares to withdraw. - * @return redeemableAssets_ The amount of assets that can be withdrawn. - * @return resultingShares_ The amount of shares that will be burned. + * @param owner The address to check the withdrawal for. + * @param assets The amount of requested shares to withdraw. + * @return redeemableAssets The amount of assets that can be withdrawn. + * @return resultingShares The amount of shares that will be burned. */ - function previewWithdraw(address owner_, uint256 assets_) external view returns (uint256 redeemableAssets_, uint256 resultingShares_); + function previewWithdraw(address owner, uint256 assets) external view returns (uint256 redeemableAssets, uint256 resultingShares); /** * @dev Returns the owner and amount of shares associated with a withdrawal request. @@ -191,6 +218,29 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function requests(uint128 requestId) external view returns (address owner, uint256 shares); + /** + * @dev Returns the pending requests by owner. + * @param owner Address of the account to check for pending requests. + * @return requestIds Array of request identifiers. + * @return shares Array of shares associated with each request. + */ + function requests(address owner) external view returns (uint128[] memory requestIds, uint256[] memory shares); + + /** + * @dev Returns the pending requests by owner and request ID range. + * Only returns requests that belong to the specified owner. + * @param owner Address of the account to check for pending requests. + * @param firstRequestId First request ID to include in the result. + * @param lastRequestId Last request ID to include in the result. + * @return requestIds Array of request identifiers. + * @return shares Array of shares associated with each request. + */ + function requests(address owner, uint128 firstRequestId, uint128 lastRequestId) + external view returns ( + uint128[] memory requestIds, + uint256[] memory shares + ); + /** * @dev Returns the address of the security admin. * @param securityAdmin Address of the security admin. diff --git a/contracts/interfaces/IMapleWithdrawalManagerStorage.sol b/contracts/interfaces/IMapleWithdrawalManagerStorage.sol index 9b40966..eb94f51 100644 --- a/contracts/interfaces/IMapleWithdrawalManagerStorage.sol +++ b/contracts/interfaces/IMapleWithdrawalManagerStorage.sol @@ -3,6 +3,13 @@ pragma solidity ^0.8.7; interface IMapleWithdrawalManagerStorage { + /** + * @dev Returns the identifier of the last withdrawal request made by a specific user. + * @param account Address of the user. + * @return requestId Identifier of the last withdrawal request made by the user. + */ + function lastRequestIds(address account) external view returns (uint128 requestId); + /** * @dev Returns the address of the pool contract. * @return pool Address of the pool contract. @@ -36,12 +43,11 @@ interface IMapleWithdrawalManagerStorage { function manualSharesAvailable(address owner) external view returns (uint256 sharesAvailable); /** - * @dev Returns the request identifier of an account. - * Returns zero if the account does not have a withdrawal request. - * @param account Address of the account. - * @return requestId Identifier of the withdrawal request. + * @dev Returns the number of pending withdrawal requests for a specific user. + * @param account Account to retrieve the request count for. + * @return requestCount Amount of requests pending redemption for the user. */ - function requestIds(address account) external view returns (uint128 requestId); + function requestCount(address account) external view returns (uint256 requestCount); /** * @dev Returns the first and last withdrawal requests pending redemption. @@ -49,5 +55,4 @@ interface IMapleWithdrawalManagerStorage { * @return lastRequestId Identifier of the last created withdrawal request. */ function queue() external view returns (uint128 nextRequestId, uint128 lastRequestId); - } diff --git a/contracts/proxy/MapleWithdrawalManagerStorage.sol b/contracts/proxy/MapleWithdrawalManagerStorage.sol index 71dc763..dea3368 100644 --- a/contracts/proxy/MapleWithdrawalManagerStorage.sol +++ b/contracts/proxy/MapleWithdrawalManagerStorage.sol @@ -35,8 +35,9 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(address => bool) public override isManualWithdrawal; // Defines which users use automated withdrawals (false by default). - mapping(address => uint128) public override requestIds; // Maps users to their withdrawal requests identifiers. + mapping(address => uint128) public override lastRequestIds; // Maps users to their last withdrawal request. mapping(address => uint256) public override manualSharesAvailable; // Shares available to withdraw for a given manual owner. + mapping(address => uint256) public override requestCount; // Maps users to the number of pending requests. } diff --git a/tests/fuzz/AddSharesFuzz.t.sol b/tests/fuzz/AddSharesFuzz.t.sol index 93e8176..2fb5d33 100644 --- a/tests/fuzz/AddSharesFuzz.t.sol +++ b/tests/fuzz/AddSharesFuzz.t.sol @@ -10,22 +10,14 @@ contract AddSharesFuzzTests is TestBase { } function testFuzz_addShares(uint256[50] memory amount_, address[50] calldata account_) external { - address owner_; - uint128 lastRequestId; - uint256 shares_; uint256 totalShares_; + uint128 requestId_; + uint256 shares_; for (uint256 i; i < account_.length; ++i) { amount_[i] = bound(amount_[i], 1, 1e29); - - if (withdrawalManager.requestIds(account_[i]) > 0) { - vm.prank(pm); - vm.expectRevert("WM:AS:IN_QUEUE"); - withdrawalManager.addShares(amount_[i], account_[i]); - break; - } - + pool.mint(pm, amount_[i]); vm.startPrank(pm); @@ -40,11 +32,11 @@ contract AddSharesFuzzTests is TestBase { assertEq(lastRequestId, i + 1); - ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId); + (requestId_, shares_) = getLastRequestByOwner(account_[i]); - assertEq(shares_, amount_[i]); - assertEq(withdrawalManager.totalShares(), totalShares_); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + assertEq(shares_, amount_[i]); + assertEq(withdrawalManager.totalShares(), totalShares_); + assertEq(requestId_, lastRequestId); } } diff --git a/tests/fuzz/RemoveSharesFuzz.t.sol b/tests/fuzz/RemoveSharesFuzz.t.sol index e8311b5..86519a7 100644 --- a/tests/fuzz/RemoveSharesFuzz.t.sol +++ b/tests/fuzz/RemoveSharesFuzz.t.sol @@ -15,12 +15,14 @@ contract RemoveSharesFuzzTests is TestBase { uint128 lastRequestId; uint256 shares_; uint256 totalShares_; - + uint128 requestId_; + for (uint256 i; i < account_.length; ++i) { amount0_[i] = bound(amount0_[i], 1, 1e29); amount1_[i] = bound(amount1_[i], 1, 1e29); - if (withdrawalManager.requestIds(account_[i]) > 0) break; + ( requestId_, ) = getLastRequestByOwner(account_[i]); + if (requestId_ > 0) break; pool.mint(pm, amount0_[i]); @@ -36,10 +38,12 @@ contract RemoveSharesFuzzTests is TestBase { ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId); - assertEq(shares_, amount0_[i]); - assertEq(withdrawalManager.totalShares(), totalShares_); - assertEq(lastRequestId, i + 1); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + ( requestId_, ) = getLastRequestByOwner(account_[i]); + + assertEq(shares_, amount0_[i]); + assertEq(withdrawalManager.totalShares(), totalShares_); + assertEq(lastRequestId, i + 1); + assertEq(requestId_, lastRequestId); if (amount1_[i] > amount0_[i]) { vm.prank(pm); @@ -61,10 +65,11 @@ contract RemoveSharesFuzzTests is TestBase { assertEq(withdrawalManager.totalShares(), totalShares_); assertEq(lastRequestId, i + 1); + ( requestId_, ) = getLastRequestByOwner(owner_); if (amount0_[i] == amount1_[i]) { - assertEq(withdrawalManager.requestIds(owner_), 0); + assertEq(requestId_, 0); } else { - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + assertEq(requestId_, lastRequestId); } } } diff --git a/tests/integration/EndToEndTests.t.sol b/tests/integration/EndToEndTests.t.sol index e128f02..2f86064 100644 --- a/tests/integration/EndToEndTests.t.sol +++ b/tests/integration/EndToEndTests.t.sol @@ -26,6 +26,8 @@ contract EndToEndTests is TestBase { uint256 totalInitialShares = shares1 + shares2 + shares3; + uint128 requestId_; + // Simulate shares being sent to PM pool.mint(pm, totalInitialShares); @@ -45,21 +47,27 @@ contract EndToEndTests is TestBase { assertQueue({ nextRequestId: 1, lastRequestId: 1 }); assertRequest({ requestId: 1, shares: shares1, owner: lp1 }); - assertEq(withdrawalManager.requestIds(lp1), 1); + ( requestId_, ) = getLastRequestByOwner(lp1); + + assertEq(requestId_, 1); assertEq(pool.balanceOf(address(withdrawalManager)), shares1); withdrawalManager.addShares(shares2, lp2); assertQueue({ nextRequestId: 1, lastRequestId: 2 }); assertRequest({ requestId: 2, shares: shares2, owner: lp2 }); - assertEq(withdrawalManager.requestIds(lp2), 2); + ( requestId_, ) = getLastRequestByOwner(lp2); + + assertEq(requestId_, 2); assertEq(pool.balanceOf(address(withdrawalManager)), shares1 + shares2); withdrawalManager.addShares(shares3, lp3); assertQueue({ nextRequestId: 1, lastRequestId: 3 }); assertRequest({ requestId: 3, shares: shares3, owner: lp3 }); - assertEq(withdrawalManager.requestIds(lp3), 3); + ( requestId_, ) = getLastRequestByOwner(lp3); + + assertEq(requestId_, 3); assertEq(pool.balanceOf(address(withdrawalManager)), shares1 + shares2 + shares3); vm.stopPrank(); @@ -77,7 +85,9 @@ contract EndToEndTests is TestBase { assertQueue({ nextRequestId: 1, lastRequestId: 3 }); assertRequest({ requestId: 1, shares: shares1 / 2, owner: lp1 }); - assertEq(withdrawalManager.requestIds(lp1), 1); + ( requestId_, ) = getLastRequestByOwner(lp1); + + assertEq(requestId_, 1); // Pool Delegate process rest of request 1 + half of request 2 vm.prank(poolDelegate); @@ -86,15 +96,19 @@ contract EndToEndTests is TestBase { // Shares of lp2 remain locked in wm assertEq(withdrawalManager.totalShares(), totalInitialShares - shares1); + ( requestId_, ) = getLastRequestByOwner(lp1); + // Lp1 is removed from queue, although the `request` data structure remains populated. assertQueue({ nextRequestId: 2, lastRequestId: 3 }); assertRequest({ requestId: 1, shares: 0, owner: address(0) }); - assertEq(withdrawalManager.requestIds(lp1), 0); + assertEq(requestId_, 0); // Lp2 is still on the queue, and had it's manual shares incremented. assertRequest({ requestId: 2, shares: shares2 / 2, owner: lp2 }); - assertEq(withdrawalManager.requestIds(lp2), 2); + ( requestId_, ) = getLastRequestByOwner(lp2); + + assertEq(requestId_, 2); assertEq(withdrawalManager.manualSharesAvailable(lp2), shares2 / 2); vm.prank(pm); @@ -121,12 +135,16 @@ contract EndToEndTests is TestBase { function testFuzz_fullFLow_fixedExchangeRate(address[10] memory lps, bool[10] memory isManual, uint256[10] memory shares) external { uint256 totalShares; uint128 inQueue; + uint128 requestId_; // Iterate through all users and add shares to pool manager for (uint256 i = 0; i < 10; i++) { vm.assume(lps[i] != address(0)); + + ( requestId_, ) = getLastRequestByOwner(lps[i]); + // If it's a unique user, add to the queue - if (withdrawalManager.requestIds(lps[i]) == 0) { + if (requestId_ == 0) { uint256 sharesRequested = bound(shares[i], 1, 1e18); // Save each LP value to verify later diff --git a/tests/unit/AddShares.t.sol b/tests/unit/AddShares.t.sol index e4a1fa5..ffb70ba 100644 --- a/tests/unit/AddShares.t.sol +++ b/tests/unit/AddShares.t.sol @@ -28,13 +28,30 @@ contract AddSharesTests is TestBase { withdrawalManager.addShares(0, lp); } - function test_addShares_alreadyInQueue() external { + function test_addShares_multiple_requests() external{ vm.prank(pm); withdrawalManager.addShares(1, lp); vm.prank(pm); - vm.expectRevert("WM:AS:IN_QUEUE"); withdrawalManager.addShares(1, lp); + + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + + assertEq(lastRequestId_, 2); + + ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); + + assertEq(owner_, lp); + assertEq(shares_, 1); + + (uint128[] memory requestIds, uint256[] memory requestShares) = withdrawalManager.requests(lp); + + assertEq(requestIds.length, 2); + assertEq(requestShares.length, 2); + assertEq(requestIds[0], 1); + assertEq(requestIds[1], 2); + assertEq(requestShares[0], 1); + assertEq(requestShares[1], 1); } function test_addShares_failedTransfer() external { @@ -44,9 +61,9 @@ contract AddSharesTests is TestBase { } function test_addShares_newRequestAddedToQueue() external { - ( , uint128 lastRequestId ) = withdrawalManager.queue(); + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); - assertEq(lastRequestId, 0); + assertEq(lastRequestId_, 0); vm.expectEmit(); emit RequestCreated(1, lp, 1); @@ -54,17 +71,17 @@ contract AddSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(1, lp); - ( , lastRequestId ) = withdrawalManager.queue(); + ( , lastRequestId_ ) = withdrawalManager.queue(); - assertEq(lastRequestId, 1); + assertEq(lastRequestId_, 1); } function test_addShares_newRequestAddedToQueue_manual() external { withdrawalManager.__setManualWithdrawal(lp, true); - ( , uint128 lastRequestId ) = withdrawalManager.queue(); + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); - assertEq(lastRequestId, 0); + assertEq(lastRequestId_, 0); vm.expectEmit(); emit RequestCreated(1, lp, 1); @@ -72,27 +89,30 @@ contract AddSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(1, lp); - ( , lastRequestId ) = withdrawalManager.queue(); + ( , lastRequestId_ ) = withdrawalManager.queue(); - assertEq(lastRequestId, 1); + assertEq(lastRequestId_, 1); } function test_addShares_success() external { + uint128 requestId_; + vm.expectEmit(); emit RequestCreated(1, lp, 1); vm.prank(pm); withdrawalManager.addShares(1, lp); - ( , uint128 lastRequestId ) = withdrawalManager.queue(); + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); - assertEq(lastRequestId, 1); + assertEq(lastRequestId_, 1); - ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId); - - assertEq(shares_, 1); - assertEq(withdrawalManager.totalShares(), 1); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); + ( requestId_,) = getLastRequestByOwner(owner_); + + assertEq(shares_, 1); + assertEq(withdrawalManager.totalShares(), 1); + assertEq(requestId_, lastRequestId_); address lp2 = makeAddr("lp2"); @@ -102,15 +122,17 @@ contract AddSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(1, lp2); - ( , lastRequestId ) = withdrawalManager.queue(); + ( , lastRequestId_ ) = withdrawalManager.queue(); + + assertEq(lastRequestId_, 2); - assertEq(lastRequestId, 2); + ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); - ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId); + ( requestId_,) = getLastRequestByOwner(owner_); - assertEq(shares_, 1); - assertEq(withdrawalManager.totalShares(), 2); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + assertEq(shares_, 1); + assertEq(withdrawalManager.totalShares(), 2); + assertEq(requestId_, lastRequestId_); } } diff --git a/tests/unit/CreateInstance.t.sol b/tests/unit/CreateInstance.t.sol index cee2f2a..07a6099 100644 --- a/tests/unit/CreateInstance.t.sol +++ b/tests/unit/CreateInstance.t.sol @@ -74,9 +74,9 @@ contract CreateInstanceTests is TestBase { assertEq(withdrawalManager_.pool(), address(pool)); assertEq(withdrawalManager_.poolManager(), pm); - ( uint128 nextRequestId, ) = withdrawalManager_.queue(); + ( uint128 nextRequestId_, ) = withdrawalManager_.queue(); - assertEq(nextRequestId, 1); + assertEq(nextRequestId_, 1); } } diff --git a/tests/unit/ProcessExit.t.sol b/tests/unit/ProcessExit.t.sol index b075bdb..c1f6161 100644 --- a/tests/unit/ProcessExit.t.sol +++ b/tests/unit/ProcessExit.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.7; -import { TestBase } from "../utils/TestBase.sol"; +import { TestBase, console } from "../utils/TestBase.sol"; // TODO: Add ManualSharesDecreased event to tests contract ProcessExitTests is TestBase { @@ -45,6 +45,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesToRedeem); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); vm.prank(pm); vm.expectRevert("WM:PE:TOO_MANY_SHARES"); @@ -56,6 +57,8 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setRequest(1, lp, sharesToRedeem); withdrawalManager.__setTotalShares(sharesToRedeem); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem); + withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); asset.burn(address(pool), assetsDeposited); @@ -69,6 +72,8 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setRequest(1, lp, sharesToRedeem); withdrawalManager.__setTotalShares(sharesToRedeem); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem); + withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); pool.burn(wm, 1); @@ -81,7 +86,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesToRedeem); withdrawalManager.__setTotalShares(sharesToRedeem); - withdrawalManager.__setOwnerRequest(lp, 0); + withdrawalManager.__setLastRequest(lp, 0); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem); assertEq(pool.balanceOf(lp), 0); @@ -93,8 +98,8 @@ contract ProcessExitTests is TestBase { assertEq(pool.balanceOf(lp), sharesToRedeem); assertEq(pool.balanceOf(wm), 0); - assertEq(withdrawalManager.totalShares(), 0); - assertEq(withdrawalManager.requestIds(lp), 0); + assertEq(withdrawalManager.totalShares(), 0); + assertEq(withdrawalManager.lastRequestIds(lp), 0); assertEq(withdrawalManager.manualSharesAvailable(lp), 0); assertRequest({ requestId: 1, owner: lp, shares: sharesToRedeem }); @@ -105,7 +110,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setRequest(1, lp, sharesToRedeem / 2); withdrawalManager.__setTotalShares(sharesToRedeem); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem / 2); - + // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -120,17 +125,17 @@ contract ProcessExitTests is TestBase { assertEq(withdrawalManager.totalShares(), sharesToRedeem / 2); - assertEq(withdrawalManager.requestIds(lp), 1); + assertEq(withdrawalManager.lastRequestIds(lp), 1); assertRequest({ requestId: 1, owner: lp, shares: sharesToRedeem / 2 }); } function test_processExit_automatic() external { vm.prank(pm); - ( uint256 redeemableShares, uint256 resultingAssets ) = withdrawalManager.processExit(sharesToRedeem, wm); + ( uint256 redeemableShares_, uint256 resultingAssets_ ) = withdrawalManager.processExit(sharesToRedeem, wm); - assertEq(redeemableShares, sharesToRedeem); - assertEq(resultingAssets, assetsDeposited); + assertEq(redeemableShares_, sharesToRedeem); + assertEq(resultingAssets_, assetsDeposited); } } diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index 13400e6..eb3a202 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -86,23 +86,88 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + assertEq(withdrawalManager.totalShares(), sharesLocked); - assertEq(withdrawalManager.requestIds(lp), 0); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); + assertEq(requestIdsLp_.length, 0); + assertEq(sharesLp_.length, 0); assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 2, lastRequestId: 1 }); } + function test_processRedemptions_manual_multiple_requests() external { + withdrawalManager.__setManualWithdrawal(lp, true); + withdrawalManager.__setRequest(1, lp, sharesLocked / 2); + withdrawalManager.__setRequest(2, lp, sharesLocked / 2); + withdrawalManager.__setQueue(1, 2); + withdrawalManager.__setUserRequestCount(lp, 2); + + vm.prank(poolDelegate); + withdrawalManager.processRedemptions(sharesLocked); + + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + + assertEq(withdrawalManager.totalShares(), sharesLocked); + assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); + assertEq(requestIdsLp_.length, 0); + assertEq(sharesLp_.length, 0); + + assertRequest({ requestId: 1, owner: address(0), shares: 0 }); + assertRequest({ requestId: 2, owner: address(0), shares: 0 }); + + assertQueue({ nextRequestId: 3, lastRequestId: 2 }); + } + + function test_processRedemptions_manual_multipleLps_multiple_requests() external { + + address lp2 = makeAddr("lp2"); + address lp3 = makeAddr("lp3"); + + withdrawalManager.__setManualWithdrawal(lp, true); + withdrawalManager.__setManualWithdrawal(lp2, true); + withdrawalManager.__setManualWithdrawal(lp3, true); + withdrawalManager.__setRequest(1, lp, sharesLocked / 4); + withdrawalManager.__setRequest(2, lp2, sharesLocked / 4); + withdrawalManager.__setRequest(3, lp3, sharesLocked / 4); + withdrawalManager.__setRequest(4, lp, sharesLocked / 4); + withdrawalManager.__setQueue(1, 4); + withdrawalManager.__setUserRequestCount(lp, 2); + withdrawalManager.__setUserRequestCount(lp2, 1); + withdrawalManager.__setUserRequestCount(lp3, 1); + + vm.prank(poolDelegate); + withdrawalManager.processRedemptions(sharesLocked); + + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + + assertEq(withdrawalManager.totalShares(), sharesLocked); + assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked / 2); + assertEq(withdrawalManager.manualSharesAvailable(lp2), sharesLocked / 4); + assertEq(withdrawalManager.manualSharesAvailable(lp3), sharesLocked / 4); + assertEq(requestIdsLp_.length, 0); + assertEq(sharesLp_.length, 0); + + assertRequest({ requestId: 1, owner: address(0), shares: 0 }); + assertRequest({ requestId: 2, owner: address(0), shares: 0 }); + assertRequest({ requestId: 3, owner: address(0), shares: 0 }); + assertRequest({ requestId: 4, owner: address(0), shares: 0 }); + + assertQueue({ nextRequestId: 5, lastRequestId: 4 }); + } + function test_processRedemptions_manual_partial() external { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -110,9 +175,16 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked / 2); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + assertEq(withdrawalManager.totalShares(), sharesLocked); - assertEq(withdrawalManager.requestIds(lp), 1); + assertEq(lastRequestId_, 1); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked / 2); + assertEq(requestIdsLp_.length, 1); + assertEq(sharesLp_.length, 1); + assertEq(requestIdsLp_[0], 1); + assertEq(sharesLp_[0], sharesLocked / 2); assertRequest({ requestId: 1, owner: lp, shares: sharesLocked / 2 }); @@ -123,6 +195,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); // Add extra liquidity. asset.mint(address(pool), assetsDeposited); @@ -130,9 +203,12 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(2 * sharesLocked); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + assertEq(withdrawalManager.totalShares(), sharesLocked); - assertEq(withdrawalManager.requestIds(lp), 0); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); + assertEq(requestIdsLp_.length, 0); + assertEq(sharesLp_.length, 0); assertRequest({ requestId: 1, owner: address(0), shares: 0 }); @@ -142,6 +218,7 @@ contract ProcessRedemptionsTests is TestBase { function test_processRedemptions_automatic_complete() external { withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); vm.expectEmit(); emit RequestProcessed(1, lp, sharesLocked, assetsDeposited); @@ -152,9 +229,11 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - assertEq(withdrawalManager.totalShares(), 0); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); - assertEq(withdrawalManager.requestIds(lp), 0); + assertEq(withdrawalManager.totalShares(), 0); + assertEq(requestIdsLp_.length, 0); + assertEq(sharesLp_.length, 0); assertRequest({ requestId: 1, owner: address(0), shares: 0 }); @@ -164,6 +243,7 @@ contract ProcessRedemptionsTests is TestBase { function test_processRedemptions_automatic_partial() external { withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -177,9 +257,15 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked / 2); - assertEq(withdrawalManager.totalShares(), sharesLocked / 2); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); - assertEq(withdrawalManager.requestIds(lp), 1); + assertEq(withdrawalManager.totalShares(), sharesLocked / 2); + assertEq(lastRequestId_, 1); + assertEq(requestIdsLp_.length, 1); + assertEq(sharesLp_.length, 1); + assertEq(requestIdsLp_[0], 1); + assertEq(sharesLp_[0], sharesLocked / 2); assertRequest({ requestId: 1, owner: lp, shares: sharesLocked / 2 }); @@ -189,6 +275,7 @@ contract ProcessRedemptionsTests is TestBase { function test_processRedemptions_automatic_overkill() external { withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); + withdrawalManager.__setUserRequestCount(lp, 1); // Add extra liquidity. asset.mint(address(pool), assetsDeposited); @@ -202,9 +289,11 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(2 * sharesLocked); - assertEq(withdrawalManager.totalShares(), 0); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); - assertEq(withdrawalManager.requestIds(lp), 0); + assertEq(withdrawalManager.totalShares(), 0); + assertEq(requestIdsLp_.length, 0); + assertEq(sharesLp_.length, 0); assertRequest({ requestId: 1, owner: address(0), shares: 0 }); @@ -218,6 +307,8 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setRequest(1, lp1, 100e18); withdrawalManager.__setRequest(2, lp2, 150e18); withdrawalManager.__setQueue(1, 2); + withdrawalManager.__setUserRequestCount(lp1, 1); + withdrawalManager.__setUserRequestCount(lp2, 1); vm.expectEmit(); emit RequestProcessed(1, lp1, 100e18, 40e18); @@ -234,10 +325,14 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - assertEq(withdrawalManager.totalShares(), 0); + (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2); - assertEq(withdrawalManager.requestIds(lp1), 0); - assertEq(withdrawalManager.requestIds(lp2), 0); + assertEq(withdrawalManager.totalShares(), 0); + assertEq(requestIdsLp1_.length, 0); + assertEq(sharesLp1_.length, 0); + assertEq(requestIdsLp2_.length, 0); + assertEq(sharesLp2_.length, 0); assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertRequest({ requestId: 2, owner: address(0), shares: 0 }); @@ -254,14 +349,14 @@ contract ComplexRedemptionTests is TestBase { event RequestRemoved(uint128 indexed requestId); function test_processRedemptions_complex() external { - uint256 totalAssets = 100e18; - uint256 totalShares = 250e18; - uint256 sharesToProcess = 200e18; + uint256 totalAssets_ = 100e18; + uint256 totalShares_ = 250e18; + uint256 sharesToProcess_ = 200e18; - asset.mint(address(pool), totalShares); - pool.mint(wm, totalShares); + asset.mint(address(pool), totalShares_); + pool.mint(wm, totalShares_); - poolManager.__setTotalAssets(totalAssets); + poolManager.__setTotalAssets(totalAssets_); withdrawalManager.__setRequest(1, address(0), 0); // Already processed withdrawalManager.__setRequest(2, address(2), 100e18); // Fully processed @@ -271,9 +366,14 @@ contract ComplexRedemptionTests is TestBase { withdrawalManager.__setRequest(6, address(6), 25e18); // Out of shares withdrawalManager.__setManualWithdrawal(address(3), true); - withdrawalManager.__setTotalShares(totalShares); + withdrawalManager.__setTotalShares(totalShares_); withdrawalManager.__setQueue(2, 6); + withdrawalManager.__setUserRequestCount(address(2), 1); + withdrawalManager.__setUserRequestCount(address(3), 1); + withdrawalManager.__setUserRequestCount(address(5), 1); + withdrawalManager.__setUserRequestCount(address(6), 1); + vm.expectEmit(); emit RequestProcessed(2, address(2), 100e18, 40e18); @@ -287,13 +387,27 @@ contract ComplexRedemptionTests is TestBase { emit RequestDecreased(5, 50e18); vm.prank(poolDelegate); - withdrawalManager.processRedemptions(sharesToProcess); - - assertEq(withdrawalManager.requestIds(address(2)), 0); - assertEq(withdrawalManager.requestIds(address(3)), 0); - assertEq(withdrawalManager.requestIds(address(4)), 0); - assertEq(withdrawalManager.requestIds(address(5)), 5); - assertEq(withdrawalManager.requestIds(address(6)), 6); + withdrawalManager.processRedemptions(sharesToProcess_); + + (uint128[] memory requestIds2_, uint256[] memory shares2_) = withdrawalManager.requests(address(2)); + (uint128[] memory requestIds3_, uint256[] memory shares3_) = withdrawalManager.requests(address(3)); + (uint128[] memory requestIds5_, uint256[] memory shares5_) = withdrawalManager.requests(address(5)); + (uint128[] memory requestIds6_, uint256[] memory shares6_) = withdrawalManager.requests(address(6)); + + assertEq(requestIds2_.length, 0); + assertEq(shares2_.length, 0); + assertEq(requestIds3_.length, 0); + assertEq(shares3_.length, 0); + + assertEq(requestIds5_.length, 1); + assertEq(shares5_.length, 1); + assertEq(requestIds5_[0], 5); + assertEq(shares5_[0], 25e18); + + assertEq(requestIds6_.length, 1); + assertEq(shares6_.length, 1); + assertEq(requestIds6_[0], 6); + assertEq(shares6_[0], 25e18); assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertRequest({ requestId: 2, owner: address(0), shares: 0 }); @@ -305,7 +419,7 @@ contract ComplexRedemptionTests is TestBase { assertEq(withdrawalManager.manualSharesAvailable(address(3)), 50e18); // Shares from the manual request are not redeemed. - assertEq(withdrawalManager.totalShares(), totalShares - sharesToProcess + 50e18); + assertEq(withdrawalManager.totalShares(), totalShares_ - sharesToProcess_ + 50e18); // Request `5` is partially processed and becomes the next request. assertQueue({ nextRequestId: 5, lastRequestId: 6 }); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 2641cab..0b2fda5 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -20,21 +20,27 @@ contract RemoveRequestTests is TestBase { globals.__setFunctionPaused(true); vm.expectRevert("WM:PAUSED"); - withdrawalManager.removeRequest(lp); + withdrawalManager.removeRequest(lp, new uint128[](0)); } function test_removeRequest_notProtocolAdmin() external { vm.expectRevert("WM:NOT_PD_OR_GOV_OR_OA"); - withdrawalManager.removeRequest(lp); + withdrawalManager.removeRequest(lp, new uint128[](0)); } function test_removeRequest_notInQueue() external { + uint128[] memory requestIds_ = new uint128[](1); + requestIds_[0] = 1; + vm.prank(poolDelegate); vm.expectRevert("WM:RR:NOT_IN_QUEUE"); - withdrawalManager.removeRequest(lp); + withdrawalManager.removeRequest(lp, requestIds_); } function test_removeRequest_failedTransfer() external { + uint128[] memory requestIds_ = new uint128[](1); + requestIds_[0] = 1; + vm.prank(pm); withdrawalManager.addShares(1, lp); @@ -42,38 +48,49 @@ contract RemoveRequestTests is TestBase { vm.prank(poolDelegate); vm.expectRevert("WM:RR:TRANSFER_FAIL"); - withdrawalManager.removeRequest(lp); + withdrawalManager.removeRequest(lp, requestIds_); } + // TODO: Add test with multiple requests to remove. function test_removeRequest_success() external { + uint128 lastRequestIdLp_; + vm.prank(pm); withdrawalManager.addShares(2, lp); - ( , uint128 lastRequestId ) = withdrawalManager.queue(); + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); - ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId); + ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); - assertEq(shares_, 2); - assertEq(withdrawalManager.totalShares(), 2); - assertEq(lastRequestId, 1); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); + + assertEq(shares_, 2); + assertEq(withdrawalManager.totalShares(), 2); + assertEq(lastRequestId_, 1); + assertEq(lastRequestIdLp_, lastRequestId_); + + uint128[] memory requestIds = new uint128[](1); + requestIds[0] = lastRequestId_; vm.expectEmit(); emit RequestRemoved(1); vm.prank(poolDelegate); - withdrawalManager.removeRequest(lp); + withdrawalManager.removeRequest(lp, requestIds); + + ( , lastRequestId_ ) = withdrawalManager.queue(); - ( , lastRequestId ) = withdrawalManager.queue(); + ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); - ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId); + ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); + ( uint128 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); - assertEq(lastRequestId, 1); - assertEq(shares_, 0); - assertEq(owner_, address(0)); - assertEq(withdrawalManager.requestIds(owner_), 0); - assertEq(withdrawalManager.requestIds(lp), 0); - assertEq(withdrawalManager.totalShares(), 0); + assertEq(lastRequestId_, 1); + assertEq(shares_, 0); + assertEq(owner_, address(0)); + assertEq(lastRequestIdLp_, 0); + assertEq(lastRequestByOwner_, 0); + assertEq(withdrawalManager.totalShares(), 0); } } diff --git a/tests/unit/RemoveShares.t.sol b/tests/unit/RemoveShares.t.sol index 43ba227..013cc8b 100644 --- a/tests/unit/RemoveShares.t.sol +++ b/tests/unit/RemoveShares.t.sol @@ -30,10 +30,12 @@ contract RemoveSharesTests is TestBase { } function test_removeShares_notInQueue() external { - assertEq(withdrawalManager.requestIds(pm), 0); + ( uint128 lastRequestId_,) = getLastRequestByOwner(pm); + + assertEq(lastRequestId_, 0); vm.prank(pm); - vm.expectRevert("WM:RS:NOT_IN_QUEUE"); + vm.expectRevert("WM:RS:NO_REQUESTS"); withdrawalManager.removeShares(1, lp); } @@ -58,62 +60,49 @@ contract RemoveSharesTests is TestBase { } function test_removeShares_success_decreaseRequest() external { + uint128 lastRequestId_; + vm.prank(pm); withdrawalManager.addShares(2, lp); - ( , uint128 lastRequestId ) = withdrawalManager.queue(); - - ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId); - - assertEq(shares_, 2); - assertEq(withdrawalManager.totalShares(), 2); - assertEq(lastRequestId, 1); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); - vm.expectEmit(); emit RequestDecreased(1, 1); vm.prank(pm); withdrawalManager.removeShares(1, lp); - ( , lastRequestId ) = withdrawalManager.queue(); + ( , lastRequestId_ ) = withdrawalManager.queue(); + + ( address owner_ , uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); - ( , shares_ ) = withdrawalManager.requests(lastRequestId); + ( lastRequestId_, ) = getLastRequestByOwner(owner_); - assertEq(shares_, 1); - assertEq(withdrawalManager.totalShares(), 1); - assertEq(lastRequestId, 1); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); + assertEq(shares_, 1); + assertEq(withdrawalManager.totalShares(), 1); + assertEq(lastRequestId_, 1); } function test_removeShares_success_cancelRequest() external { + uint128 lastRequestId_; + vm.prank(pm); withdrawalManager.addShares(2, lp); - - ( , uint128 lastRequestId ) = withdrawalManager.queue(); - - ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId); - - assertEq(shares_, 2); - assertEq(withdrawalManager.totalShares(), 2); - assertEq(lastRequestId, 1); - assertEq(withdrawalManager.requestIds(owner_), lastRequestId); - vm.expectEmit(); emit RequestRemoved(1); vm.prank(pm); withdrawalManager.removeShares(2, lp); - ( , lastRequestId ) = withdrawalManager.queue(); + ( , lastRequestId_ ) = withdrawalManager.queue(); + + ( address owner_ , uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); - ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId); + ( lastRequestId_, ) = getLastRequestByOwner(owner_); - assertEq(lastRequestId, 1); - assertEq(owner_, address(0)); - assertEq(shares_, 0); - assertEq(withdrawalManager.requestIds(owner_), 0); - assertEq(withdrawalManager.totalShares(), 0); + assertEq(lastRequestId_, 0); + assertEq(owner_, address(0)); + assertEq(shares_, 0); + assertEq(withdrawalManager.totalShares(), 0); } } diff --git a/tests/unit/SetManualWithdrawal.t.sol b/tests/unit/SetManualWithdrawal.t.sol index f36aff4..465aac7 100644 --- a/tests/unit/SetManualWithdrawal.t.sol +++ b/tests/unit/SetManualWithdrawal.t.sol @@ -19,14 +19,6 @@ contract SetManualWithdrawalTests is TestBase { withdrawalManager.setManualWithdrawal(lp, true); } - function test_setManualWithdrawal_existingRequest() external { - withdrawalManager.__setRequest(1, lp, 100e18); - - vm.prank(poolDelegate); - vm.expectRevert("WM:SMW:IN_QUEUE"); - withdrawalManager.setManualWithdrawal(lp, true); - } - function test_setManualWithdrawal_success() external { assertEq(withdrawalManager.isManualWithdrawal(lp), false); diff --git a/tests/unit/UpdateShares.t.sol b/tests/unit/UpdateShares.t.sol new file mode 100644 index 0000000..eb9d358 --- /dev/null +++ b/tests/unit/UpdateShares.t.sol @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { TestBase } from "../utils/TestBase.sol"; + +contract UpdateSharesFailureTests is TestBase { + + function setUp() public override { + super.setUp(); + uint256 mintAmount = 200; + + // Simulate LP transfer into PM. + pool.mint(pm, mintAmount); + + vm.prank(pm); + pool.approve(address(withdrawalManager), mintAmount); + } + + function test_updateShares_invalidRequest() external { + vm.expectRevert("WM:US:INVALID_REQUEST"); + withdrawalManager.updateShares(1, 1); + } + + function test_updateShares_notOwner() external { + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + vm.prank(address(0x12)); + vm.expectRevert("WM:US:NOT_OWNER"); + withdrawalManager.updateShares(1, 1); + } + + function test_updateShares_noChange() external { + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + vm.prank(lp); + vm.expectRevert("WM:US:NO_CHANGE"); + withdrawalManager.updateShares(1, 1); + } + + function test_updateShares_failed_increase_insufficient_shares() external{ + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + vm.expectRevert("WM:AS:FAILED_TRANSFER"); + vm.prank(lp); + withdrawalManager.updateShares(1, 100); + } + + function test_updateShares_failed_transfer_insufficient_tokens() external{ + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + vm.expectRevert("WM:AS:FAILED_TRANSFER"); + vm.prank(lp); + withdrawalManager.updateShares(1, 100); + } + + function test_updateShares_failed_update_request_already_removed() external{ + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + uint128[] memory requestIds = new uint128[](1); + requestIds[0] = 1; + + vm.prank(governor); + withdrawalManager.removeRequest(lp, requestIds); + + vm.expectRevert("WM:US:INVALID_REQUEST"); + vm.prank(lp); + withdrawalManager.updateShares(1, 100); + } +} + +contract UpdateSharesSuccessTests is TestBase { + + event RequestCreated(uint128 indexed requestId, address indexed owner, uint256 shares); + + event RequestDecreased(uint128 indexed requestId, uint256 shares); + + event RequestRemoved(uint128 indexed requestId); + + function setUp() public override { + super.setUp(); + uint256 mintAmount = 200; + + pool.mint(pm, mintAmount); // Simulate LP transfer into PM. + pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. + + vm.prank(pm); + pool.approve(address(withdrawalManager), mintAmount); + + vm.prank(lp); + pool.approve(address(withdrawalManager), mintAmount); + } + + function test_updateShares_increase() external { + vm.prank(pm); + uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestIdBefore_, 1, "Request ID should be 1"); + assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); + assertEq(sharesBefore_.length, 1, "Shares length should be 1"); + assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); + assertEq(sharesBefore_[0], 1, "Shares should be 1"); + + vm.prank(lp); + vm.expectEmit(); + emit RequestRemoved(requestIdBefore_); + emit RequestCreated(requestIdBefore_ + 1, lp, 2); + // Increase shares from 1 to 2 creates a new request and removes the old one. + uint128 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 2); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(newRequestId_, 2, "Request ID should be 2"); + assertEq(requestIds_.length, 1, "Request IDs length should be 1"); + assertEq(shares_.length, 1, "Shares length should be 1"); + assertEq(requestIds_[0], 2, "Request ID should be 2"); + assertEq(shares_[0], 2, "Shares should be 2"); + } + + function test_updateShares_remove_request() external{ + vm.prank(pm); + uint128 requestId_ = withdrawalManager.addShares(1, lp); + + vm.prank(lp); + vm.expectEmit(); + emit RequestRemoved(requestId_); + + uint128 newRequestId_ = withdrawalManager.updateShares(requestId_, 0); + + assertEq(newRequestId_, 0, "Request ID should be 0"); + } + + function test_updateShares_decrease() external { + vm.prank(pm); + uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestIdBefore_, 1, "Request ID Before should be 1 (addShares)"); + assertEq(requestIdsBefore_.length, 1, "Request IDs length before should be 1"); + assertEq(sharesBefore_.length, 1, "Shares length before should be 1"); + assertEq(requestIdsBefore_[0], 1, "Request ID before should be 1"); + assertEq(sharesBefore_[0], 2, "Shares before should be 2"); + + vm.prank(lp); + vm.expectEmit(); + emit RequestDecreased(requestIdBefore_, 1); + // Decrease shares from 2 to 1 keeps the same request ID. + uint128 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 1); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(newRequestId_, 1, "Request ID should be 1 (addShares)"); + assertEq(requestIds_.length, 1, "Request IDs length should be 1"); + assertEq(shares_.length, 1, "Shares length should be 1"); + assertEq(requestIds_[0], 1, "Request ID should be 1"); + assertEq(shares_[0], 1, "Shares should be 1"); + } + + function test_update_shares_multiple_lps_requestsByOwner() external { + address lp2_ = makeAddr("lp2"); + address lp3_ = makeAddr("lp3"); + + uint256 requestAmount1_ = 10; + uint256 requestAmount2_ = 20; + + pool.mint(pm, 3000); + pool.mint(lp, 1000); + pool.mint(lp2_, 1000); + pool.mint(lp3_, 1000); + + vm.prank(lp3_); + pool.approve(address(withdrawalManager), 1000); + + vm.startPrank(pm); + + vm.expectEmit(); + emit RequestCreated(1, lp, requestAmount1_); + withdrawalManager.addShares(requestAmount1_, lp); + vm.expectEmit(); + emit RequestCreated(2, lp, requestAmount2_); + withdrawalManager.addShares(requestAmount2_, lp); + + vm.expectEmit(); + emit RequestCreated(3, lp2_, requestAmount1_); + withdrawalManager.addShares(requestAmount1_, lp2_); + vm.expectEmit(); + emit RequestCreated(4, lp2_, requestAmount2_); + withdrawalManager.addShares(requestAmount2_, lp2_); + + vm.expectEmit(); + emit RequestCreated(5, lp3_, requestAmount1_); + withdrawalManager.addShares(requestAmount1_, lp3_); + vm.expectEmit(); + emit RequestCreated(6, lp3_, requestAmount2_); + withdrawalManager.addShares(requestAmount2_, lp3_); + + vm.stopPrank(); + + { + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2_); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3_); + + assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); + assertEq(requestIdsLp2_.length, 2, "LP2 request IDs length should be 2"); + assertEq(requestIdsLp3_.length, 2, "LP3 request IDs length should be 2"); + + assertEq(sharesLp_.length, 2, "LP shares length should be 2"); + assertEq(sharesLp2_.length, 2, "LP2 shares length should be 2"); + assertEq(sharesLp3_.length, 2, "LP3 shares length should be 2"); + + assertEq(requestIdsLp_[0], 1, "LP request ID index 0 should be 1"); + assertEq(requestIdsLp_[1], 2, "LP request ID index 1 should be 2"); + assertEq(requestIdsLp2_[0], 3, "LP2 request ID index 0 should be 3"); + assertEq(requestIdsLp2_[1], 4, "LP2 request ID index 1 should be 4"); + assertEq(requestIdsLp3_[0], 5, "LP3 request ID index 0 should be 5"); + assertEq(requestIdsLp3_[1], 6, "LP3 request ID index 1 should be 6"); + + assertEq(sharesLp_[0], requestAmount1_, "LP shares index 0 is incorrect"); + assertEq(sharesLp_[1], requestAmount2_, "LP shares index 1 is incorrect"); + assertEq(sharesLp2_[0], requestAmount1_, "LP2 shares index 0 is incorrect"); + assertEq(sharesLp2_[1], requestAmount2_, "LP2 shares index 1 is incorrect"); + assertEq(sharesLp3_[0], requestAmount1_, "LP3 shares index 0 is incorrect"); + assertEq(sharesLp3_[1], requestAmount2_, "LP3 shares index 1 is incorrect"); + } + { + // Decrease a request + vm.prank(lp); + uint128 requestIdLp_ = 1; + vm.expectEmit(); + emit RequestDecreased(requestIdLp_, 1); + uint128 updatedRequestIdLp_ = withdrawalManager.updateShares(requestIdLp_, requestAmount1_ - 1); + assertEq(updatedRequestIdLp_, requestIdLp_, "Updated request ID should be 1"); + + // Remove a request + vm.prank(lp2_); + uint128 requestIdLp2_ = 3; + vm.expectEmit(); + emit RequestRemoved(requestIdLp2_); + uint128 updatedRequestIdLp2_ = withdrawalManager.updateShares(requestIdLp2_, 0); + assertEq(updatedRequestIdLp2_, 0, "Updated request ID should be 0"); + + // Increase a request + vm.prank(lp3_); + uint128 requestIdLp3_ = 5; + vm.expectEmit(); + emit RequestRemoved(requestIdLp3_); + emit RequestCreated(7, lp3_, requestAmount2_ + 10); + uint128 updatedRequestIdLp3 = withdrawalManager.updateShares(requestIdLp3_, requestAmount2_ + 10); + assertEq(updatedRequestIdLp3, 7, "Updated request ID should be 7"); + + vm.stopPrank(); + } + + { + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2_); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3_); + + assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); + assertEq(requestIdsLp2_.length, 1, "LP2 request IDs length should be 1"); + assertEq(requestIdsLp3_.length, 2, "LP3 request IDs length should be 2"); + + assertEq(sharesLp_.length, 2, "LP shares length should be 2"); + assertEq(sharesLp2_.length, 1, "LP2 shares length should be 2"); + assertEq(sharesLp3_.length, 2, "LP3 shares length should be 2"); + + assertEq(requestIdsLp_[0], 1, "LP request ID index 0 should be 1"); + assertEq(requestIdsLp_[1], 2, "LP request ID index 1 should be 2"); + assertEq(requestIdsLp2_[0], 4, "LP2 request ID index 1 should be 4"); + assertEq(requestIdsLp3_[0], 6, "LP3 request ID index 0 should be 5"); + assertEq(requestIdsLp3_[1], 7, "LP3 request ID index 1 should be 7"); + + assertEq(sharesLp_[0], requestAmount1_ - 1, "LP shares index 0 is incorrect"); + assertEq(sharesLp_[1], requestAmount2_, "LP shares index 1 is incorrect"); + assertEq(sharesLp2_[0], requestAmount2_, "LP2 shares index 0 is incorrect"); + assertEq(sharesLp3_[0], requestAmount2_, "LP3 shares index 0 is incorrect"); + assertEq(sharesLp3_[1], requestAmount2_ + 10, "LP3 shares index 1 is incorrect"); + } + } + +} + +contract UpdateSharesBatchFailureTests is TestBase { + + function setUp() public override { + super.setUp(); + uint256 mintAmount = 200; + + pool.mint(pm, mintAmount); // Simulate LP transfer into PM. + pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. + + vm.prank(pm); + pool.approve(address(withdrawalManager), mintAmount); + + vm.prank(lp); + pool.approve(address(withdrawalManager), mintAmount); + } + + function test_updateSharesBatch_invalid_array_lengths() external { + uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory sharesToUpdate_ = new uint256[](2); + + vm.expectRevert("WM:USB:ARRAY_LENGTH_MISMATCH"); + withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + } + + function test_updateSharesBatch_invalid_request() external { + uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory sharesToUpdate_ = new uint256[](1); + + vm.expectRevert("WM:US:INVALID_REQUEST"); + withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + } + + function test_updateSharesBatch_invalid_owner() external { + uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory sharesToUpdate_ = new uint256[](1); + + vm.prank(pm); + uint128 requestId_ = withdrawalManager.addShares(1, lp); + + requestIdsToUpdate_[0] = requestId_; + sharesToUpdate_[0] = 2; + + vm.prank(address(0x12)); + vm.expectRevert("WM:US:NOT_OWNER"); + withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + } + + function test_updateSharesBatch_no_change_in_shares() external { + uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory sharesToUpdate_ = new uint256[](1); + + vm.prank(pm); + uint128 requestId_ = withdrawalManager.addShares(1, lp); + + requestIdsToUpdate_[0] = requestId_; + sharesToUpdate_[0] = 1; + + vm.prank(lp); + vm.expectRevert("WM:US:NO_CHANGE"); + withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + } +} + +contract UpdateSharesBatchSuccessTests is TestBase { + + event RequestCreated(uint128 indexed requestId, address indexed owner, uint256 shares); + + event RequestDecreased(uint128 indexed requestId, uint256 shares); + + event RequestRemoved(uint128 indexed requestId); + + function setUp() public override { + super.setUp(); + uint256 mintAmount = 200; + + pool.mint(pm, mintAmount); // Simulate LP transfer into PM. + pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. + + vm.prank(pm); + pool.approve(address(withdrawalManager), mintAmount); + + vm.prank(lp); + pool.approve(address(withdrawalManager), mintAmount); + } + + + function test_updateSharesBatch_increase_single_request() external { + vm.prank(pm); + uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestIdBefore_, 1, "Request ID should be 1"); + assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); + assertEq(sharesBefore_.length, 1, "Shares length should be 1"); + assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); + assertEq(sharesBefore_[0], 1, "Shares should be 1"); + + uint128[] memory requestIdsToUpdate_ = new uint128[](1); + requestIdsToUpdate_[0] = requestIdBefore_; + + uint256[] memory sharesToUpdate_ = new uint256[](1); + sharesToUpdate_[0] = 2; + + vm.prank(lp); + vm.expectEmit(); + emit RequestRemoved(requestIdBefore_); + emit RequestCreated(requestIdBefore_ + 1, lp, 2); + + // Increase shares creates a new request and removes the old one. + uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + + assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); + assertEq(newRequestIds_[0], 2, "New request ID should be 2"); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(requestIds_.length, 1, "Request IDs length should be 1"); + assertEq(shares_.length, 1, "Shares length should be 1"); + assertEq(requestIds_[0], 2, "Request ID should be 2"); + assertEq(shares_[0], 2, "Shares should be 2"); + } + + function test_updateSharesBatch_increase_multiple_requests() external { + vm.startPrank(pm); + uint128 requestId1Before_ = withdrawalManager.addShares(1, lp); + uint128 requestId2Before_ = withdrawalManager.addShares(1, lp); + vm.stopPrank(); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestId1Before_, 1, "Request ID should be 1"); + assertEq(requestId2Before_, 2, "Request ID should be 1"); + assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); + assertEq(sharesBefore_.length, 2, "Shares length should be 2"); + assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); + assertEq(sharesBefore_[0], 1, "Shares should be 1"); + assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); + assertEq(sharesBefore_[1], 1, "Shares should be 1"); + + uint128[] memory requestIdsToUpdate_ = new uint128[](2); + requestIdsToUpdate_[0] = requestId1Before_; + requestIdsToUpdate_[1] = requestId2Before_; + + uint256[] memory sharesToUpdate_ = new uint256[](2); + sharesToUpdate_[0] = 2; + sharesToUpdate_[1] = 3; + + vm.prank(lp); + vm.expectEmit(); + emit RequestRemoved(requestId1Before_); + emit RequestCreated(3, lp, 2); + + vm.expectEmit(); + emit RequestRemoved(requestId2Before_); + emit RequestCreated(4, lp, 3); + + // Increase shares creates a new request and removes the old one. + uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + + assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); + assertEq(newRequestIds_[0], 3, "New request ID should be 3"); + assertEq(newRequestIds_[1], 4, "New request ID should be 4"); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(requestIds_.length, 2, "Request IDs length should be 1"); + assertEq(shares_.length, 2, "Shares length should be 1"); + assertEq(requestIds_[0], 3, "Request ID should be 2"); + assertEq(shares_[0], 2, "Shares should be 2"); + assertEq(requestIds_[1], 4, "Request ID should be 3"); + assertEq(shares_[1], 3, "Shares should be 3"); + } + + function test_updateSharesBatch_decrease_single_request() external { + vm.prank(pm); + uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestIdBefore_, 1, "Request ID should be 1"); + assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); + assertEq(sharesBefore_.length, 1, "Shares length should be 1"); + assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); + assertEq(sharesBefore_[0], 2, "Shares should be 2"); + + uint128[] memory requestIdsToUpdate_ = new uint128[](1); + requestIdsToUpdate_[0] = requestIdBefore_; + + uint256[] memory sharesToUpdate_ = new uint256[](1); + sharesToUpdate_[0] = 1; + + vm.prank(lp); + vm.expectEmit(); + emit RequestDecreased(requestIdBefore_, 1); + + // Decrease in shares updates the existing request. + uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + + assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); + assertEq(newRequestIds_[0], 1, "New request ID should be 1"); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(requestIds_.length, 1, "Request IDs length should be 1"); + assertEq(shares_.length, 1, "Shares length should be 1"); + assertEq(requestIds_[0], 1, "Request ID should be 1"); + assertEq(shares_[0], 1, "Shares should be 1"); + } + + function test_updateSharesBatch_decrease_multiple_requests() external { + vm.startPrank(pm); + uint128 requestId1Before_ = withdrawalManager.addShares(2, lp); + uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); + vm.stopPrank(); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestId1Before_, 1, "Request ID should be 1"); + assertEq(requestId2Before_, 2, "Request ID should be 1"); + assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); + assertEq(sharesBefore_.length, 2, "Shares length should be 2"); + assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); + assertEq(sharesBefore_[0], 2, "Shares should be 2"); + assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); + assertEq(sharesBefore_[1], 2, "Shares should be 2"); + + uint128[] memory requestIdsToUpdate_ = new uint128[](2); + requestIdsToUpdate_[0] = requestId1Before_; + requestIdsToUpdate_[1] = requestId2Before_; + + uint256[] memory sharesToUpdate_ = new uint256[](2); + sharesToUpdate_[0] = 1; + sharesToUpdate_[1] = 1; + + vm.prank(lp); + vm.expectEmit(); + emit RequestDecreased(requestId1Before_, 1); + + vm.expectEmit(); + emit RequestDecreased(requestId2Before_, 1); + + // Decrease in shares updates the existing requests. + uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + + assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); + assertEq(newRequestIds_[0], 1, "New request ID should be 1"); + assertEq(newRequestIds_[1], 2, "New request ID should be 2"); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(requestIds_.length, 2, "Request IDs length should be 1"); + assertEq(shares_.length, 2, "Shares length should be 1"); + assertEq(requestIds_[0], 1, "Request ID should be 1"); + assertEq(shares_[0], 1, "Shares should be 1"); + assertEq(requestIds_[1], 2, "Request ID should be 2"); + assertEq(shares_[1], 1, "Shares should be 1"); + } + + function test_updateSharesBatch_increase_and_decrease_multiple_requests() external { + vm.startPrank(pm); + uint128 requestId1Before_ = withdrawalManager.addShares(1, lp); + uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); + vm.stopPrank(); + + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + + assertEq(requestId1Before_, 1, "Request ID should be 1"); + assertEq(requestId2Before_, 2, "Request ID should be 1"); + assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); + assertEq(sharesBefore_.length, 2, "Shares length should be 2"); + assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); + assertEq(sharesBefore_[0], 1, "Shares should be 1"); + assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); + assertEq(sharesBefore_[1], 2, "Shares should be 2"); + + uint128[] memory requestIdsToUpdate_ = new uint128[](2); + requestIdsToUpdate_[0] = requestId1Before_; + requestIdsToUpdate_[1] = requestId2Before_; + + uint256[] memory sharesToUpdate_ = new uint256[](2); + sharesToUpdate_[0] = 2; + sharesToUpdate_[1] = 1; + + vm.prank(lp); + vm.expectEmit(); + emit RequestRemoved(requestId1Before_); + emit RequestCreated(3, lp, 2); + + vm.expectEmit(); + emit RequestDecreased(requestId2Before_, 1); + + // Decrease in shares updates the existing requests. + uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + + assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); + assertEq(newRequestIds_[0], 3, "New request ID should be 3"); + assertEq(newRequestIds_[1], 2, "New request ID should be 2"); + + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + + assertEq(requestIds_.length, 2, "Request IDs length should be 1"); + assertEq(shares_.length, 2, "Shares length should be 1"); + assertEq(requestIds_[0], 2, "Request ID should be 2"); + assertEq(shares_[0], 1, "Shares should be 1"); + assertEq(requestIds_[1], 3, "Request ID should be 3"); + assertEq(shares_[1], 2, "Shares should be 2"); + } + +} diff --git a/tests/unit/ViewFunctions.t.sol b/tests/unit/ViewFunctions.t.sol index c5624bb..4ba72f1 100644 --- a/tests/unit/ViewFunctions.t.sol +++ b/tests/unit/ViewFunctions.t.sol @@ -24,4 +24,154 @@ contract ViewFunctionsTests is TestBase { assertEq(redeemableShares, 0); } + function test_requests_by_owner() external { + address lp1 = makeAddr("lp1"); + address lp2 = makeAddr("lp2"); + address lp3 = makeAddr("lp3"); + address lp4 = makeAddr("lp4"); + + uint256 assetsDeposited_ = 100e18; + + withdrawalManager.__setRequest(1, lp1, assetsDeposited_); + withdrawalManager.__setRequest(2, lp2, assetsDeposited_); + withdrawalManager.__setRequest(3, lp3, assetsDeposited_); + withdrawalManager.__setRequest(4, lp4, assetsDeposited_); + withdrawalManager.__setRequest(5, lp1, assetsDeposited_); + + withdrawalManager.__setUserRequestCount(lp1, 2); + withdrawalManager.__setUserRequestCount(lp2, 1); + withdrawalManager.__setUserRequestCount(lp3, 1); + withdrawalManager.__setUserRequestCount(lp4, 1); + + withdrawalManager.__setQueue(1, 5); + + (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requests(lp1); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3); + (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requests(lp4); + + + assertEq(requestIdsLp1_.length, 2); + assertEq(requestIdsLp1_[0], 1); + assertEq(requestIdsLp1_[1], 5); + + assertEq(sharesLp1_.length, 2); + assertEq(sharesLp1_[0], assetsDeposited_); + assertEq(sharesLp1_[1], assetsDeposited_); + + assertEq(requestIdsLp2_.length, 1); + assertEq(requestIdsLp2_[0], 2); + + assertEq(sharesLp2_.length, 1); + assertEq(sharesLp2_[0], assetsDeposited_); + + assertEq(requestIdsLp3_.length, 1); + assertEq(requestIdsLp3_[0], 3); + + assertEq(sharesLp3_.length, 1); + assertEq(sharesLp3_[0], assetsDeposited_); + + assertEq(requestIdsLp4_.length, 1); + assertEq(requestIdsLp4_[0], 4); + + assertEq(sharesLp4_.length, 1); + assertEq(sharesLp4_[0], assetsDeposited_); + } + + function test_requests_by_owner_and_range() external { + address lp1 = makeAddr("lp1"); + address lp2 = makeAddr("lp2"); + address lp3 = makeAddr("lp3"); + address lp4 = makeAddr("lp4"); + + uint256 assetsDeposited_ = 100e18; + + withdrawalManager.__setRequest(1, lp1, assetsDeposited_); + withdrawalManager.__setRequest(2, lp2, assetsDeposited_); + withdrawalManager.__setRequest(3, lp3, assetsDeposited_); + withdrawalManager.__setRequest(4, lp4, assetsDeposited_); + withdrawalManager.__setRequest(5, lp1, assetsDeposited_); + + withdrawalManager.__setUserRequestCount(lp1, 2); + withdrawalManager.__setUserRequestCount(lp2, 1); + withdrawalManager.__setUserRequestCount(lp3, 1); + withdrawalManager.__setUserRequestCount(lp4, 1); + + withdrawalManager.__setQueue(1, 5); + + (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requests(lp1, 1, 5); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2, 1, 5); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3, 1, 5); + (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requests(lp4, 1, 5); + + + assertEq(requestIdsLp1_.length, 2); + assertEq(requestIdsLp1_[0], 1); + assertEq(requestIdsLp1_[1], 5); + + assertEq(sharesLp1_.length, 2); + assertEq(sharesLp1_[0], assetsDeposited_); + assertEq(sharesLp1_[1], assetsDeposited_); + + assertEq(requestIdsLp2_.length, 1); + assertEq(requestIdsLp2_[0], 2); + + assertEq(sharesLp2_.length, 1); + assertEq(sharesLp2_[0], assetsDeposited_); + + assertEq(requestIdsLp3_.length, 1); + assertEq(requestIdsLp3_[0], 3); + + assertEq(sharesLp3_.length, 1); + assertEq(sharesLp3_[0], assetsDeposited_); + + assertEq(requestIdsLp4_.length, 1); + assertEq(requestIdsLp4_[0], 4); + + assertEq(sharesLp4_.length, 1); + assertEq(sharesLp4_[0], assetsDeposited_); + } + + function test_requests_by_requestId() external { + address lp2 = makeAddr("lp2"); + address lp3 = makeAddr("lp3"); + address lp4 = makeAddr("lp4"); + + uint256 assetsDeposited_ = 100e18; + + withdrawalManager.__setRequest(1, lp, assetsDeposited_); + withdrawalManager.__setRequest(2, lp2, assetsDeposited_); + withdrawalManager.__setRequest(3, lp3, assetsDeposited_); + withdrawalManager.__setRequest(4, lp4, assetsDeposited_); + withdrawalManager.__setRequest(5, lp, assetsDeposited_); + + withdrawalManager.__setUserRequestCount(lp, 2); + withdrawalManager.__setUserRequestCount(lp2, 1); + withdrawalManager.__setUserRequestCount(lp3, 1); + withdrawalManager.__setUserRequestCount(lp4, 1); + + withdrawalManager.__setQueue(1, 5); + + (address ownerLp1_, uint256 sharesLp1_) = withdrawalManager.requests(1); + (address ownerLp2_, uint256 sharesLp2_) = withdrawalManager.requests(2); + (address ownerLp3_, uint256 sharesLp3_) = withdrawalManager.requests(3); + (address ownerLp4_, uint256 sharesLp4_) = withdrawalManager.requests(4); + (address ownerLp1_2_, uint256 sharesLp1_2_) = withdrawalManager.requests(5); + + assertEq(ownerLp1_, lp); + assertEq(sharesLp1_, assetsDeposited_); + + assertEq(ownerLp2_, lp2); + assertEq(sharesLp2_, assetsDeposited_); + + assertEq(ownerLp3_, lp3); + assertEq(sharesLp3_, assetsDeposited_); + + assertEq(ownerLp4_, lp4); + assertEq(sharesLp4_, assetsDeposited_); + + assertEq(ownerLp1_2_, lp); + assertEq(sharesLp1_2_, assetsDeposited_); + } + } diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index ccb252b..6f4cdbf 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -17,8 +17,8 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { isManualWithdrawal[owner_] = isManual_; } - function __setOwnerRequest(address owner_, uint128 requestId_) external { - requestIds[owner_] = requestId_; + function __setLastRequest(address owner_, uint128 requestId_) external { + lastRequestIds[owner_] = requestId_; } function __setQueue(uint128 nextRequestId_, uint128 lastRequestId_) external { @@ -27,12 +27,16 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setRequest(uint128 requestId_, address owner_, uint256 shares_) external { - requestIds[owner_] = requestId_; queue.requests[requestId_] = WithdrawalRequest(owner_, shares_); + lastRequestIds[owner_] = requestId_; } function __setTotalShares(uint256 totalShares_) external { totalShares = totalShares_; } + function __setUserRequestCount(address owner_, uint256 requestCount_) external { + requestCount[owner_] = requestCount_; + } + } diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index 88d813c..6509f6e 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -85,4 +85,10 @@ contract TestBase is Test { assertEq(lastRequestId_, lastRequestId); } + function getLastRequestByOwner(address owner) internal view returns (uint128 lastRequestId, uint256 shares) { + ( uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(owner); + + return requestIds_.length == 0 ? (0,0) : (requestIds_[requestIds_.length - 1], shares_[shares_.length - 1]); + } + } From 2a64e1e2ae5277cb831fea1d39bb08e70db8267e Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:41:28 +0400 Subject: [PATCH 03/21] feat: TODO Resolutions (SC-20711) (#32) * feat: fixed todo's Signed-off-by: calmacfadden * feat: added multiple remove tests Signed-off-by: calmacfadden * feat:reordered some functions Signed-off-by: calmacfadden * feat: fix formatting --------- Signed-off-by: calmacfadden Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleWithdrawalManager.sol | 60 ++++++++++++------- .../interfaces/IMapleWithdrawalManager.sol | 7 +-- tests/unit/AddShares.t.sol | 2 +- tests/unit/ProcessRedemptions.t.sol | 28 ++++----- tests/unit/RemoveRequest.t.sol | 52 +++++++++++++++- tests/unit/UpdateShares.t.sol | 40 ++++++------- tests/unit/ViewFunctions.t.sol | 16 ++--- tests/utils/TestBase.sol | 2 +- 8 files changed, 133 insertions(+), 74 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 5ffb820..a18424a 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -43,7 +43,6 @@ import { MapleWithdrawalManagerStorage } from "./proxy/MapleWithdrawalManagerSto */ -// TODO: Ordering of functions contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManagerStorage , MapleProxiedInternals { /**************************************************************************************************************************************/ @@ -129,7 +128,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } /**************************************************************************************************************************************/ - /*** State-Changing Functions ***/ + /*** State-Changing Functions - OnlyPoolManager ***/ /**************************************************************************************************************************************/ function addShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint128 lastRequestId_) { @@ -152,6 +151,24 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag : _processManualExit(shares_, owner_); } + function removeShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint256 sharesReturned_) { + require(shares_ > 0, "WM:RS:ZERO_SHARES"); + + uint128 lastRequestId_ = lastRequestIds[owner_]; + + require(lastRequestId_ > 0, "WM:RS:NO_REQUESTS"); + + WithdrawalRequest memory request_ = queue.requests[lastRequestId_]; + + require(request_.shares >= shares_, "WM:RS:INSUFFICIENT_SHARES"); + + sharesReturned_ = _removeShares(lastRequestId_, shares_, request_.owner, request_.shares); + } + + /**************************************************************************************************************************************/ + /*** State-Changing Functions - onlyRedeemer ***/ + /**************************************************************************************************************************************/ + function processRedemptions(uint256 maxSharesToProcess_) external override whenProtocolNotPaused nonReentrant onlyRedeemer { require(maxSharesToProcess_ > 0, "WM:PR:ZERO_SHARES"); @@ -181,21 +198,10 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag queue.nextRequestId = nextRequestId_; } - function removeShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint256 sharesReturned_) { - require(shares_ > 0, "WM:RS:ZERO_SHARES"); - - uint128 lastRequestId_ = lastRequestIds[owner_]; - - require(lastRequestId_ > 0, "WM:RS:NO_REQUESTS"); - - WithdrawalRequest memory request_ = queue.requests[lastRequestId_]; - - require(request_.shares >= shares_, "WM:RS:INSUFFICIENT_SHARES"); - - sharesReturned_ = _removeShares(lastRequestId_, shares_, request_.owner, request_.shares); - } + /**************************************************************************************************************************************/ + /*** State-Changing Functions - Admin functions ***/ + /**************************************************************************************************************************************/ - // TODO: To be more gas efficient accumulate shares to be removed and then remove them all at once from totalShares. function removeRequest( address owner_, uint128[] calldata requestIds_ @@ -204,6 +210,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag require(owner_ != address(0), "WM:RR:ZERO_OWNER"); require(requestIds_.length > 0, "WM:RR:ZERO_REQUESTS"); + uint256 sharesToRemove_; + WithdrawalRequest memory withdrawalRequest_; for (uint256 i = 0; i < requestIds_.length; ++i) { @@ -214,10 +222,12 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(owner_, requestIds_[i]); - totalShares -= withdrawalRequest_.shares; + sharesToRemove_ += withdrawalRequest_.shares; require(ERC20Helper.transfer(pool, owner_, withdrawalRequest_.shares), "WM:RR:TRANSFER_FAIL"); } + + totalShares -= sharesToRemove_; } function setManualWithdrawal(address owner_, bool isManual_) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { @@ -226,11 +236,14 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag emit ManualWithdrawalSet(owner_, isManual_); } - // TODO: Do both updateShares need nonReentrant guards? + /**************************************************************************************************************************************/ + /*** Unprivileged External Functions ***/ + /**************************************************************************************************************************************/ + function updateShares( uint128 requestId_, uint256 newSharesTotal_ - ) public override whenProtocolNotPaused returns (uint128 updatedRequestId_) + ) public override whenProtocolNotPaused nonReentrant returns (uint128 updatedRequestId_) { WithdrawalRequest memory request_ = queue.requests[requestId_]; @@ -240,7 +253,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag uint256 sharesToRemove_ = newSharesTotal_ < request_.shares ? request_.shares - newSharesTotal_ : request_.shares; - _removeShares(requestId_, sharesToRemove_, request_.owner, request_.shares); // Removes shares and will cancel the request if there are no shares remaining. + // Removes shares and will cancel the request if there are no shares remaining. + _removeShares(requestId_, sharesToRemove_, request_.owner, request_.shares); if (newSharesTotal_ > request_.shares) updatedRequestId_ = _addRequest(request_.owner, newSharesTotal_); @@ -484,16 +498,16 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag shares_ = queue.requests[requestId_].shares; } - function requests(address owner_) + function requestsByOwner(address owner_) external view override returns ( uint128[] memory requestIds_, uint256[] memory shares_ ) { - ( requestIds_, shares_ ) = requests(owner_, queue.nextRequestId, queue.lastRequestId); + ( requestIds_, shares_ ) = requestsByOwnerRange(owner_, queue.nextRequestId, queue.lastRequestId); } - function requests(address owner_, uint128 firstRequestId_, uint128 lastRequestId_) + function requestsByOwnerRange(address owner_, uint128 firstRequestId_, uint128 lastRequestId_) public view override returns ( uint128[] memory requestIds_, uint256[] memory shares_ diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index 6bca5c9..d1ebecf 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -5,8 +5,6 @@ import { IMapleProxied } from "../../modules/maple-proxy-factory/contracts/inter import { IMapleWithdrawalManagerStorage } from "./IMapleWithdrawalManagerStorage.sol"; -// TODO: naming of two functions with requests() -// TODO: Add disclaimer about running out of gas on requests() without pagination interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxied { /**************************************************************************************************************************************/ @@ -220,11 +218,12 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi /** * @dev Returns the pending requests by owner. + * NOTE: This function may run out of gas if there are too many requests. Use the overload with pagination. * @param owner Address of the account to check for pending requests. * @return requestIds Array of request identifiers. * @return shares Array of shares associated with each request. */ - function requests(address owner) external view returns (uint128[] memory requestIds, uint256[] memory shares); + function requestsByOwner(address owner) external view returns (uint128[] memory requestIds, uint256[] memory shares); /** * @dev Returns the pending requests by owner and request ID range. @@ -235,7 +234,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @return requestIds Array of request identifiers. * @return shares Array of shares associated with each request. */ - function requests(address owner, uint128 firstRequestId, uint128 lastRequestId) + function requestsByOwnerRange(address owner, uint128 firstRequestId, uint128 lastRequestId) external view returns ( uint128[] memory requestIds, uint256[] memory shares diff --git a/tests/unit/AddShares.t.sol b/tests/unit/AddShares.t.sol index ffb70ba..f3d7dec 100644 --- a/tests/unit/AddShares.t.sol +++ b/tests/unit/AddShares.t.sol @@ -44,7 +44,7 @@ contract AddSharesTests is TestBase { assertEq(owner_, lp); assertEq(shares_, 1); - (uint128[] memory requestIds, uint256[] memory requestShares) = withdrawalManager.requests(lp); + (uint128[] memory requestIds, uint256[] memory requestShares) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds.length, 2); assertEq(requestShares.length, 2); diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index eb3a202..a8a840d 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -91,7 +91,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); @@ -113,7 +113,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); @@ -146,7 +146,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked / 2); @@ -175,7 +175,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked / 2); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); @@ -203,7 +203,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(2 * sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); @@ -229,7 +229,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), 0); assertEq(requestIdsLp_.length, 0); @@ -257,7 +257,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked / 2); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked / 2); @@ -289,7 +289,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(2 * sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), 0); assertEq(requestIdsLp_.length, 0); @@ -325,8 +325,8 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requests(lp); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2); + (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwner(lp); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2); assertEq(withdrawalManager.totalShares(), 0); assertEq(requestIdsLp1_.length, 0); @@ -389,10 +389,10 @@ contract ComplexRedemptionTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesToProcess_); - (uint128[] memory requestIds2_, uint256[] memory shares2_) = withdrawalManager.requests(address(2)); - (uint128[] memory requestIds3_, uint256[] memory shares3_) = withdrawalManager.requests(address(3)); - (uint128[] memory requestIds5_, uint256[] memory shares5_) = withdrawalManager.requests(address(5)); - (uint128[] memory requestIds6_, uint256[] memory shares6_) = withdrawalManager.requests(address(6)); + (uint128[] memory requestIds2_, uint256[] memory shares2_) = withdrawalManager.requestsByOwner(address(2)); + (uint128[] memory requestIds3_, uint256[] memory shares3_) = withdrawalManager.requestsByOwner(address(3)); + (uint128[] memory requestIds5_, uint256[] memory shares5_) = withdrawalManager.requestsByOwner(address(5)); + (uint128[] memory requestIds6_, uint256[] memory shares6_) = withdrawalManager.requestsByOwner(address(6)); assertEq(requestIds2_.length, 0); assertEq(shares2_.length, 0); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 0b2fda5..89e20c0 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -10,10 +10,10 @@ contract RemoveRequestTests is TestBase { function setUp() public override { super.setUp(); - pool.mint(pm, 2); + pool.mint(pm, 4); vm.prank(pm); - pool.approve(address(withdrawalManager), 2); + pool.approve(address(withdrawalManager), 4); } function test_removeRequest_protocolPaused() external { @@ -51,7 +51,6 @@ contract RemoveRequestTests is TestBase { withdrawalManager.removeRequest(lp, requestIds_); } - // TODO: Add test with multiple requests to remove. function test_removeRequest_success() external { uint128 lastRequestIdLp_; @@ -93,4 +92,51 @@ contract RemoveRequestTests is TestBase { assertEq(withdrawalManager.totalShares(), 0); } + function test_removeRequest_multiple_requests_success() external { + uint128 lastRequestIdLp_; + + vm.startPrank(pm); + withdrawalManager.addShares(2, lp); + withdrawalManager.addShares(2, lp); + vm.stopPrank(); + + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + + ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); + + ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); + + assertEq(shares_, 2); + assertEq(withdrawalManager.totalShares(), 4); + assertEq(lastRequestId_, 2); + assertEq(lastRequestIdLp_, lastRequestId_); + + uint128[] memory requestIds = new uint128[](2); + requestIds[0] = 1; + requestIds[1] = 2; + + vm.expectEmit(); + emit RequestRemoved(1); + + vm.expectEmit(); + emit RequestRemoved(2); + + vm.prank(poolDelegate); + withdrawalManager.removeRequest(lp, requestIds); + + ( , lastRequestId_ ) = withdrawalManager.queue(); + + ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); + + ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); + ( uint128 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); + + assertEq(lastRequestId_, 2); + assertEq(shares_, 0); + assertEq(owner_, address(0)); + assertEq(lastRequestIdLp_, 0); + assertEq(lastRequestByOwner_, 0); + assertEq(withdrawalManager.totalShares(), 0); + } + } diff --git a/tests/unit/UpdateShares.t.sol b/tests/unit/UpdateShares.t.sol index eb9d358..623e057 100644 --- a/tests/unit/UpdateShares.t.sol +++ b/tests/unit/UpdateShares.t.sol @@ -99,7 +99,7 @@ contract UpdateSharesSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); @@ -114,7 +114,7 @@ contract UpdateSharesSuccessTests is TestBase { // Increase shares from 1 to 2 creates a new request and removes the old one. uint128 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 2); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(newRequestId_, 2, "Request ID should be 2"); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); @@ -140,7 +140,7 @@ contract UpdateSharesSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID Before should be 1 (addShares)"); assertEq(requestIdsBefore_.length, 1, "Request IDs length before should be 1"); @@ -154,7 +154,7 @@ contract UpdateSharesSuccessTests is TestBase { // Decrease shares from 2 to 1 keeps the same request ID. uint128 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 1); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(newRequestId_, 1, "Request ID should be 1 (addShares)"); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); @@ -204,9 +204,9 @@ contract UpdateSharesSuccessTests is TestBase { vm.stopPrank(); { - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2_); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3_); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); assertEq(requestIdsLp2_.length, 2, "LP2 request IDs length should be 2"); @@ -260,9 +260,9 @@ contract UpdateSharesSuccessTests is TestBase { } { - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requests(lp); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2_); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3_); + (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); assertEq(requestIdsLp2_.length, 1, "LP2 request IDs length should be 1"); @@ -378,7 +378,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); @@ -403,7 +403,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); assertEq(newRequestIds_[0], 2, "New request ID should be 2"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); assertEq(shares_.length, 1, "Shares length should be 1"); @@ -417,7 +417,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(1, lp); vm.stopPrank(); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); assertEq(requestId2Before_, 2, "Request ID should be 1"); @@ -452,7 +452,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(newRequestIds_[0], 3, "New request ID should be 3"); assertEq(newRequestIds_[1], 4, "New request ID should be 4"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 2, "Request IDs length should be 1"); assertEq(shares_.length, 2, "Shares length should be 1"); @@ -466,7 +466,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); @@ -490,7 +490,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); assertEq(newRequestIds_[0], 1, "New request ID should be 1"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); assertEq(shares_.length, 1, "Shares length should be 1"); @@ -504,7 +504,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); assertEq(requestId2Before_, 2, "Request ID should be 1"); @@ -537,7 +537,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(newRequestIds_[0], 1, "New request ID should be 1"); assertEq(newRequestIds_[1], 2, "New request ID should be 2"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 2, "Request IDs length should be 1"); assertEq(shares_.length, 2, "Shares length should be 1"); @@ -553,7 +553,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requests(lp); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); assertEq(requestId2Before_, 2, "Request ID should be 1"); @@ -587,7 +587,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(newRequestIds_[0], 3, "New request ID should be 3"); assertEq(newRequestIds_[1], 2, "New request ID should be 2"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(lp); + (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 2, "Request IDs length should be 1"); assertEq(shares_.length, 2, "Shares length should be 1"); diff --git a/tests/unit/ViewFunctions.t.sol b/tests/unit/ViewFunctions.t.sol index 4ba72f1..57c88dc 100644 --- a/tests/unit/ViewFunctions.t.sol +++ b/tests/unit/ViewFunctions.t.sol @@ -45,10 +45,10 @@ contract ViewFunctionsTests is TestBase { withdrawalManager.__setQueue(1, 5); - (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requests(lp1); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3); - (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requests(lp4); + (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwner(lp1); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3); + (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requestsByOwner(lp4); assertEq(requestIdsLp1_.length, 2); @@ -99,10 +99,10 @@ contract ViewFunctionsTests is TestBase { withdrawalManager.__setQueue(1, 5); - (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requests(lp1, 1, 5); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requests(lp2, 1, 5); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requests(lp3, 1, 5); - (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requests(lp4, 1, 5); + (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwnerRange(lp1, 1, 5); + (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwnerRange(lp2, 1, 5); + (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwnerRange(lp3, 1, 5); + (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requestsByOwnerRange(lp4, 1, 5); assertEq(requestIdsLp1_.length, 2); diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index 6509f6e..2e9c296 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -86,7 +86,7 @@ contract TestBase is Test { } function getLastRequestByOwner(address owner) internal view returns (uint128 lastRequestId, uint256 shares) { - ( uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requests(owner); + ( uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(owner); return requestIds_.length == 0 ? (0,0) : (requestIds_[requestIds_.length - 1], shares_[shares_.length - 1]); } From 8333ed43b59442e867ab021048834c8410ec10f1 Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:31:59 +0400 Subject: [PATCH 04/21] feat: added userRequestSummaries (#33) * feat: added userRequestSummaries Signed-off-by: calmacfadden * fix: missing share changes Signed-off-by: calmacfadden * fix: missing natspec Signed-off-by: calmacfadden * feat: added tests for userRequestSummaries Signed-off-by: calmacfadden * chore: comments Signed-off-by: calmacfadden * chore: formatting --------- Signed-off-by: calmacfadden Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleWithdrawalManager.sol | 14 +- .../IMapleWithdrawalManagerStorage.sol | 7 +- .../proxy/MapleWithdrawalManagerStorage.sol | 9 +- tests/unit/AddShares.t.sol | 25 +++ tests/unit/ProcessExit.t.sol | 6 +- tests/unit/ProcessRedemptions.t.sol | 146 ++++++++++++++++-- tests/unit/RemoveRequest.t.sol | 20 +++ tests/unit/RemoveShares.t.sol | 21 +++ tests/unit/UpdateShares.t.sol | 128 ++++++++++++++- tests/unit/ViewFunctions.t.sol | 24 +-- tests/utils/Harnesses.sol | 5 +- 11 files changed, 356 insertions(+), 49 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index a18424a..8d2dc54 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -285,7 +285,9 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag queue.requests[lastRequestId_] = WithdrawalRequest(owner_, shares_); lastRequestIds[owner_] = lastRequestId_; - requestCount[owner_]++; + + ++userRequestSummaries[owner_].requestCount; + userRequestSummaries[owner_].escrowedSharesTotal += shares_; // Increase the number of shares locked. totalShares += shares_; @@ -310,6 +312,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(owner_, requestId_); } else { queue.requests[requestId_].shares = sharesRemaining_; + userRequestSummaries[owner_].escrowedSharesTotal -= sharesToRemove_; emit RequestDecreased(requestId_, sharesToRemove_); } @@ -398,7 +401,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } else { // Update the withdrawal request. queue.requests[requestId_].shares = sharesRemaining_; - + userRequestSummaries[request_.owner].escrowedSharesTotal -= processedShares_; + emit RequestDecreased(requestId_, processedShares_); } @@ -416,9 +420,11 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function _removeRequest(address owner_, uint128 requestId_) internal { + --userRequestSummaries[owner_].requestCount; + userRequestSummaries[owner_].escrowedSharesTotal -= queue.requests[requestId_].shares; + delete lastRequestIds[owner_]; delete queue.requests[requestId_]; - requestCount[owner_]--; emit RequestRemoved(requestId_); } @@ -514,7 +520,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag ) { WithdrawalRequest memory request_; - uint256 count_ = requestCount[owner_]; + uint256 count_ = userRequestSummaries[owner_].requestCount; uint256 index_; uint256 requestsAdded_; diff --git a/contracts/interfaces/IMapleWithdrawalManagerStorage.sol b/contracts/interfaces/IMapleWithdrawalManagerStorage.sol index eb94f51..a076584 100644 --- a/contracts/interfaces/IMapleWithdrawalManagerStorage.sol +++ b/contracts/interfaces/IMapleWithdrawalManagerStorage.sol @@ -44,10 +44,11 @@ interface IMapleWithdrawalManagerStorage { /** * @dev Returns the number of pending withdrawal requests for a specific user. - * @param account Account to retrieve the request count for. - * @return requestCount Amount of requests pending redemption for the user. + * @param account Account to retrieve the request count for. + * @return requestCount Amount of requests pending redemption for the user. + * @return escrowedSharesTotal Total amount of shares escrowed for the user. */ - function requestCount(address account) external view returns (uint256 requestCount); + function userRequestSummaries(address account) external view returns (uint256 requestCount, uint256 escrowedSharesTotal); /** * @dev Returns the first and last withdrawal requests pending redemption. diff --git a/contracts/proxy/MapleWithdrawalManagerStorage.sol b/contracts/proxy/MapleWithdrawalManagerStorage.sol index dea3368..1f5ef7a 100644 --- a/contracts/proxy/MapleWithdrawalManagerStorage.sol +++ b/contracts/proxy/MapleWithdrawalManagerStorage.sol @@ -20,12 +20,17 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(uint128 => WithdrawalRequest) requests; // Maps withdrawal requests to their positions in the queue. } + struct UserRequestSummary { + uint256 requestCount; + uint256 escrowedSharesTotal; // Escrowed shares yet to be processed. + } + /**************************************************************************************************************************************/ /*** State Variables ***/ /**************************************************************************************************************************************/ uint256 internal _locked; // Used when checking for reentrancy. - + address public override pool; address public override poolManager; @@ -39,5 +44,5 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(address => uint256) public override manualSharesAvailable; // Shares available to withdraw for a given manual owner. - mapping(address => uint256) public override requestCount; // Maps users to the number of pending requests. + mapping(address => UserRequestSummary) public override userRequestSummaries; // Maps users to their request summaries. } diff --git a/tests/unit/AddShares.t.sol b/tests/unit/AddShares.t.sol index f3d7dec..5554ce2 100644 --- a/tests/unit/AddShares.t.sol +++ b/tests/unit/AddShares.t.sol @@ -39,6 +39,11 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 2); + ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 2); + assertEq(escrowedSharesTotal_, 2); + ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); assertEq(owner_, lp); @@ -74,6 +79,11 @@ contract AddSharesTests is TestBase { ( , lastRequestId_ ) = withdrawalManager.queue(); assertEq(lastRequestId_, 1); + + ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 1); + assertEq(escrowedSharesTotal_, 1); } function test_addShares_newRequestAddedToQueue_manual() external { @@ -92,6 +102,11 @@ contract AddSharesTests is TestBase { ( , lastRequestId_ ) = withdrawalManager.queue(); assertEq(lastRequestId_, 1); + + ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 1); + assertEq(escrowedSharesTotal_, 1); } function test_addShares_success() external { @@ -107,6 +122,11 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 1); + ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 1); + assertEq(escrowedSharesTotal_, 1); + ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); ( requestId_,) = getLastRequestByOwner(owner_); @@ -126,6 +146,11 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 2); + ( requestCount_, escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 1); + assertEq(escrowedSharesTotal_, 1); + ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); ( requestId_,) = getLastRequestByOwner(owner_); diff --git a/tests/unit/ProcessExit.t.sol b/tests/unit/ProcessExit.t.sol index c1f6161..6d6da9c 100644 --- a/tests/unit/ProcessExit.t.sol +++ b/tests/unit/ProcessExit.t.sol @@ -45,7 +45,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesToRedeem); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesToRedeem); vm.prank(pm); vm.expectRevert("WM:PE:TOO_MANY_SHARES"); @@ -58,7 +58,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setTotalShares(sharesToRedeem); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesToRedeem); asset.burn(address(pool), assetsDeposited); @@ -73,7 +73,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setTotalShares(sharesToRedeem); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesToRedeem); pool.burn(wm, 1); diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index a8a840d..cf80d7f 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -86,7 +86,12 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); + + ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 1); + assertEq(escrowedSharesTotal_, sharesLocked); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); @@ -101,6 +106,11 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + + ( requestCount_, escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 0); + assertEq(escrowedSharesTotal_, 0); } function test_processRedemptions_manual_multiple_requests() external { @@ -108,7 +118,12 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setRequest(1, lp, sharesLocked / 2); withdrawalManager.__setRequest(2, lp, sharesLocked / 2); withdrawalManager.__setQueue(1, 2); - withdrawalManager.__setUserRequestCount(lp, 2); + withdrawalManager.__setUserRequestCount(lp, 2, sharesLocked); + + ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 2); + assertEq(escrowedSharesTotal_, sharesLocked); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); @@ -124,6 +139,11 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 2, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 3, lastRequestId: 2 }); + + ( requestCount_, escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCount_, 0); + assertEq(escrowedSharesTotal_, 0); } function test_processRedemptions_manual_multipleLps_multiple_requests() external { @@ -139,9 +159,22 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setRequest(3, lp3, sharesLocked / 4); withdrawalManager.__setRequest(4, lp, sharesLocked / 4); withdrawalManager.__setQueue(1, 4); - withdrawalManager.__setUserRequestCount(lp, 2); - withdrawalManager.__setUserRequestCount(lp2, 1); - withdrawalManager.__setUserRequestCount(lp3, 1); + withdrawalManager.__setUserRequestCount(lp, 2, sharesLocked / 2); + withdrawalManager.__setUserRequestCount(lp2, 1, sharesLocked / 4); + withdrawalManager.__setUserRequestCount(lp3, 1, sharesLocked / 4); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); + ( uint256 requestCountLp3_, uint256 escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, sharesLocked / 2); + + assertEq(requestCountLp2_, 1); + assertEq(escrowedSharesTotalLp2_, sharesLocked / 4); + + assertEq(requestCountLp3_, 1); + assertEq(escrowedSharesTotalLp3_, sharesLocked / 4); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); @@ -161,13 +194,31 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 4, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 5, lastRequestId: 4 }); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + ( requestCountLp2_, escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); + ( requestCountLp3_, escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); + + assertEq(requestCountLp2_, 0); + assertEq(escrowedSharesTotalLp2_, 0); + + assertEq(requestCountLp3_, 0); + assertEq(escrowedSharesTotalLp3_, 0); } function test_processRedemptions_manual_partial() external { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked); // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -189,13 +240,23 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 1, owner: lp, shares: sharesLocked / 2 }); assertQueue({ nextRequestId: 1, lastRequestId: 1 }); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked / 2); } function test_processRedemptions_manual_overkill() external { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked); // Add extra liquidity. asset.mint(address(pool), assetsDeposited); @@ -213,12 +274,22 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } function test_processRedemptions_automatic_complete() external { withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked); vm.expectEmit(); emit RequestProcessed(1, lp, sharesLocked, assetsDeposited); @@ -238,12 +309,22 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } function test_processRedemptions_automatic_partial() external { withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked); // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -270,12 +351,22 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 1, owner: lp, shares: sharesLocked / 2 }); assertQueue({ nextRequestId: 1, lastRequestId: 1 }); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked / 2); } function test_processRedemptions_automatic_overkill() external { withdrawalManager.__setRequest(1, lp, sharesLocked); withdrawalManager.__setQueue(1, 1); - withdrawalManager.__setUserRequestCount(lp, 1); + withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, sharesLocked); // Add extra liquidity. asset.mint(address(pool), assetsDeposited); @@ -298,6 +389,11 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 1, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } function test_processRedemptions_multiple() external { @@ -307,8 +403,17 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setRequest(1, lp1, 100e18); withdrawalManager.__setRequest(2, lp2, 150e18); withdrawalManager.__setQueue(1, 2); - withdrawalManager.__setUserRequestCount(lp1, 1); - withdrawalManager.__setUserRequestCount(lp2, 1); + withdrawalManager.__setUserRequestCount(lp1, 1, 100e18); + withdrawalManager.__setUserRequestCount(lp2, 1, 150e18); + + ( uint256 requestCountLp1_, uint256 escrowedSharesTotalLp1_ ) = withdrawalManager.userRequestSummaries(lp1); + ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); + + assertEq(requestCountLp1_, 1); + assertEq(escrowedSharesTotalLp1_, 100e18); + + assertEq(requestCountLp2_, 1); + assertEq(escrowedSharesTotalLp2_, 150e18); vm.expectEmit(); emit RequestProcessed(1, lp1, 100e18, 40e18); @@ -338,6 +443,15 @@ contract ProcessRedemptionsTests is TestBase { assertRequest({ requestId: 2, owner: address(0), shares: 0 }); assertQueue({ nextRequestId: 3, lastRequestId: 2 }); + + ( requestCountLp1_, escrowedSharesTotalLp1_ ) = withdrawalManager.userRequestSummaries(lp1); + ( requestCountLp2_, escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); + + assertEq(requestCountLp1_, 0); + assertEq(escrowedSharesTotalLp1_, 0); + + assertEq(requestCountLp2_, 0); + assertEq(escrowedSharesTotalLp2_, 0); } } @@ -369,10 +483,10 @@ contract ComplexRedemptionTests is TestBase { withdrawalManager.__setTotalShares(totalShares_); withdrawalManager.__setQueue(2, 6); - withdrawalManager.__setUserRequestCount(address(2), 1); - withdrawalManager.__setUserRequestCount(address(3), 1); - withdrawalManager.__setUserRequestCount(address(5), 1); - withdrawalManager.__setUserRequestCount(address(6), 1); + withdrawalManager.__setUserRequestCount(address(2), 1, 100e18); + withdrawalManager.__setUserRequestCount(address(3), 1, 50e18); + withdrawalManager.__setUserRequestCount(address(5), 1, 75e18); + withdrawalManager.__setUserRequestCount(address(6), 1, 25e18); vm.expectEmit(); emit RequestProcessed(2, address(2), 100e18, 40e18); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 89e20c0..3570d36 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -57,6 +57,11 @@ contract RemoveRequestTests is TestBase { vm.prank(pm); withdrawalManager.addShares(2, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); + ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); @@ -90,6 +95,11 @@ contract RemoveRequestTests is TestBase { assertEq(lastRequestIdLp_, 0); assertEq(lastRequestByOwner_, 0); assertEq(withdrawalManager.totalShares(), 0); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } function test_removeRequest_multiple_requests_success() external { @@ -111,6 +121,11 @@ contract RemoveRequestTests is TestBase { assertEq(lastRequestId_, 2); assertEq(lastRequestIdLp_, lastRequestId_); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 4); + uint128[] memory requestIds = new uint128[](2); requestIds[0] = 1; requestIds[1] = 2; @@ -137,6 +152,11 @@ contract RemoveRequestTests is TestBase { assertEq(lastRequestIdLp_, 0); assertEq(lastRequestByOwner_, 0); assertEq(withdrawalManager.totalShares(), 0); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } } diff --git a/tests/unit/RemoveShares.t.sol b/tests/unit/RemoveShares.t.sol index 013cc8b..645cb60 100644 --- a/tests/unit/RemoveShares.t.sol +++ b/tests/unit/RemoveShares.t.sol @@ -65,12 +65,22 @@ contract RemoveSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(2, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); + vm.expectEmit(); emit RequestDecreased(1, 1); vm.prank(pm); withdrawalManager.removeShares(1, lp); + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 1); + ( , lastRequestId_ ) = withdrawalManager.queue(); ( address owner_ , uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); @@ -87,6 +97,12 @@ contract RemoveSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(2, lp); + + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); + vm.expectEmit(); emit RequestRemoved(1); @@ -103,6 +119,11 @@ contract RemoveSharesTests is TestBase { assertEq(owner_, address(0)); assertEq(shares_, 0); assertEq(withdrawalManager.totalShares(), 0); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } } diff --git a/tests/unit/UpdateShares.t.sol b/tests/unit/UpdateShares.t.sol index 623e057..ac2ebc1 100644 --- a/tests/unit/UpdateShares.t.sol +++ b/tests/unit/UpdateShares.t.sol @@ -99,6 +99,11 @@ contract UpdateSharesSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 1); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); @@ -121,12 +126,22 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(shares_.length, 1, "Shares length should be 1"); assertEq(requestIds_[0], 2, "Request ID should be 2"); assertEq(shares_[0], 2, "Shares should be 2"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); } function test_updateShares_remove_request() external{ vm.prank(pm); uint128 requestId_ = withdrawalManager.addShares(1, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 1); + vm.prank(lp); vm.expectEmit(); emit RequestRemoved(requestId_); @@ -134,12 +149,22 @@ contract UpdateSharesSuccessTests is TestBase { uint128 newRequestId_ = withdrawalManager.updateShares(requestId_, 0); assertEq(newRequestId_, 0, "Request ID should be 0"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 0); + assertEq(escrowedSharesTotalLp_, 0); } function test_updateShares_decrease() external { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID Before should be 1 (addShares)"); @@ -161,6 +186,11 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(shares_.length, 1, "Shares length should be 1"); assertEq(requestIds_[0], 1, "Request ID should be 1"); assertEq(shares_[0], 1, "Shares should be 1"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 1); } function test_update_shares_multiple_lps_requestsByOwner() external { @@ -183,6 +213,7 @@ contract UpdateSharesSuccessTests is TestBase { vm.expectEmit(); emit RequestCreated(1, lp, requestAmount1_); withdrawalManager.addShares(requestAmount1_, lp); + vm.expectEmit(); emit RequestCreated(2, lp, requestAmount2_); withdrawalManager.addShares(requestAmount2_, lp); @@ -190,6 +221,7 @@ contract UpdateSharesSuccessTests is TestBase { vm.expectEmit(); emit RequestCreated(3, lp2_, requestAmount1_); withdrawalManager.addShares(requestAmount1_, lp2_); + vm.expectEmit(); emit RequestCreated(4, lp2_, requestAmount2_); withdrawalManager.addShares(requestAmount2_, lp2_); @@ -197,12 +229,29 @@ contract UpdateSharesSuccessTests is TestBase { vm.expectEmit(); emit RequestCreated(5, lp3_, requestAmount1_); withdrawalManager.addShares(requestAmount1_, lp3_); + vm.expectEmit(); emit RequestCreated(6, lp3_, requestAmount2_); withdrawalManager.addShares(requestAmount2_, lp3_); vm.stopPrank(); + + { + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2_); + ( uint256 requestCountLp3_, uint256 escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3_); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, requestAmount1_ + requestAmount2_); + + assertEq(requestCountLp2_, 2); + assertEq(escrowedSharesTotalLp2_, requestAmount1_ + requestAmount2_); + + assertEq(requestCountLp3_, 2); + assertEq(escrowedSharesTotalLp3_, requestAmount1_ + requestAmount2_); + } + { (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); @@ -284,6 +333,21 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(sharesLp3_[0], requestAmount2_, "LP3 shares index 0 is incorrect"); assertEq(sharesLp3_[1], requestAmount2_ + 10, "LP3 shares index 1 is incorrect"); } + + { + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2_); + ( uint256 requestCountLp3_, uint256 escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3_); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, requestAmount1_ - 1 + requestAmount2_); + + assertEq(requestCountLp2_, 1); + assertEq(escrowedSharesTotalLp2_, requestAmount2_); + + assertEq(requestCountLp3_, 2); + assertEq(escrowedSharesTotalLp3_, requestAmount2_ + 10 + requestAmount2_); + } } } @@ -378,6 +442,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 1); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); @@ -409,6 +478,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(shares_.length, 1, "Shares length should be 1"); assertEq(requestIds_[0], 2, "Request ID should be 2"); assertEq(shares_[0], 2, "Shares should be 2"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); } function test_updateSharesBatch_increase_multiple_requests() external { @@ -417,10 +491,15 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(1, lp); vm.stopPrank(); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 2); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); - assertEq(requestId2Before_, 2, "Request ID should be 1"); + assertEq(requestId2Before_, 2, "Request ID should be 2"); assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); assertEq(sharesBefore_.length, 2, "Shares length should be 2"); assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); @@ -454,18 +533,28 @@ contract UpdateSharesBatchSuccessTests is TestBase { (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - assertEq(requestIds_.length, 2, "Request IDs length should be 1"); - assertEq(shares_.length, 2, "Shares length should be 1"); - assertEq(requestIds_[0], 3, "Request ID should be 2"); + assertEq(requestIds_.length, 2, "Request IDs length should be 2"); + assertEq(shares_.length, 2, "Shares length should be 2"); + assertEq(requestIds_[0], 3, "Request ID should be 3"); assertEq(shares_[0], 2, "Shares should be 2"); - assertEq(requestIds_[1], 4, "Request ID should be 3"); + assertEq(requestIds_[1], 4, "Request ID should be 4"); assertEq(shares_[1], 3, "Shares should be 3"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 5); } function test_updateSharesBatch_decrease_single_request() external { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 2); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); @@ -496,6 +585,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(shares_.length, 1, "Shares length should be 1"); assertEq(requestIds_[0], 1, "Request ID should be 1"); assertEq(shares_[0], 1, "Shares should be 1"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 1); + assertEq(escrowedSharesTotalLp_, 1); } function test_updateSharesBatch_decrease_multiple_requests() external { @@ -504,6 +598,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 4); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); @@ -539,12 +638,17 @@ contract UpdateSharesBatchSuccessTests is TestBase { (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - assertEq(requestIds_.length, 2, "Request IDs length should be 1"); - assertEq(shares_.length, 2, "Shares length should be 1"); + assertEq(requestIds_.length, 2, "Request IDs length should be 2"); + assertEq(shares_.length, 2, "Shares length should be 2"); assertEq(requestIds_[0], 1, "Request ID should be 1"); assertEq(shares_[0], 1, "Shares should be 1"); assertEq(requestIds_[1], 2, "Request ID should be 2"); assertEq(shares_[1], 1, "Shares should be 1"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 2); } function test_updateSharesBatch_increase_and_decrease_multiple_requests() external { @@ -553,6 +657,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); + ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 3); + (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); @@ -595,6 +704,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(shares_[0], 1, "Shares should be 1"); assertEq(requestIds_[1], 3, "Request ID should be 3"); assertEq(shares_[1], 2, "Shares should be 2"); + + ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + + assertEq(requestCountLp_, 2); + assertEq(escrowedSharesTotalLp_, 3); } } diff --git a/tests/unit/ViewFunctions.t.sol b/tests/unit/ViewFunctions.t.sol index 57c88dc..1bf41d5 100644 --- a/tests/unit/ViewFunctions.t.sol +++ b/tests/unit/ViewFunctions.t.sol @@ -38,10 +38,10 @@ contract ViewFunctionsTests is TestBase { withdrawalManager.__setRequest(4, lp4, assetsDeposited_); withdrawalManager.__setRequest(5, lp1, assetsDeposited_); - withdrawalManager.__setUserRequestCount(lp1, 2); - withdrawalManager.__setUserRequestCount(lp2, 1); - withdrawalManager.__setUserRequestCount(lp3, 1); - withdrawalManager.__setUserRequestCount(lp4, 1); + withdrawalManager.__setUserRequestCount(lp1, 2, assetsDeposited_ * 2); + withdrawalManager.__setUserRequestCount(lp2, 1, assetsDeposited_); + withdrawalManager.__setUserRequestCount(lp3, 1, assetsDeposited_); + withdrawalManager.__setUserRequestCount(lp4, 1, assetsDeposited_); withdrawalManager.__setQueue(1, 5); @@ -92,10 +92,10 @@ contract ViewFunctionsTests is TestBase { withdrawalManager.__setRequest(4, lp4, assetsDeposited_); withdrawalManager.__setRequest(5, lp1, assetsDeposited_); - withdrawalManager.__setUserRequestCount(lp1, 2); - withdrawalManager.__setUserRequestCount(lp2, 1); - withdrawalManager.__setUserRequestCount(lp3, 1); - withdrawalManager.__setUserRequestCount(lp4, 1); + withdrawalManager.__setUserRequestCount(lp1, 2, assetsDeposited_ * 2); + withdrawalManager.__setUserRequestCount(lp2, 1, assetsDeposited_); + withdrawalManager.__setUserRequestCount(lp3, 1, assetsDeposited_); + withdrawalManager.__setUserRequestCount(lp4, 1, assetsDeposited_); withdrawalManager.__setQueue(1, 5); @@ -145,10 +145,10 @@ contract ViewFunctionsTests is TestBase { withdrawalManager.__setRequest(4, lp4, assetsDeposited_); withdrawalManager.__setRequest(5, lp, assetsDeposited_); - withdrawalManager.__setUserRequestCount(lp, 2); - withdrawalManager.__setUserRequestCount(lp2, 1); - withdrawalManager.__setUserRequestCount(lp3, 1); - withdrawalManager.__setUserRequestCount(lp4, 1); + withdrawalManager.__setUserRequestCount(lp, 2, assetsDeposited_ * 2); + withdrawalManager.__setUserRequestCount(lp2, 1, assetsDeposited_); + withdrawalManager.__setUserRequestCount(lp3, 1, assetsDeposited_); + withdrawalManager.__setUserRequestCount(lp4, 1, assetsDeposited_); withdrawalManager.__setQueue(1, 5); diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index 6f4cdbf..33af81a 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -35,8 +35,9 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { totalShares = totalShares_; } - function __setUserRequestCount(address owner_, uint256 requestCount_) external { - requestCount[owner_] = requestCount_; + function __setUserRequestCount(address owner_, uint256 requestCount_, uint256 escrowSharesTotal_) external { + userRequestSummaries[owner_].requestCount = requestCount_; + userRequestSummaries[owner_].escrowedSharesTotal = escrowSharesTotal_; } } From 0b48f84c549c37a0298a10e16041cecbd2804807 Mon Sep 17 00:00:00 2001 From: Danilo Kitanovic <116795362+kitanovicd@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:13:04 +0200 Subject: [PATCH 05/21] fix: keep track of withdraw requests per user (SC-21032) (#34) * fix: keep track of withdraw requests per user * feat: keep only active redeem requests in userRequests array * fix: update failing tests and fix indexes * fix: bring back console import * chore: import style * chore: library format * feat: upgrade removeRequest test for more complex scenario * fix: remove userRequestSummaries and bring back userRequests function * chore: rename mapping * fix: remove isPresent mapping * fix: renamings * feat: unit tests for library * fix: remove empty lines at the end of the files * Revert "fix: remove empty lines at the end of the files" This reverts commit 312e3207f9b6a17a8944d9540dc2273156bd9968. * chore: format * chore: formatting * fix: add extra require statements * feat: add invalid range test * fix: rename removeByValue to remove * fix: remove padination request getter * chore: Fix formatting, return value and test ordering * chore: Add TODO --------- Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleWithdrawalManager.sol | 74 ++++------- .../interfaces/IMapleWithdrawalManager.sol | 23 ++-- .../IMapleWithdrawalManagerStorage.sol | 17 +-- .../proxy/MapleWithdrawalManagerStorage.sol | 14 +- contracts/utils/SortedArray.sol | 114 +++++++++++++++++ tests/unit/AddShares.t.sol | 25 +--- tests/unit/ProcessExit.t.sol | 15 ++- tests/unit/ProcessRedemptions.t.sol | 120 ++++-------------- tests/unit/RemoveRequest.t.sol | 68 ++++++---- tests/unit/RemoveShares.t.sol | 20 +-- tests/unit/SortedArray/Get.t.sol | 29 +++++ tests/unit/SortedArray/Push.t.sol | 46 +++++++ tests/unit/SortedArray/Remove.t.sol | 51 ++++++++ tests/unit/UpdateShares.t.sol | 114 ++++------------- tests/unit/ViewFunctions.t.sol | 54 -------- tests/utils/Harnesses.sol | 54 +++++++- tests/utils/TestBase.sol | 27 ++++ 17 files changed, 465 insertions(+), 400 deletions(-) create mode 100644 contracts/utils/SortedArray.sol create mode 100644 tests/unit/SortedArray/Get.t.sol create mode 100644 tests/unit/SortedArray/Push.t.sol create mode 100644 tests/unit/SortedArray/Remove.t.sol diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 8d2dc54..c1f0533 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -16,6 +16,8 @@ import { import { MapleWithdrawalManagerStorage } from "./proxy/MapleWithdrawalManagerStorage.sol"; +import { SortedArray } from "./utils/SortedArray.sol"; + /* ███╗ ███╗ █████╗ ██████╗ ██╗ ███████╗ @@ -152,9 +154,9 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function removeShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint256 sharesReturned_) { - require(shares_ > 0, "WM:RS:ZERO_SHARES"); + require(shares_ > 0, "WM:RS:ZERO_SHARES"); - uint128 lastRequestId_ = lastRequestIds[owner_]; + uint128 lastRequestId_ = SortedArray.getLast(userRequests[owner_]); require(lastRequestId_ > 0, "WM:RS:NO_REQUESTS"); @@ -284,10 +286,9 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag lastRequestId_ = ++queue.lastRequestId; queue.requests[lastRequestId_] = WithdrawalRequest(owner_, shares_); - lastRequestIds[owner_] = lastRequestId_; - - ++userRequestSummaries[owner_].requestCount; - userRequestSummaries[owner_].escrowedSharesTotal += shares_; + userEscrowedShares[owner_] += shares_; + + SortedArray.push(userRequests[owner_], lastRequestId_); // Increase the number of shares locked. totalShares += shares_; @@ -312,7 +313,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(owner_, requestId_); } else { queue.requests[requestId_].shares = sharesRemaining_; - userRequestSummaries[owner_].escrowedSharesTotal -= sharesToRemove_; + userEscrowedShares[owner_] -= sharesToRemove_; emit RequestDecreased(requestId_, sharesToRemove_); } @@ -400,9 +401,9 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(request_.owner, requestId_); } else { // Update the withdrawal request. - queue.requests[requestId_].shares = sharesRemaining_; - userRequestSummaries[request_.owner].escrowedSharesTotal -= processedShares_; - + queue.requests[requestId_].shares = sharesRemaining_; + userEscrowedShares[request_.owner] -= processedShares_; + emit RequestDecreased(requestId_, processedShares_); } @@ -420,10 +421,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function _removeRequest(address owner_, uint128 requestId_) internal { - --userRequestSummaries[owner_].requestCount; - userRequestSummaries[owner_].escrowedSharesTotal -= queue.requests[requestId_].shares; - - delete lastRequestIds[owner_]; + userEscrowedShares[owner_] -= queue.requests[requestId_].shares; + SortedArray.remove(userRequests[owner_], requestId_); delete queue.requests[requestId_]; emit RequestRemoved(requestId_); @@ -499,50 +498,21 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag return ( redeemableAssets_, resultingShares_ ); // NOTE: Withdrawal not implemented use redeem instead } + function requestIds(address owner_) external view override returns (uint128 requestId_) { + requestId_ = SortedArray.getLast(userRequests[owner_]); + } + function requests(uint128 requestId_) external view override returns (address owner_, uint256 shares_) { owner_ = queue.requests[requestId_].owner; shares_ = queue.requests[requestId_].shares; } - function requestsByOwner(address owner_) - external view override returns ( - uint128[] memory requestIds_, - uint256[] memory shares_ - ) - { - ( requestIds_, shares_ ) = requestsByOwnerRange(owner_, queue.nextRequestId, queue.lastRequestId); - } - - function requestsByOwnerRange(address owner_, uint128 firstRequestId_, uint128 lastRequestId_) - public view override returns ( - uint128[] memory requestIds_, - uint256[] memory shares_ - ) - { - WithdrawalRequest memory request_; - uint256 count_ = userRequestSummaries[owner_].requestCount; - uint256 index_; - uint256 requestsAdded_; - - requestIds_ = new uint128[](count_); - shares_ = new uint256[](count_); + function requestsByOwner(address owner_) external view override returns (uint128[] memory requestIds_, uint256[] memory shares_) { + requestIds_ = SortedArray.getAllValues(userRequests[owner_]); + shares_ = new uint256[](requestIds_.length); - while (firstRequestId_ <= lastRequestId_) { - - if(requestsAdded_ == count_) { - break; - } - - request_ = queue.requests[firstRequestId_]; - - if (request_.owner == owner_) { - requestIds_[index_] = firstRequestId_; - shares_[index_] = request_.shares; - ++index_; - ++requestsAdded_; - } - - ++firstRequestId_; + for (uint256 i = 0; i < requestIds_.length; ++i) { + shares_[i] = queue.requests[requestIds_[i]].shares; } } diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index d1ebecf..9d01d72 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -208,6 +208,14 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function previewWithdraw(address owner, uint256 assets) external view returns (uint256 redeemableAssets, uint256 resultingShares); + /** + * @dev Returns the last request id for a given owner. + * Function must exist for backwards compatibility with the old implementation where we supported only one request per owner. + * @param owner The account to check the last request id for. + * @return requestId The id of the last valid withdrawal request for the account. + */ + function requestIds(address owner) external view returns (uint128 requestId); + /** * @dev Returns the owner and amount of shares associated with a withdrawal request. * @param requestId Identifier of the withdrawal request. @@ -225,21 +233,6 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function requestsByOwner(address owner) external view returns (uint128[] memory requestIds, uint256[] memory shares); - /** - * @dev Returns the pending requests by owner and request ID range. - * Only returns requests that belong to the specified owner. - * @param owner Address of the account to check for pending requests. - * @param firstRequestId First request ID to include in the result. - * @param lastRequestId Last request ID to include in the result. - * @return requestIds Array of request identifiers. - * @return shares Array of shares associated with each request. - */ - function requestsByOwnerRange(address owner, uint128 firstRequestId, uint128 lastRequestId) - external view returns ( - uint128[] memory requestIds, - uint256[] memory shares - ); - /** * @dev Returns the address of the security admin. * @param securityAdmin Address of the security admin. diff --git a/contracts/interfaces/IMapleWithdrawalManagerStorage.sol b/contracts/interfaces/IMapleWithdrawalManagerStorage.sol index a076584..7288bc7 100644 --- a/contracts/interfaces/IMapleWithdrawalManagerStorage.sol +++ b/contracts/interfaces/IMapleWithdrawalManagerStorage.sol @@ -3,13 +3,6 @@ pragma solidity ^0.8.7; interface IMapleWithdrawalManagerStorage { - /** - * @dev Returns the identifier of the last withdrawal request made by a specific user. - * @param account Address of the user. - * @return requestId Identifier of the last withdrawal request made by the user. - */ - function lastRequestIds(address account) external view returns (uint128 requestId); - /** * @dev Returns the address of the pool contract. * @return pool Address of the pool contract. @@ -43,12 +36,11 @@ interface IMapleWithdrawalManagerStorage { function manualSharesAvailable(address owner) external view returns (uint256 sharesAvailable); /** - * @dev Returns the number of pending withdrawal requests for a specific user. - * @param account Account to retrieve the request count for. - * @return requestCount Amount of requests pending redemption for the user. - * @return escrowedSharesTotal Total amount of shares escrowed for the user. + * @dev Returns the amount of shares escrowed for a specific user yet to be processed. + * @param owner The address of the owner of shares. + * @return escrowedShares Amount of shares escrowed for the user. */ - function userRequestSummaries(address account) external view returns (uint256 requestCount, uint256 escrowedSharesTotal); + function userEscrowedShares(address owner) external view returns (uint256 escrowedShares); /** * @dev Returns the first and last withdrawal requests pending redemption. @@ -56,4 +48,5 @@ interface IMapleWithdrawalManagerStorage { * @return lastRequestId Identifier of the last created withdrawal request. */ function queue() external view returns (uint128 nextRequestId, uint128 lastRequestId); + } diff --git a/contracts/proxy/MapleWithdrawalManagerStorage.sol b/contracts/proxy/MapleWithdrawalManagerStorage.sol index 1f5ef7a..42510ad 100644 --- a/contracts/proxy/MapleWithdrawalManagerStorage.sol +++ b/contracts/proxy/MapleWithdrawalManagerStorage.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.7; import { IMapleWithdrawalManagerStorage } from "../interfaces/IMapleWithdrawalManagerStorage.sol"; +import { SortedArray } from "../utils/SortedArray.sol"; + contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { /**************************************************************************************************************************************/ @@ -20,11 +22,6 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(uint128 => WithdrawalRequest) requests; // Maps withdrawal requests to their positions in the queue. } - struct UserRequestSummary { - uint256 requestCount; - uint256 escrowedSharesTotal; // Escrowed shares yet to be processed. - } - /**************************************************************************************************************************************/ /*** State Variables ***/ /**************************************************************************************************************************************/ @@ -40,9 +37,12 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(address => bool) public override isManualWithdrawal; // Defines which users use automated withdrawals (false by default). - mapping(address => uint128) public override lastRequestIds; // Maps users to their last withdrawal request. + mapping(address => uint128) internal __deprecated_requestIds; // Maps users to their last withdrawal request. mapping(address => uint256) public override manualSharesAvailable; // Shares available to withdraw for a given manual owner. - mapping(address => UserRequestSummary) public override userRequestSummaries; // Maps users to their request summaries. + mapping(address => uint256) public override userEscrowedShares; // Maps users to their escrowed shares yet to be processed. + + mapping(address => SortedArray.Array) internal userRequests; // Maps users to their withdrawal requests. + } diff --git a/contracts/utils/SortedArray.sol b/contracts/utils/SortedArray.sol new file mode 100644 index 0000000..d711c75 --- /dev/null +++ b/contracts/utils/SortedArray.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +// TODO: How to best handle library interfaces? +library SortedArray { + + struct Array { + uint128[] values; + + mapping(uint128 => uint256) valueToIndex; + } + + /**************************************************************************************************************************************/ + /*** Write Functions ***/ + /**************************************************************************************************************************************/ + + /** + * @dev Pushes a value to the array. + * It is expected that the value is biggest so far so it will be added at the end of the array. + * @param array_ The array to push the value to. + * @param value_ The value to push to the array. + */ + function push(Array storage array_, uint128 value_) internal { + require(getLast(array_) < value_, "SA:P:NOT_LARGEST"); + + array_.values.push(value_); + array_.valueToIndex[value_] = length(array_) - 1; + } + + /** + * @dev Removes a value from the array. + * It shifts the rest of the array to the left. + * It is expected by contract that uses this library to check that the value is present in the array before call of the function. + * @param array_ The array to remove the value from. + * @param value_ The value to remove from the array. + */ + function remove(Array storage array_, uint128 value_) internal { + uint256 length_ = length(array_); + + if (length_ == 1) { + _deleteValue(array_, value_); + return; + } + + uint256 index_ = array_.valueToIndex[value_]; + + for (uint256 i = index_; i <= length_ - 2; i++) { + uint128 nextValue_ = array_.values[i + 1]; + array_.values[i] = nextValue_; + array_.valueToIndex[nextValue_] = i; + } + + _deleteValue(array_, value_); + } + + /**************************************************************************************************************************************/ + /*** View Functions ***/ + /**************************************************************************************************************************************/ + + /** + * @dev Gets a value from the array at given index. + * @param array_ The array to get the value from. + * @param index_ The index of the value to get from the array. + * @return value_ The value at the given index. + */ + function get(Array storage array_, uint256 index_) internal view returns (uint128 value_) { + require(index_ < length(array_), "SA:G:OUT_OF_BOUNDS"); + value_ = array_.values[index_]; + } + + /** + * @dev Gets the length of the array. + * @param array_ The array to get the length of. + * @return length_ The length of the array. + */ + function length(Array storage array_) internal view returns (uint256 length_) { + length_ = array_.values.length; + } + + /** + * @dev Gets all values from the array. + * @param array_ The array to get the values from. + * @return values_ All values from the array. + */ + function getAllValues(Array storage array_) internal view returns (uint128[] memory values_) { + values_ = array_.values; + } + + /** + * @dev Gets the last value in the array. + * @param array_ The array to get the last value from. + * @return value_ The last value in the array. + */ + function getLast(Array storage array_) internal view returns (uint128 value_) { + uint256 length_ = length(array_); + return length_ > 0 ? array_.values[length_ - 1] : 0; + } + + /**************************************************************************************************************************************/ + /*** Private Functions ***/ + /**************************************************************************************************************************************/ + + /** + * @dev Deletes a value from the array. + * It is expected that array is already shifted so this will just pop the last and flag given value as not present. + * @param array_ The array to delete the value from. + * @param value_ The value to delete from the array. + */ + function _deleteValue(Array storage array_, uint128 value_) private { + array_.values.pop(); + delete array_.valueToIndex[value_]; + } + +} diff --git a/tests/unit/AddShares.t.sol b/tests/unit/AddShares.t.sol index 5554ce2..8d148d8 100644 --- a/tests/unit/AddShares.t.sol +++ b/tests/unit/AddShares.t.sol @@ -39,10 +39,7 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 2); - ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 2); - assertEq(escrowedSharesTotal_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); @@ -80,10 +77,7 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 1); - ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 1); - assertEq(escrowedSharesTotal_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); } function test_addShares_newRequestAddedToQueue_manual() external { @@ -103,10 +97,7 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 1); - ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 1); - assertEq(escrowedSharesTotal_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); } function test_addShares_success() external { @@ -122,10 +113,7 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 1); - ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 1); - assertEq(escrowedSharesTotal_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); ( requestId_,) = getLastRequestByOwner(owner_); @@ -146,10 +134,7 @@ contract AddSharesTests is TestBase { assertEq(lastRequestId_, 2); - ( requestCount_, escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 1); - assertEq(escrowedSharesTotal_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); diff --git a/tests/unit/ProcessExit.t.sol b/tests/unit/ProcessExit.t.sol index 6d6da9c..611944e 100644 --- a/tests/unit/ProcessExit.t.sol +++ b/tests/unit/ProcessExit.t.sol @@ -86,7 +86,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setRequest(1, lp, sharesToRedeem); withdrawalManager.__setTotalShares(sharesToRedeem); - withdrawalManager.__setLastRequest(lp, 0); + withdrawalManager.__setLastRequest(lp, 2); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem); assertEq(pool.balanceOf(lp), 0); @@ -98,8 +98,12 @@ contract ProcessExitTests is TestBase { assertEq(pool.balanceOf(lp), sharesToRedeem); assertEq(pool.balanceOf(wm), 0); + ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + assertEq(lastRequestId_, 2); + + assertEq(withdrawalManager.requestIds(lp), lastRequestId_); + assertEq(withdrawalManager.totalShares(), 0); - assertEq(withdrawalManager.lastRequestIds(lp), 0); assertEq(withdrawalManager.manualSharesAvailable(lp), 0); assertRequest({ requestId: 1, owner: lp, shares: sharesToRedeem }); @@ -123,9 +127,12 @@ contract ProcessExitTests is TestBase { assertEq(pool.balanceOf(lp), sharesToRedeem / 2); assertEq(pool.balanceOf(wm), sharesToRedeem / 2); - assertEq(withdrawalManager.totalShares(), sharesToRedeem / 2); + ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + assertEq(lastRequestId_, 1); - assertEq(withdrawalManager.lastRequestIds(lp), 1); + assertEq(withdrawalManager.requestIds(lp), lastRequestId_); + + assertEq(withdrawalManager.totalShares(), sharesToRedeem / 2); assertRequest({ requestId: 1, owner: lp, shares: sharesToRedeem / 2 }); } diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index cf80d7f..3db5e2c 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -88,10 +88,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 1); withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); - ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 1); - assertEq(escrowedSharesTotal_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); @@ -107,10 +104,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 2, lastRequestId: 1 }); - ( requestCount_, escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 0); - assertEq(escrowedSharesTotal_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_processRedemptions_manual_multiple_requests() external { @@ -120,10 +114,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 2); withdrawalManager.__setUserRequestCount(lp, 2, sharesLocked); - ( uint256 requestCount_, uint256 escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 2); - assertEq(escrowedSharesTotal_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); @@ -140,10 +131,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 3, lastRequestId: 2 }); - ( requestCount_, escrowedSharesTotal_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCount_, 0); - assertEq(escrowedSharesTotal_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_processRedemptions_manual_multipleLps_multiple_requests() external { @@ -163,18 +151,9 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setUserRequestCount(lp2, 1, sharesLocked / 4); withdrawalManager.__setUserRequestCount(lp3, 1, sharesLocked / 4); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); - ( uint256 requestCountLp3_, uint256 escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, sharesLocked / 2); - - assertEq(requestCountLp2_, 1); - assertEq(escrowedSharesTotalLp2_, sharesLocked / 4); - - assertEq(requestCountLp3_, 1); - assertEq(escrowedSharesTotalLp3_, sharesLocked / 4); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked / 2); + assertEq(withdrawalManager.userEscrowedShares(lp2), sharesLocked / 4); + assertEq(withdrawalManager.userEscrowedShares(lp3), sharesLocked / 4); vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); @@ -195,18 +174,9 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 5, lastRequestId: 4 }); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - ( requestCountLp2_, escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); - ( requestCountLp3_, escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); - - assertEq(requestCountLp2_, 0); - assertEq(escrowedSharesTotalLp2_, 0); - - assertEq(requestCountLp3_, 0); - assertEq(escrowedSharesTotalLp3_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); + assertEq(withdrawalManager.userEscrowedShares(lp2), 0); + assertEq(withdrawalManager.userEscrowedShares(lp3), 0); } function test_processRedemptions_manual_partial() external { @@ -215,10 +185,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 1); withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -241,10 +208,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 1, lastRequestId: 1 }); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked / 2); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked / 2); } function test_processRedemptions_manual_overkill() external { @@ -253,10 +217,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 1); withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); // Add extra liquidity. asset.mint(address(pool), assetsDeposited); @@ -275,10 +236,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 2, lastRequestId: 1 }); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_processRedemptions_automatic_complete() external { @@ -286,10 +244,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 1); withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); vm.expectEmit(); emit RequestProcessed(1, lp, sharesLocked, assetsDeposited); @@ -310,10 +265,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 2, lastRequestId: 1 }); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_processRedemptions_automatic_partial() external { @@ -321,10 +273,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 1); withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); @@ -352,10 +301,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 1, lastRequestId: 1 }); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked / 2); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked / 2); } function test_processRedemptions_automatic_overkill() external { @@ -363,10 +309,7 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setQueue(1, 1); withdrawalManager.__setUserRequestCount(lp, 1, sharesLocked); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, sharesLocked); + assertEq(withdrawalManager.userEscrowedShares(lp), sharesLocked); // Add extra liquidity. asset.mint(address(pool), assetsDeposited); @@ -390,10 +333,7 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 2, lastRequestId: 1 }); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_processRedemptions_multiple() external { @@ -406,14 +346,8 @@ contract ProcessRedemptionsTests is TestBase { withdrawalManager.__setUserRequestCount(lp1, 1, 100e18); withdrawalManager.__setUserRequestCount(lp2, 1, 150e18); - ( uint256 requestCountLp1_, uint256 escrowedSharesTotalLp1_ ) = withdrawalManager.userRequestSummaries(lp1); - ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); - - assertEq(requestCountLp1_, 1); - assertEq(escrowedSharesTotalLp1_, 100e18); - - assertEq(requestCountLp2_, 1); - assertEq(escrowedSharesTotalLp2_, 150e18); + assertEq(withdrawalManager.userEscrowedShares(lp1), 100e18); + assertEq(withdrawalManager.userEscrowedShares(lp2), 150e18); vm.expectEmit(); emit RequestProcessed(1, lp1, 100e18, 40e18); @@ -444,14 +378,8 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 3, lastRequestId: 2 }); - ( requestCountLp1_, escrowedSharesTotalLp1_ ) = withdrawalManager.userRequestSummaries(lp1); - ( requestCountLp2_, escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2); - - assertEq(requestCountLp1_, 0); - assertEq(escrowedSharesTotalLp1_, 0); - - assertEq(requestCountLp2_, 0); - assertEq(escrowedSharesTotalLp2_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp1), 0); + assertEq(withdrawalManager.userEscrowedShares(lp2), 0); } } diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 3570d36..9b7f371 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -57,10 +57,7 @@ contract RemoveRequestTests is TestBase { vm.prank(pm); withdrawalManager.addShares(2, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); @@ -96,18 +93,16 @@ contract RemoveRequestTests is TestBase { assertEq(lastRequestByOwner_, 0); assertEq(withdrawalManager.totalShares(), 0); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_removeRequest_multiple_requests_success() external { uint128 lastRequestIdLp_; vm.startPrank(pm); - withdrawalManager.addShares(2, lp); - withdrawalManager.addShares(2, lp); + withdrawalManager.addShares(1, lp); + withdrawalManager.addShares(1, lp); + withdrawalManager.addShares(1, lp); vm.stopPrank(); ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); @@ -116,22 +111,15 @@ contract RemoveRequestTests is TestBase { ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); - assertEq(shares_, 2); - assertEq(withdrawalManager.totalShares(), 4); - assertEq(lastRequestId_, 2); + assertEq(shares_, 1); + assertEq(withdrawalManager.totalShares(), 3); + assertEq(lastRequestId_, 3); assertEq(lastRequestIdLp_, lastRequestId_); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); + assertEq(withdrawalManager.userEscrowedShares(lp), 3); - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 4); - - uint128[] memory requestIds = new uint128[](2); - requestIds[0] = 1; - requestIds[1] = 2; - - vm.expectEmit(); - emit RequestRemoved(1); + uint128[] memory requestIds = new uint128[](1); + requestIds[0] = 2; vm.expectEmit(); emit RequestRemoved(2); @@ -146,17 +134,41 @@ contract RemoveRequestTests is TestBase { ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); ( uint128 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); - assertEq(lastRequestId_, 2); + assertEq(lastRequestId_, 3); + assertEq(shares_, 1); + assertEq(owner_, lp); + assertEq(lastRequestIdLp_, 3); + assertEq(lastRequestByOwner_, 3); + assertEq(withdrawalManager.totalShares(), 2); + + assertEq(withdrawalManager.userEscrowedShares(lp), 2); + + requestIds = new uint128[](2); + requestIds[0] = 1; + requestIds[1] = 3; + + vm.expectEmit(); + emit RequestRemoved(1); + emit RequestRemoved(3); + + vm.prank(poolDelegate); + withdrawalManager.removeRequest(lp, requestIds); + + ( , lastRequestId_ ) = withdrawalManager.queue(); + + ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); + + ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); + ( lastRequestByOwner_, ) = getLastRequestByOwner(owner_); + + assertEq(lastRequestId_, 3); assertEq(shares_, 0); assertEq(owner_, address(0)); assertEq(lastRequestIdLp_, 0); assertEq(lastRequestByOwner_, 0); assertEq(withdrawalManager.totalShares(), 0); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } } diff --git a/tests/unit/RemoveShares.t.sol b/tests/unit/RemoveShares.t.sol index 645cb60..dd0f897 100644 --- a/tests/unit/RemoveShares.t.sol +++ b/tests/unit/RemoveShares.t.sol @@ -65,10 +65,7 @@ contract RemoveSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(2, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); vm.expectEmit(); emit RequestDecreased(1, 1); @@ -76,10 +73,7 @@ contract RemoveSharesTests is TestBase { vm.prank(pm); withdrawalManager.removeShares(1, lp); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); ( , lastRequestId_ ) = withdrawalManager.queue(); @@ -98,10 +92,7 @@ contract RemoveSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(2, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); vm.expectEmit(); emit RequestRemoved(1); @@ -120,10 +111,7 @@ contract RemoveSharesTests is TestBase { assertEq(shares_, 0); assertEq(withdrawalManager.totalShares(), 0); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } } diff --git a/tests/unit/SortedArray/Get.t.sol b/tests/unit/SortedArray/Get.t.sol new file mode 100644 index 0000000..5d661d1 --- /dev/null +++ b/tests/unit/SortedArray/Get.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { SortedArrayTestBase } from "../../utils/TestBase.sol"; + +contract GetTests is SortedArrayTestBase { + + function test_get_outOfBounds() external { + array.push(1); + vm.expectRevert("SA:G:OUT_OF_BOUNDS"); + array.get(1); + } + + function test_get_singleValue() external { + array.push(1); + assertEq(array.get(0), 1); + } + + function test_get_multipleValues() external { + array.push(1); + array.push(2); + array.push(3); + + assertEq(array.get(0), 1); + assertEq(array.get(1), 2); + assertEq(array.get(2), 3); + } + +} diff --git a/tests/unit/SortedArray/Push.t.sol b/tests/unit/SortedArray/Push.t.sol new file mode 100644 index 0000000..410183e --- /dev/null +++ b/tests/unit/SortedArray/Push.t.sol @@ -0,0 +1,46 @@ + +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { SortedArrayTestBase } from "../../utils/TestBase.sol"; + +// TODO: Update tests to use sequential numbers with gaps. +contract PushTests is SortedArrayTestBase { + + function test_push_failed_outOfOrder() external { + array.push(3); + + vm.expectRevert("SA:P:NOT_LARGEST"); + array.push(1); + } + + function test_push_singleValue() external { + array.push(1); + + uint128[] memory expectedValues = new uint128[](1); + expectedValues[0] = 1; + + assertArray(expectedValues); + assertElementAtIndex(0, 1); + + assertEq(array.length(), 1); + } + + function test_push_multipleValues_inOrder() external { + array.push(1); + array.push(2); + array.push(3); + + uint128[] memory expectedValues = new uint128[](3); + expectedValues[0] = 1; + expectedValues[1] = 2; + expectedValues[2] = 3; + + assertArray(expectedValues); + assertElementAtIndex(0, 1); + assertElementAtIndex(1, 2); + assertElementAtIndex(2, 3); + assertEq(array.length(), 3); + } + +} diff --git a/tests/unit/SortedArray/Remove.t.sol b/tests/unit/SortedArray/Remove.t.sol new file mode 100644 index 0000000..fc8f39d --- /dev/null +++ b/tests/unit/SortedArray/Remove.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { SortedArrayTestBase } from "../../utils/TestBase.sol"; + +contract RemoveTests is SortedArrayTestBase { + + function test_remove_singleValue() external { + array.push(1); + array.remove(1); + + uint128[] memory expectedValues = new uint128[](0); + assertArray(expectedValues); + + assertEq(array.length(), 0); + } + + function test_remove_multipleValues() external { + array.push(1); + array.push(2); + array.push(3); + + array.remove(2); + + uint128[] memory expectedValues = new uint128[](2); + expectedValues[0] = 1; + expectedValues[1] = 3; + + assertArray(expectedValues); + assertElementAtIndex(0, 1); + assertElementAtIndex(1, 3); + + assertEq(array.length(), 2); + + array.remove(1); + + expectedValues = new uint128[](1); + expectedValues[0] = 3; + + assertArray(expectedValues); + assertElementAtIndex(0, 3); + + assertEq(array.length(), 1); + + array.remove(3); + + assertArray(new uint128[](0)); + assertEq(array.length(), 0); + } + +} diff --git a/tests/unit/UpdateShares.t.sol b/tests/unit/UpdateShares.t.sol index ac2ebc1..bea8a77 100644 --- a/tests/unit/UpdateShares.t.sol +++ b/tests/unit/UpdateShares.t.sol @@ -99,10 +99,7 @@ contract UpdateSharesSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -127,20 +124,14 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(requestIds_[0], 2, "Request ID should be 2"); assertEq(shares_[0], 2, "Shares should be 2"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); } function test_updateShares_remove_request() external{ vm.prank(pm); uint128 requestId_ = withdrawalManager.addShares(1, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); vm.prank(lp); vm.expectEmit(); @@ -150,20 +141,14 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(newRequestId_, 0, "Request ID should be 0"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 0); - assertEq(escrowedSharesTotalLp_, 0); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); } function test_updateShares_decrease() external { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -187,10 +172,7 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(requestIds_[0], 1, "Request ID should be 1"); assertEq(shares_[0], 1, "Shares should be 1"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); } function test_update_shares_multiple_lps_requestsByOwner() external { @@ -237,20 +219,9 @@ contract UpdateSharesSuccessTests is TestBase { vm.stopPrank(); - { - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2_); - ( uint256 requestCountLp3_, uint256 escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3_); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, requestAmount1_ + requestAmount2_); - - assertEq(requestCountLp2_, 2); - assertEq(escrowedSharesTotalLp2_, requestAmount1_ + requestAmount2_); - - assertEq(requestCountLp3_, 2); - assertEq(escrowedSharesTotalLp3_, requestAmount1_ + requestAmount2_); - } + assertEq(withdrawalManager.userEscrowedShares(lp), requestAmount1_ + requestAmount2_); + assertEq(withdrawalManager.userEscrowedShares(lp2_), requestAmount1_ + requestAmount2_); + assertEq(withdrawalManager.userEscrowedShares(lp3_), requestAmount1_ + requestAmount2_); { (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); @@ -334,20 +305,9 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(sharesLp3_[1], requestAmount2_ + 10, "LP3 shares index 1 is incorrect"); } - { - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - ( uint256 requestCountLp2_, uint256 escrowedSharesTotalLp2_ ) = withdrawalManager.userRequestSummaries(lp2_); - ( uint256 requestCountLp3_, uint256 escrowedSharesTotalLp3_ ) = withdrawalManager.userRequestSummaries(lp3_); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, requestAmount1_ - 1 + requestAmount2_); - - assertEq(requestCountLp2_, 1); - assertEq(escrowedSharesTotalLp2_, requestAmount2_); - - assertEq(requestCountLp3_, 2); - assertEq(escrowedSharesTotalLp3_, requestAmount2_ + 10 + requestAmount2_); - } + assertEq(withdrawalManager.userEscrowedShares(lp), requestAmount1_ - 1 + requestAmount2_); + assertEq(withdrawalManager.userEscrowedShares(lp2_), requestAmount2_); + assertEq(withdrawalManager.userEscrowedShares(lp3_), requestAmount2_ + 10 + requestAmount2_); } } @@ -442,10 +402,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -479,10 +436,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIds_[0], 2, "Request ID should be 2"); assertEq(shares_[0], 2, "Shares should be 2"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); } function test_updateSharesBatch_increase_multiple_requests() external { @@ -491,10 +445,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(1, lp); vm.stopPrank(); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -540,20 +491,14 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIds_[1], 4, "Request ID should be 4"); assertEq(shares_[1], 3, "Shares should be 3"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 5); + assertEq(withdrawalManager.userEscrowedShares(lp), 5); } function test_updateSharesBatch_decrease_single_request() external { vm.prank(pm); uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -586,10 +531,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIds_[0], 1, "Request ID should be 1"); assertEq(shares_[0], 1, "Shares should be 1"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 1); - assertEq(escrowedSharesTotalLp_, 1); + assertEq(withdrawalManager.userEscrowedShares(lp), 1); } function test_updateSharesBatch_decrease_multiple_requests() external { @@ -598,10 +540,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 4); + assertEq(withdrawalManager.userEscrowedShares(lp), 4); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -645,10 +584,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIds_[1], 2, "Request ID should be 2"); assertEq(shares_[1], 1, "Shares should be 1"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); } function test_updateSharesBatch_increase_and_decrease_multiple_requests() external { @@ -657,10 +593,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); - ( uint256 requestCountLp_, uint256 escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 3); + assertEq(withdrawalManager.userEscrowedShares(lp), 3); (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); @@ -705,10 +638,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIds_[1], 3, "Request ID should be 3"); assertEq(shares_[1], 2, "Shares should be 2"); - ( requestCountLp_, escrowedSharesTotalLp_ ) = withdrawalManager.userRequestSummaries(lp); - - assertEq(requestCountLp_, 2); - assertEq(escrowedSharesTotalLp_, 3); + assertEq(withdrawalManager.userEscrowedShares(lp), 3); } } diff --git a/tests/unit/ViewFunctions.t.sol b/tests/unit/ViewFunctions.t.sol index 1bf41d5..b66601a 100644 --- a/tests/unit/ViewFunctions.t.sol +++ b/tests/unit/ViewFunctions.t.sol @@ -78,60 +78,6 @@ contract ViewFunctionsTests is TestBase { assertEq(sharesLp4_[0], assetsDeposited_); } - function test_requests_by_owner_and_range() external { - address lp1 = makeAddr("lp1"); - address lp2 = makeAddr("lp2"); - address lp3 = makeAddr("lp3"); - address lp4 = makeAddr("lp4"); - - uint256 assetsDeposited_ = 100e18; - - withdrawalManager.__setRequest(1, lp1, assetsDeposited_); - withdrawalManager.__setRequest(2, lp2, assetsDeposited_); - withdrawalManager.__setRequest(3, lp3, assetsDeposited_); - withdrawalManager.__setRequest(4, lp4, assetsDeposited_); - withdrawalManager.__setRequest(5, lp1, assetsDeposited_); - - withdrawalManager.__setUserRequestCount(lp1, 2, assetsDeposited_ * 2); - withdrawalManager.__setUserRequestCount(lp2, 1, assetsDeposited_); - withdrawalManager.__setUserRequestCount(lp3, 1, assetsDeposited_); - withdrawalManager.__setUserRequestCount(lp4, 1, assetsDeposited_); - - withdrawalManager.__setQueue(1, 5); - - (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwnerRange(lp1, 1, 5); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwnerRange(lp2, 1, 5); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwnerRange(lp3, 1, 5); - (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requestsByOwnerRange(lp4, 1, 5); - - - assertEq(requestIdsLp1_.length, 2); - assertEq(requestIdsLp1_[0], 1); - assertEq(requestIdsLp1_[1], 5); - - assertEq(sharesLp1_.length, 2); - assertEq(sharesLp1_[0], assetsDeposited_); - assertEq(sharesLp1_[1], assetsDeposited_); - - assertEq(requestIdsLp2_.length, 1); - assertEq(requestIdsLp2_[0], 2); - - assertEq(sharesLp2_.length, 1); - assertEq(sharesLp2_[0], assetsDeposited_); - - assertEq(requestIdsLp3_.length, 1); - assertEq(requestIdsLp3_[0], 3); - - assertEq(sharesLp3_.length, 1); - assertEq(sharesLp3_[0], assetsDeposited_); - - assertEq(requestIdsLp4_.length, 1); - assertEq(requestIdsLp4_[0], 4); - - assertEq(sharesLp4_.length, 1); - assertEq(sharesLp4_[0], assetsDeposited_); - } - function test_requests_by_requestId() external { address lp2 = makeAddr("lp2"); address lp3 = makeAddr("lp3"); diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index 33af81a..c6e7210 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.7; import { MapleWithdrawalManager } from "../../contracts/MapleWithdrawalManager.sol"; +import { SortedArray } from "../../contracts/utils/SortedArray.sol"; + contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { function locked() external view returns (uint256) { @@ -18,7 +20,8 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setLastRequest(address owner_, uint128 requestId_) external { - lastRequestIds[owner_] = requestId_; + SortedArray.push(userRequests[owner_], requestId_); + queue.lastRequestId = requestId_; } function __setQueue(uint128 nextRequestId_, uint128 lastRequestId_) external { @@ -27,8 +30,10 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setRequest(uint128 requestId_, address owner_, uint256 shares_) external { + queue.lastRequestId = requestId_; queue.requests[requestId_] = WithdrawalRequest(owner_, shares_); - lastRequestIds[owner_] = requestId_; + + SortedArray.push(userRequests[owner_], requestId_); } function __setTotalShares(uint256 totalShares_) external { @@ -36,8 +41,49 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setUserRequestCount(address owner_, uint256 requestCount_, uint256 escrowSharesTotal_) external { - userRequestSummaries[owner_].requestCount = requestCount_; - userRequestSummaries[owner_].escrowedSharesTotal = escrowSharesTotal_; + uint256 currentRequestCount_ = SortedArray.length(userRequests[owner_]); + + if (requestCount_ < currentRequestCount_) { + for (uint256 i = requestCount_; i < currentRequestCount_; i++) { + SortedArray.remove(userRequests[owner_], uint128(i)); + } + } else { + for (uint256 i = currentRequestCount_; i > requestCount_; i++) { + SortedArray.push(userRequests[owner_], uint128(i)); + } + } + + userEscrowedShares[owner_] = escrowSharesTotal_; + } + +} + +contract SortedArrayHarness { + + SortedArray.Array array; + + function push(uint128 value_) external { + SortedArray.push(array, value_); + } + + function remove(uint128 value_) external { + SortedArray.remove(array, value_); + } + + function get(uint256 index_) external view returns (uint128) { + return SortedArray.get(array, index_); + } + + function length() external view returns (uint256) { + return SortedArray.length(array); + } + + function getAllValues() external view returns (uint128[] memory) { + return SortedArray.getAllValues(array); + } + + function getLast() external view returns (uint128) { + return SortedArray.getLast(array); } } diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index 2e9c296..a6a0a68 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -10,6 +10,8 @@ import { MapleWithdrawalManagerInitializer } from "../../contracts/proxy/MapleWi import { MapleWithdrawalManagerHarness } from "./Harnesses.sol"; import { MockFactory, MockGlobals, MockPool, MockPoolManager } from "./Mocks.sol"; +import { SortedArrayHarness } from "./Harnesses.sol"; + contract TestBase is Test { address internal governor = makeAddr("governor"); @@ -92,3 +94,28 @@ contract TestBase is Test { } } + +contract SortedArrayTestBase is Test { + + SortedArrayHarness public array; + + function setUp() public virtual { + array = new SortedArrayHarness(); + } + + function assertArray(uint128[] memory values) internal { + assertEq(array.length(), values.length); + + uint128[] memory arrayValues = array.getAllValues(); + + for (uint256 i = 0; i < values.length; i++) { + assertEq(arrayValues[i], values[i]); + } + } + + function assertElementAtIndex(uint256 index, uint128 value) internal { + assertEq(array.get(index), value); + } + +} + From 97a6fcb927e5874202f52aef3da30e45953abac1 Mon Sep 17 00:00:00 2001 From: Danilo Kitanovic <116795362+kitanovicd@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:02:19 +0200 Subject: [PATCH 06/21] feat: Update removeShares to iterate over all user's requests (SC-21057) (#35) * feat: update removeShares to iterate over all user's requests * chore: format * chore: remove unnecessary return statement * fix: update push tests for SortedArray library * chore: formatting --------- Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleWithdrawalManager.sol | 14 ++-- tests/unit/RemoveShares.t.sol | 109 +++++++++++++++++++++++++-- tests/unit/SortedArray/Push.t.sol | 17 ++--- 3 files changed, 120 insertions(+), 20 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index c1f0533..0521eb5 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -156,15 +156,17 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag function removeShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint256 sharesReturned_) { require(shares_ > 0, "WM:RS:ZERO_SHARES"); - uint128 lastRequestId_ = SortedArray.getLast(userRequests[owner_]); + uint256 totalEscrowedShares_ = userEscrowedShares[owner_]; - require(lastRequestId_ > 0, "WM:RS:NO_REQUESTS"); + require(totalEscrowedShares_ >= shares_, "WM:RS:INSUFFICIENT_SHARES"); - WithdrawalRequest memory request_ = queue.requests[lastRequestId_]; + while (sharesReturned_ < shares_) { + uint128 requestId_ = SortedArray.getLast(userRequests[owner_]); + WithdrawalRequest memory request_ = queue.requests[requestId_]; - require(request_.shares >= shares_, "WM:RS:INSUFFICIENT_SHARES"); - - sharesReturned_ = _removeShares(lastRequestId_, shares_, request_.owner, request_.shares); + uint256 sharesToRemove_ = _min(shares_ - sharesReturned_, request_.shares); + sharesReturned_ += _removeShares(requestId_, sharesToRemove_, owner_, request_.shares); + } } /**************************************************************************************************************************************/ diff --git a/tests/unit/RemoveShares.t.sol b/tests/unit/RemoveShares.t.sol index dd0f897..153575b 100644 --- a/tests/unit/RemoveShares.t.sol +++ b/tests/unit/RemoveShares.t.sol @@ -12,10 +12,10 @@ contract RemoveSharesTests is TestBase { super.setUp(); // Simulate LP transfer into PM. - pool.mint(pm, 2); + pool.mint(pm, 7); vm.prank(pm); - pool.approve(address(withdrawalManager), 2); + pool.approve(address(withdrawalManager), 7); } function test_removeShares_notPoolManager() external { @@ -35,7 +35,7 @@ contract RemoveSharesTests is TestBase { assertEq(lastRequestId_, 0); vm.prank(pm); - vm.expectRevert("WM:RS:NO_REQUESTS"); + vm.expectRevert("WM:RS:INSUFFICIENT_SHARES"); withdrawalManager.removeShares(1, lp); } @@ -71,8 +71,9 @@ contract RemoveSharesTests is TestBase { emit RequestDecreased(1, 1); vm.prank(pm); - withdrawalManager.removeShares(1, lp); + uint256 sharesReturned_ = withdrawalManager.removeShares(1, lp); + assertEq(sharesReturned_ , 1); assertEq(withdrawalManager.userEscrowedShares(lp), 1); ( , lastRequestId_ ) = withdrawalManager.queue(); @@ -98,7 +99,9 @@ contract RemoveSharesTests is TestBase { emit RequestRemoved(1); vm.prank(pm); - withdrawalManager.removeShares(2, lp); + uint256 sharesReturned_ = withdrawalManager.removeShares(2, lp); + + assertEq(sharesReturned_, 2); ( , lastRequestId_ ) = withdrawalManager.queue(); @@ -114,4 +117,100 @@ contract RemoveSharesTests is TestBase { assertEq(withdrawalManager.userEscrowedShares(lp), 0); } + function test_removeShares_success_partial_multipleRequests() external { + vm.startPrank(pm); + withdrawalManager.addShares(2, lp); + withdrawalManager.addShares(4, lp); + withdrawalManager.addShares(1, lp); + vm.stopPrank(); + + assertEq(withdrawalManager.userEscrowedShares(lp), 7); + assertEq(withdrawalManager.totalShares() , 7); + + ( uint128[] memory requestIds_, uint256[] memory shares_ ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIds_.length, 3); + assertEq(shares_.length , 3); + + ( uint128 lastRequestId_, uint256 lastShares_ ) = getLastRequestByOwner(lp); + + assertEq(lastRequestId_, 3); + assertEq(lastShares_ , 1); + + vm.prank(pm); + uint256 sharesReturned_ = withdrawalManager.removeShares(3, lp); + + assertEq(sharesReturned_, 3); + assertEq(withdrawalManager.userEscrowedShares(lp), 4); + assertEq(withdrawalManager.totalShares() , 4); + + ( lastRequestId_, lastShares_ ) = getLastRequestByOwner(lp); + + assertEq(lastRequestId_, 2); + assertEq(lastShares_ , 2); + + ( requestIds_, shares_ ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIds_.length, 2); + assertEq(shares_.length , 2); + + assertRequest(1, lp, 2); + assertRequest(2, lp, 2); + + vm.prank(pm); + sharesReturned_ = withdrawalManager.removeShares(2, lp); + + assertEq(sharesReturned_, 2); + assertEq(withdrawalManager.userEscrowedShares(lp), 2); + assertEq(withdrawalManager.totalShares() , 2); + + ( requestIds_, shares_ ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIds_.length, 1); + assertEq(shares_.length , 1); + + assertRequest(1, lp, 2); + } + + function test_removeShares_success_cancelAllRequests() external { + vm.startPrank(pm); + withdrawalManager.addShares(2, lp); + withdrawalManager.addShares(4, lp); + withdrawalManager.addShares(1, lp); + vm.stopPrank(); + + assertEq(withdrawalManager.userEscrowedShares(lp), 7); + assertEq(withdrawalManager.totalShares() , 7); + + ( uint128[] memory requestIds_, uint256[] memory shares_ ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIds_.length, 3); + assertEq(shares_.length , 3); + + ( uint128 lastRequestId_, uint256 lastShares_ ) = getLastRequestByOwner(lp); + + assertEq(lastRequestId_, 3); + assertEq(lastShares_ , 1); + + vm.prank(pm); + uint256 sharesReturned_ = withdrawalManager.removeShares(7, lp); + + assertEq(sharesReturned_, 7); + assertEq(withdrawalManager.userEscrowedShares(lp), 0); + assertEq(withdrawalManager.totalShares() , 0); + + ( requestIds_, shares_ ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIds_.length, 0); + assertEq(shares_.length , 0); + + assertEq(withdrawalManager.userEscrowedShares(lp), 0); + assertEq(withdrawalManager.totalShares() , 0); + + ( lastRequestId_, lastShares_ ) = getLastRequestByOwner(lp); + + assertEq(lastRequestId_, 0); + assertEq(lastShares_ , 0); + } + } diff --git a/tests/unit/SortedArray/Push.t.sol b/tests/unit/SortedArray/Push.t.sol index 410183e..333bfbb 100644 --- a/tests/unit/SortedArray/Push.t.sol +++ b/tests/unit/SortedArray/Push.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.7; import { SortedArrayTestBase } from "../../utils/TestBase.sol"; -// TODO: Update tests to use sequential numbers with gaps. contract PushTests is SortedArrayTestBase { function test_push_failed_outOfOrder() external { @@ -15,31 +14,31 @@ contract PushTests is SortedArrayTestBase { } function test_push_singleValue() external { - array.push(1); + array.push(3); uint128[] memory expectedValues = new uint128[](1); - expectedValues[0] = 1; + expectedValues[0] = 3; assertArray(expectedValues); - assertElementAtIndex(0, 1); + assertElementAtIndex(0, 3); assertEq(array.length(), 1); } function test_push_multipleValues_inOrder() external { array.push(1); - array.push(2); array.push(3); + array.push(7); uint128[] memory expectedValues = new uint128[](3); expectedValues[0] = 1; - expectedValues[1] = 2; - expectedValues[2] = 3; + expectedValues[1] = 3; + expectedValues[2] = 7; assertArray(expectedValues); assertElementAtIndex(0, 1); - assertElementAtIndex(1, 2); - assertElementAtIndex(2, 3); + assertElementAtIndex(1, 3); + assertElementAtIndex(2, 7); assertEq(array.length(), 3); } From 01a636f2d0e146777cb7cb62e04ba7f1ae785572 Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:08:27 +0400 Subject: [PATCH 07/21] feat: Added migrator for withdrawal manager storage (sc-21217) (#37) * feat: added migrator * feat: added unit tests Signed-off-by: calmacfadden * feat: unit tests and migrator Signed-off-by: calmacfadden * chore: formatting Signed-off-by: calmacfadden * chore: formatting Signed-off-by: calmacfadden * chore: formatting Signed-off-by: calmacfadden * chore: formatting Signed-off-by: calmacfadden * feat: fixed storage layout bug Signed-off-by: calmacfadden * chore: rename and formatting Signed-off-by: calmacfadden * chore: removed file Signed-off-by: calmacfadden * fix: fix test Signed-off-by: calmacfadden --------- Signed-off-by: calmacfadden --- .../MapleWithdrawalManagerMigratorV200.sol | 26 +++++++ .../unit/MapleWithdrawalManagerMigrator.t.sol | 77 +++++++++++++++++++ tests/utils/Harnesses.sol | 9 +++ tests/utils/Mocks.sol | 2 +- 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 contracts/proxy/MapleWithdrawalManagerMigratorV200.sol create mode 100644 tests/unit/MapleWithdrawalManagerMigrator.t.sol diff --git a/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol b/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol new file mode 100644 index 0000000..bf2c7bf --- /dev/null +++ b/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { MapleProxiedInternals } from "../../modules/maple-proxy-factory/contracts/MapleProxiedInternals.sol"; + +import { SortedArray } from "../utils/SortedArray.sol"; + +import { MapleWithdrawalManagerStorage } from "./MapleWithdrawalManagerStorage.sol"; + +contract MapleWithdrawalManagerMigratorV200 is MapleProxiedInternals, MapleWithdrawalManagerStorage { + + fallback() external { + uint128 nextRequestId_ = queue.nextRequestId; + uint128 lastRequestId_ = queue.lastRequestId; + + for (uint128 i = nextRequestId_; i <= lastRequestId_; ++i) { + WithdrawalRequest storage request = queue.requests[i]; + + if (request.owner != address(0)) { + userEscrowedShares[request.owner] += request.shares; + SortedArray.push(userRequests[request.owner], i); + } + } + } + +} diff --git a/tests/unit/MapleWithdrawalManagerMigrator.t.sol b/tests/unit/MapleWithdrawalManagerMigrator.t.sol new file mode 100644 index 0000000..7897339 --- /dev/null +++ b/tests/unit/MapleWithdrawalManagerMigrator.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { MapleWithdrawalManagerMigratorV200 } from "../../contracts/proxy/MapleWithdrawalManagerMigratorV200.sol"; +import { TestBase } from "../utils/TestBase.sol"; + +contract WithdrawalManagerMigrateTests is TestBase { + + address internal migrator; + + function setUp() public override { + super.setUp(); + + migrator = address(new MapleWithdrawalManagerMigratorV200()); + } + + function test_migrate_notFactory() external { + vm.expectRevert("WM:M:NOT_FACTORY"); + withdrawalManager.migrate(migrator, ""); + } + + function test_migrate_protocolPaused() external { + globals.__setFunctionPaused(true); + + vm.expectRevert("WM:PAUSED"); + withdrawalManager.migrate(migrator, ""); + } + + function test_migrate_success_xxx() external { + withdrawalManager.__setRequestLegacy(10, address(0x01), 1); + withdrawalManager.__setRequestLegacy(12, address(0x01), 2); + withdrawalManager.__setRequestLegacy(13, address(0x02), 4); + withdrawalManager.__setRequestLegacy(15, address(0x03), 5); + + withdrawalManager.__setQueue(10, 15); + + vm.prank(address(factory)); + withdrawalManager.migrate(migrator,""); + + ( uint128 nextRequestId_, uint128 lastRequestId_ ) = withdrawalManager.queue(); + + assert(nextRequestId_ == 10); + assert(lastRequestId_ == 15); + + assertEq(withdrawalManager.userEscrowedShares(address(0x01)), 3); + assertEq(withdrawalManager.userEscrowedShares(address(0x02)), 4); + assertEq(withdrawalManager.userEscrowedShares(address(0x03)), 5); + + address owner_; + uint256 shares_; + + ( owner_, shares_ ) = withdrawalManager.requests(10); + assertEq(owner_, address(0x01)); + assertEq(shares_, 1); + + ( owner_, shares_ ) = withdrawalManager.requests(11); + assertEq(owner_, address(0)); + assertEq(shares_, 0); + + ( owner_, shares_ ) = withdrawalManager.requests(12); + assertEq(owner_, address(0x01)); + assertEq(shares_, 2); + + ( owner_, shares_ ) = withdrawalManager.requests(13); + assertEq(owner_, address(0x02)); + assertEq(shares_, 4); + + ( owner_, shares_ ) = withdrawalManager.requests(14); + assertEq(owner_, address(0)); + assertEq(shares_, 0); + + ( owner_, shares_ ) = withdrawalManager.requests(15); + assertEq(owner_, address(0x03)); + assertEq(shares_, 5); + } + +} diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index c6e7210..3a2e78c 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -29,6 +29,15 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { queue.lastRequestId = lastRequestId_; } + function __setRequestLegacy(uint128 requestId_, address owner_, uint256 shares_) external { + // Used to setup the withdrawal manager storage in the old format. + // This function is only used to unit test the migrator so we can setup wm storage without pushing to the sorted array. + queue.lastRequestId = requestId_; + queue.requests[requestId_] = WithdrawalRequest(owner_, shares_); + + __deprecated_requestIds[owner_] = requestId_; + } + function __setRequest(uint128 requestId_, address owner_, uint256 shares_) external { queue.lastRequestId = requestId_; queue.requests[requestId_] = WithdrawalRequest(owner_, shares_); diff --git a/tests/utils/Mocks.sol b/tests/utils/Mocks.sol index facfe09..1963060 100644 --- a/tests/utils/Mocks.sol +++ b/tests/utils/Mocks.sol @@ -108,7 +108,7 @@ contract MockPool is MockERC20 { asset_ = address(_asset); } - function redeem(uint256, address, address) external returns (uint256 assets_) { + function redeem(uint256, address, address) external view returns (uint256 assets_) { assets_; // Ignore variable } From d33568448f55b4a144df25f920d40543300dfac0 Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:19:55 +0400 Subject: [PATCH 08/21] feat: Changed wm interfaces to use uint256 (SC-21446) (#38) * feat: changed wm interfaces to use uint256 Signed-off-by: calmacfadden * feat: more casting Signed-off-by: calmacfadden * pr fixes Signed-off-by: calmacfadden --------- Signed-off-by: calmacfadden --- contracts/MapleWithdrawalManager.sol | 63 +++++---- .../interfaces/IMapleWithdrawalManager.sol | 24 ++-- contracts/utils/SortedArray.sol | 18 +-- tests/fuzz/AddSharesFuzz.t.sol | 4 +- tests/fuzz/RemoveSharesFuzz.t.sol | 4 +- tests/integration/EndToEndTests.t.sol | 14 +- tests/unit/AddShares.t.sol | 14 +- tests/unit/ProcessExit.t.sol | 6 +- tests/unit/ProcessRedemptions.t.sol | 44 +++--- tests/unit/RemoveRequest.t.sol | 28 ++-- tests/unit/RemoveShares.t.sol | 18 +-- tests/unit/SortedArray/Push.t.sol | 4 +- tests/unit/SortedArray/Remove.t.sol | 8 +- tests/unit/UpdateShares.t.sol | 126 +++++++++--------- tests/unit/ViewFunctions.t.sol | 8 +- tests/utils/Harnesses.sol | 26 ++-- tests/utils/TestBase.sol | 16 +-- 17 files changed, 215 insertions(+), 210 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 0521eb5..970c66d 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -133,7 +133,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /*** State-Changing Functions - OnlyPoolManager ***/ /**************************************************************************************************************************************/ - function addShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint128 lastRequestId_) { + function addShares(uint256 shares_, address owner_) external override onlyPoolManager returns (uint256 lastRequestId_) { require(shares_ > 0, "WM:AS:ZERO_SHARES"); lastRequestId_ = _addRequest(owner_, shares_); @@ -161,8 +161,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag require(totalEscrowedShares_ >= shares_, "WM:RS:INSUFFICIENT_SHARES"); while (sharesReturned_ < shares_) { - uint128 requestId_ = SortedArray.getLast(userRequests[owner_]); - WithdrawalRequest memory request_ = queue.requests[requestId_]; + uint256 requestId_ = SortedArray.getLast(userRequests[owner_]); + WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; uint256 sharesToRemove_ = _min(shares_ - sharesReturned_, request_.shares); sharesReturned_ += _removeShares(requestId_, sharesToRemove_, owner_, request_.shares); @@ -181,8 +181,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag // Revert if there are insufficient assets to redeem all shares. require(maxSharesToProcess_ == redeemableShares_, "WM:PR:LOW_LIQUIDITY"); - uint128 nextRequestId_ = queue.nextRequestId; - uint128 lastRequestId_ = queue.lastRequestId; + uint256 nextRequestId_ = queue.nextRequestId; + uint256 lastRequestId_ = queue.lastRequestId; // Iterate through the loop and process as many requests as possible. // Stop iterating when there are no more shares to process or if you have reached the end of the queue. @@ -199,7 +199,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } // Adjust the new start of the queue. - queue.nextRequestId = nextRequestId_; + queue.nextRequestId = _toUint128(nextRequestId_); } /**************************************************************************************************************************************/ @@ -208,7 +208,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag function removeRequest( address owner_, - uint128[] calldata requestIds_ + uint256[] calldata requestIds_ ) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { require(owner_ != address(0), "WM:RR:ZERO_OWNER"); @@ -219,7 +219,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag WithdrawalRequest memory withdrawalRequest_; for (uint256 i = 0; i < requestIds_.length; ++i) { - withdrawalRequest_ = queue.requests[requestIds_[i]]; + withdrawalRequest_ = queue.requests[_toUint128(requestIds_[i])]; require(withdrawalRequest_.shares > 0, "WM:RR:NOT_IN_QUEUE"); require(withdrawalRequest_.owner == owner_, "WM:RR:NOT_OWNER"); @@ -245,11 +245,11 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /**************************************************************************************************************************************/ function updateShares( - uint128 requestId_, + uint256 requestId_, uint256 newSharesTotal_ - ) public override whenProtocolNotPaused nonReentrant returns (uint128 updatedRequestId_) + ) public override whenProtocolNotPaused nonReentrant returns (uint256 updatedRequestId_) { - WithdrawalRequest memory request_ = queue.requests[requestId_]; + WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; require(request_.owner != address(0), "WM:US:INVALID_REQUEST"); require(request_.owner == msg.sender, "WM:US:NOT_OWNER"); @@ -267,9 +267,9 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function updateSharesBatch( - uint128[] memory requestIds_, + uint256[] memory requestIds_, uint256[] calldata newSharesTotals_ - ) external override whenProtocolNotPaused returns (uint128[] memory updatedRequestIds_) + ) external override whenProtocolNotPaused returns (uint256[] memory updatedRequestIds_) { require(requestIds_.length == newSharesTotals_.length, "WM:USB:ARRAY_LENGTH_MISMATCH"); @@ -284,10 +284,10 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /*** Internal Functions ***/ /**************************************************************************************************************************************/ - function _addRequest(address owner_, uint256 shares_) internal returns (uint128 lastRequestId_) { + function _addRequest(address owner_, uint256 shares_) internal returns (uint256 lastRequestId_) { lastRequestId_ = ++queue.lastRequestId; - queue.requests[lastRequestId_] = WithdrawalRequest(owner_, shares_); + queue.requests[_toUint128(lastRequestId_)] = WithdrawalRequest(owner_, shares_); userEscrowedShares[owner_] += shares_; SortedArray.push(userRequests[owner_], lastRequestId_); @@ -300,7 +300,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag emit RequestCreated(lastRequestId_, owner_, shares_); } - function _removeShares(uint128 requestId_, + function _removeShares(uint256 requestId_, uint256 sharesToRemove_, address owner_, uint256 currentShares_ @@ -314,7 +314,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag if (sharesRemaining_ == 0) { _removeRequest(owner_, requestId_); } else { - queue.requests[requestId_].shares = sharesRemaining_; + queue.requests[_toUint128(requestId_)].shares = sharesRemaining_; userEscrowedShares[owner_] -= sharesToRemove_; emit RequestDecreased(requestId_, sharesToRemove_); @@ -370,7 +370,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function _processRequest( - uint128 requestId_, + uint256 requestId_, uint256 maximumSharesToProcess_ ) internal returns ( @@ -378,7 +378,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag bool isProcessed_ ) { - WithdrawalRequest memory request_ = queue.requests[requestId_]; + WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; // If the request has already been cancelled, skip it. if (request_.owner == address(0)) return (0, true); @@ -403,7 +403,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(request_.owner, requestId_); } else { // Update the withdrawal request. - queue.requests[requestId_].shares = sharesRemaining_; + queue.requests[_toUint128(requestId_)].shares = sharesRemaining_; userEscrowedShares[request_.owner] -= processedShares_; emit RequestDecreased(requestId_, processedShares_); @@ -422,14 +422,19 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } } - function _removeRequest(address owner_, uint128 requestId_) internal { - userEscrowedShares[owner_] -= queue.requests[requestId_].shares; + function _removeRequest(address owner_, uint256 requestId_) internal { + userEscrowedShares[owner_] -= queue.requests[_toUint128(requestId_)].shares; SortedArray.remove(userRequests[owner_], requestId_); - delete queue.requests[requestId_]; + delete queue.requests[_toUint128(requestId_)]; emit RequestRemoved(requestId_); } + function _toUint128(uint256 input_) internal pure returns (uint128 output_) { + require(input_ <= uint256(type(uint128).max), "WM:TU:UINT256_CAST"); + output_ = uint128(input_); + } + /**************************************************************************************************************************************/ /*** View Functions ***/ /**************************************************************************************************************************************/ @@ -500,21 +505,21 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag return ( redeemableAssets_, resultingShares_ ); // NOTE: Withdrawal not implemented use redeem instead } - function requestIds(address owner_) external view override returns (uint128 requestId_) { + function requestIds(address owner_) external view override returns (uint256 requestId_) { requestId_ = SortedArray.getLast(userRequests[owner_]); } - function requests(uint128 requestId_) external view override returns (address owner_, uint256 shares_) { - owner_ = queue.requests[requestId_].owner; - shares_ = queue.requests[requestId_].shares; + function requests(uint256 requestId_) external view override returns (address owner_, uint256 shares_) { + owner_ = queue.requests[_toUint128(requestId_)].owner; + shares_ = queue.requests[_toUint128(requestId_)].shares; } - function requestsByOwner(address owner_) external view override returns (uint128[] memory requestIds_, uint256[] memory shares_) { + function requestsByOwner(address owner_) external view override returns (uint256[] memory requestIds_, uint256[] memory shares_) { requestIds_ = SortedArray.getAllValues(userRequests[owner_]); shares_ = new uint256[](requestIds_.length); for (uint256 i = 0; i < requestIds_.length; ++i) { - shares_[i] = queue.requests[requestIds_[i]].shares; + shares_[i] = queue.requests[_toUint128(requestIds_[i])].shares; } } diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index 9d01d72..eaa2b12 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -38,14 +38,14 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param owner Address of the owner of the shares. * @param shares Amount of shares requested for redemption. */ - event RequestCreated(uint128 indexed requestId, address indexed owner, uint256 shares); + event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); /** * @dev Emitted when a withdrawal request is updated. * @param requestId Identifier of the withdrawal request. * @param shares Amount of shares reduced during a redemption request. */ - event RequestDecreased(uint128 indexed requestId, uint256 shares); + event RequestDecreased(uint256 indexed requestId, uint256 shares); /** * @dev Emitted when a withdrawal request is processed. @@ -54,13 +54,13 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param shares Amount of redeemable shares. * @param assets Amount of withdrawable assets. */ - event RequestProcessed(uint128 indexed requestId, address indexed owner, uint256 shares, uint256 assets); + event RequestProcessed(uint256 indexed requestId, address indexed owner, uint256 shares, uint256 assets); /** * @dev Emitted when a withdrawal request is removed. * @param requestId Identifier of the withdrawal request. */ - event RequestRemoved(uint128 indexed requestId); + event RequestRemoved(uint256 indexed requestId); /**************************************************************************************************************************************/ /*** State-Changing Functions ***/ @@ -71,7 +71,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param shares Amount of shares to add. * @param owner Address of the owner of shares. */ - function addShares(uint256 shares, address owner) external returns (uint128 lastRequestId); + function addShares(uint256 shares, address owner) external returns (uint256 lastRequestId); /** * @dev Processes a withdrawal request. @@ -105,7 +105,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param owner Address of the owner of shares. * @param requestIds Array of identifiers of the withdrawal requests to remove. */ - function removeRequest(address owner, uint128[] calldata requestIds) external; + function removeRequest(address owner, uint256[] calldata requestIds) external; /** * @dev Defines if an account will withdraw shares manually or automatically. @@ -125,7 +125,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param newSharesTotal New total amount of shares pending redemption. * @return currentRequestId Identifier of the withdrawal request that was updated or created. */ - function updateShares(uint128 requestId, uint256 newSharesTotal) external returns (uint128 currentRequestId); + function updateShares(uint256 requestId, uint256 newSharesTotal) external returns (uint256 currentRequestId); /** * @dev Updates the total amount of shares pending redemption in batch. @@ -134,9 +134,9 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @return currentRequestIds Array of identifiers of the withdrawal requests that were updated or created. */ function updateSharesBatch( - uint128[] calldata requestIds, + uint256[] calldata requestIds, uint256[] calldata newSharesTotals - ) external returns (uint128[] memory currentRequestIds); + ) external returns (uint256[] memory currentRequestIds); /**************************************************************************************************************************************/ /*** View Functions ***/ @@ -214,7 +214,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @param owner The account to check the last request id for. * @return requestId The id of the last valid withdrawal request for the account. */ - function requestIds(address owner) external view returns (uint128 requestId); + function requestIds(address owner) external view returns (uint256 requestId); /** * @dev Returns the owner and amount of shares associated with a withdrawal request. @@ -222,7 +222,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @return owner Address of the share owner. * @return shares Amount of shares pending redemption. */ - function requests(uint128 requestId) external view returns (address owner, uint256 shares); + function requests(uint256 requestId) external view returns (address owner, uint256 shares); /** * @dev Returns the pending requests by owner. @@ -231,7 +231,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi * @return requestIds Array of request identifiers. * @return shares Array of shares associated with each request. */ - function requestsByOwner(address owner) external view returns (uint128[] memory requestIds, uint256[] memory shares); + function requestsByOwner(address owner) external view returns (uint256[] memory requestIds, uint256[] memory shares); /** * @dev Returns the address of the security admin. diff --git a/contracts/utils/SortedArray.sol b/contracts/utils/SortedArray.sol index d711c75..5ada4cf 100644 --- a/contracts/utils/SortedArray.sol +++ b/contracts/utils/SortedArray.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.7; library SortedArray { struct Array { - uint128[] values; + uint256[] values; - mapping(uint128 => uint256) valueToIndex; + mapping(uint256 => uint256) valueToIndex; } /**************************************************************************************************************************************/ @@ -20,7 +20,7 @@ library SortedArray { * @param array_ The array to push the value to. * @param value_ The value to push to the array. */ - function push(Array storage array_, uint128 value_) internal { + function push(Array storage array_, uint256 value_) internal { require(getLast(array_) < value_, "SA:P:NOT_LARGEST"); array_.values.push(value_); @@ -34,7 +34,7 @@ library SortedArray { * @param array_ The array to remove the value from. * @param value_ The value to remove from the array. */ - function remove(Array storage array_, uint128 value_) internal { + function remove(Array storage array_, uint256 value_) internal { uint256 length_ = length(array_); if (length_ == 1) { @@ -45,7 +45,7 @@ library SortedArray { uint256 index_ = array_.valueToIndex[value_]; for (uint256 i = index_; i <= length_ - 2; i++) { - uint128 nextValue_ = array_.values[i + 1]; + uint256 nextValue_ = array_.values[i + 1]; array_.values[i] = nextValue_; array_.valueToIndex[nextValue_] = i; } @@ -63,7 +63,7 @@ library SortedArray { * @param index_ The index of the value to get from the array. * @return value_ The value at the given index. */ - function get(Array storage array_, uint256 index_) internal view returns (uint128 value_) { + function get(Array storage array_, uint256 index_) internal view returns (uint256 value_) { require(index_ < length(array_), "SA:G:OUT_OF_BOUNDS"); value_ = array_.values[index_]; } @@ -82,7 +82,7 @@ library SortedArray { * @param array_ The array to get the values from. * @return values_ All values from the array. */ - function getAllValues(Array storage array_) internal view returns (uint128[] memory values_) { + function getAllValues(Array storage array_) internal view returns (uint256[] memory values_) { values_ = array_.values; } @@ -91,7 +91,7 @@ library SortedArray { * @param array_ The array to get the last value from. * @return value_ The last value in the array. */ - function getLast(Array storage array_) internal view returns (uint128 value_) { + function getLast(Array storage array_) internal view returns (uint256 value_) { uint256 length_ = length(array_); return length_ > 0 ? array_.values[length_ - 1] : 0; } @@ -106,7 +106,7 @@ library SortedArray { * @param array_ The array to delete the value from. * @param value_ The value to delete from the array. */ - function _deleteValue(Array storage array_, uint128 value_) private { + function _deleteValue(Array storage array_, uint256 value_) private { array_.values.pop(); delete array_.valueToIndex[value_]; } diff --git a/tests/fuzz/AddSharesFuzz.t.sol b/tests/fuzz/AddSharesFuzz.t.sol index 2fb5d33..67d6bbc 100644 --- a/tests/fuzz/AddSharesFuzz.t.sol +++ b/tests/fuzz/AddSharesFuzz.t.sol @@ -10,9 +10,9 @@ contract AddSharesFuzzTests is TestBase { } function testFuzz_addShares(uint256[50] memory amount_, address[50] calldata account_) external { - uint128 lastRequestId; + uint256 lastRequestId; uint256 totalShares_; - uint128 requestId_; + uint256 requestId_; uint256 shares_; for (uint256 i; i < account_.length; ++i) { diff --git a/tests/fuzz/RemoveSharesFuzz.t.sol b/tests/fuzz/RemoveSharesFuzz.t.sol index 86519a7..b44d15a 100644 --- a/tests/fuzz/RemoveSharesFuzz.t.sol +++ b/tests/fuzz/RemoveSharesFuzz.t.sol @@ -12,10 +12,10 @@ contract RemoveSharesFuzzTests is TestBase { function testFuzz_removeShares(address[50] calldata account_, uint256[50] memory amount0_, uint256[50] memory amount1_) external { address owner_; - uint128 lastRequestId; + uint256 lastRequestId; uint256 shares_; uint256 totalShares_; - uint128 requestId_; + uint256 requestId_; for (uint256 i; i < account_.length; ++i) { amount0_[i] = bound(amount0_[i], 1, 1e29); diff --git a/tests/integration/EndToEndTests.t.sol b/tests/integration/EndToEndTests.t.sol index 2f86064..3d2f24d 100644 --- a/tests/integration/EndToEndTests.t.sol +++ b/tests/integration/EndToEndTests.t.sol @@ -6,7 +6,7 @@ import { console, TestBase } from "../utils/TestBase.sol"; contract EndToEndTests is TestBase { // Helper storage variable for fuzz test - mapping(uint128 => address) lpsRequest; + mapping(uint256 => address) lpsRequest; mapping(address => bool) manualLps; mapping(address => uint256) lpShares; @@ -26,7 +26,7 @@ contract EndToEndTests is TestBase { uint256 totalInitialShares = shares1 + shares2 + shares3; - uint128 requestId_; + uint256 requestId_; // Simulate shares being sent to PM pool.mint(pm, totalInitialShares); @@ -134,8 +134,8 @@ contract EndToEndTests is TestBase { function testFuzz_fullFLow_fixedExchangeRate(address[10] memory lps, bool[10] memory isManual, uint256[10] memory shares) external { uint256 totalShares; - uint128 inQueue; - uint128 requestId_; + uint256 inQueue; + uint256 requestId_; // Iterate through all users and add shares to pool manager for (uint256 i = 0; i < 10; i++) { @@ -178,12 +178,12 @@ contract EndToEndTests is TestBase { withdrawalManager.processRedemptions(sharesToProcess); // To avoid doing the same iteration as the function `processRedemptions`, fetch the queue state, then check it's integrity. - ( uint128 nextRequestId_, uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( uint256 nextRequestId_, uint256 lastRequestId_ ) = withdrawalManager.queue(); uint256 sharesProcessed; // First, check that the requests processes are correct - for (uint128 i = 1; i < nextRequestId_; i++) { + for (uint256 i = 1; i < nextRequestId_; i++) { address lp = lpsRequest[i]; assertRequest({ requestId: i, shares: 0, owner: address(0) }); @@ -205,7 +205,7 @@ contract EndToEndTests is TestBase { } // Finally, check that all subsequent requests are still on the queue. - for (uint128 i = nextRequestId_ + 1; i <= lastRequestId_; i++) { + for (uint256 i = nextRequestId_ + 1; i <= lastRequestId_; i++) { address lp = lpsRequest[i]; assertRequest({ requestId: i, shares: lpShares[lp], owner: lp }); diff --git a/tests/unit/AddShares.t.sol b/tests/unit/AddShares.t.sol index 8d148d8..67e90e1 100644 --- a/tests/unit/AddShares.t.sol +++ b/tests/unit/AddShares.t.sol @@ -5,7 +5,7 @@ import { TestBase } from "../utils/TestBase.sol"; contract AddSharesTests is TestBase { - event RequestCreated(uint128 indexed requestId, address indexed owner, uint256 shares); + event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); function setUp() public override { super.setUp(); @@ -35,7 +35,7 @@ contract AddSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(1, lp); - ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( , uint256 lastRequestId_ ) = withdrawalManager.queue(); assertEq(lastRequestId_, 2); @@ -46,7 +46,7 @@ contract AddSharesTests is TestBase { assertEq(owner_, lp); assertEq(shares_, 1); - (uint128[] memory requestIds, uint256[] memory requestShares) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds, uint256[] memory requestShares) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds.length, 2); assertEq(requestShares.length, 2); @@ -63,7 +63,7 @@ contract AddSharesTests is TestBase { } function test_addShares_newRequestAddedToQueue() external { - ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( , uint256 lastRequestId_ ) = withdrawalManager.queue(); assertEq(lastRequestId_, 0); @@ -83,7 +83,7 @@ contract AddSharesTests is TestBase { function test_addShares_newRequestAddedToQueue_manual() external { withdrawalManager.__setManualWithdrawal(lp, true); - ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( , uint256 lastRequestId_ ) = withdrawalManager.queue(); assertEq(lastRequestId_, 0); @@ -101,7 +101,7 @@ contract AddSharesTests is TestBase { } function test_addShares_success() external { - uint128 requestId_; + uint256 requestId_; vm.expectEmit(); emit RequestCreated(1, lp, 1); @@ -109,7 +109,7 @@ contract AddSharesTests is TestBase { vm.prank(pm); withdrawalManager.addShares(1, lp); - ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( , uint256 lastRequestId_ ) = withdrawalManager.queue(); assertEq(lastRequestId_, 1); diff --git a/tests/unit/ProcessExit.t.sol b/tests/unit/ProcessExit.t.sol index 611944e..50bcc94 100644 --- a/tests/unit/ProcessExit.t.sol +++ b/tests/unit/ProcessExit.t.sol @@ -6,7 +6,7 @@ import { TestBase, console } from "../utils/TestBase.sol"; // TODO: Add ManualSharesDecreased event to tests contract ProcessExitTests is TestBase { - event RequestRemoved(uint128 indexed requestId); + event RequestRemoved(uint256 indexed requestId); uint256 assetsDeposited = 100e18; uint256 sharesToRedeem = 250e18; @@ -98,7 +98,7 @@ contract ProcessExitTests is TestBase { assertEq(pool.balanceOf(lp), sharesToRedeem); assertEq(pool.balanceOf(wm), 0); - ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + ( uint256 lastRequestId_, ) = getLastRequestByOwner(lp); assertEq(lastRequestId_, 2); assertEq(withdrawalManager.requestIds(lp), lastRequestId_); @@ -127,7 +127,7 @@ contract ProcessExitTests is TestBase { assertEq(pool.balanceOf(lp), sharesToRedeem / 2); assertEq(pool.balanceOf(wm), sharesToRedeem / 2); - ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + ( uint256 lastRequestId_, ) = getLastRequestByOwner(lp); assertEq(lastRequestId_, 1); assertEq(withdrawalManager.requestIds(lp), lastRequestId_); diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index 3db5e2c..f607c3c 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -7,9 +7,9 @@ import { TestBase } from "../utils/TestBase.sol"; // TODO: Add test case for reentrancy check contract ProcessRedemptionsTests is TestBase { - event RequestDecreased(uint128 indexed requestId, uint256 shares); - event RequestProcessed(uint128 indexed requestId, address indexed owner, uint256 shares, uint256 assets); - event RequestRemoved(uint128 indexed requestId); + event RequestDecreased(uint256 indexed requestId, uint256 shares); + event RequestProcessed(uint256 indexed requestId, address indexed owner, uint256 shares, uint256 assets); + event RequestRemoved(uint256 indexed requestId); uint256 assetsDeposited = 100e18; uint256 sharesLocked = 250e18; @@ -93,7 +93,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); @@ -119,7 +119,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); @@ -158,7 +158,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked / 2); @@ -193,8 +193,8 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked / 2); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); - ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + ( uint256 lastRequestId_, ) = getLastRequestByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(lastRequestId_, 1); @@ -225,7 +225,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(2 * sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked); assertEq(withdrawalManager.manualSharesAvailable(lp), sharesLocked); @@ -255,7 +255,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), 0); assertEq(requestIdsLp_.length, 0); @@ -287,8 +287,8 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked / 2); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); - ( uint128 lastRequestId_, ) = getLastRequestByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + ( uint256 lastRequestId_, ) = getLastRequestByOwner(lp); assertEq(withdrawalManager.totalShares(), sharesLocked / 2); assertEq(lastRequestId_, 1); @@ -323,7 +323,7 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(2 * sharesLocked); - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); assertEq(withdrawalManager.totalShares(), 0); assertEq(requestIdsLp_.length, 0); @@ -364,8 +364,8 @@ contract ProcessRedemptionsTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesLocked); - (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwner(lp); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2); + (uint256[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2); assertEq(withdrawalManager.totalShares(), 0); assertEq(requestIdsLp1_.length, 0); @@ -386,9 +386,9 @@ contract ProcessRedemptionsTests is TestBase { contract ComplexRedemptionTests is TestBase { - event RequestDecreased(uint128 indexed requestId, uint256 shares); - event RequestProcessed(uint128 indexed requestId, address indexed owner, uint256 shares, uint256 assets); - event RequestRemoved(uint128 indexed requestId); + event RequestDecreased(uint256 indexed requestId, uint256 shares); + event RequestProcessed(uint256 indexed requestId, address indexed owner, uint256 shares, uint256 assets); + event RequestRemoved(uint256 indexed requestId); function test_processRedemptions_complex() external { uint256 totalAssets_ = 100e18; @@ -431,10 +431,10 @@ contract ComplexRedemptionTests is TestBase { vm.prank(poolDelegate); withdrawalManager.processRedemptions(sharesToProcess_); - (uint128[] memory requestIds2_, uint256[] memory shares2_) = withdrawalManager.requestsByOwner(address(2)); - (uint128[] memory requestIds3_, uint256[] memory shares3_) = withdrawalManager.requestsByOwner(address(3)); - (uint128[] memory requestIds5_, uint256[] memory shares5_) = withdrawalManager.requestsByOwner(address(5)); - (uint128[] memory requestIds6_, uint256[] memory shares6_) = withdrawalManager.requestsByOwner(address(6)); + (uint256[] memory requestIds2_, uint256[] memory shares2_) = withdrawalManager.requestsByOwner(address(2)); + (uint256[] memory requestIds3_, uint256[] memory shares3_) = withdrawalManager.requestsByOwner(address(3)); + (uint256[] memory requestIds5_, uint256[] memory shares5_) = withdrawalManager.requestsByOwner(address(5)); + (uint256[] memory requestIds6_, uint256[] memory shares6_) = withdrawalManager.requestsByOwner(address(6)); assertEq(requestIds2_.length, 0); assertEq(shares2_.length, 0); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 9b7f371..20d8a02 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -5,7 +5,7 @@ import { TestBase } from "../utils/TestBase.sol"; contract RemoveRequestTests is TestBase { - event RequestRemoved(uint128 indexed requestId); + event RequestRemoved(uint256 indexed requestId); function setUp() public override { super.setUp(); @@ -20,16 +20,16 @@ contract RemoveRequestTests is TestBase { globals.__setFunctionPaused(true); vm.expectRevert("WM:PAUSED"); - withdrawalManager.removeRequest(lp, new uint128[](0)); + withdrawalManager.removeRequest(lp, new uint256[](0)); } function test_removeRequest_notProtocolAdmin() external { vm.expectRevert("WM:NOT_PD_OR_GOV_OR_OA"); - withdrawalManager.removeRequest(lp, new uint128[](0)); + withdrawalManager.removeRequest(lp, new uint256[](0)); } function test_removeRequest_notInQueue() external { - uint128[] memory requestIds_ = new uint128[](1); + uint256[] memory requestIds_ = new uint256[](1); requestIds_[0] = 1; vm.prank(poolDelegate); @@ -38,7 +38,7 @@ contract RemoveRequestTests is TestBase { } function test_removeRequest_failedTransfer() external { - uint128[] memory requestIds_ = new uint128[](1); + uint256[] memory requestIds_ = new uint256[](1); requestIds_[0] = 1; vm.prank(pm); @@ -52,14 +52,14 @@ contract RemoveRequestTests is TestBase { } function test_removeRequest_success() external { - uint128 lastRequestIdLp_; + uint256 lastRequestIdLp_; vm.prank(pm); withdrawalManager.addShares(2, lp); assertEq(withdrawalManager.userEscrowedShares(lp), 2); - ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( , uint256 lastRequestId_ ) = withdrawalManager.queue(); ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); @@ -70,7 +70,7 @@ contract RemoveRequestTests is TestBase { assertEq(lastRequestId_, 1); assertEq(lastRequestIdLp_, lastRequestId_); - uint128[] memory requestIds = new uint128[](1); + uint256[] memory requestIds = new uint256[](1); requestIds[0] = lastRequestId_; vm.expectEmit(); @@ -84,7 +84,7 @@ contract RemoveRequestTests is TestBase { ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); - ( uint128 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); + ( uint256 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); assertEq(lastRequestId_, 1); assertEq(shares_, 0); @@ -97,7 +97,7 @@ contract RemoveRequestTests is TestBase { } function test_removeRequest_multiple_requests_success() external { - uint128 lastRequestIdLp_; + uint256 lastRequestIdLp_; vm.startPrank(pm); withdrawalManager.addShares(1, lp); @@ -105,7 +105,7 @@ contract RemoveRequestTests is TestBase { withdrawalManager.addShares(1, lp); vm.stopPrank(); - ( , uint128 lastRequestId_ ) = withdrawalManager.queue(); + ( , uint256 lastRequestId_ ) = withdrawalManager.queue(); ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); @@ -118,7 +118,7 @@ contract RemoveRequestTests is TestBase { assertEq(withdrawalManager.userEscrowedShares(lp), 3); - uint128[] memory requestIds = new uint128[](1); + uint256[] memory requestIds = new uint256[](1); requestIds[0] = 2; vm.expectEmit(); @@ -132,7 +132,7 @@ contract RemoveRequestTests is TestBase { ( owner_, shares_ ) = withdrawalManager.requests(lastRequestId_); ( lastRequestIdLp_, ) = getLastRequestByOwner(lp); - ( uint128 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); + ( uint256 lastRequestByOwner_, ) = getLastRequestByOwner(owner_); assertEq(lastRequestId_, 3); assertEq(shares_, 1); @@ -143,7 +143,7 @@ contract RemoveRequestTests is TestBase { assertEq(withdrawalManager.userEscrowedShares(lp), 2); - requestIds = new uint128[](2); + requestIds = new uint256[](2); requestIds[0] = 1; requestIds[1] = 3; diff --git a/tests/unit/RemoveShares.t.sol b/tests/unit/RemoveShares.t.sol index 153575b..6737478 100644 --- a/tests/unit/RemoveShares.t.sol +++ b/tests/unit/RemoveShares.t.sol @@ -5,8 +5,8 @@ import { TestBase } from "../utils/TestBase.sol"; contract RemoveSharesTests is TestBase { - event RequestDecreased(uint128 indexed requestId, uint256 shares); - event RequestRemoved(uint128 indexed requestId); + event RequestDecreased(uint256 indexed requestId, uint256 shares); + event RequestRemoved(uint256 indexed requestId); function setUp() public override { super.setUp(); @@ -30,7 +30,7 @@ contract RemoveSharesTests is TestBase { } function test_removeShares_notInQueue() external { - ( uint128 lastRequestId_,) = getLastRequestByOwner(pm); + ( uint256 lastRequestId_,) = getLastRequestByOwner(pm); assertEq(lastRequestId_, 0); @@ -60,7 +60,7 @@ contract RemoveSharesTests is TestBase { } function test_removeShares_success_decreaseRequest() external { - uint128 lastRequestId_; + uint256 lastRequestId_; vm.prank(pm); withdrawalManager.addShares(2, lp); @@ -88,7 +88,7 @@ contract RemoveSharesTests is TestBase { } function test_removeShares_success_cancelRequest() external { - uint128 lastRequestId_; + uint256 lastRequestId_; vm.prank(pm); withdrawalManager.addShares(2, lp); @@ -127,12 +127,12 @@ contract RemoveSharesTests is TestBase { assertEq(withdrawalManager.userEscrowedShares(lp), 7); assertEq(withdrawalManager.totalShares() , 7); - ( uint128[] memory requestIds_, uint256[] memory shares_ ) = withdrawalManager.requestsByOwner(lp); + ( uint256[] memory requestIds_, uint256[] memory shares_ ) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 3); assertEq(shares_.length , 3); - ( uint128 lastRequestId_, uint256 lastShares_ ) = getLastRequestByOwner(lp); + ( uint256 lastRequestId_, uint256 lastShares_ ) = getLastRequestByOwner(lp); assertEq(lastRequestId_, 3); assertEq(lastShares_ , 1); @@ -182,12 +182,12 @@ contract RemoveSharesTests is TestBase { assertEq(withdrawalManager.userEscrowedShares(lp), 7); assertEq(withdrawalManager.totalShares() , 7); - ( uint128[] memory requestIds_, uint256[] memory shares_ ) = withdrawalManager.requestsByOwner(lp); + ( uint256[] memory requestIds_, uint256[] memory shares_ ) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 3); assertEq(shares_.length , 3); - ( uint128 lastRequestId_, uint256 lastShares_ ) = getLastRequestByOwner(lp); + ( uint256 lastRequestId_, uint256 lastShares_ ) = getLastRequestByOwner(lp); assertEq(lastRequestId_, 3); assertEq(lastShares_ , 1); diff --git a/tests/unit/SortedArray/Push.t.sol b/tests/unit/SortedArray/Push.t.sol index 333bfbb..d33d27b 100644 --- a/tests/unit/SortedArray/Push.t.sol +++ b/tests/unit/SortedArray/Push.t.sol @@ -16,7 +16,7 @@ contract PushTests is SortedArrayTestBase { function test_push_singleValue() external { array.push(3); - uint128[] memory expectedValues = new uint128[](1); + uint256[] memory expectedValues = new uint256[](1); expectedValues[0] = 3; assertArray(expectedValues); @@ -30,7 +30,7 @@ contract PushTests is SortedArrayTestBase { array.push(3); array.push(7); - uint128[] memory expectedValues = new uint128[](3); + uint256[] memory expectedValues = new uint256[](3); expectedValues[0] = 1; expectedValues[1] = 3; expectedValues[2] = 7; diff --git a/tests/unit/SortedArray/Remove.t.sol b/tests/unit/SortedArray/Remove.t.sol index fc8f39d..8bac4d0 100644 --- a/tests/unit/SortedArray/Remove.t.sol +++ b/tests/unit/SortedArray/Remove.t.sol @@ -9,7 +9,7 @@ contract RemoveTests is SortedArrayTestBase { array.push(1); array.remove(1); - uint128[] memory expectedValues = new uint128[](0); + uint256[] memory expectedValues = new uint256[](0); assertArray(expectedValues); assertEq(array.length(), 0); @@ -22,7 +22,7 @@ contract RemoveTests is SortedArrayTestBase { array.remove(2); - uint128[] memory expectedValues = new uint128[](2); + uint256[] memory expectedValues = new uint256[](2); expectedValues[0] = 1; expectedValues[1] = 3; @@ -34,7 +34,7 @@ contract RemoveTests is SortedArrayTestBase { array.remove(1); - expectedValues = new uint128[](1); + expectedValues = new uint256[](1); expectedValues[0] = 3; assertArray(expectedValues); @@ -44,7 +44,7 @@ contract RemoveTests is SortedArrayTestBase { array.remove(3); - assertArray(new uint128[](0)); + assertArray(new uint256[](0)); assertEq(array.length(), 0); } diff --git a/tests/unit/UpdateShares.t.sol b/tests/unit/UpdateShares.t.sol index bea8a77..9ce1a39 100644 --- a/tests/unit/UpdateShares.t.sol +++ b/tests/unit/UpdateShares.t.sol @@ -61,7 +61,7 @@ contract UpdateSharesFailureTests is TestBase { vm.prank(pm); withdrawalManager.addShares(1, lp); - uint128[] memory requestIds = new uint128[](1); + uint256[] memory requestIds = new uint256[](1); requestIds[0] = 1; vm.prank(governor); @@ -75,11 +75,11 @@ contract UpdateSharesFailureTests is TestBase { contract UpdateSharesSuccessTests is TestBase { - event RequestCreated(uint128 indexed requestId, address indexed owner, uint256 shares); + event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); - event RequestDecreased(uint128 indexed requestId, uint256 shares); + event RequestDecreased(uint256 indexed requestId, uint256 shares); - event RequestRemoved(uint128 indexed requestId); + event RequestRemoved(uint256 indexed requestId); function setUp() public override { super.setUp(); @@ -97,11 +97,11 @@ contract UpdateSharesSuccessTests is TestBase { function test_updateShares_increase() external { vm.prank(pm); - uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); + uint256 requestIdBefore_ = withdrawalManager.addShares(1, lp); assertEq(withdrawalManager.userEscrowedShares(lp), 1); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); @@ -114,9 +114,9 @@ contract UpdateSharesSuccessTests is TestBase { emit RequestRemoved(requestIdBefore_); emit RequestCreated(requestIdBefore_ + 1, lp, 2); // Increase shares from 1 to 2 creates a new request and removes the old one. - uint128 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 2); + uint256 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 2); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(newRequestId_, 2, "Request ID should be 2"); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); @@ -129,7 +129,7 @@ contract UpdateSharesSuccessTests is TestBase { function test_updateShares_remove_request() external{ vm.prank(pm); - uint128 requestId_ = withdrawalManager.addShares(1, lp); + uint256 requestId_ = withdrawalManager.addShares(1, lp); assertEq(withdrawalManager.userEscrowedShares(lp), 1); @@ -137,7 +137,7 @@ contract UpdateSharesSuccessTests is TestBase { vm.expectEmit(); emit RequestRemoved(requestId_); - uint128 newRequestId_ = withdrawalManager.updateShares(requestId_, 0); + uint256 newRequestId_ = withdrawalManager.updateShares(requestId_, 0); assertEq(newRequestId_, 0, "Request ID should be 0"); @@ -146,11 +146,11 @@ contract UpdateSharesSuccessTests is TestBase { function test_updateShares_decrease() external { vm.prank(pm); - uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); + uint256 requestIdBefore_ = withdrawalManager.addShares(2, lp); assertEq(withdrawalManager.userEscrowedShares(lp), 2); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID Before should be 1 (addShares)"); assertEq(requestIdsBefore_.length, 1, "Request IDs length before should be 1"); @@ -162,9 +162,9 @@ contract UpdateSharesSuccessTests is TestBase { vm.expectEmit(); emit RequestDecreased(requestIdBefore_, 1); // Decrease shares from 2 to 1 keeps the same request ID. - uint128 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 1); + uint256 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 1); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(newRequestId_, 1, "Request ID should be 1 (addShares)"); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); @@ -224,9 +224,9 @@ contract UpdateSharesSuccessTests is TestBase { assertEq(withdrawalManager.userEscrowedShares(lp3_), requestAmount1_ + requestAmount2_); { - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); + (uint256[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); assertEq(requestIdsLp2_.length, 2, "LP2 request IDs length should be 2"); @@ -253,36 +253,36 @@ contract UpdateSharesSuccessTests is TestBase { { // Decrease a request vm.prank(lp); - uint128 requestIdLp_ = 1; + uint256 requestIdLp_ = 1; vm.expectEmit(); emit RequestDecreased(requestIdLp_, 1); - uint128 updatedRequestIdLp_ = withdrawalManager.updateShares(requestIdLp_, requestAmount1_ - 1); + uint256 updatedRequestIdLp_ = withdrawalManager.updateShares(requestIdLp_, requestAmount1_ - 1); assertEq(updatedRequestIdLp_, requestIdLp_, "Updated request ID should be 1"); // Remove a request vm.prank(lp2_); - uint128 requestIdLp2_ = 3; + uint256 requestIdLp2_ = 3; vm.expectEmit(); emit RequestRemoved(requestIdLp2_); - uint128 updatedRequestIdLp2_ = withdrawalManager.updateShares(requestIdLp2_, 0); + uint256 updatedRequestIdLp2_ = withdrawalManager.updateShares(requestIdLp2_, 0); assertEq(updatedRequestIdLp2_, 0, "Updated request ID should be 0"); // Increase a request vm.prank(lp3_); - uint128 requestIdLp3_ = 5; + uint256 requestIdLp3_ = 5; vm.expectEmit(); emit RequestRemoved(requestIdLp3_); emit RequestCreated(7, lp3_, requestAmount2_ + 10); - uint128 updatedRequestIdLp3 = withdrawalManager.updateShares(requestIdLp3_, requestAmount2_ + 10); + uint256 updatedRequestIdLp3 = withdrawalManager.updateShares(requestIdLp3_, requestAmount2_ + 10); assertEq(updatedRequestIdLp3, 7, "Updated request ID should be 7"); vm.stopPrank(); } { - (uint128[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); + (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); + (uint256[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); assertEq(requestIdsLp2_.length, 1, "LP2 request IDs length should be 1"); @@ -329,7 +329,7 @@ contract UpdateSharesBatchFailureTests is TestBase { } function test_updateSharesBatch_invalid_array_lengths() external { - uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory requestIdsToUpdate_ = new uint256[](1); uint256[] memory sharesToUpdate_ = new uint256[](2); vm.expectRevert("WM:USB:ARRAY_LENGTH_MISMATCH"); @@ -337,7 +337,7 @@ contract UpdateSharesBatchFailureTests is TestBase { } function test_updateSharesBatch_invalid_request() external { - uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory requestIdsToUpdate_ = new uint256[](1); uint256[] memory sharesToUpdate_ = new uint256[](1); vm.expectRevert("WM:US:INVALID_REQUEST"); @@ -345,11 +345,11 @@ contract UpdateSharesBatchFailureTests is TestBase { } function test_updateSharesBatch_invalid_owner() external { - uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory requestIdsToUpdate_ = new uint256[](1); uint256[] memory sharesToUpdate_ = new uint256[](1); vm.prank(pm); - uint128 requestId_ = withdrawalManager.addShares(1, lp); + uint256 requestId_ = withdrawalManager.addShares(1, lp); requestIdsToUpdate_[0] = requestId_; sharesToUpdate_[0] = 2; @@ -360,11 +360,11 @@ contract UpdateSharesBatchFailureTests is TestBase { } function test_updateSharesBatch_no_change_in_shares() external { - uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory requestIdsToUpdate_ = new uint256[](1); uint256[] memory sharesToUpdate_ = new uint256[](1); vm.prank(pm); - uint128 requestId_ = withdrawalManager.addShares(1, lp); + uint256 requestId_ = withdrawalManager.addShares(1, lp); requestIdsToUpdate_[0] = requestId_; sharesToUpdate_[0] = 1; @@ -377,11 +377,11 @@ contract UpdateSharesBatchFailureTests is TestBase { contract UpdateSharesBatchSuccessTests is TestBase { - event RequestCreated(uint128 indexed requestId, address indexed owner, uint256 shares); + event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); - event RequestDecreased(uint128 indexed requestId, uint256 shares); + event RequestDecreased(uint256 indexed requestId, uint256 shares); - event RequestRemoved(uint128 indexed requestId); + event RequestRemoved(uint256 indexed requestId); function setUp() public override { super.setUp(); @@ -400,11 +400,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { function test_updateSharesBatch_increase_single_request() external { vm.prank(pm); - uint128 requestIdBefore_ = withdrawalManager.addShares(1, lp); + uint256 requestIdBefore_ = withdrawalManager.addShares(1, lp); assertEq(withdrawalManager.userEscrowedShares(lp), 1); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); @@ -412,7 +412,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); assertEq(sharesBefore_[0], 1, "Shares should be 1"); - uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory requestIdsToUpdate_ = new uint256[](1); requestIdsToUpdate_[0] = requestIdBefore_; uint256[] memory sharesToUpdate_ = new uint256[](1); @@ -424,12 +424,12 @@ contract UpdateSharesBatchSuccessTests is TestBase { emit RequestCreated(requestIdBefore_ + 1, lp, 2); // Increase shares creates a new request and removes the old one. - uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); assertEq(newRequestIds_[0], 2, "New request ID should be 2"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); assertEq(shares_.length, 1, "Shares length should be 1"); @@ -441,13 +441,13 @@ contract UpdateSharesBatchSuccessTests is TestBase { function test_updateSharesBatch_increase_multiple_requests() external { vm.startPrank(pm); - uint128 requestId1Before_ = withdrawalManager.addShares(1, lp); - uint128 requestId2Before_ = withdrawalManager.addShares(1, lp); + uint256 requestId1Before_ = withdrawalManager.addShares(1, lp); + uint256 requestId2Before_ = withdrawalManager.addShares(1, lp); vm.stopPrank(); assertEq(withdrawalManager.userEscrowedShares(lp), 2); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); assertEq(requestId2Before_, 2, "Request ID should be 2"); @@ -458,7 +458,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); assertEq(sharesBefore_[1], 1, "Shares should be 1"); - uint128[] memory requestIdsToUpdate_ = new uint128[](2); + uint256[] memory requestIdsToUpdate_ = new uint256[](2); requestIdsToUpdate_[0] = requestId1Before_; requestIdsToUpdate_[1] = requestId2Before_; @@ -476,13 +476,13 @@ contract UpdateSharesBatchSuccessTests is TestBase { emit RequestCreated(4, lp, 3); // Increase shares creates a new request and removes the old one. - uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); assertEq(newRequestIds_[0], 3, "New request ID should be 3"); assertEq(newRequestIds_[1], 4, "New request ID should be 4"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 2, "Request IDs length should be 2"); assertEq(shares_.length, 2, "Shares length should be 2"); @@ -496,11 +496,11 @@ contract UpdateSharesBatchSuccessTests is TestBase { function test_updateSharesBatch_decrease_single_request() external { vm.prank(pm); - uint128 requestIdBefore_ = withdrawalManager.addShares(2, lp); + uint256 requestIdBefore_ = withdrawalManager.addShares(2, lp); assertEq(withdrawalManager.userEscrowedShares(lp), 2); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIdBefore_, 1, "Request ID should be 1"); assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); @@ -508,7 +508,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); assertEq(sharesBefore_[0], 2, "Shares should be 2"); - uint128[] memory requestIdsToUpdate_ = new uint128[](1); + uint256[] memory requestIdsToUpdate_ = new uint256[](1); requestIdsToUpdate_[0] = requestIdBefore_; uint256[] memory sharesToUpdate_ = new uint256[](1); @@ -519,12 +519,12 @@ contract UpdateSharesBatchSuccessTests is TestBase { emit RequestDecreased(requestIdBefore_, 1); // Decrease in shares updates the existing request. - uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); assertEq(newRequestIds_[0], 1, "New request ID should be 1"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 1, "Request IDs length should be 1"); assertEq(shares_.length, 1, "Shares length should be 1"); @@ -536,13 +536,13 @@ contract UpdateSharesBatchSuccessTests is TestBase { function test_updateSharesBatch_decrease_multiple_requests() external { vm.startPrank(pm); - uint128 requestId1Before_ = withdrawalManager.addShares(2, lp); - uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); + uint256 requestId1Before_ = withdrawalManager.addShares(2, lp); + uint256 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); assertEq(withdrawalManager.userEscrowedShares(lp), 4); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); assertEq(requestId2Before_, 2, "Request ID should be 1"); @@ -553,7 +553,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); assertEq(sharesBefore_[1], 2, "Shares should be 2"); - uint128[] memory requestIdsToUpdate_ = new uint128[](2); + uint256[] memory requestIdsToUpdate_ = new uint256[](2); requestIdsToUpdate_[0] = requestId1Before_; requestIdsToUpdate_[1] = requestId2Before_; @@ -569,13 +569,13 @@ contract UpdateSharesBatchSuccessTests is TestBase { emit RequestDecreased(requestId2Before_, 1); // Decrease in shares updates the existing requests. - uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); assertEq(newRequestIds_[0], 1, "New request ID should be 1"); assertEq(newRequestIds_[1], 2, "New request ID should be 2"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 2, "Request IDs length should be 2"); assertEq(shares_.length, 2, "Shares length should be 2"); @@ -589,13 +589,13 @@ contract UpdateSharesBatchSuccessTests is TestBase { function test_updateSharesBatch_increase_and_decrease_multiple_requests() external { vm.startPrank(pm); - uint128 requestId1Before_ = withdrawalManager.addShares(1, lp); - uint128 requestId2Before_ = withdrawalManager.addShares(2, lp); + uint256 requestId1Before_ = withdrawalManager.addShares(1, lp); + uint256 requestId2Before_ = withdrawalManager.addShares(2, lp); vm.stopPrank(); assertEq(withdrawalManager.userEscrowedShares(lp), 3); - (uint128[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); assertEq(requestId1Before_, 1, "Request ID should be 1"); assertEq(requestId2Before_, 2, "Request ID should be 1"); @@ -606,7 +606,7 @@ contract UpdateSharesBatchSuccessTests is TestBase { assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); assertEq(sharesBefore_[1], 2, "Shares should be 2"); - uint128[] memory requestIdsToUpdate_ = new uint128[](2); + uint256[] memory requestIdsToUpdate_ = new uint256[](2); requestIdsToUpdate_[0] = requestId1Before_; requestIdsToUpdate_[1] = requestId2Before_; @@ -623,13 +623,13 @@ contract UpdateSharesBatchSuccessTests is TestBase { emit RequestDecreased(requestId2Before_, 1); // Decrease in shares updates the existing requests. - uint128[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); + uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); assertEq(newRequestIds_[0], 3, "New request ID should be 3"); assertEq(newRequestIds_[1], 2, "New request ID should be 2"); - (uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); + (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); assertEq(requestIds_.length, 2, "Request IDs length should be 1"); assertEq(shares_.length, 2, "Shares length should be 1"); diff --git a/tests/unit/ViewFunctions.t.sol b/tests/unit/ViewFunctions.t.sol index b66601a..0260240 100644 --- a/tests/unit/ViewFunctions.t.sol +++ b/tests/unit/ViewFunctions.t.sol @@ -45,10 +45,10 @@ contract ViewFunctionsTests is TestBase { withdrawalManager.__setQueue(1, 5); - (uint128[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwner(lp1); - (uint128[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2); - (uint128[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3); - (uint128[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requestsByOwner(lp4); + (uint256[] memory requestIdsLp1_, uint256[] memory sharesLp1_) = withdrawalManager.requestsByOwner(lp1); + (uint256[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2); + (uint256[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3); + (uint256[] memory requestIdsLp4_, uint256[] memory sharesLp4_) = withdrawalManager.requestsByOwner(lp4); assertEq(requestIdsLp1_.length, 2); diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index 3a2e78c..7284b8c 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -19,14 +19,14 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { isManualWithdrawal[owner_] = isManual_; } - function __setLastRequest(address owner_, uint128 requestId_) external { + function __setLastRequest(address owner_, uint256 requestId_) external { SortedArray.push(userRequests[owner_], requestId_); - queue.lastRequestId = requestId_; + queue.lastRequestId = _toUint128(requestId_); } - function __setQueue(uint128 nextRequestId_, uint128 lastRequestId_) external { - queue.nextRequestId = nextRequestId_; - queue.lastRequestId = lastRequestId_; + function __setQueue(uint256 nextRequestId_, uint256 lastRequestId_) external { + queue.nextRequestId = _toUint128(nextRequestId_); + queue.lastRequestId = _toUint128(lastRequestId_); } function __setRequestLegacy(uint128 requestId_, address owner_, uint256 shares_) external { @@ -54,11 +54,11 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { if (requestCount_ < currentRequestCount_) { for (uint256 i = requestCount_; i < currentRequestCount_; i++) { - SortedArray.remove(userRequests[owner_], uint128(i)); + SortedArray.remove(userRequests[owner_], _toUint128(i)); } } else { for (uint256 i = currentRequestCount_; i > requestCount_; i++) { - SortedArray.push(userRequests[owner_], uint128(i)); + SortedArray.push(userRequests[owner_], _toUint128(i)); } } @@ -71,15 +71,15 @@ contract SortedArrayHarness { SortedArray.Array array; - function push(uint128 value_) external { + function push(uint256 value_) external { SortedArray.push(array, value_); } - function remove(uint128 value_) external { + function remove(uint256 value_) external { SortedArray.remove(array, value_); } - function get(uint256 index_) external view returns (uint128) { + function get(uint256 index_) external view returns (uint256) { return SortedArray.get(array, index_); } @@ -87,12 +87,12 @@ contract SortedArrayHarness { return SortedArray.length(array); } - function getAllValues() external view returns (uint128[] memory) { + function getAllValues() external view returns (uint256[] memory) { return SortedArray.getAllValues(array); } - function getLast() external view returns (uint128) { + function getLast() external view returns (uint256) { return SortedArray.getLast(array); } -} +} diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index a6a0a68..0050d0c 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -73,22 +73,22 @@ contract TestBase is Test { wm = address(withdrawalManager); } - function assertRequest(uint128 requestId, address owner, uint256 shares) internal { + function assertRequest(uint256 requestId, address owner, uint256 shares) internal { ( address owner_, uint256 shares_ ) = withdrawalManager.requests(requestId); assertEq(owner_, owner); assertEq(shares_, shares); } - function assertQueue(uint128 nextRequestId, uint128 lastRequestId) internal { - ( uint128 nextRequestId_, uint128 lastRequestId_ ) = withdrawalManager.queue(); + function assertQueue(uint256 nextRequestId, uint256 lastRequestId) internal { + ( uint256 nextRequestId_, uint256 lastRequestId_ ) = withdrawalManager.queue(); assertEq(nextRequestId_, nextRequestId); assertEq(lastRequestId_, lastRequestId); } - function getLastRequestByOwner(address owner) internal view returns (uint128 lastRequestId, uint256 shares) { - ( uint128[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(owner); + function getLastRequestByOwner(address owner) internal view returns (uint256 lastRequestId, uint256 shares) { + ( uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(owner); return requestIds_.length == 0 ? (0,0) : (requestIds_[requestIds_.length - 1], shares_[shares_.length - 1]); } @@ -103,17 +103,17 @@ contract SortedArrayTestBase is Test { array = new SortedArrayHarness(); } - function assertArray(uint128[] memory values) internal { + function assertArray(uint256[] memory values) internal { assertEq(array.length(), values.length); - uint128[] memory arrayValues = array.getAllValues(); + uint256[] memory arrayValues = array.getAllValues(); for (uint256 i = 0; i < values.length; i++) { assertEq(arrayValues[i], values[i]); } } - function assertElementAtIndex(uint256 index, uint128 value) internal { + function assertElementAtIndex(uint256 index, uint256 value) internal { assertEq(array.get(index), value); } From cd8f632508694b1a05372834db1aec43586292ed Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Wed, 3 Sep 2025 09:17:01 +0400 Subject: [PATCH 09/21] chore: update format (#39) --- contracts/MapleWithdrawalManager.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 970c66d..21b545d 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -288,7 +288,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag lastRequestId_ = ++queue.lastRequestId; queue.requests[_toUint128(lastRequestId_)] = WithdrawalRequest(owner_, shares_); - userEscrowedShares[owner_] += shares_; + userEscrowedShares[owner_] += shares_; SortedArray.push(userRequests[owner_], lastRequestId_); @@ -315,7 +315,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(owner_, requestId_); } else { queue.requests[_toUint128(requestId_)].shares = sharesRemaining_; - userEscrowedShares[owner_] -= sharesToRemove_; + userEscrowedShares[owner_] -= sharesToRemove_; emit RequestDecreased(requestId_, sharesToRemove_); } @@ -404,7 +404,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } else { // Update the withdrawal request. queue.requests[_toUint128(requestId_)].shares = sharesRemaining_; - userEscrowedShares[request_.owner] -= processedShares_; + userEscrowedShares[request_.owner] -= processedShares_; emit RequestDecreased(requestId_, processedShares_); } From 20aaf69fcf5742865562f389ec75ff88e1d4f92a Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:42:00 +0400 Subject: [PATCH 10/21] feat: Add `removeSharesById()` Functionality (#40) * feat: Add removeSharesById Functionality * fix: err messages + test cases * feat: Add additional require * chore: format * fix: test cases * feat: Update Interface and Tests * feat: Update Test with multiple LPs * chore: fix formatting * chore: fix stack too deep * chore: fix test name * chore: update impl * chore: address PR comments * chore: fix events --- contracts/MapleWithdrawalManager.sol | 47 +- .../interfaces/IMapleWithdrawalManager.sol | 33 +- tests/unit/AddShares.t.sol | 6 +- tests/unit/ProcessExit.t.sol | 4 +- tests/unit/ProcessRedemptions.t.sol | 12 +- tests/unit/RemoveRequest.t.sol | 2 - tests/unit/RemoveShares.t.sol | 7 +- tests/unit/RemoveSharesById.t.sol | 338 +++++++++ tests/unit/SetManualWithdrawal.t.sol | 2 - tests/unit/UpdateShares.t.sol | 644 ------------------ tests/utils/Mocks.sol | 2 +- tests/utils/TestBase.sol | 6 + 12 files changed, 377 insertions(+), 726 deletions(-) create mode 100644 tests/unit/RemoveSharesById.t.sol delete mode 100644 tests/unit/UpdateShares.t.sol diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 21b545d..67ca43e 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -209,7 +209,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag function removeRequest( address owner_, uint256[] calldata requestIds_ - ) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins + ) + external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { require(owner_ != address(0), "WM:RR:ZERO_OWNER"); require(requestIds_.length > 0, "WM:RR:ZERO_REQUESTS"); @@ -244,40 +245,22 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /*** Unprivileged External Functions ***/ /**************************************************************************************************************************************/ - function updateShares( + function removeSharesById( uint256 requestId_, - uint256 newSharesTotal_ - ) public override whenProtocolNotPaused nonReentrant returns (uint256 updatedRequestId_) + uint256 sharesToRemove_ + ) + public override whenProtocolNotPaused nonReentrant returns (uint256 sharesReturned_, uint256 sharesRemaining_) { WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; - require(request_.owner != address(0), "WM:US:INVALID_REQUEST"); - require(request_.owner == msg.sender, "WM:US:NOT_OWNER"); - require(request_.shares != newSharesTotal_, "WM:US:NO_CHANGE"); - - uint256 sharesToRemove_ = newSharesTotal_ < request_.shares ? request_.shares - newSharesTotal_ : request_.shares; + require(request_.owner != address(0), "WM:RSBI:INVALID_REQUEST"); + require(request_.owner == msg.sender, "WM:RSBI:NOT_OWNER"); + require(sharesToRemove_ != 0, "WM:RSBI:NO_CHANGE"); + require(sharesToRemove_ <= request_.shares, "WM:RSBI:INSUFFICIENT_SHARES"); // Removes shares and will cancel the request if there are no shares remaining. - _removeShares(requestId_, sharesToRemove_, request_.owner, request_.shares); - - if (newSharesTotal_ > request_.shares) - updatedRequestId_ = _addRequest(request_.owner, newSharesTotal_); - else - updatedRequestId_ = newSharesTotal_ == 0 ? 0 : requestId_; - } - - function updateSharesBatch( - uint256[] memory requestIds_, - uint256[] calldata newSharesTotals_ - ) external override whenProtocolNotPaused returns (uint256[] memory updatedRequestIds_) - { - require(requestIds_.length == newSharesTotals_.length, "WM:USB:ARRAY_LENGTH_MISMATCH"); - - for (uint256 i = 0; i < requestIds_.length; ++i) { - requestIds_[i] = updateShares(requestIds_[i], newSharesTotals_[i]); - } - - updatedRequestIds_ = requestIds_; + sharesReturned_ = _removeShares(requestId_, sharesToRemove_, request_.owner, request_.shares); + sharesRemaining_ = request_.shares - sharesToRemove_; } /**************************************************************************************************************************************/ @@ -300,11 +283,13 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag emit RequestCreated(lastRequestId_, owner_, shares_); } - function _removeShares(uint256 requestId_, + function _removeShares( + uint256 requestId_, uint256 sharesToRemove_, address owner_, uint256 currentShares_ - ) internal returns (uint256 sharesReturned_) + ) + internal returns (uint256 sharesReturned_) { uint256 sharesRemaining_ = currentShares_ - sharesToRemove_; diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index eaa2b12..fb24d4f 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -99,6 +99,15 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function removeShares(uint256 shares, address owner) external returns (uint256 sharesReturned); + /** + * @dev Remove shares from a specific withdrawal request. + * @param requestId Identifier of the withdrawal request that is being updated. + * @param sharesToRemove Amount of shares to remove from the request. + * @return sharesReturned Amount of shares that were returned. + * @return sharesRemaining Amount of shares remaining in the request. + */ + function removeSharesById(uint256 requestId, uint256 sharesToRemove) external returns (uint256 sharesReturned, uint256 sharesRemaining); + /** * @dev Removes withdrawal requests from the queue. * Can only be called by the pool delegate. @@ -114,30 +123,6 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function setManualWithdrawal(address account, bool isManual) external; - - /** - * @dev Updates the total amount of shares pending redemption. - * 1. If newSharesTotal is zero then the request will be removed. - * 2. If newSharesTotal is less than the current shares then the request will be decreased. - * 3. If newSharesTotal is greater than the current shares then the current request will be cancelled - * and a new request will be created at the end of the queue. - * @param requestId Identifier of the withdrawal request that is being updated. - * @param newSharesTotal New total amount of shares pending redemption. - * @return currentRequestId Identifier of the withdrawal request that was updated or created. - */ - function updateShares(uint256 requestId, uint256 newSharesTotal) external returns (uint256 currentRequestId); - - /** - * @dev Updates the total amount of shares pending redemption in batch. - * @param requestIds Array of identifiers of the withdrawal requests that are being updated. - * @param newSharesTotals Array of new total amounts of shares pending redemption. - * @return currentRequestIds Array of identifiers of the withdrawal requests that were updated or created. - */ - function updateSharesBatch( - uint256[] calldata requestIds, - uint256[] calldata newSharesTotals - ) external returns (uint256[] memory currentRequestIds); - /**************************************************************************************************************************************/ /*** View Functions ***/ /**************************************************************************************************************************************/ diff --git a/tests/unit/AddShares.t.sol b/tests/unit/AddShares.t.sol index 67e90e1..f29233a 100644 --- a/tests/unit/AddShares.t.sol +++ b/tests/unit/AddShares.t.sol @@ -5,8 +5,6 @@ import { TestBase } from "../utils/TestBase.sol"; contract AddSharesTests is TestBase { - event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); - function setUp() public override { super.setUp(); @@ -28,7 +26,7 @@ contract AddSharesTests is TestBase { withdrawalManager.addShares(0, lp); } - function test_addShares_multiple_requests() external{ + function test_addShares_multiple_requests() external{ vm.prank(pm); withdrawalManager.addShares(1, lp); @@ -117,7 +115,7 @@ contract AddSharesTests is TestBase { ( address owner_, uint256 shares_ ) = withdrawalManager.requests(lastRequestId_); ( requestId_,) = getLastRequestByOwner(owner_); - + assertEq(shares_, 1); assertEq(withdrawalManager.totalShares(), 1); assertEq(requestId_, lastRequestId_); diff --git a/tests/unit/ProcessExit.t.sol b/tests/unit/ProcessExit.t.sol index 50bcc94..a05b1e9 100644 --- a/tests/unit/ProcessExit.t.sol +++ b/tests/unit/ProcessExit.t.sol @@ -6,8 +6,6 @@ import { TestBase, console } from "../utils/TestBase.sol"; // TODO: Add ManualSharesDecreased event to tests contract ProcessExitTests is TestBase { - event RequestRemoved(uint256 indexed requestId); - uint256 assetsDeposited = 100e18; uint256 sharesToRedeem = 250e18; @@ -114,7 +112,7 @@ contract ProcessExitTests is TestBase { withdrawalManager.__setRequest(1, lp, sharesToRedeem / 2); withdrawalManager.__setTotalShares(sharesToRedeem); withdrawalManager.__setManualSharesAvailable(lp, sharesToRedeem / 2); - + // Only half of the liquidity is available. asset.burn(address(pool), assetsDeposited / 2); diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index f607c3c..d41130c 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -7,10 +7,6 @@ import { TestBase } from "../utils/TestBase.sol"; // TODO: Add test case for reentrancy check contract ProcessRedemptionsTests is TestBase { - event RequestDecreased(uint256 indexed requestId, uint256 shares); - event RequestProcessed(uint256 indexed requestId, address indexed owner, uint256 shares, uint256 assets); - event RequestRemoved(uint256 indexed requestId); - uint256 assetsDeposited = 100e18; uint256 sharesLocked = 250e18; @@ -137,7 +133,7 @@ contract ProcessRedemptionsTests is TestBase { function test_processRedemptions_manual_multipleLps_multiple_requests() external { address lp2 = makeAddr("lp2"); - address lp3 = makeAddr("lp3"); + address lp3 = makeAddr("lp3"); withdrawalManager.__setManualWithdrawal(lp, true); withdrawalManager.__setManualWithdrawal(lp2, true); @@ -386,10 +382,6 @@ contract ProcessRedemptionsTests is TestBase { contract ComplexRedemptionTests is TestBase { - event RequestDecreased(uint256 indexed requestId, uint256 shares); - event RequestProcessed(uint256 indexed requestId, address indexed owner, uint256 shares, uint256 assets); - event RequestRemoved(uint256 indexed requestId); - function test_processRedemptions_complex() external { uint256 totalAssets_ = 100e18; uint256 totalShares_ = 250e18; @@ -445,7 +437,7 @@ contract ComplexRedemptionTests is TestBase { assertEq(shares5_.length, 1); assertEq(requestIds5_[0], 5); assertEq(shares5_[0], 25e18); - + assertEq(requestIds6_.length, 1); assertEq(shares6_.length, 1); assertEq(requestIds6_[0], 6); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 20d8a02..b8bdadf 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -5,8 +5,6 @@ import { TestBase } from "../utils/TestBase.sol"; contract RemoveRequestTests is TestBase { - event RequestRemoved(uint256 indexed requestId); - function setUp() public override { super.setUp(); diff --git a/tests/unit/RemoveShares.t.sol b/tests/unit/RemoveShares.t.sol index 6737478..2b93314 100644 --- a/tests/unit/RemoveShares.t.sol +++ b/tests/unit/RemoveShares.t.sol @@ -4,10 +4,7 @@ pragma solidity ^0.8.7; import { TestBase } from "../utils/TestBase.sol"; contract RemoveSharesTests is TestBase { - - event RequestDecreased(uint256 indexed requestId, uint256 shares); - event RequestRemoved(uint256 indexed requestId); - + function setUp() public override { super.setUp(); @@ -212,5 +209,5 @@ contract RemoveSharesTests is TestBase { assertEq(lastRequestId_, 0); assertEq(lastShares_ , 0); } - + } diff --git a/tests/unit/RemoveSharesById.t.sol b/tests/unit/RemoveSharesById.t.sol new file mode 100644 index 0000000..583000f --- /dev/null +++ b/tests/unit/RemoveSharesById.t.sol @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { TestBase } from "../utils/TestBase.sol"; + +contract RemoveSharesByIdFailureTests is TestBase { + + function setUp() public override { + super.setUp(); + uint256 mintAmount = 200; + + // Simulate LP transfer into PM. + pool.mint(pm, mintAmount); + + vm.prank(pm); + pool.approve(address(withdrawalManager), mintAmount); + } + + function test_removeSharesById_protocolPaused() external { + globals.__setFunctionPaused(true); + + vm.prank(lp); + vm.expectRevert("WM:PAUSED"); + withdrawalManager.removeSharesById(1, 1); + } + + function test_removeSharesById_maxUint128Exceeded() external { + vm.prank(lp); + vm.expectRevert("WM:TU:UINT256_CAST"); + withdrawalManager.removeSharesById(type(uint256).max, 1); + } + + function test_removeSharesById_invalidRequest() external { + vm.expectRevert("WM:RSBI:INVALID_REQUEST"); + withdrawalManager.removeSharesById(1, 1); + } + + function test_removeSharesById_requestAlreadyRemoved() external { + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + uint256[] memory requestIds = new uint256[](1); + requestIds[0] = 1; + + vm.prank(governor); + withdrawalManager.removeRequest(lp, requestIds); + + vm.expectRevert("WM:RSBI:INVALID_REQUEST"); + vm.prank(lp); + withdrawalManager.removeSharesById(1, 100); + } + + function test_removeSharesById_zeroRequestId() external { + vm.prank(lp); + vm.expectRevert("WM:RSBI:INVALID_REQUEST"); + withdrawalManager.removeSharesById(0, 1); // Request ID 0 should be invalid + } + + function test_removeSharesById_notOwner() external { + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(1, lp); + + vm.prank(address(0x12)); + vm.expectRevert("WM:RSBI:NOT_OWNER"); + withdrawalManager.removeSharesById(requestId, 1); + } + + function test_removeSharesById_noChange() external { + vm.prank(pm); + withdrawalManager.addShares(1, lp); + + vm.prank(lp); + vm.expectRevert("WM:RSBI:NO_CHANGE"); + withdrawalManager.removeSharesById(1, 0); + } + + function test_removeSharesById_insufficientShares() external { + vm.prank(pm); + withdrawalManager.addShares(5, lp); + + // Try to remove 10 shares (more than the 5 available) + vm.prank(lp); + vm.expectRevert("WM:RSBI:INSUFFICIENT_SHARES"); + withdrawalManager.removeSharesById(1, 10); + } + + function test_removeSharesById_transferFail() external { + vm.prank(pm); + withdrawalManager.addShares(5, lp); + + // Burn the tokens from the withdrawal manager to simulate transfer failure + pool.burn(address(withdrawalManager), 5); + + vm.prank(lp); + vm.expectRevert("WM:RS:TRANSFER_FAIL"); + withdrawalManager.removeSharesById(1, 1); + } + +} + +contract RemoveSharesByIdSuccessTests is TestBase { + + address lp2 = makeAddr("lp2"); + + address ownerForRequest1; + address ownerForRequest2; + address ownerForRequest3; + address ownerForRequest4; + + uint256 requestAmount1; + uint256 requestAmount2; + + uint256 sharesForRequest1; + uint256 sharesForRequest2; + uint256 sharesForRequest3; + uint256 sharesForRequest4; + + function setUp() public override { + super.setUp(); + uint256 mintAmount = 200; + + pool.mint(pm, mintAmount); // Simulate LP transfer into PM. + pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. + + vm.prank(pm); + pool.approve(address(withdrawalManager), mintAmount); + + vm.prank(lp); + pool.approve(address(withdrawalManager), mintAmount); + } + + function test_removeSharesById_remove_request() external{ + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(1, lp); + + ( address ownerBefore, uint256 sharesBefore ) = withdrawalManager.requests(requestId); + + assertEq(ownerBefore, lp); + assertEq(sharesBefore, 1); + + ( uint256[] memory requestIdsBefore, uint256[] memory sharesPerRequestBefore ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIdsBefore.length, 1); + assertEq(sharesPerRequestBefore.length, 1); + assertEq(requestIdsBefore[0], requestId); + assertEq(sharesPerRequestBefore[0], 1); + + assertEq(withdrawalManager.userEscrowedShares(lp), 1); + + vm.prank(lp); + vm.expectEmit(); + emit RequestRemoved(requestId); + + ( uint256 sharesReturned_, uint256 sharesRemaining_ ) = withdrawalManager.removeSharesById(requestId, 1); + + assertEq(sharesReturned_, 1); + assertEq(sharesRemaining_, 0); + + ( address ownerAfter, uint256 sharesAfter ) = withdrawalManager.requests(requestId); + + assertEq(ownerAfter, address(0)); + assertEq(sharesAfter, 0); + + ( uint256[] memory requestIdsAfter, uint256[] memory sharesPerRequestAfter ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIdsAfter.length, 0); + assertEq(sharesPerRequestAfter.length, 0); + + assertEq(withdrawalManager.userEscrowedShares(lp), 0); + } + + function test_removeSharesById_decrease() external { + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(2, lp); + + ( address ownerBefore, uint256 sharesBefore ) = withdrawalManager.requests(requestId); + + assertEq(ownerBefore, lp); + assertEq(sharesBefore, 2); + + ( uint256[] memory requestIdsBefore, uint256[] memory sharesPerRequestBefore ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIdsBefore.length, 1); + assertEq(sharesPerRequestBefore.length, 1); + assertEq(requestIdsBefore[0], requestId); + assertEq(sharesPerRequestBefore[0], 2); + + assertEq(withdrawalManager.userEscrowedShares(lp), 2); + + vm.prank(lp); + vm.expectEmit(); + emit RequestDecreased(requestId, 1); + + ( uint256 sharesReturned_, uint256 sharesRemaining_ ) = withdrawalManager.removeSharesById(requestId, 1); + + assertEq(sharesReturned_, 1); + assertEq(sharesRemaining_, 1); + + ( address ownerAfter, uint256 sharesAfter ) = withdrawalManager.requests(requestId); + + assertEq(ownerAfter, lp); + assertEq(sharesAfter, 1); + + ( uint256[] memory requestIdsAfter, uint256[] memory sharesPerRequestAfter ) = withdrawalManager.requestsByOwner(lp); + + assertEq(requestIdsAfter.length, 1); + assertEq(sharesPerRequestAfter.length, 1); + assertEq(requestIdsAfter[0], requestId); + assertEq(sharesPerRequestAfter[0], 1); + + assertEq(withdrawalManager.userEscrowedShares(lp), 1); + } + + function test_removeSharesById_multipleLPsWithMultipleRequests() external { + requestAmount1 = 10; + requestAmount2 = 20; + + pool.mint(pm, 3000); + pool.mint(lp, 1000); + pool.mint(lp2, 1000); + + // Create multiple requests for both LPs + vm.startPrank(pm); + + vm.expectEmit(); + emit RequestCreated(1, lp, requestAmount1); + withdrawalManager.addShares(requestAmount1, lp); + + vm.expectEmit(); + emit RequestCreated(2, lp, requestAmount2); + withdrawalManager.addShares(requestAmount2, lp); + + vm.expectEmit(); + emit RequestCreated(3, lp2, requestAmount1); + withdrawalManager.addShares(requestAmount1, lp2); + + vm.expectEmit(); + emit RequestCreated(4, lp2, requestAmount2); + withdrawalManager.addShares(requestAmount2, lp2); + + vm.stopPrank(); + + assertEq(withdrawalManager.userEscrowedShares(lp), requestAmount1 + requestAmount2); + assertEq(withdrawalManager.userEscrowedShares(lp2), requestAmount1 + requestAmount2); + + ( ownerForRequest1, sharesForRequest1 ) = withdrawalManager.requests(1); + ( ownerForRequest2, sharesForRequest2 ) = withdrawalManager.requests(2); + ( ownerForRequest3, sharesForRequest3 ) = withdrawalManager.requests(3); + ( ownerForRequest4, sharesForRequest4 ) = withdrawalManager.requests(4); + + assertEq(ownerForRequest1, lp); + assertEq(ownerForRequest2, lp); + assertEq(ownerForRequest3, lp2); + assertEq(ownerForRequest4, lp2); + + assertEq(sharesForRequest1, requestAmount1); + assertEq(sharesForRequest2, requestAmount2); + assertEq(sharesForRequest3, requestAmount1); + assertEq(sharesForRequest4, requestAmount2); + + ( uint256[] memory requestIdsLp1Before, uint256[] memory sharesLp1Before ) = withdrawalManager.requestsByOwner(lp); + ( uint256[] memory requestIdsLp2Before, uint256[] memory sharesLp2Before ) = withdrawalManager.requestsByOwner(lp2); + + assertEq(requestIdsLp1Before.length, 2); + assertEq(sharesLp1Before.length, 2); + + assertEq(requestIdsLp2Before.length, 2); + assertEq(sharesLp2Before.length, 2); + + assertEq(requestIdsLp1Before[0], 1); + assertEq(requestIdsLp1Before[1], 2); + + assertEq(requestIdsLp2Before[0], 3); + assertEq(requestIdsLp2Before[1], 4); + + assertEq(sharesLp1Before[0], requestAmount1); + assertEq(sharesLp1Before[1], requestAmount2); + + assertEq(sharesLp2Before[0], requestAmount1); + assertEq(sharesLp2Before[1], requestAmount2); + + // Decrease LP1's first request + vm.prank(lp); + vm.expectEmit(); + emit RequestDecreased(1, 1); + ( uint256 sharesReturnedLp, uint256 sharesRemainingLp ) = withdrawalManager.removeSharesById(1, 1); + + assertEq(sharesReturnedLp, 1); + assertEq(sharesRemainingLp, requestAmount1 - 1); + + // Remove LP2's first request completely + vm.prank(lp2); + vm.expectEmit(); + emit RequestRemoved(3); + ( uint256 sharesReturnedLp2, uint256 sharesRemainingLp2 ) = withdrawalManager.removeSharesById(3, requestAmount1); + + assertEq(sharesReturnedLp2, requestAmount1); + assertEq(sharesRemainingLp2, 0); + + assertEq(withdrawalManager.userEscrowedShares(lp), requestAmount1 - 1 + requestAmount2); + assertEq(withdrawalManager.userEscrowedShares(lp2), requestAmount2); + + ( ownerForRequest1, sharesForRequest1 ) = withdrawalManager.requests(1); + ( ownerForRequest2, sharesForRequest2 ) = withdrawalManager.requests(2); + ( ownerForRequest3, sharesForRequest3 ) = withdrawalManager.requests(3); + ( ownerForRequest4, sharesForRequest4 ) = withdrawalManager.requests(4); + + assertEq(ownerForRequest1, lp); // LP's first request should be decreased but still exist + assertEq(ownerForRequest2, lp); // LP's second request should be unchanged + assertEq(ownerForRequest3, address(0)); // LP2's first request should be removed + assertEq(ownerForRequest4, lp2); // LP2's second request should be unchanged + + assertEq(sharesForRequest1, requestAmount1 - 1); // LP's first request decreased + assertEq(sharesForRequest2, requestAmount2); // LP's second request unchanged + assertEq(sharesForRequest3, 0); // LP2's first request removed + assertEq(sharesForRequest4, requestAmount2); // LP2's second request unchanged + + ( uint256[] memory requestIdsLp1After, uint256[] memory sharesLp1After ) = withdrawalManager.requestsByOwner(lp); + ( uint256[] memory requestIdsLp2After, uint256[] memory sharesLp2After ) = withdrawalManager.requestsByOwner(lp2); + + assertEq(requestIdsLp1After.length, 2); + assertEq(sharesLp1After.length, 2); + + assertEq(requestIdsLp2After.length, 1); + assertEq(sharesLp2After.length, 1); + + assertEq(requestIdsLp1After[0], 1); + assertEq(requestIdsLp1After[1], 2); + + assertEq(requestIdsLp2After[0], 4); + + assertEq(sharesLp1After[0], requestAmount1 - 1); + assertEq(sharesLp1After[1], requestAmount2); + + assertEq(sharesLp2After[0], requestAmount2); + } + +} diff --git a/tests/unit/SetManualWithdrawal.t.sol b/tests/unit/SetManualWithdrawal.t.sol index 465aac7..c301ec8 100644 --- a/tests/unit/SetManualWithdrawal.t.sol +++ b/tests/unit/SetManualWithdrawal.t.sol @@ -5,8 +5,6 @@ import { TestBase } from "../utils/TestBase.sol"; contract SetManualWithdrawalTests is TestBase { - event ManualWithdrawalSet(address indexed account, bool isManual); - function test_setManualWithdrawal_protocolPaused() external { globals.__setFunctionPaused(true); diff --git a/tests/unit/UpdateShares.t.sol b/tests/unit/UpdateShares.t.sol deleted file mode 100644 index 9ce1a39..0000000 --- a/tests/unit/UpdateShares.t.sol +++ /dev/null @@ -1,644 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.7; - -import { TestBase } from "../utils/TestBase.sol"; - -contract UpdateSharesFailureTests is TestBase { - - function setUp() public override { - super.setUp(); - uint256 mintAmount = 200; - - // Simulate LP transfer into PM. - pool.mint(pm, mintAmount); - - vm.prank(pm); - pool.approve(address(withdrawalManager), mintAmount); - } - - function test_updateShares_invalidRequest() external { - vm.expectRevert("WM:US:INVALID_REQUEST"); - withdrawalManager.updateShares(1, 1); - } - - function test_updateShares_notOwner() external { - vm.prank(pm); - withdrawalManager.addShares(1, lp); - - vm.prank(address(0x12)); - vm.expectRevert("WM:US:NOT_OWNER"); - withdrawalManager.updateShares(1, 1); - } - - function test_updateShares_noChange() external { - vm.prank(pm); - withdrawalManager.addShares(1, lp); - - vm.prank(lp); - vm.expectRevert("WM:US:NO_CHANGE"); - withdrawalManager.updateShares(1, 1); - } - - function test_updateShares_failed_increase_insufficient_shares() external{ - vm.prank(pm); - withdrawalManager.addShares(1, lp); - - vm.expectRevert("WM:AS:FAILED_TRANSFER"); - vm.prank(lp); - withdrawalManager.updateShares(1, 100); - } - - function test_updateShares_failed_transfer_insufficient_tokens() external{ - vm.prank(pm); - withdrawalManager.addShares(1, lp); - - vm.expectRevert("WM:AS:FAILED_TRANSFER"); - vm.prank(lp); - withdrawalManager.updateShares(1, 100); - } - - function test_updateShares_failed_update_request_already_removed() external{ - vm.prank(pm); - withdrawalManager.addShares(1, lp); - - uint256[] memory requestIds = new uint256[](1); - requestIds[0] = 1; - - vm.prank(governor); - withdrawalManager.removeRequest(lp, requestIds); - - vm.expectRevert("WM:US:INVALID_REQUEST"); - vm.prank(lp); - withdrawalManager.updateShares(1, 100); - } -} - -contract UpdateSharesSuccessTests is TestBase { - - event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); - - event RequestDecreased(uint256 indexed requestId, uint256 shares); - - event RequestRemoved(uint256 indexed requestId); - - function setUp() public override { - super.setUp(); - uint256 mintAmount = 200; - - pool.mint(pm, mintAmount); // Simulate LP transfer into PM. - pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. - - vm.prank(pm); - pool.approve(address(withdrawalManager), mintAmount); - - vm.prank(lp); - pool.approve(address(withdrawalManager), mintAmount); - } - - function test_updateShares_increase() external { - vm.prank(pm); - uint256 requestIdBefore_ = withdrawalManager.addShares(1, lp); - - assertEq(withdrawalManager.userEscrowedShares(lp), 1); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIdBefore_, 1, "Request ID should be 1"); - assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); - assertEq(sharesBefore_.length, 1, "Shares length should be 1"); - assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); - assertEq(sharesBefore_[0], 1, "Shares should be 1"); - - vm.prank(lp); - vm.expectEmit(); - emit RequestRemoved(requestIdBefore_); - emit RequestCreated(requestIdBefore_ + 1, lp, 2); - // Increase shares from 1 to 2 creates a new request and removes the old one. - uint256 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 2); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(newRequestId_, 2, "Request ID should be 2"); - assertEq(requestIds_.length, 1, "Request IDs length should be 1"); - assertEq(shares_.length, 1, "Shares length should be 1"); - assertEq(requestIds_[0], 2, "Request ID should be 2"); - assertEq(shares_[0], 2, "Shares should be 2"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 2); - } - - function test_updateShares_remove_request() external{ - vm.prank(pm); - uint256 requestId_ = withdrawalManager.addShares(1, lp); - - assertEq(withdrawalManager.userEscrowedShares(lp), 1); - - vm.prank(lp); - vm.expectEmit(); - emit RequestRemoved(requestId_); - - uint256 newRequestId_ = withdrawalManager.updateShares(requestId_, 0); - - assertEq(newRequestId_, 0, "Request ID should be 0"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 0); - } - - function test_updateShares_decrease() external { - vm.prank(pm); - uint256 requestIdBefore_ = withdrawalManager.addShares(2, lp); - - assertEq(withdrawalManager.userEscrowedShares(lp), 2); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIdBefore_, 1, "Request ID Before should be 1 (addShares)"); - assertEq(requestIdsBefore_.length, 1, "Request IDs length before should be 1"); - assertEq(sharesBefore_.length, 1, "Shares length before should be 1"); - assertEq(requestIdsBefore_[0], 1, "Request ID before should be 1"); - assertEq(sharesBefore_[0], 2, "Shares before should be 2"); - - vm.prank(lp); - vm.expectEmit(); - emit RequestDecreased(requestIdBefore_, 1); - // Decrease shares from 2 to 1 keeps the same request ID. - uint256 newRequestId_ = withdrawalManager.updateShares(requestIdBefore_, 1); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(newRequestId_, 1, "Request ID should be 1 (addShares)"); - assertEq(requestIds_.length, 1, "Request IDs length should be 1"); - assertEq(shares_.length, 1, "Shares length should be 1"); - assertEq(requestIds_[0], 1, "Request ID should be 1"); - assertEq(shares_[0], 1, "Shares should be 1"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 1); - } - - function test_update_shares_multiple_lps_requestsByOwner() external { - address lp2_ = makeAddr("lp2"); - address lp3_ = makeAddr("lp3"); - - uint256 requestAmount1_ = 10; - uint256 requestAmount2_ = 20; - - pool.mint(pm, 3000); - pool.mint(lp, 1000); - pool.mint(lp2_, 1000); - pool.mint(lp3_, 1000); - - vm.prank(lp3_); - pool.approve(address(withdrawalManager), 1000); - - vm.startPrank(pm); - - vm.expectEmit(); - emit RequestCreated(1, lp, requestAmount1_); - withdrawalManager.addShares(requestAmount1_, lp); - - vm.expectEmit(); - emit RequestCreated(2, lp, requestAmount2_); - withdrawalManager.addShares(requestAmount2_, lp); - - vm.expectEmit(); - emit RequestCreated(3, lp2_, requestAmount1_); - withdrawalManager.addShares(requestAmount1_, lp2_); - - vm.expectEmit(); - emit RequestCreated(4, lp2_, requestAmount2_); - withdrawalManager.addShares(requestAmount2_, lp2_); - - vm.expectEmit(); - emit RequestCreated(5, lp3_, requestAmount1_); - withdrawalManager.addShares(requestAmount1_, lp3_); - - vm.expectEmit(); - emit RequestCreated(6, lp3_, requestAmount2_); - withdrawalManager.addShares(requestAmount2_, lp3_); - - vm.stopPrank(); - - - assertEq(withdrawalManager.userEscrowedShares(lp), requestAmount1_ + requestAmount2_); - assertEq(withdrawalManager.userEscrowedShares(lp2_), requestAmount1_ + requestAmount2_); - assertEq(withdrawalManager.userEscrowedShares(lp3_), requestAmount1_ + requestAmount2_); - - { - (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); - (uint256[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); - (uint256[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); - - assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); - assertEq(requestIdsLp2_.length, 2, "LP2 request IDs length should be 2"); - assertEq(requestIdsLp3_.length, 2, "LP3 request IDs length should be 2"); - - assertEq(sharesLp_.length, 2, "LP shares length should be 2"); - assertEq(sharesLp2_.length, 2, "LP2 shares length should be 2"); - assertEq(sharesLp3_.length, 2, "LP3 shares length should be 2"); - - assertEq(requestIdsLp_[0], 1, "LP request ID index 0 should be 1"); - assertEq(requestIdsLp_[1], 2, "LP request ID index 1 should be 2"); - assertEq(requestIdsLp2_[0], 3, "LP2 request ID index 0 should be 3"); - assertEq(requestIdsLp2_[1], 4, "LP2 request ID index 1 should be 4"); - assertEq(requestIdsLp3_[0], 5, "LP3 request ID index 0 should be 5"); - assertEq(requestIdsLp3_[1], 6, "LP3 request ID index 1 should be 6"); - - assertEq(sharesLp_[0], requestAmount1_, "LP shares index 0 is incorrect"); - assertEq(sharesLp_[1], requestAmount2_, "LP shares index 1 is incorrect"); - assertEq(sharesLp2_[0], requestAmount1_, "LP2 shares index 0 is incorrect"); - assertEq(sharesLp2_[1], requestAmount2_, "LP2 shares index 1 is incorrect"); - assertEq(sharesLp3_[0], requestAmount1_, "LP3 shares index 0 is incorrect"); - assertEq(sharesLp3_[1], requestAmount2_, "LP3 shares index 1 is incorrect"); - } - { - // Decrease a request - vm.prank(lp); - uint256 requestIdLp_ = 1; - vm.expectEmit(); - emit RequestDecreased(requestIdLp_, 1); - uint256 updatedRequestIdLp_ = withdrawalManager.updateShares(requestIdLp_, requestAmount1_ - 1); - assertEq(updatedRequestIdLp_, requestIdLp_, "Updated request ID should be 1"); - - // Remove a request - vm.prank(lp2_); - uint256 requestIdLp2_ = 3; - vm.expectEmit(); - emit RequestRemoved(requestIdLp2_); - uint256 updatedRequestIdLp2_ = withdrawalManager.updateShares(requestIdLp2_, 0); - assertEq(updatedRequestIdLp2_, 0, "Updated request ID should be 0"); - - // Increase a request - vm.prank(lp3_); - uint256 requestIdLp3_ = 5; - vm.expectEmit(); - emit RequestRemoved(requestIdLp3_); - emit RequestCreated(7, lp3_, requestAmount2_ + 10); - uint256 updatedRequestIdLp3 = withdrawalManager.updateShares(requestIdLp3_, requestAmount2_ + 10); - assertEq(updatedRequestIdLp3, 7, "Updated request ID should be 7"); - - vm.stopPrank(); - } - - { - (uint256[] memory requestIdsLp_, uint256[] memory sharesLp_) = withdrawalManager.requestsByOwner(lp); - (uint256[] memory requestIdsLp2_, uint256[] memory sharesLp2_) = withdrawalManager.requestsByOwner(lp2_); - (uint256[] memory requestIdsLp3_, uint256[] memory sharesLp3_) = withdrawalManager.requestsByOwner(lp3_); - - assertEq(requestIdsLp_.length, 2, "LP request IDs length should be 2"); - assertEq(requestIdsLp2_.length, 1, "LP2 request IDs length should be 1"); - assertEq(requestIdsLp3_.length, 2, "LP3 request IDs length should be 2"); - - assertEq(sharesLp_.length, 2, "LP shares length should be 2"); - assertEq(sharesLp2_.length, 1, "LP2 shares length should be 2"); - assertEq(sharesLp3_.length, 2, "LP3 shares length should be 2"); - - assertEq(requestIdsLp_[0], 1, "LP request ID index 0 should be 1"); - assertEq(requestIdsLp_[1], 2, "LP request ID index 1 should be 2"); - assertEq(requestIdsLp2_[0], 4, "LP2 request ID index 1 should be 4"); - assertEq(requestIdsLp3_[0], 6, "LP3 request ID index 0 should be 5"); - assertEq(requestIdsLp3_[1], 7, "LP3 request ID index 1 should be 7"); - - assertEq(sharesLp_[0], requestAmount1_ - 1, "LP shares index 0 is incorrect"); - assertEq(sharesLp_[1], requestAmount2_, "LP shares index 1 is incorrect"); - assertEq(sharesLp2_[0], requestAmount2_, "LP2 shares index 0 is incorrect"); - assertEq(sharesLp3_[0], requestAmount2_, "LP3 shares index 0 is incorrect"); - assertEq(sharesLp3_[1], requestAmount2_ + 10, "LP3 shares index 1 is incorrect"); - } - - assertEq(withdrawalManager.userEscrowedShares(lp), requestAmount1_ - 1 + requestAmount2_); - assertEq(withdrawalManager.userEscrowedShares(lp2_), requestAmount2_); - assertEq(withdrawalManager.userEscrowedShares(lp3_), requestAmount2_ + 10 + requestAmount2_); - } - -} - -contract UpdateSharesBatchFailureTests is TestBase { - - function setUp() public override { - super.setUp(); - uint256 mintAmount = 200; - - pool.mint(pm, mintAmount); // Simulate LP transfer into PM. - pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. - - vm.prank(pm); - pool.approve(address(withdrawalManager), mintAmount); - - vm.prank(lp); - pool.approve(address(withdrawalManager), mintAmount); - } - - function test_updateSharesBatch_invalid_array_lengths() external { - uint256[] memory requestIdsToUpdate_ = new uint256[](1); - uint256[] memory sharesToUpdate_ = new uint256[](2); - - vm.expectRevert("WM:USB:ARRAY_LENGTH_MISMATCH"); - withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - } - - function test_updateSharesBatch_invalid_request() external { - uint256[] memory requestIdsToUpdate_ = new uint256[](1); - uint256[] memory sharesToUpdate_ = new uint256[](1); - - vm.expectRevert("WM:US:INVALID_REQUEST"); - withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - } - - function test_updateSharesBatch_invalid_owner() external { - uint256[] memory requestIdsToUpdate_ = new uint256[](1); - uint256[] memory sharesToUpdate_ = new uint256[](1); - - vm.prank(pm); - uint256 requestId_ = withdrawalManager.addShares(1, lp); - - requestIdsToUpdate_[0] = requestId_; - sharesToUpdate_[0] = 2; - - vm.prank(address(0x12)); - vm.expectRevert("WM:US:NOT_OWNER"); - withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - } - - function test_updateSharesBatch_no_change_in_shares() external { - uint256[] memory requestIdsToUpdate_ = new uint256[](1); - uint256[] memory sharesToUpdate_ = new uint256[](1); - - vm.prank(pm); - uint256 requestId_ = withdrawalManager.addShares(1, lp); - - requestIdsToUpdate_[0] = requestId_; - sharesToUpdate_[0] = 1; - - vm.prank(lp); - vm.expectRevert("WM:US:NO_CHANGE"); - withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - } -} - -contract UpdateSharesBatchSuccessTests is TestBase { - - event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); - - event RequestDecreased(uint256 indexed requestId, uint256 shares); - - event RequestRemoved(uint256 indexed requestId); - - function setUp() public override { - super.setUp(); - uint256 mintAmount = 200; - - pool.mint(pm, mintAmount); // Simulate LP transfer into PM. - pool.mint(lp, mintAmount); // Give LP some shares to use to increase shares. - - vm.prank(pm); - pool.approve(address(withdrawalManager), mintAmount); - - vm.prank(lp); - pool.approve(address(withdrawalManager), mintAmount); - } - - - function test_updateSharesBatch_increase_single_request() external { - vm.prank(pm); - uint256 requestIdBefore_ = withdrawalManager.addShares(1, lp); - - assertEq(withdrawalManager.userEscrowedShares(lp), 1); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIdBefore_, 1, "Request ID should be 1"); - assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); - assertEq(sharesBefore_.length, 1, "Shares length should be 1"); - assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); - assertEq(sharesBefore_[0], 1, "Shares should be 1"); - - uint256[] memory requestIdsToUpdate_ = new uint256[](1); - requestIdsToUpdate_[0] = requestIdBefore_; - - uint256[] memory sharesToUpdate_ = new uint256[](1); - sharesToUpdate_[0] = 2; - - vm.prank(lp); - vm.expectEmit(); - emit RequestRemoved(requestIdBefore_); - emit RequestCreated(requestIdBefore_ + 1, lp, 2); - - // Increase shares creates a new request and removes the old one. - uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - - assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); - assertEq(newRequestIds_[0], 2, "New request ID should be 2"); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIds_.length, 1, "Request IDs length should be 1"); - assertEq(shares_.length, 1, "Shares length should be 1"); - assertEq(requestIds_[0], 2, "Request ID should be 2"); - assertEq(shares_[0], 2, "Shares should be 2"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 2); - } - - function test_updateSharesBatch_increase_multiple_requests() external { - vm.startPrank(pm); - uint256 requestId1Before_ = withdrawalManager.addShares(1, lp); - uint256 requestId2Before_ = withdrawalManager.addShares(1, lp); - vm.stopPrank(); - - assertEq(withdrawalManager.userEscrowedShares(lp), 2); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestId1Before_, 1, "Request ID should be 1"); - assertEq(requestId2Before_, 2, "Request ID should be 2"); - assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); - assertEq(sharesBefore_.length, 2, "Shares length should be 2"); - assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); - assertEq(sharesBefore_[0], 1, "Shares should be 1"); - assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); - assertEq(sharesBefore_[1], 1, "Shares should be 1"); - - uint256[] memory requestIdsToUpdate_ = new uint256[](2); - requestIdsToUpdate_[0] = requestId1Before_; - requestIdsToUpdate_[1] = requestId2Before_; - - uint256[] memory sharesToUpdate_ = new uint256[](2); - sharesToUpdate_[0] = 2; - sharesToUpdate_[1] = 3; - - vm.prank(lp); - vm.expectEmit(); - emit RequestRemoved(requestId1Before_); - emit RequestCreated(3, lp, 2); - - vm.expectEmit(); - emit RequestRemoved(requestId2Before_); - emit RequestCreated(4, lp, 3); - - // Increase shares creates a new request and removes the old one. - uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - - assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); - assertEq(newRequestIds_[0], 3, "New request ID should be 3"); - assertEq(newRequestIds_[1], 4, "New request ID should be 4"); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIds_.length, 2, "Request IDs length should be 2"); - assertEq(shares_.length, 2, "Shares length should be 2"); - assertEq(requestIds_[0], 3, "Request ID should be 3"); - assertEq(shares_[0], 2, "Shares should be 2"); - assertEq(requestIds_[1], 4, "Request ID should be 4"); - assertEq(shares_[1], 3, "Shares should be 3"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 5); - } - - function test_updateSharesBatch_decrease_single_request() external { - vm.prank(pm); - uint256 requestIdBefore_ = withdrawalManager.addShares(2, lp); - - assertEq(withdrawalManager.userEscrowedShares(lp), 2); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIdBefore_, 1, "Request ID should be 1"); - assertEq(requestIdsBefore_.length, 1, "Request IDs length should be 1"); - assertEq(sharesBefore_.length, 1, "Shares length should be 1"); - assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); - assertEq(sharesBefore_[0], 2, "Shares should be 2"); - - uint256[] memory requestIdsToUpdate_ = new uint256[](1); - requestIdsToUpdate_[0] = requestIdBefore_; - - uint256[] memory sharesToUpdate_ = new uint256[](1); - sharesToUpdate_[0] = 1; - - vm.prank(lp); - vm.expectEmit(); - emit RequestDecreased(requestIdBefore_, 1); - - // Decrease in shares updates the existing request. - uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - - assertEq(newRequestIds_.length, 1, "New request IDs length should be 1"); - assertEq(newRequestIds_[0], 1, "New request ID should be 1"); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIds_.length, 1, "Request IDs length should be 1"); - assertEq(shares_.length, 1, "Shares length should be 1"); - assertEq(requestIds_[0], 1, "Request ID should be 1"); - assertEq(shares_[0], 1, "Shares should be 1"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 1); - } - - function test_updateSharesBatch_decrease_multiple_requests() external { - vm.startPrank(pm); - uint256 requestId1Before_ = withdrawalManager.addShares(2, lp); - uint256 requestId2Before_ = withdrawalManager.addShares(2, lp); - vm.stopPrank(); - - assertEq(withdrawalManager.userEscrowedShares(lp), 4); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestId1Before_, 1, "Request ID should be 1"); - assertEq(requestId2Before_, 2, "Request ID should be 1"); - assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); - assertEq(sharesBefore_.length, 2, "Shares length should be 2"); - assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); - assertEq(sharesBefore_[0], 2, "Shares should be 2"); - assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); - assertEq(sharesBefore_[1], 2, "Shares should be 2"); - - uint256[] memory requestIdsToUpdate_ = new uint256[](2); - requestIdsToUpdate_[0] = requestId1Before_; - requestIdsToUpdate_[1] = requestId2Before_; - - uint256[] memory sharesToUpdate_ = new uint256[](2); - sharesToUpdate_[0] = 1; - sharesToUpdate_[1] = 1; - - vm.prank(lp); - vm.expectEmit(); - emit RequestDecreased(requestId1Before_, 1); - - vm.expectEmit(); - emit RequestDecreased(requestId2Before_, 1); - - // Decrease in shares updates the existing requests. - uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - - assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); - assertEq(newRequestIds_[0], 1, "New request ID should be 1"); - assertEq(newRequestIds_[1], 2, "New request ID should be 2"); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIds_.length, 2, "Request IDs length should be 2"); - assertEq(shares_.length, 2, "Shares length should be 2"); - assertEq(requestIds_[0], 1, "Request ID should be 1"); - assertEq(shares_[0], 1, "Shares should be 1"); - assertEq(requestIds_[1], 2, "Request ID should be 2"); - assertEq(shares_[1], 1, "Shares should be 1"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 2); - } - - function test_updateSharesBatch_increase_and_decrease_multiple_requests() external { - vm.startPrank(pm); - uint256 requestId1Before_ = withdrawalManager.addShares(1, lp); - uint256 requestId2Before_ = withdrawalManager.addShares(2, lp); - vm.stopPrank(); - - assertEq(withdrawalManager.userEscrowedShares(lp), 3); - - (uint256[] memory requestIdsBefore_, uint256[] memory sharesBefore_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestId1Before_, 1, "Request ID should be 1"); - assertEq(requestId2Before_, 2, "Request ID should be 1"); - assertEq(requestIdsBefore_.length, 2, "Request IDs length should be 2"); - assertEq(sharesBefore_.length, 2, "Shares length should be 2"); - assertEq(requestIdsBefore_[0], 1, "Request ID should be 1"); - assertEq(sharesBefore_[0], 1, "Shares should be 1"); - assertEq(requestIdsBefore_[1], 2, "Request ID should be 2"); - assertEq(sharesBefore_[1], 2, "Shares should be 2"); - - uint256[] memory requestIdsToUpdate_ = new uint256[](2); - requestIdsToUpdate_[0] = requestId1Before_; - requestIdsToUpdate_[1] = requestId2Before_; - - uint256[] memory sharesToUpdate_ = new uint256[](2); - sharesToUpdate_[0] = 2; - sharesToUpdate_[1] = 1; - - vm.prank(lp); - vm.expectEmit(); - emit RequestRemoved(requestId1Before_); - emit RequestCreated(3, lp, 2); - - vm.expectEmit(); - emit RequestDecreased(requestId2Before_, 1); - - // Decrease in shares updates the existing requests. - uint256[] memory newRequestIds_ = withdrawalManager.updateSharesBatch(requestIdsToUpdate_, sharesToUpdate_); - - assertEq(newRequestIds_.length, 2, "New request IDs length should be 2"); - assertEq(newRequestIds_[0], 3, "New request ID should be 3"); - assertEq(newRequestIds_[1], 2, "New request ID should be 2"); - - (uint256[] memory requestIds_, uint256[] memory shares_) = withdrawalManager.requestsByOwner(lp); - - assertEq(requestIds_.length, 2, "Request IDs length should be 1"); - assertEq(shares_.length, 2, "Shares length should be 1"); - assertEq(requestIds_[0], 2, "Request ID should be 2"); - assertEq(shares_[0], 1, "Shares should be 1"); - assertEq(requestIds_[1], 3, "Request ID should be 3"); - assertEq(shares_[1], 2, "Shares should be 2"); - - assertEq(withdrawalManager.userEscrowedShares(lp), 3); - } - -} diff --git a/tests/utils/Mocks.sol b/tests/utils/Mocks.sol index 1963060..4706c38 100644 --- a/tests/utils/Mocks.sol +++ b/tests/utils/Mocks.sol @@ -108,7 +108,7 @@ contract MockPool is MockERC20 { asset_ = address(_asset); } - function redeem(uint256, address, address) external view returns (uint256 assets_) { + function redeem(uint256, address, address) external pure returns (uint256 assets_) { assets_; // Ignore variable } diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index 0050d0c..3ea0383 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -35,6 +35,12 @@ contract TestBase is Test { MapleWithdrawalManagerFactory internal factory; MapleWithdrawalManagerHarness internal withdrawalManager; + event ManualWithdrawalSet(address indexed account, bool isManual); + event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); + event RequestDecreased(uint256 indexed requestId, uint256 shares); + event RequestProcessed(uint256 indexed requestId, address indexed owner, uint256 shares, uint256 assets); + event RequestRemoved(uint256 indexed requestId); + function setUp() public virtual { // Create all mocks. asset = new MockERC20("Wrapped Ether", "WETH", 18); From 7b31fbd66f1b165caa6191bd0e4cd09dafdb7c60 Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:53:37 +0400 Subject: [PATCH 11/21] feat: WithdrawalManager access control changes (SC-21760) (#41) * feat: changed access control Signed-off-by: calmacfadden * feat: added tests Signed-off-by: calmacfadden --------- Signed-off-by: calmacfadden --- contracts/MapleWithdrawalManager.sol | 20 ++++++++++---------- tests/unit/ProcessRedemptions.t.sol | 22 +++++++--------------- tests/unit/RemoveRequest.t.sol | 10 ++++++++-- tests/unit/RemoveSharesById.t.sol | 2 +- tests/unit/SetManualWithdrawal.t.sol | 10 ++++++++-- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 67ca43e..75e38dc 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -62,27 +62,22 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } modifier onlyRedeemer { - address globals_ = globals(); - require( msg.sender == IPoolManagerLike(poolManager).poolDelegate() || - msg.sender == IGlobalsLike(globals_).governor() || - msg.sender == IGlobalsLike(globals_).operationalAdmin() || - IGlobalsLike(globals_).isInstanceOf("WITHDRAWAL_REDEEMER", msg.sender), + IGlobalsLike(globals()).isInstanceOf("WITHDRAWAL_REDEEMER", msg.sender), "WM:NOT_REDEEMER" ); _; } - modifier onlyPoolDelegateOrProtocolAdmins { + modifier onlyPoolDelegateOrOperationalAdmin { address globals_ = globals(); require( msg.sender == IPoolManagerLike(poolManager).poolDelegate() || - msg.sender == IGlobalsLike(globals_).governor() || msg.sender == IGlobalsLike(globals_).operationalAdmin(), - "WM:NOT_PD_OR_GOV_OR_OA" + "WM:NOT_POOL_DELEG_OR_OPS_ADMIN" ); _; @@ -210,7 +205,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag address owner_, uint256[] calldata requestIds_ ) - external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins + external override whenProtocolNotPaused onlyPoolDelegateOrOperationalAdmin { require(owner_ != address(0), "WM:RR:ZERO_OWNER"); require(requestIds_.length > 0, "WM:RR:ZERO_REQUESTS"); @@ -235,7 +230,12 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag totalShares -= sharesToRemove_; } - function setManualWithdrawal(address owner_, bool isManual_) external override whenProtocolNotPaused onlyPoolDelegateOrProtocolAdmins { + function setManualWithdrawal( + address owner_, + bool isManual_ + ) + external override whenProtocolNotPaused onlyPoolDelegateOrOperationalAdmin + { isManualWithdrawal[owner_] = isManual_; emit ManualWithdrawalSet(owner_, isManual_); diff --git a/tests/unit/ProcessRedemptions.t.sol b/tests/unit/ProcessRedemptions.t.sol index d41130c..83549a4 100644 --- a/tests/unit/ProcessRedemptions.t.sol +++ b/tests/unit/ProcessRedemptions.t.sol @@ -18,6 +18,8 @@ contract ProcessRedemptionsTests is TestBase { poolManager.__setTotalAssets(assetsDeposited); withdrawalManager.__setTotalShares(sharesLocked); + + globals.__setIsInstanceOf(false); } function test_processRedemptions_protocolPaused() external { @@ -28,8 +30,12 @@ contract ProcessRedemptionsTests is TestBase { } function test_processRedemptions_notRedeemer() external { - globals.__setIsInstanceOf(false); + vm.expectRevert("WM:NOT_REDEEMER"); + withdrawalManager.processRedemptions(sharesLocked); + } + function test_processRedemptions_governorNotAllowed() external { + vm.prank(governor); vm.expectRevert("WM:NOT_REDEEMER"); withdrawalManager.processRedemptions(sharesLocked); } @@ -55,20 +61,6 @@ contract ProcessRedemptionsTests is TestBase { assertQueue({ nextRequestId: 1, lastRequestId: 0 }); } - function test_processRedemptions_governor() external { - vm.prank(governor); - withdrawalManager.processRedemptions(sharesLocked); - - assertQueue({ nextRequestId: 1, lastRequestId: 0 }); - } - - function test_processRedemptions_operationalAdmin() external { - vm.prank(operationalAdmin); - withdrawalManager.processRedemptions(sharesLocked); - - assertQueue({ nextRequestId: 1, lastRequestId: 0 }); - } - function test_processRedemptions_bot() external { globals.__setIsInstanceOf(true); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index b8bdadf..964e104 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -21,8 +21,14 @@ contract RemoveRequestTests is TestBase { withdrawalManager.removeRequest(lp, new uint256[](0)); } - function test_removeRequest_notProtocolAdmin() external { - vm.expectRevert("WM:NOT_PD_OR_GOV_OR_OA"); + function test_removeRequest_notPoolDelegateOrAdmin() external { + vm.expectRevert("WM:NOT_POOL_DELEG_OR_OPS_ADMIN"); + withdrawalManager.removeRequest(lp, new uint256[](0)); + } + + function test_removeRequest_governorNotAllowed() external { + vm.prank(governor); + vm.expectRevert("WM:NOT_POOL_DELEG_OR_OPS_ADMIN"); withdrawalManager.removeRequest(lp, new uint256[](0)); } diff --git a/tests/unit/RemoveSharesById.t.sol b/tests/unit/RemoveSharesById.t.sol index 583000f..ad81aeb 100644 --- a/tests/unit/RemoveSharesById.t.sol +++ b/tests/unit/RemoveSharesById.t.sol @@ -42,7 +42,7 @@ contract RemoveSharesByIdFailureTests is TestBase { uint256[] memory requestIds = new uint256[](1); requestIds[0] = 1; - vm.prank(governor); + vm.prank(operationalAdmin); withdrawalManager.removeRequest(lp, requestIds); vm.expectRevert("WM:RSBI:INVALID_REQUEST"); diff --git a/tests/unit/SetManualWithdrawal.t.sol b/tests/unit/SetManualWithdrawal.t.sol index c301ec8..c946221 100644 --- a/tests/unit/SetManualWithdrawal.t.sol +++ b/tests/unit/SetManualWithdrawal.t.sol @@ -12,8 +12,14 @@ contract SetManualWithdrawalTests is TestBase { withdrawalManager.setManualWithdrawal(lp, true); } - function test_setManualWithdrawal_notProtocolAdmin() external { - vm.expectRevert("WM:NOT_PD_OR_GOV_OR_OA"); + function test_setManualWithdrawal_notPoolDelegateOrOpsAdmin() external { + vm.expectRevert("WM:NOT_POOL_DELEG_OR_OPS_ADMIN"); + withdrawalManager.setManualWithdrawal(lp, true); + } + + function test_setManualWithdrawal_governotNotAllowed() external { + vm.prank(governor); + vm.expectRevert("WM:NOT_POOL_DELEG_OR_OPS_ADMIN"); withdrawalManager.setManualWithdrawal(lp, true); } From d2725904f8c681b450da60c1bcaf63c17060b046 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:36:58 +0400 Subject: [PATCH 12/21] feat: Internal Audit fixes (#42) * feat: update event per OC request * feat: Internal audit fixes * feat: fix foundry TOML * fix: comment out fuzz tests * fix: comment out fuzz tests * fix: comment out fuzz tests * fix: Alignment --- contracts/MapleWithdrawalManager.sol | 16 ++--- .../interfaces/IMapleWithdrawalManager.sol | 3 +- .../MapleWithdrawalManagerMigratorV200.sol | 13 ++-- .../proxy/MapleWithdrawalManagerStorage.sol | 2 +- contracts/utils/SortedArray.sol | 65 +++++++++---------- foundry.toml | 4 +- tests/fuzz/AddSharesFuzz.t.sol | 4 +- tests/fuzz/RemoveSharesFuzz.t.sol | 4 +- tests/utils/Harnesses.sol | 12 ++-- 9 files changed, 62 insertions(+), 61 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 75e38dc..2bec7ed 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -156,7 +156,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag require(totalEscrowedShares_ >= shares_, "WM:RS:INSUFFICIENT_SHARES"); while (sharesReturned_ < shares_) { - uint256 requestId_ = SortedArray.getLast(userRequests[owner_]); + uint256 requestId_ = SortedArray.getLast(_userRequests[owner_]); WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; uint256 sharesToRemove_ = _min(shares_ - sharesReturned_, request_.shares); @@ -233,8 +233,8 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag function setManualWithdrawal( address owner_, bool isManual_ - ) - external override whenProtocolNotPaused onlyPoolDelegateOrOperationalAdmin + ) + external override whenProtocolNotPaused onlyPoolDelegateOrOperationalAdmin { isManualWithdrawal[owner_] = isManual_; @@ -273,7 +273,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag queue.requests[_toUint128(lastRequestId_)] = WithdrawalRequest(owner_, shares_); userEscrowedShares[owner_] += shares_; - SortedArray.push(userRequests[owner_], lastRequestId_); + SortedArray.push(_userRequests[owner_], lastRequestId_); // Increase the number of shares locked. totalShares += shares_; @@ -398,7 +398,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag if (isManualWithdrawal[request_.owner]) { manualSharesAvailable[request_.owner] += processedShares_; - emit ManualSharesIncreased(request_.owner, processedShares_); + emit ManualSharesIncreased(requestId_, request_.owner, processedShares_); } else { // Otherwise, just adjust totalShares and perform the redeem. totalShares -= processedShares_; @@ -409,7 +409,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag function _removeRequest(address owner_, uint256 requestId_) internal { userEscrowedShares[owner_] -= queue.requests[_toUint128(requestId_)].shares; - SortedArray.remove(userRequests[owner_], requestId_); + SortedArray.remove(_userRequests[owner_], requestId_); delete queue.requests[_toUint128(requestId_)]; emit RequestRemoved(requestId_); @@ -491,7 +491,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function requestIds(address owner_) external view override returns (uint256 requestId_) { - requestId_ = SortedArray.getLast(userRequests[owner_]); + requestId_ = SortedArray.getLast(_userRequests[owner_]); } function requests(uint256 requestId_) external view override returns (address owner_, uint256 shares_) { @@ -500,7 +500,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function requestsByOwner(address owner_) external view override returns (uint256[] memory requestIds_, uint256[] memory shares_) { - requestIds_ = SortedArray.getAllValues(userRequests[owner_]); + requestIds_ = SortedArray.getAllValues(_userRequests[owner_]); shares_ = new uint256[](requestIds_.length); for (uint256 i = 0; i < requestIds_.length; ++i) { diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index fb24d4f..50b8fd1 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -20,10 +20,11 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi /** * @dev Emitted when a manual redemption is processed. + * @param requestId Identifier of the withdrawal request. * @param owner Address of the account. * @param sharesAdded Amount of shares added to the redeemable amount. */ - event ManualSharesIncreased(address indexed owner, uint256 sharesAdded); + event ManualSharesIncreased(uint256 indexed requestId, address indexed owner, uint256 sharesAdded); /** * @dev Emitted when the withdrawal type of an account is updated. diff --git a/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol b/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol index bf2c7bf..1c4316c 100644 --- a/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol +++ b/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol @@ -14,12 +14,13 @@ contract MapleWithdrawalManagerMigratorV200 is MapleProxiedInternals, MapleWithd uint128 lastRequestId_ = queue.lastRequestId; for (uint128 i = nextRequestId_; i <= lastRequestId_; ++i) { - WithdrawalRequest storage request = queue.requests[i]; - - if (request.owner != address(0)) { - userEscrowedShares[request.owner] += request.shares; - SortedArray.push(userRequests[request.owner], i); - } + WithdrawalRequest memory request_ = queue.requests[i]; + + if (request_.owner == address(0)) continue; + + userEscrowedShares[request_.owner] += request_.shares; + + SortedArray.push(_userRequests[request_.owner], i); } } diff --git a/contracts/proxy/MapleWithdrawalManagerStorage.sol b/contracts/proxy/MapleWithdrawalManagerStorage.sol index 42510ad..00faadc 100644 --- a/contracts/proxy/MapleWithdrawalManagerStorage.sol +++ b/contracts/proxy/MapleWithdrawalManagerStorage.sol @@ -43,6 +43,6 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(address => uint256) public override userEscrowedShares; // Maps users to their escrowed shares yet to be processed. - mapping(address => SortedArray.Array) internal userRequests; // Maps users to their withdrawal requests. + mapping(address => SortedArray.Array) internal _userRequests; // Maps users to their withdrawal requests. } diff --git a/contracts/utils/SortedArray.sol b/contracts/utils/SortedArray.sol index 5ada4cf..e030850 100644 --- a/contracts/utils/SortedArray.sol +++ b/contracts/utils/SortedArray.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.7; -// TODO: How to best handle library interfaces? library SortedArray { struct Array { @@ -17,40 +16,40 @@ library SortedArray { /** * @dev Pushes a value to the array. * It is expected that the value is biggest so far so it will be added at the end of the array. - * @param array_ The array to push the value to. + * @param array The array to push the value to. * @param value_ The value to push to the array. */ - function push(Array storage array_, uint256 value_) internal { - require(getLast(array_) < value_, "SA:P:NOT_LARGEST"); + function push(Array storage array, uint256 value_) internal { + require(getLast(array) < value_, "SA:P:NOT_LARGEST"); - array_.values.push(value_); - array_.valueToIndex[value_] = length(array_) - 1; + array.values.push(value_); + array.valueToIndex[value_] = length(array) - 1; } /** * @dev Removes a value from the array. * It shifts the rest of the array to the left. * It is expected by contract that uses this library to check that the value is present in the array before call of the function. - * @param array_ The array to remove the value from. + * @param array The array to remove the value from. * @param value_ The value to remove from the array. */ - function remove(Array storage array_, uint256 value_) internal { - uint256 length_ = length(array_); + function remove(Array storage array, uint256 value_) internal { + uint256 length_ = length(array); if (length_ == 1) { - _deleteValue(array_, value_); + _deleteValue(array, value_); return; } - uint256 index_ = array_.valueToIndex[value_]; + uint256 index_ = array.valueToIndex[value_]; for (uint256 i = index_; i <= length_ - 2; i++) { - uint256 nextValue_ = array_.values[i + 1]; - array_.values[i] = nextValue_; - array_.valueToIndex[nextValue_] = i; + uint256 nextValue_ = array.values[i + 1]; + array.values[i] = nextValue_; + array.valueToIndex[nextValue_] = i; } - _deleteValue(array_, value_); + _deleteValue(array, value_); } /**************************************************************************************************************************************/ @@ -59,41 +58,41 @@ library SortedArray { /** * @dev Gets a value from the array at given index. - * @param array_ The array to get the value from. + * @param array The array to get the value from. * @param index_ The index of the value to get from the array. * @return value_ The value at the given index. */ - function get(Array storage array_, uint256 index_) internal view returns (uint256 value_) { - require(index_ < length(array_), "SA:G:OUT_OF_BOUNDS"); - value_ = array_.values[index_]; + function get(Array storage array, uint256 index_) internal view returns (uint256 value_) { + require(index_ < length(array), "SA:G:OUT_OF_BOUNDS"); + value_ = array.values[index_]; } /** * @dev Gets the length of the array. - * @param array_ The array to get the length of. + * @param array The array to get the length of. * @return length_ The length of the array. */ - function length(Array storage array_) internal view returns (uint256 length_) { - length_ = array_.values.length; + function length(Array storage array) internal view returns (uint256 length_) { + length_ = array.values.length; } /** * @dev Gets all values from the array. - * @param array_ The array to get the values from. + * @param array The array to get the values from. * @return values_ All values from the array. */ - function getAllValues(Array storage array_) internal view returns (uint256[] memory values_) { - values_ = array_.values; + function getAllValues(Array storage array) internal view returns (uint256[] memory values_) { + values_ = array.values; } /** * @dev Gets the last value in the array. - * @param array_ The array to get the last value from. + * @param array The array to get the last value from. * @return value_ The last value in the array. */ - function getLast(Array storage array_) internal view returns (uint256 value_) { - uint256 length_ = length(array_); - return length_ > 0 ? array_.values[length_ - 1] : 0; + function getLast(Array storage array) internal view returns (uint256 value_) { + uint256 length_ = length(array); + return length_ > 0 ? array.values[length_ - 1] : 0; } /**************************************************************************************************************************************/ @@ -103,12 +102,12 @@ library SortedArray { /** * @dev Deletes a value from the array. * It is expected that array is already shifted so this will just pop the last and flag given value as not present. - * @param array_ The array to delete the value from. + * @param array The array to delete the value from. * @param value_ The value to delete from the array. */ - function _deleteValue(Array storage array_, uint256 value_) private { - array_.values.pop(); - delete array_.valueToIndex[value_]; + function _deleteValue(Array storage array, uint256 value_) private { + array.values.pop(); + delete array.valueToIndex[value_]; } } diff --git a/foundry.toml b/foundry.toml index 05293fe..172f501 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,8 +8,8 @@ verbosity = 3 # The verbosity of tests block_timestamp = 1_622_400_000 # Timestamp for tests (non-zero) fuzz_runs = 100 # Number of fuzz runs -[profile.deep] -fuzz_runs = 1000 +[profile.ci.fuzz] +runs = 1000 [profile.super_deep] fuzz_runs = 50000 diff --git a/tests/fuzz/AddSharesFuzz.t.sol b/tests/fuzz/AddSharesFuzz.t.sol index 67d6bbc..0c079c4 100644 --- a/tests/fuzz/AddSharesFuzz.t.sol +++ b/tests/fuzz/AddSharesFuzz.t.sol @@ -9,7 +9,7 @@ contract AddSharesFuzzTests is TestBase { super.setUp(); } - function testFuzz_addShares(uint256[50] memory amount_, address[50] calldata account_) external { + function testFuzz_addShares(uint256[10] memory amount_, address[10] calldata account_) external { uint256 lastRequestId; uint256 totalShares_; uint256 requestId_; @@ -17,7 +17,7 @@ contract AddSharesFuzzTests is TestBase { for (uint256 i; i < account_.length; ++i) { amount_[i] = bound(amount_[i], 1, 1e29); - + pool.mint(pm, amount_[i]); vm.startPrank(pm); diff --git a/tests/fuzz/RemoveSharesFuzz.t.sol b/tests/fuzz/RemoveSharesFuzz.t.sol index b44d15a..52eed7d 100644 --- a/tests/fuzz/RemoveSharesFuzz.t.sol +++ b/tests/fuzz/RemoveSharesFuzz.t.sol @@ -9,14 +9,14 @@ contract RemoveSharesFuzzTests is TestBase { super.setUp(); } - function testFuzz_removeShares(address[50] calldata account_, uint256[50] memory amount0_, uint256[50] memory amount1_) external { + function testFuzz_removeShares(address[10] calldata account_, uint256[10] memory amount0_, uint256[10] memory amount1_) external { address owner_; uint256 lastRequestId; uint256 shares_; uint256 totalShares_; uint256 requestId_; - + for (uint256 i; i < account_.length; ++i) { amount0_[i] = bound(amount0_[i], 1, 1e29); amount1_[i] = bound(amount1_[i], 1, 1e29); diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index 7284b8c..44689f0 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -20,7 +20,7 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setLastRequest(address owner_, uint256 requestId_) external { - SortedArray.push(userRequests[owner_], requestId_); + SortedArray.push(_userRequests[owner_], requestId_); queue.lastRequestId = _toUint128(requestId_); } @@ -42,7 +42,7 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { queue.lastRequestId = requestId_; queue.requests[requestId_] = WithdrawalRequest(owner_, shares_); - SortedArray.push(userRequests[owner_], requestId_); + SortedArray.push(_userRequests[owner_], requestId_); } function __setTotalShares(uint256 totalShares_) external { @@ -50,15 +50,15 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setUserRequestCount(address owner_, uint256 requestCount_, uint256 escrowSharesTotal_) external { - uint256 currentRequestCount_ = SortedArray.length(userRequests[owner_]); + uint256 currentRequestCount_ = SortedArray.length(_userRequests[owner_]); if (requestCount_ < currentRequestCount_) { for (uint256 i = requestCount_; i < currentRequestCount_; i++) { - SortedArray.remove(userRequests[owner_], _toUint128(i)); + SortedArray.remove(_userRequests[owner_], _toUint128(i)); } } else { for (uint256 i = currentRequestCount_; i > requestCount_; i++) { - SortedArray.push(userRequests[owner_], _toUint128(i)); + SortedArray.push(_userRequests[owner_], _toUint128(i)); } } @@ -95,4 +95,4 @@ contract SortedArrayHarness { return SortedArray.getLast(array); } -} +} From 31481c14edd9f95f0ad8a6f189ff500e3f88c5ff Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:35:30 +0400 Subject: [PATCH 13/21] feat: Use external over public (#44) --- contracts/MapleWithdrawalManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 2bec7ed..5268f92 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -249,7 +249,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag uint256 requestId_, uint256 sharesToRemove_ ) - public override whenProtocolNotPaused nonReentrant returns (uint256 sharesReturned_, uint256 sharesRemaining_) + external override whenProtocolNotPaused nonReentrant returns (uint256 sharesReturned_, uint256 sharesRemaining_) { WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; From 27385213a4c363f9ad909676fff9857e8466650b Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:35:49 +0400 Subject: [PATCH 14/21] Audit: Fix for loop usage for removing shares via admin (#45) --- contracts/MapleWithdrawalManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 5268f92..9d1da8a 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -223,10 +223,10 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _removeRequest(owner_, requestIds_[i]); sharesToRemove_ += withdrawalRequest_.shares; - - require(ERC20Helper.transfer(pool, owner_, withdrawalRequest_.shares), "WM:RR:TRANSFER_FAIL"); } + require(ERC20Helper.transfer(pool, owner_, sharesToRemove_), "WM:RR:TRANSFER_FAIL"); + totalShares -= sharesToRemove_; } From 335ea608e8344dba2d85f832804e46c4f8aabaa0 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:40:06 +0400 Subject: [PATCH 15/21] Audit: Add comment clarifying usage for `removeRequest` on WM (#46) * Audit: Add comment clarifying usage for removeRequest on WM * chore: add comment --- contracts/MapleWithdrawalManager.sol | 1 + contracts/interfaces/IMapleWithdrawalManager.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 9d1da8a..06053ac 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -201,6 +201,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /*** State-Changing Functions - Admin functions ***/ /**************************************************************************************************************************************/ + // NOTE: Not to be used in a router based system where the router is managing user requests. function removeRequest( address owner_, uint256[] calldata requestIds_ diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index 50b8fd1..e51637b 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -112,6 +112,7 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi /** * @dev Removes withdrawal requests from the queue. * Can only be called by the pool delegate. + * NOTE: Not to be used in a router based system where the router is managing user requests. * @param owner Address of the owner of shares. * @param requestIds Array of identifiers of the withdrawal requests to remove. */ From 74cf0c348ee3606a97eb7e2c35d38b67a1474f48 Mon Sep 17 00:00:00 2001 From: Vedran Bidin Date: Wed, 15 Oct 2025 11:22:45 +0700 Subject: [PATCH 16/21] fix: Add bot support for `removeRequest()` (SC-22023) (#47) * fix: add bot support for removeRequests() * chore: Fix format --------- Co-authored-by: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> --- contracts/MapleWithdrawalManager.sol | 31 ++++++++++++++-------------- tests/unit/RemoveRequest.t.sol | 10 +++------ 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 06053ac..287b881 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -61,16 +61,6 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _locked = 1; } - modifier onlyRedeemer { - require( - msg.sender == IPoolManagerLike(poolManager).poolDelegate() || - IGlobalsLike(globals()).isInstanceOf("WITHDRAWAL_REDEEMER", msg.sender), - "WM:NOT_REDEEMER" - ); - - _; - } - modifier onlyPoolDelegateOrOperationalAdmin { address globals_ = globals(); @@ -89,6 +79,19 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag _; } + modifier onlyRedeemer { + address globals_ = globals(); + + require( + IGlobalsLike(globals_).isInstanceOf("WITHDRAWAL_REDEEMER", msg.sender) || + msg.sender == IPoolManagerLike(poolManager).poolDelegate() || + msg.sender == IGlobalsLike(globals_).operationalAdmin(), + "WM:NOT_REDEEMER" + ); + + _; + } + modifier whenProtocolNotPaused() { require(!IGlobalsLike(globals()).isFunctionPaused(msg.sig), "WM:PAUSED"); _; @@ -165,7 +168,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } /**************************************************************************************************************************************/ - /*** State-Changing Functions - onlyRedeemer ***/ + /*** State-Changing Functions - OnlyRedeemer ***/ /**************************************************************************************************************************************/ function processRedemptions(uint256 maxSharesToProcess_) external override whenProtocolNotPaused nonReentrant onlyRedeemer { @@ -197,16 +200,12 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag queue.nextRequestId = _toUint128(nextRequestId_); } - /**************************************************************************************************************************************/ - /*** State-Changing Functions - Admin functions ***/ - /**************************************************************************************************************************************/ - // NOTE: Not to be used in a router based system where the router is managing user requests. function removeRequest( address owner_, uint256[] calldata requestIds_ ) - external override whenProtocolNotPaused onlyPoolDelegateOrOperationalAdmin + external override whenProtocolNotPaused onlyRedeemer { require(owner_ != address(0), "WM:RR:ZERO_OWNER"); require(requestIds_.length > 0, "WM:RR:ZERO_REQUESTS"); diff --git a/tests/unit/RemoveRequest.t.sol b/tests/unit/RemoveRequest.t.sol index 964e104..50a98ce 100644 --- a/tests/unit/RemoveRequest.t.sol +++ b/tests/unit/RemoveRequest.t.sol @@ -21,14 +21,10 @@ contract RemoveRequestTests is TestBase { withdrawalManager.removeRequest(lp, new uint256[](0)); } - function test_removeRequest_notPoolDelegateOrAdmin() external { - vm.expectRevert("WM:NOT_POOL_DELEG_OR_OPS_ADMIN"); - withdrawalManager.removeRequest(lp, new uint256[](0)); - } + function test_removeRequest_notAuthorized() external { + globals.__setIsInstanceOf(false); - function test_removeRequest_governorNotAllowed() external { - vm.prank(governor); - vm.expectRevert("WM:NOT_POOL_DELEG_OR_OPS_ADMIN"); + vm.expectRevert("WM:NOT_REDEEMER"); withdrawalManager.removeRequest(lp, new uint256[](0)); } From 28b18e0fb23f9f7ed763d22e0233991fc9794494 Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:02:28 +0400 Subject: [PATCH 17/21] Audit: Swap Array to LinkedList (#43) * feat: Swap Array to LinkedList * feat: update sorted linked list * fix: rename array to list * fix: remove get function from library * fix: cache list size * chore: format: * fix: remove unneccesary casting --------- Co-authored-by: Danilo --- contracts/MapleWithdrawalManager.sol | 21 +-- .../MapleWithdrawalManagerMigratorV200.sol | 4 +- .../proxy/MapleWithdrawalManagerStorage.sol | 4 +- contracts/utils/SortedArray.sol | 113 --------------- contracts/utils/SortedLinkedList.sol | 132 ++++++++++++++++++ tests/unit/SortedArray/Get.t.sol | 29 ---- tests/unit/SortedArray/Push.t.sol | 42 +++--- tests/unit/SortedArray/Remove.t.sol | 46 +++--- tests/utils/Harnesses.sol | 38 +++-- tests/utils/TestBase.sol | 20 ++- 10 files changed, 221 insertions(+), 228 deletions(-) delete mode 100644 contracts/utils/SortedArray.sol create mode 100644 contracts/utils/SortedLinkedList.sol delete mode 100644 tests/unit/SortedArray/Get.t.sol diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 287b881..515390d 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -16,7 +16,7 @@ import { import { MapleWithdrawalManagerStorage } from "./proxy/MapleWithdrawalManagerStorage.sol"; -import { SortedArray } from "./utils/SortedArray.sol"; +import { SortedLinkedList } from "./utils/SortedLinkedList.sol"; /* @@ -159,7 +159,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag require(totalEscrowedShares_ >= shares_, "WM:RS:INSUFFICIENT_SHARES"); while (sharesReturned_ < shares_) { - uint256 requestId_ = SortedArray.getLast(_userRequests[owner_]); + uint256 requestId_ = SortedLinkedList.getLast(_userRequests[owner_]); WithdrawalRequest memory request_ = queue.requests[_toUint128(requestId_)]; uint256 sharesToRemove_ = _min(shares_ - sharesReturned_, request_.shares); @@ -273,7 +273,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag queue.requests[_toUint128(lastRequestId_)] = WithdrawalRequest(owner_, shares_); userEscrowedShares[owner_] += shares_; - SortedArray.push(_userRequests[owner_], lastRequestId_); + SortedLinkedList.push(_userRequests[owner_], _toUint128(lastRequestId_)); // Increase the number of shares locked. totalShares += shares_; @@ -409,7 +409,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag function _removeRequest(address owner_, uint256 requestId_) internal { userEscrowedShares[owner_] -= queue.requests[_toUint128(requestId_)].shares; - SortedArray.remove(_userRequests[owner_], requestId_); + SortedLinkedList.remove(_userRequests[owner_], _toUint128(requestId_)); delete queue.requests[_toUint128(requestId_)]; emit RequestRemoved(requestId_); @@ -491,7 +491,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function requestIds(address owner_) external view override returns (uint256 requestId_) { - requestId_ = SortedArray.getLast(_userRequests[owner_]); + requestId_ = SortedLinkedList.getLast(_userRequests[owner_]); } function requests(uint256 requestId_) external view override returns (address owner_, uint256 shares_) { @@ -500,11 +500,14 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag } function requestsByOwner(address owner_) external view override returns (uint256[] memory requestIds_, uint256[] memory shares_) { - requestIds_ = SortedArray.getAllValues(_userRequests[owner_]); - shares_ = new uint256[](requestIds_.length); + uint128[] memory requestIdsByOwner_ = SortedLinkedList.getAllValues(_userRequests[owner_]); - for (uint256 i = 0; i < requestIds_.length; ++i) { - shares_[i] = queue.requests[_toUint128(requestIds_[i])].shares; + requestIds_ = new uint256[](requestIdsByOwner_.length); + shares_ = new uint256[](requestIdsByOwner_.length); + + for (uint256 i = 0; i < requestIdsByOwner_.length; ++i) { + requestIds_[i] = requestIdsByOwner_[i]; + shares_[i] = queue.requests[requestIdsByOwner_[i]].shares; } } diff --git a/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol b/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol index 1c4316c..e5d3034 100644 --- a/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol +++ b/contracts/proxy/MapleWithdrawalManagerMigratorV200.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.7; import { MapleProxiedInternals } from "../../modules/maple-proxy-factory/contracts/MapleProxiedInternals.sol"; -import { SortedArray } from "../utils/SortedArray.sol"; +import { SortedLinkedList } from "../utils/SortedLinkedList.sol"; import { MapleWithdrawalManagerStorage } from "./MapleWithdrawalManagerStorage.sol"; @@ -20,7 +20,7 @@ contract MapleWithdrawalManagerMigratorV200 is MapleProxiedInternals, MapleWithd userEscrowedShares[request_.owner] += request_.shares; - SortedArray.push(_userRequests[request_.owner], i); + SortedLinkedList.push(_userRequests[request_.owner], i); } } diff --git a/contracts/proxy/MapleWithdrawalManagerStorage.sol b/contracts/proxy/MapleWithdrawalManagerStorage.sol index 00faadc..ca92292 100644 --- a/contracts/proxy/MapleWithdrawalManagerStorage.sol +++ b/contracts/proxy/MapleWithdrawalManagerStorage.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.7; import { IMapleWithdrawalManagerStorage } from "../interfaces/IMapleWithdrawalManagerStorage.sol"; -import { SortedArray } from "../utils/SortedArray.sol"; +import { SortedLinkedList } from "../utils/SortedLinkedList.sol"; contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { @@ -43,6 +43,6 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage { mapping(address => uint256) public override userEscrowedShares; // Maps users to their escrowed shares yet to be processed. - mapping(address => SortedArray.Array) internal _userRequests; // Maps users to their withdrawal requests. + mapping(address => SortedLinkedList.List) internal _userRequests; // Maps users to their withdrawal requests. } diff --git a/contracts/utils/SortedArray.sol b/contracts/utils/SortedArray.sol deleted file mode 100644 index e030850..0000000 --- a/contracts/utils/SortedArray.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.7; - -library SortedArray { - - struct Array { - uint256[] values; - - mapping(uint256 => uint256) valueToIndex; - } - - /**************************************************************************************************************************************/ - /*** Write Functions ***/ - /**************************************************************************************************************************************/ - - /** - * @dev Pushes a value to the array. - * It is expected that the value is biggest so far so it will be added at the end of the array. - * @param array The array to push the value to. - * @param value_ The value to push to the array. - */ - function push(Array storage array, uint256 value_) internal { - require(getLast(array) < value_, "SA:P:NOT_LARGEST"); - - array.values.push(value_); - array.valueToIndex[value_] = length(array) - 1; - } - - /** - * @dev Removes a value from the array. - * It shifts the rest of the array to the left. - * It is expected by contract that uses this library to check that the value is present in the array before call of the function. - * @param array The array to remove the value from. - * @param value_ The value to remove from the array. - */ - function remove(Array storage array, uint256 value_) internal { - uint256 length_ = length(array); - - if (length_ == 1) { - _deleteValue(array, value_); - return; - } - - uint256 index_ = array.valueToIndex[value_]; - - for (uint256 i = index_; i <= length_ - 2; i++) { - uint256 nextValue_ = array.values[i + 1]; - array.values[i] = nextValue_; - array.valueToIndex[nextValue_] = i; - } - - _deleteValue(array, value_); - } - - /**************************************************************************************************************************************/ - /*** View Functions ***/ - /**************************************************************************************************************************************/ - - /** - * @dev Gets a value from the array at given index. - * @param array The array to get the value from. - * @param index_ The index of the value to get from the array. - * @return value_ The value at the given index. - */ - function get(Array storage array, uint256 index_) internal view returns (uint256 value_) { - require(index_ < length(array), "SA:G:OUT_OF_BOUNDS"); - value_ = array.values[index_]; - } - - /** - * @dev Gets the length of the array. - * @param array The array to get the length of. - * @return length_ The length of the array. - */ - function length(Array storage array) internal view returns (uint256 length_) { - length_ = array.values.length; - } - - /** - * @dev Gets all values from the array. - * @param array The array to get the values from. - * @return values_ All values from the array. - */ - function getAllValues(Array storage array) internal view returns (uint256[] memory values_) { - values_ = array.values; - } - - /** - * @dev Gets the last value in the array. - * @param array The array to get the last value from. - * @return value_ The last value in the array. - */ - function getLast(Array storage array) internal view returns (uint256 value_) { - uint256 length_ = length(array); - return length_ > 0 ? array.values[length_ - 1] : 0; - } - - /**************************************************************************************************************************************/ - /*** Private Functions ***/ - /**************************************************************************************************************************************/ - - /** - * @dev Deletes a value from the array. - * It is expected that array is already shifted so this will just pop the last and flag given value as not present. - * @param array The array to delete the value from. - * @param value_ The value to delete from the array. - */ - function _deleteValue(Array storage array, uint256 value_) private { - array.values.pop(); - delete array.valueToIndex[value_]; - } - -} diff --git a/contracts/utils/SortedLinkedList.sol b/contracts/utils/SortedLinkedList.sol new file mode 100644 index 0000000..97f1f1e --- /dev/null +++ b/contracts/utils/SortedLinkedList.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +library SortedLinkedList { + + struct Node { + uint128 next; + uint128 prev; + bool exists; + } + + struct List { + uint128 head; + uint128 tail; + uint256 size; + + mapping(uint128 => Node) nodes; + } + + /**************************************************************************************************************************************/ + /*** Write Functions ***/ + /**************************************************************************************************************************************/ + + /** + * @dev Pushes a value to the list. + * It is expected that the value is biggest so far so it will be added at the end of the list. + * @param list The list to push the value to. + * @param value_ The value to push to the list. + */ + function push(List storage list, uint128 value_) internal { + uint128 tail_ = list.tail; + + require(value_ > 0, "SLL:P:ZERO_VALUE"); + require(!contains(list, value_), "SLL:P:VALUE_EXISTS"); + require(value_ > tail_, "SLL:P:NOT_LARGEST"); + + list.nodes[value_] = Node({ + next: 0, + prev: tail_, + exists: true + }); + + if (tail_ != 0) { + list.nodes[tail_].next = value_; + } + + list.tail = value_; + + if (list.head == 0) { + list.head = value_; + } + + list.size++; + } + + /** + * @dev Removes a value from the list in O(1) time. + * @param list The list to remove the value from. + * @param value_ The value to remove from the list. + */ + function remove(List storage list, uint128 value_) internal { + require(contains(list, value_), "SLL:R:VALUE_NOT_EXISTS"); + + uint128 prev_ = list.nodes[value_].prev; + uint128 next_ = list.nodes[value_].next; + + if (prev_ != 0) { + list.nodes[prev_].next = next_; + } else { + list.head = next_; + } + + if (next_ != 0) { + list.nodes[next_].prev = prev_; + } else { + list.tail = prev_; + } + + delete list.nodes[value_]; + list.size--; + } + + /**************************************************************************************************************************************/ + /*** View Functions ***/ + /**************************************************************************************************************************************/ + + /** + * @dev Gets the length of the list. + * @param list The list to get the length of. + * @return length_ The length of the list. + */ + function length(List storage list) internal view returns (uint256 length_) { + length_ = list.size; + } + + /** + * @dev Gets all values from the list. + * @param list The list to get the values from. + * @return values_ All values from the list. + */ + function getAllValues(List storage list) internal view returns (uint128[] memory values_) { + values_ = new uint128[](list.size); + + uint128 current_ = list.head; + uint256 size_ = list.size; + + for (uint256 i = 0; i < size_; i++) { + values_[i] = current_; + current_ = list.nodes[current_].next; + } + } + + /** + * @dev Gets the last value in the list. + * @param list The list to get the last value from. + * @return value_ The last value in the list. + */ + function getLast(List storage list) internal view returns (uint128 value_) { + value_ = list.tail; + } + + /** + * @dev Checks if a value exists in the list. + * @param list The list to check. + * @param value_ The value to check for. + * @return exists_ True if the value exists in the list. + */ + function contains(List storage list, uint128 value_) internal view returns (bool exists_) { + exists_ = list.nodes[value_].exists; + } + +} diff --git a/tests/unit/SortedArray/Get.t.sol b/tests/unit/SortedArray/Get.t.sol deleted file mode 100644 index 5d661d1..0000000 --- a/tests/unit/SortedArray/Get.t.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.7; - -import { SortedArrayTestBase } from "../../utils/TestBase.sol"; - -contract GetTests is SortedArrayTestBase { - - function test_get_outOfBounds() external { - array.push(1); - vm.expectRevert("SA:G:OUT_OF_BOUNDS"); - array.get(1); - } - - function test_get_singleValue() external { - array.push(1); - assertEq(array.get(0), 1); - } - - function test_get_multipleValues() external { - array.push(1); - array.push(2); - array.push(3); - - assertEq(array.get(0), 1); - assertEq(array.get(1), 2); - assertEq(array.get(2), 3); - } - -} diff --git a/tests/unit/SortedArray/Push.t.sol b/tests/unit/SortedArray/Push.t.sol index d33d27b..7406194 100644 --- a/tests/unit/SortedArray/Push.t.sol +++ b/tests/unit/SortedArray/Push.t.sol @@ -2,44 +2,52 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.7; -import { SortedArrayTestBase } from "../../utils/TestBase.sol"; +import { SortedLinkedListTestBase } from "../../utils/TestBase.sol"; -contract PushTests is SortedArrayTestBase { +contract PushTests is SortedLinkedListTestBase { + + function test_push_failed_zeroValue() external { + vm.expectRevert("SLL:P:ZERO_VALUE"); + list.push(0); + } + + function test_push_failed_valueExists() external { + list.push(1); + + vm.expectRevert("SLL:P:VALUE_EXISTS"); + list.push(1); + } function test_push_failed_outOfOrder() external { - array.push(3); + list.push(3); - vm.expectRevert("SA:P:NOT_LARGEST"); - array.push(1); + vm.expectRevert("SLL:P:NOT_LARGEST"); + list.push(1); } function test_push_singleValue() external { - array.push(3); + list.push(3); uint256[] memory expectedValues = new uint256[](1); expectedValues[0] = 3; - assertArray(expectedValues); - assertElementAtIndex(0, 3); + assertList(expectedValues); - assertEq(array.length(), 1); + assertEq(list.length(), 1); } function test_push_multipleValues_inOrder() external { - array.push(1); - array.push(3); - array.push(7); + list.push(1); + list.push(3); + list.push(7); uint256[] memory expectedValues = new uint256[](3); expectedValues[0] = 1; expectedValues[1] = 3; expectedValues[2] = 7; - assertArray(expectedValues); - assertElementAtIndex(0, 1); - assertElementAtIndex(1, 3); - assertElementAtIndex(2, 7); - assertEq(array.length(), 3); + assertList(expectedValues); + assertEq(list.length(), 3); } } diff --git a/tests/unit/SortedArray/Remove.t.sol b/tests/unit/SortedArray/Remove.t.sol index 8bac4d0..2e4ce16 100644 --- a/tests/unit/SortedArray/Remove.t.sol +++ b/tests/unit/SortedArray/Remove.t.sol @@ -1,51 +1,51 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.7; -import { SortedArrayTestBase } from "../../utils/TestBase.sol"; +import { SortedLinkedListTestBase } from "../../utils/TestBase.sol"; -contract RemoveTests is SortedArrayTestBase { +contract RemoveTests is SortedLinkedListTestBase { + + function test_remove_failed_valueNotExists() external { + vm.expectRevert("SLL:R:VALUE_NOT_EXISTS"); + list.remove(1); + } function test_remove_singleValue() external { - array.push(1); - array.remove(1); + list.push(1); + list.remove(1); uint256[] memory expectedValues = new uint256[](0); - assertArray(expectedValues); + assertList(expectedValues); - assertEq(array.length(), 0); + assertEq(list.length(), 0); } function test_remove_multipleValues() external { - array.push(1); - array.push(2); - array.push(3); + list.push(1); + list.push(2); + list.push(3); - array.remove(2); + list.remove(2); uint256[] memory expectedValues = new uint256[](2); expectedValues[0] = 1; expectedValues[1] = 3; - assertArray(expectedValues); - assertElementAtIndex(0, 1); - assertElementAtIndex(1, 3); + assertList(expectedValues); + assertEq(list.length(), 2); - assertEq(array.length(), 2); - - array.remove(1); + list.remove(1); expectedValues = new uint256[](1); expectedValues[0] = 3; - assertArray(expectedValues); - assertElementAtIndex(0, 3); - - assertEq(array.length(), 1); + assertList(expectedValues); + assertEq(list.length(), 1); - array.remove(3); + list.remove(3); - assertArray(new uint256[](0)); - assertEq(array.length(), 0); + assertList(new uint256[](0)); + assertEq(list.length(), 0); } } diff --git a/tests/utils/Harnesses.sol b/tests/utils/Harnesses.sol index 44689f0..acf1b8f 100644 --- a/tests/utils/Harnesses.sol +++ b/tests/utils/Harnesses.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.7; import { MapleWithdrawalManager } from "../../contracts/MapleWithdrawalManager.sol"; -import { SortedArray } from "../../contracts/utils/SortedArray.sol"; +import { SortedLinkedList } from "../../contracts/utils/SortedLinkedList.sol"; contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { @@ -20,7 +20,7 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setLastRequest(address owner_, uint256 requestId_) external { - SortedArray.push(_userRequests[owner_], requestId_); + SortedLinkedList.push(_userRequests[owner_], _toUint128(requestId_)); queue.lastRequestId = _toUint128(requestId_); } @@ -42,7 +42,7 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { queue.lastRequestId = requestId_; queue.requests[requestId_] = WithdrawalRequest(owner_, shares_); - SortedArray.push(_userRequests[owner_], requestId_); + SortedLinkedList.push(_userRequests[owner_], _toUint128(requestId_)); } function __setTotalShares(uint256 totalShares_) external { @@ -50,15 +50,15 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } function __setUserRequestCount(address owner_, uint256 requestCount_, uint256 escrowSharesTotal_) external { - uint256 currentRequestCount_ = SortedArray.length(_userRequests[owner_]); + uint256 currentRequestCount_ = SortedLinkedList.length(_userRequests[owner_]); if (requestCount_ < currentRequestCount_) { for (uint256 i = requestCount_; i < currentRequestCount_; i++) { - SortedArray.remove(_userRequests[owner_], _toUint128(i)); + SortedLinkedList.remove(_userRequests[owner_], _toUint128(i)); } } else { for (uint256 i = currentRequestCount_; i > requestCount_; i++) { - SortedArray.push(_userRequests[owner_], _toUint128(i)); + SortedLinkedList.push(_userRequests[owner_], _toUint128(i)); } } @@ -67,32 +67,28 @@ contract MapleWithdrawalManagerHarness is MapleWithdrawalManager { } -contract SortedArrayHarness { +contract SortedLinkedListHarness { - SortedArray.Array array; + SortedLinkedList.List list; - function push(uint256 value_) external { - SortedArray.push(array, value_); + function push(uint128 value_) external { + SortedLinkedList.push(list, value_); } - function remove(uint256 value_) external { - SortedArray.remove(array, value_); - } - - function get(uint256 index_) external view returns (uint256) { - return SortedArray.get(array, index_); + function remove(uint128 value_) external { + SortedLinkedList.remove(list, value_); } function length() external view returns (uint256) { - return SortedArray.length(array); + return SortedLinkedList.length(list); } - function getAllValues() external view returns (uint256[] memory) { - return SortedArray.getAllValues(array); + function getAllValues() external view returns (uint128[] memory) { + return SortedLinkedList.getAllValues(list); } - function getLast() external view returns (uint256) { - return SortedArray.getLast(array); + function getLast() external view returns (uint128) { + return SortedLinkedList.getLast(list); } } diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index 3ea0383..bfa6f09 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -10,7 +10,7 @@ import { MapleWithdrawalManagerInitializer } from "../../contracts/proxy/MapleWi import { MapleWithdrawalManagerHarness } from "./Harnesses.sol"; import { MockFactory, MockGlobals, MockPool, MockPoolManager } from "./Mocks.sol"; -import { SortedArrayHarness } from "./Harnesses.sol"; +import { SortedLinkedListHarness } from "./Harnesses.sol"; contract TestBase is Test { @@ -101,27 +101,23 @@ contract TestBase is Test { } -contract SortedArrayTestBase is Test { +contract SortedLinkedListTestBase is Test { - SortedArrayHarness public array; + SortedLinkedListHarness public list; function setUp() public virtual { - array = new SortedArrayHarness(); + list = new SortedLinkedListHarness(); } - function assertArray(uint256[] memory values) internal { - assertEq(array.length(), values.length); + function assertList(uint256[] memory values) internal { + assertEq(list.length(), values.length); - uint256[] memory arrayValues = array.getAllValues(); + uint128[] memory listValues = list.getAllValues(); for (uint256 i = 0; i < values.length; i++) { - assertEq(arrayValues[i], values[i]); + assertEq(uint256(listValues[i]), values[i]); } } - function assertElementAtIndex(uint256 index, uint256 value) internal { - assertEq(array.get(index), value); - } - } From 316aa79aaf752299ca69c0ba17ec8e87948bcf4b Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:39:35 +0400 Subject: [PATCH 18/21] feat: Internal audit fixes (#48) * feat: Interal audit fixes * feat: ASCII Art --- contracts/MapleWithdrawalManager.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 515390d..07e45e8 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -27,7 +27,6 @@ import { SortedLinkedList } from "./utils/SortedLinkedList.sol"; ██║ ╚═╝ ██║██║ ██║██║ ███████╗███████╗ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝ - ██╗ ██╗██╗████████╗██╗ ██╗██████╗ ██████╗ █████╗ ██╗ ██╗ █████╗ ██╗ ██║ ██║██║╚══██╔══╝██║ ██║██╔══██╗██╔══██╗██╔══██╗██║ ██║██╔══██╗██║ ██║ █╗ ██║██║ ██║ ███████║██║ ██║██████╔╝███████║██║ █╗ ██║███████║██║ @@ -35,7 +34,6 @@ import { SortedLinkedList } from "./utils/SortedLinkedList.sol"; ╚███╔███╔╝██║ ██║ ██║ ██║██████╔╝██║ ██║██║ ██║╚███╔███╔╝██║ ██║███████╗ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝ - ███╗ ███╗ █████╗ ███╗ ██╗ █████╗ ██████╗ ███████╗██████╗ ████╗ ████║██╔══██╗████╗ ██║██╔══██╗██╔════╝ ██╔════╝██╔══██╗ ██╔████╔██║███████║██╔██╗ ██║███████║██║ ███╗█████╗ ██████╔╝ @@ -43,6 +41,13 @@ import { SortedLinkedList } from "./utils/SortedLinkedList.sol"; ██║ ╚═╝ ██║██║ ██║██║ ╚████║██║ ██║╚██████╔╝███████╗██║ ██║ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ + ██╗ ██╗██████╗ ██████╗ ██████╗ + ██║ ██║╚════██╗██╔═████╗██╔═████╗ + ██║ ██║ █████╔╝██║██╔██║██║██╔██║ + ╚██╗ ██╔╝██╔═══╝ ████╔╝██║████╔╝██║ + ╚████╔╝ ███████╗╚██████╔╝╚██████╔╝ + ╚═══╝ ╚══════╝ ╚═════╝ ╚═════╝ + */ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManagerStorage , MapleProxiedInternals { @@ -480,7 +485,7 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag require(shares_ <= sharesAvailable_, "WM:PR:TOO_MANY_SHARES"); - ( redeemableShares_, resultingAssets_ ) = _calculateRedemption(shares_); + ( redeemableShares_, resultingAssets_ ) = _calculateRedemption(shares_); // NOTE: Recommend using convertToExitAssets instead } function previewWithdraw(address owner_, uint256 assets_) From 2536a8a1719cd001182e51971e5513d39e52a866 Mon Sep 17 00:00:00 2001 From: Cal Mac Fadden <108666242+calmacfadden@users.noreply.github.com> Date: Sun, 19 Oct 2025 18:13:07 +0400 Subject: [PATCH 19/21] feat: added unit tests (#50) Signed-off-by: calmacfadden --- tests/unit/ViewFunctions.t.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/ViewFunctions.t.sol b/tests/unit/ViewFunctions.t.sol index 0260240..3c05524 100644 --- a/tests/unit/ViewFunctions.t.sol +++ b/tests/unit/ViewFunctions.t.sol @@ -5,6 +5,22 @@ import { TestBase } from "../utils/TestBase.sol"; contract ViewFunctionsTests is TestBase { + function test_asset() external { + assertEq(withdrawalManager.asset(), address(asset)); + } + + function test_globals() external { + assertEq(withdrawalManager.globals(), address(globals)); + } + + function test_governor() external { + assertEq(withdrawalManager.governor(), governor); + } + + function test_securityAdmin() external { + assertEq(withdrawalManager.securityAdmin(), securityAdmin); + } + function testFuzz_isInExitWindow(address account_) external { assertTrue(withdrawalManager.isInExitWindow(account_)); } From 76a8ba7e0a7c51a312df395593c71ee01881b16e Mon Sep 17 00:00:00 2001 From: Farhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:42:24 +0400 Subject: [PATCH 20/21] audit: Fix DOS Issue due to large number of empty requests (#49) * audit: Fix DOS Issue * chore: order event * chore: order functions * fix: PR comments * fix: PR comments * fix: code comment --- contracts/MapleWithdrawalManager.sol | 26 ++ .../interfaces/IMapleWithdrawalManager.sol | 15 + tests/unit/ProcessEmptyRedemptions.t.sol | 301 ++++++++++++++++++ tests/utils/TestBase.sol | 1 + 4 files changed, 343 insertions(+) create mode 100644 tests/unit/ProcessEmptyRedemptions.t.sol diff --git a/contracts/MapleWithdrawalManager.sol b/contracts/MapleWithdrawalManager.sol index 07e45e8..a1ccc62 100644 --- a/contracts/MapleWithdrawalManager.sol +++ b/contracts/MapleWithdrawalManager.sol @@ -176,6 +176,32 @@ contract MapleWithdrawalManager is IMapleWithdrawalManager, MapleWithdrawalManag /*** State-Changing Functions - OnlyRedeemer ***/ /**************************************************************************************************************************************/ + function processEmptyRedemptions(uint256 numberOfRequests_) external override whenProtocolNotPaused onlyRedeemer { + require(numberOfRequests_ > 0, "WM:PER:ZERO_REQUESTS"); + + uint256 nextRequestId_ = queue.nextRequestId; + uint256 lastRequestId_ = queue.lastRequestId; + uint256 requestsProcessed_ = 0; + + // Iterate through the queue and process empty requests, if the owner is address(0). + while (requestsProcessed_ < numberOfRequests_ && nextRequestId_ <= lastRequestId_) { + address owner_ = queue.requests[_toUint128(nextRequestId_)].owner; + + if (owner_ != address(0)) { + // Stop if we encounter a non-empty request. + break; + } + + ++nextRequestId_; + ++requestsProcessed_; + } + + // Update the queue's next request ID. + queue.nextRequestId = _toUint128(nextRequestId_); + + emit EmptyRedemptionsProcessed(requestsProcessed_); + } + function processRedemptions(uint256 maxSharesToProcess_) external override whenProtocolNotPaused nonReentrant onlyRedeemer { require(maxSharesToProcess_ > 0, "WM:PR:ZERO_SHARES"); diff --git a/contracts/interfaces/IMapleWithdrawalManager.sol b/contracts/interfaces/IMapleWithdrawalManager.sol index e51637b..5bb096a 100644 --- a/contracts/interfaces/IMapleWithdrawalManager.sol +++ b/contracts/interfaces/IMapleWithdrawalManager.sol @@ -11,6 +11,12 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi /*** Events ***/ /**************************************************************************************************************************************/ + /** + * @dev Emitted when empty redemption requests are processed. + * @param numberOfRequestsProcessed Number of empty requests that were processed. + */ + event EmptyRedemptionsProcessed(uint256 numberOfRequestsProcessed); + /** * @dev Emitted when a manual redemption takes place. * @param owner Address of the account. @@ -74,6 +80,15 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi */ function addShares(uint256 shares, address owner) external returns (uint256 lastRequestId); + /** + * @dev Processes empty redemption requests at the front of the queue. + * Iterates through the queue starting from the front and advances the queue's nextRequestId + * for each empty request encountered. Stops when a non-empty request is found or the + * specified number of requests has been processed. + * @param numberOfRequests Maximum number of empty requests to process. + */ + function processEmptyRedemptions(uint256 numberOfRequests) external; + /** * @dev Processes a withdrawal request. * Uses the current exchange rate to calculate the amount of assets withdrawn. diff --git a/tests/unit/ProcessEmptyRedemptions.t.sol b/tests/unit/ProcessEmptyRedemptions.t.sol new file mode 100644 index 0000000..4b2394f --- /dev/null +++ b/tests/unit/ProcessEmptyRedemptions.t.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.7; + +import { TestBase } from "../utils/TestBase.sol"; + +contract ProcessEmptyRedemptionsFailureTests is TestBase { + + function setUp() public override { + super.setUp(); + + globals.__setIsInstanceOf(false); + } + + function test_processEmptyRedemptions_protocolPaused() external { + globals.__setFunctionPaused(true); + + vm.prank(poolDelegate); + vm.expectRevert("WM:PAUSED"); + withdrawalManager.processEmptyRedemptions(1); + } + + function test_processEmptyRedemptions_notRedeemer() external { + vm.prank(lp); + vm.expectRevert("WM:NOT_REDEEMER"); + withdrawalManager.processEmptyRedemptions(1); + } + + function test_processEmptyRedemptions_governorNotAllowed() external { + vm.prank(governor); + vm.expectRevert("WM:NOT_REDEEMER"); + withdrawalManager.processEmptyRedemptions(1); + } + + function test_processEmptyRedemptions_zeroRequests() external { + vm.prank(poolDelegate); + vm.expectRevert("WM:PER:ZERO_REQUESTS"); + withdrawalManager.processEmptyRedemptions(0); + } + +} + +contract ProcessEmptyRedemptionsSuccessTests is TestBase { + + function setUp() public override { + super.setUp(); + + // Setup pool with assets and shares + pool.mint(pm, 1000e18); + + vm.prank(pm); + pool.approve(address(withdrawalManager), 1000e18); + } + + function test_processEmptyRedemptions_poolDelegate() external { + // Add a request + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(100, lp); + + assertQueue({ nextRequestId: 1, lastRequestId: 1 }); + assertRequest({ requestId: 1, owner: lp, shares: 100 }); + + // Remove the request using removeSharesById + vm.prank(lp); + withdrawalManager.removeSharesById(requestId, 100); + + // Request should now be empty + assertRequest({ requestId: 1, owner: address(0), shares: 0 }); + assertQueue({ nextRequestId: 1, lastRequestId: 1 }); + + // Process empty redemptions as pool delegate + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(1); + withdrawalManager.processEmptyRedemptions(1); + + // Queue should have moved forward + assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + } + + function test_processEmptyRedemptions_operationalAdmin() external { + // Add a request + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(100, lp); + + // Remove the request + vm.prank(lp); + withdrawalManager.removeSharesById(requestId, 100); + + // Process empty redemptions as operational admin + vm.prank(operationalAdmin); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(1); + withdrawalManager.processEmptyRedemptions(1); + + assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + } + + function test_processEmptyRedemptions_redeemer() external { + globals.__setIsInstanceOf(true); + + // Add a request + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(100, lp); + + // Remove the request + vm.prank(lp); + withdrawalManager.removeSharesById(requestId, 100); + + // Process empty redemptions as redeemer bot + vm.prank(redeemer); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(1); + withdrawalManager.processEmptyRedemptions(1); + + assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + } + + function test_processEmptyRedemptions_multipleEmptyRequests() external { + // Create 5 requests + vm.startPrank(pm); + uint256 requestId1 = withdrawalManager.addShares(100, lp); + uint256 requestId2 = withdrawalManager.addShares(200, lp); + uint256 requestId3 = withdrawalManager.addShares(300, lp); + withdrawalManager.addShares(400, lp); // requestId4 + withdrawalManager.addShares(500, lp); // requestId5 + vm.stopPrank(); + + assertQueue({ nextRequestId: 1, lastRequestId: 5 }); + + // Remove first 3 requests + vm.startPrank(lp); + withdrawalManager.removeSharesById(requestId1, 100); + withdrawalManager.removeSharesById(requestId2, 200); + withdrawalManager.removeSharesById(requestId3, 300); + vm.stopPrank(); + + // All three should now be empty + assertRequest({ requestId: 1, owner: address(0), shares: 0 }); + assertRequest({ requestId: 2, owner: address(0), shares: 0 }); + assertRequest({ requestId: 3, owner: address(0), shares: 0 }); + assertRequest({ requestId: 4, owner: lp, shares: 400 }); + assertRequest({ requestId: 5, owner: lp, shares: 500 }); + + // Process 3 empty redemptions + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(3); + withdrawalManager.processEmptyRedemptions(3); + + // Queue should have advanced by 3 + assertQueue({ nextRequestId: 4, lastRequestId: 5 }); + } + + function test_processEmptyRedemptions_stopsAtNonEmptyRequest() external { + // Create 5 requests + vm.startPrank(pm); + uint256 requestId1 = withdrawalManager.addShares(100, lp); + uint256 requestId2 = withdrawalManager.addShares(200, lp); + withdrawalManager.addShares(300, lp); // requestId3 - keep this one + withdrawalManager.addShares(400, lp); // requestId4 + withdrawalManager.addShares(500, lp); // requestId5 + vm.stopPrank(); + + assertQueue({ nextRequestId: 1, lastRequestId: 5 }); + + // Remove only first 2 requests (leave request 3) + vm.startPrank(lp); + withdrawalManager.removeSharesById(requestId1, 100); + withdrawalManager.removeSharesById(requestId2, 200); + vm.stopPrank(); + + assertRequest({ requestId: 1, owner: address(0), shares: 0 }); + assertRequest({ requestId: 2, owner: address(0), shares: 0 }); + assertRequest({ requestId: 3, owner: lp, shares: 300 }); + assertRequest({ requestId: 4, owner: lp, shares: 400 }); + assertRequest({ requestId: 5, owner: lp, shares: 500 }); + + // Try to process 5 empty redemptions, but should stop at request 3 + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(2); // Only 2 were processed + withdrawalManager.processEmptyRedemptions(5); + + // Queue should only advance by 2 (stopped at non-empty request 3) + assertQueue({ nextRequestId: 3, lastRequestId: 5 }); + + // Now remove requests 3, 4, and 5 + vm.startPrank(lp); + withdrawalManager.removeSharesById(3, 300); + withdrawalManager.removeSharesById(4, 400); + withdrawalManager.removeSharesById(5, 500); + vm.stopPrank(); + + // Process the remaining empty requests + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(3); + withdrawalManager.processEmptyRedemptions(10); // Request more than available + + // Queue should now be fully processed + assertQueue({ nextRequestId: 6, lastRequestId: 5 }); + } + + function test_processEmptyRedemptions_noEmptyRequests() external { + // Create 3 requests but don't remove any + vm.startPrank(pm); + withdrawalManager.addShares(100, lp); + withdrawalManager.addShares(200, lp); + withdrawalManager.addShares(300, lp); + vm.stopPrank(); + + assertQueue({ nextRequestId: 1, lastRequestId: 3 }); + + // Try to process empty redemptions - should process 0 requests + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(0); + withdrawalManager.processEmptyRedemptions(5); + + // Queue should not move + assertQueue({ nextRequestId: 1, lastRequestId: 3 }); + } + + function test_processEmptyRedemptions_emptyQueueNoStateChange() external { + // Queue is empty initially + assertQueue({ nextRequestId: 1, lastRequestId: 0 }); + + // Try to process empty redemptions on empty queue + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(0); + withdrawalManager.processEmptyRedemptions(10); + + // Queue should remain unchanged + assertQueue({ nextRequestId: 1, lastRequestId: 0 }); + } + + function test_processEmptyRedemptions_fullyProcessedQueueNoStateChange() external { + // Create and remove a request + vm.prank(pm); + uint256 requestId = withdrawalManager.addShares(100, lp); + + vm.prank(lp); + withdrawalManager.removeSharesById(requestId, 100); + + // Process the empty request + vm.prank(poolDelegate); + withdrawalManager.processEmptyRedemptions(1); + + assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + + // Try to process again - queue is fully processed + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(0); + withdrawalManager.processEmptyRedemptions(10); + + // Queue should remain unchanged + assertQueue({ nextRequestId: 2, lastRequestId: 1 }); + } + + function test_processEmptyRedemptions_chunkProcessing() external { + // Create 10 requests + vm.startPrank(pm); + for (uint256 i = 1; i <= 10; i++) { + withdrawalManager.addShares(i * 100, lp); + } + vm.stopPrank(); + + // Remove first 5 requests + vm.startPrank(lp); + for (uint256 i = 1; i <= 5; i++) { + withdrawalManager.removeSharesById(i, i * 100); + } + vm.stopPrank(); + + assertQueue({ nextRequestId: 1, lastRequestId: 10 }); + + // Process only 3 empty requests + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(3); + withdrawalManager.processEmptyRedemptions(3); + + assertQueue({ nextRequestId: 4, lastRequestId: 10 }); + + // Process remaining 2 empty requests + vm.prank(poolDelegate); + vm.expectEmit(); + emit EmptyRedemptionsProcessed(2); + withdrawalManager.processEmptyRedemptions(2); + + assertQueue({ nextRequestId: 6, lastRequestId: 10 }); + + // Requests 6-10 should still exist + for (uint256 i = 6; i <= 10; i++) { + assertRequest({ requestId: i, owner: lp, shares: i * 100 }); + } + } + +} diff --git a/tests/utils/TestBase.sol b/tests/utils/TestBase.sol index bfa6f09..e044fc5 100644 --- a/tests/utils/TestBase.sol +++ b/tests/utils/TestBase.sol @@ -35,6 +35,7 @@ contract TestBase is Test { MapleWithdrawalManagerFactory internal factory; MapleWithdrawalManagerHarness internal withdrawalManager; + event EmptyRedemptionsProcessed(uint256 numberOfRequestsProcessed); event ManualWithdrawalSet(address indexed account, bool isManual); event RequestCreated(uint256 indexed requestId, address indexed owner, uint256 shares); event RequestDecreased(uint256 indexed requestId, uint256 shares); From e1114052544defeadf2cf0463c5a1df3c45eb86c Mon Sep 17 00:00:00 2001 From: 0xfarhaan <59924029+0xfarhaan@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:04:17 +0400 Subject: [PATCH 21/21] chore: Update README --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6814524..3cb7988 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Withdrawal Manager Queue -![CI](https://github.com/maple-labs/withdrawal-manager-queue-private/actions/workflows/ci.yml/badge.svg) +![CI](https://github.com/maple-labs/withdrawal-manager-queue/actions/workflows/ci.yml/badge.svg) [![GitBook - Documentation](https://img.shields.io/badge/GitBook-Documentation-orange?logo=gitbook&logoColor=white)](https://maplefinance.gitbook.io/maple/maple-for-developers/protocol-overview) [![Foundry][foundry-badge]][foundry] -[![License: BUSL 1.1](https://img.shields.io/badge/License-BUSL%201.1-blue.svg)](https://github.com/maple-labs/withdrawal-manager-queue-private/blob/main/LICENSE) +[![License: BUSL 1.1](https://img.shields.io/badge/License-BUSL%201.1-blue.svg)](https://github.com/maple-labs/withdrawal-manager-queue/blob/main/LICENSE) [foundry]: https://getfoundry.sh/ [foundry-badge]: https://img.shields.io/badge/Built%20with-Foundry-FFDB1C.svg @@ -23,12 +23,13 @@ Versions of dependencies can be checked with `git submodule status`. This project was built using [Foundry](https://book.getfoundry.sh/). Refer to installation instructions [here](https://github.com/foundry-rs/foundry#installation). ```sh -git clone git@github.com:maple-labs/withdrawal-manager-queue-private.git -cd withdrawal-manager-queue-private +git clone git@github.com:maple-labs/withdrawal-manager-queue.git +cd withdrawal-manager-queue forge install ``` ## Audit Reports +For all audit reports please refer to https://docs.maple.finance/technical-resources/security/security ## Bug Bounty