The getBatchIds
function within the smart contract contains a vulnerability that can lead to an Out-of-Gas (OOG) error and Denial-of-Service (DoS). This vulnerability arises when a large number of withdrawal requests are accumulated for a single user, causing the _withdrawalIds
array to grow excessively. The function’s nested loop structure, which iterates over _withdrawalIds
and withdrawalBatches
arrays, increases gas consumption linearly with the array size, potentially leading to gas limit exhaustion.
Function Involved: getBatchIds(uint256[] memory _withdrawalIds)
Issue: The _withdrawalIds
array, passed as a parameter to getBatchIds
, can become very large as a result of users making frequent or small withdrawals. The function contains a nested loop that iterates over both _withdrawalIds
and the withdrawalBatches
arrays, leading to an exponential increase in gas costs as the array size grows.
Scenario: Users can accumulate a large number of withdrawal requests, either intentionally or unintentionally, resulting in a long list of withdrawal IDs. When querying the corresponding batch IDs using getBatchIds
, the large size of the _withdrawalIds
array, combined with the nested loops, may cause the function to hit the gas limit, resulting in a transaction revert.
The user calls onTokenTransfer
or withdraw
which leads to _withdraw
being called internally in the PriorityPool
contract.
If there isn’t enough liquidity in the pool to satisfy the withdrawal request (i.e., toWithdraw
> totalQueued
), the contract will queue the remaining amount for withdrawal by calling withdrawalPool.queueWithdrawal
.
queueWithdrawal
Adds Withdrawal to the PoolWhen queueWithdrawal
is called, it performs the following:
Checks that the withdrawal amount is above minWithdrawalAmount
.
Transfers the amount from the user to the contract.
Pushes a new Withdrawal
struct to the queuedWithdrawals
array.
Updates the user's withdrawal list (queuedWithdrawalsByAccount
) by appending the new withdrawalId
.
Each withdrawal request by the user results in a new withdrawal ID being created and stored in the mappings queuedWithdrawalsByAccount
and withdrawalOwners
.
A single user can request many small withdrawals. For example:
If the contract allows the user to request multiple withdrawals and the user is not constrained by any time limits or minimum amount (other than minWithdrawalAmount
), they can repeatedly call functions that trigger _withdraw
and end up queuing many small withdrawals.
Each call to queueWithdrawal
adds a new entry to the queuedWithdrawals
array, and the user’s queuedWithdrawalsByAccount
mapping grows in size.
Over time, this results in the user having many withdrawal IDs.
Eventually, the user wants to retrieve information about the batches corresponding to their many withdrawal requests or calls getFinalizedWithdrawalIdsByOwner
function, which calls getBatchIds
function.
The user calls getBatchIds
, passing in the array of _withdrawalIds
that was generated when they queued the withdrawals. This array can contain hundreds or even thousands of IDs, depending on how many withdrawal requests the user made.
Here, the function does the following:
Iterates over the entire _withdrawalIds
array (loop over i
).
For each withdrawal ID, it then iterates over the entire withdrawalBatches
array (loop over j
), checking whether the withdrawalId
matches the batch's indexOfLastWithdrawal
.
Since both loops can grow large, the gas consumption increases exponentially as the number of _withdrawalIds
increases.
getBatchIds
If the user has accumulated a large number of withdrawal IDs (say, 500–1000), and the withdrawalBatches
array is also large, the nested loops will quickly cause the function to consume more gas than is allowed by the block gas limit.
As a result, the transaction will revert due to Out-of-Gas (OOG), causing the getBatchIds
function to fail.
Denial of Service for Large Withdrawal Requests:
Any user (or attacker) with a large number of queued withdrawals can trigger this gas exhaustion issue.
Once the number of _withdrawalIds
becomes too large to process within a single transaction (due to the nested loops), it becomes impossible for the user to retrieve their batch IDs using getBatchIds
.
The user’s withdrawal information becomes inaccessible via this function, effectively causing a Denial-of-Service for that user.
This DoS condition could also impact the whole contract if batch queries are essential for other parts of the contract's operation (e.g., processing withdrawals in batches).
If you run the testLargeInput
function from below, you will see that it will revert due to OOG:
Denial-of-Service (DoS): A user with a large number of withdrawal requests could unintentionally cause their query to fail due to an OOG error. This could prevent them from retrieving their batch IDs. Additionally, a malicious user could exploit this by intentionally submitting many small withdrawals to overload the system, potentially preventing other users from querying their batch IDs.
Gas Limit Issues: The vulnerability primarily affects contract performance by leading to excessively high gas costs, especially in situations where multiple users are queuing and querying withdrawals.
Manual Review
Batch Processing of Withdrawal Queries: Limit the number of withdrawal IDs that can be passed to getBatchIds
in a single transaction. For instance, restrict users to querying only a maximum of 100 withdrawal IDs at a time, allowing the rest to be processed in subsequent calls.
Pagination Implementation: Introduce pagination to allow users to query their withdrawal IDs and batch IDs over several smaller transactions instead of handling them all at once. This would reduce gas costs per transaction.
Direct Mapping Optimization: Consider storing a direct mapping between each withdrawalId
and its corresponding batchId
. This would remove the need for iterating through the withdrawalBatches
array and reduce the gas consumption by eliminating the nested loop.
Frequency or Volume Limits: Implement a limit on how frequently or how many withdrawal requests a single user can queue within a specific timeframe. This could prevent malicious users from spamming the system with small withdrawal requests.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.