DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Unbounded Gas Consumption in `getUserDeposits` Can Lead to DoS via Large Deposit Enumeration

Summary

The function getUserDeposits in PerpetualVault returns all deposit IDs stored in an EnumerableSet by iterating over its entire length.

If a user has accumulated a very large number of deposits, this iteration may consume excessive gas, exceeding block limits.

Vulnerability Details

The code implementation is as follows:

/**
* @notice
* get all deposit ids of a user
* @param user address of a user
*/
function getUserDeposits(address user) external view returns (uint256[] memory depositIds) {
uint256 length = EnumerableSet.length(userDeposits[user]);
depositIds = new uint256[](length);
for (uint8 i = 0; i < length; ) {
depositIds[i] = EnumerableSet.at(userDeposits[user], i);
unchecked {
i = i + 1;
}
}
}

Because there is no pagination or limit on the number of iterations, a user with thousands—or tens of thousands—of deposits may trigger an iteration that uses more gas than is available in a single block. Although this is a view function (and thus not directly subject to state‑changing gas limits when called off‑chain), if used in on‑chain calls (or indirectly by keepers or UIs that call it as part of a transaction), it could revert or become prohibitively expensive.

  • A user makes 10,000 separate deposits over time, resulting in an EnumerableSet containing 10,000 deposit IDs.

  • When a call to getUserDeposits(user) is made, the function attempts to allocate and populate an array with 10,000 elements.

  • Even if this function is only used by off‑chain interfaces, an attacker could deliberately spam deposits to cause a legitimate user’s deposit list to grow excessively large, causing subsequent calls to exceed gas limits (especially if used in conjunction with on‑chain processes).

  • The function fails (or reverts) due to gas exhaustion, preventing retrieval of deposit data and disrupting any dependent user interface or automation.

Impact

Frontend applications and off‑chain services may be unable to retrieve deposit data for users with many deposits.

Tools Used

Manual Review

Recommendations

Modify the function to return deposits in pages (with a defined start index and limit) so that no single call iterates over an unbounded number of elements.

function getUserDeposits(address user, uint256 start, uint256 limit) external view returns (uint256[] memory) {
uint256[] memory deposits = new uint256[](limit);
for (uint256 i = 0; i < limit; i++) {
if (start + i >= EnumerableSet.length(userDeposits[user])) break;
deposits[i] = EnumerableSet.at(userDeposits[user], start + i);
}
return deposits;
}
Updates

Lead Judging Commences

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.