Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4f63ee4
Update README.md new logo (#28)
calmacfadden May 23, 2025
ecccf0a
feat: Multiple Requests per Owner (SC-20630) (#30)
calmacfadden Jun 29, 2025
2a64e1e
feat: TODO Resolutions (SC-20711) (#32)
calmacfadden Jul 16, 2025
8333ed4
feat: added userRequestSummaries (#33)
calmacfadden Jul 17, 2025
0b48f84
fix: keep track of withdraw requests per user (SC-21032) (#34)
kitanovicd Jul 29, 2025
97a6fcb
feat: Update removeShares to iterate over all user's requests (SC-210…
kitanovicd Jul 30, 2025
01a636f
feat: Added migrator for withdrawal manager storage (sc-21217) (#37)
calmacfadden Aug 29, 2025
d335684
feat: Changed wm interfaces to use uint256 (SC-21446) (#38)
calmacfadden Sep 1, 2025
cd8f632
chore: update format (#39)
0xfarhaan Sep 3, 2025
20aaf69
feat: Add `removeSharesById()` Functionality (#40)
0xfarhaan Sep 10, 2025
7b31fbd
feat: WithdrawalManager access control changes (SC-21760) (#41)
calmacfadden Sep 18, 2025
d272590
feat: Internal Audit fixes (#42)
0xfarhaan Sep 25, 2025
31481c1
feat: Use external over public (#44)
0xfarhaan Oct 13, 2025
2738521
Audit: Fix for loop usage for removing shares via admin (#45)
0xfarhaan Oct 13, 2025
335ea60
Audit: Add comment clarifying usage for `removeRequest` on WM (#46)
0xfarhaan Oct 13, 2025
74cf0c3
fix: Add bot support for `removeRequest()` (SC-22023) (#47)
vbidin Oct 15, 2025
28b18e0
Audit: Swap Array to LinkedList (#43)
0xfarhaan Oct 16, 2025
316aa79
feat: Internal audit fixes (#48)
0xfarhaan Oct 17, 2025
2536a8a
feat: added unit tests (#50)
calmacfadden Oct 19, 2025
76a8ba7
audit: Fix DOS Issue due to large number of empty requests (#49)
0xfarhaan Oct 20, 2025
e111405
chore: Update README
0xfarhaan Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ docs/

# Dotenv file
.env

# Vscode
.vscode*
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -41,5 +42,5 @@ For all information related to the ongoing bug bounty for these contracts run by
---

<p align="center">
<img src="https://user-images.githubusercontent.com/44272939/196706799-fe96d294-f700-41e7-a65f-2d754d0a6eac.gif" height="100" />
<img src="https://github.com/maple-labs/maple-metadata/blob/796e313f3b2fd4960929910cd09a9716688a4410/assets/maplelogo.png" height="100" />
</p>
276 changes: 197 additions & 79 deletions contracts/MapleWithdrawalManager.sol

Large diffs are not rendered by default.

90 changes: 67 additions & 23 deletions contracts/interfaces/IMapleWithdrawalManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -20,10 +26,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.
Expand All @@ -38,14 +45,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.
Expand All @@ -54,13 +61,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 ***/
Expand All @@ -71,7 +78,16 @@ 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 (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.
Expand Down Expand Up @@ -100,11 +116,22 @@ interface IMapleWithdrawalManager is IMapleWithdrawalManagerStorage, IMapleProxi
function removeShares(uint256 shares, address owner) external returns (uint256 sharesReturned);

/**
* @dev Removes a withdrawal request from the queue.
* @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.
* @param owner Address of the owner of shares.
* 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.
*/
function removeRequest(address owner) external;
function removeRequest(address owner, uint256[] calldata requestIds) external;

/**
* @dev Defines if an account will withdraw shares manually or automatically.
Expand Down Expand Up @@ -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.
Expand All @@ -176,20 +203,37 @@ 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);

/**
* @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 previewWithdraw(address owner_, uint256 assets_) external view returns (uint256 redeemableAssets_, uint256 resultingShares_);
function requestIds(address owner) external view returns (uint256 requestId);

/**
* @dev Returns the owner and amount of shares associated with a withdrawal request.
* @param requestId Identifier of the withdrawal request.
* @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.
* 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 requestsByOwner(address owner) external view returns (uint256[] memory requestIds, uint256[] memory shares);

/**
* @dev Returns the address of the security admin.
Expand Down
11 changes: 5 additions & 6 deletions contracts/interfaces/IMapleWithdrawalManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,17 @@ 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 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 requestIds(address account) external view returns (uint128 requestId);
function userEscrowedShares(address owner) external view returns (uint256 escrowedShares);

/**
* @dev Returns the first and last withdrawal requests pending redemption.
* @return nextRequestId Identifier of the next withdrawal request that will be processed.
* @return lastRequestId Identifier of the last created withdrawal request.
*/
function queue() external view returns (uint128 nextRequestId, uint128 lastRequestId);

}
27 changes: 27 additions & 0 deletions contracts/proxy/MapleWithdrawalManagerMigratorV200.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.7;

import { MapleProxiedInternals } from "../../modules/maple-proxy-factory/contracts/MapleProxiedInternals.sol";

import { SortedLinkedList } from "../utils/SortedLinkedList.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 memory request_ = queue.requests[i];

if (request_.owner == address(0)) continue;

userEscrowedShares[request_.owner] += request_.shares;

SortedLinkedList.push(_userRequests[request_.owner], i);
}
}

}
10 changes: 8 additions & 2 deletions contracts/proxy/MapleWithdrawalManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.7;

import { IMapleWithdrawalManagerStorage } from "../interfaces/IMapleWithdrawalManagerStorage.sol";

import { SortedLinkedList } from "../utils/SortedLinkedList.sol";

contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage {

/**************************************************************************************************************************************/
Expand All @@ -25,7 +27,7 @@ contract MapleWithdrawalManagerStorage is IMapleWithdrawalManagerStorage {
/**************************************************************************************************************************************/

uint256 internal _locked; // Used when checking for reentrancy.

address public override pool;
address public override poolManager;

Expand All @@ -35,8 +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 requestIds; // Maps users to their withdrawal requests identifiers.
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 => uint256) public override userEscrowedShares; // Maps users to their escrowed shares yet to be processed.

mapping(address => SortedLinkedList.List) internal _userRequests; // Maps users to their withdrawal requests.

}
Loading