Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Invalid

`withdrawNFT` can be DOSed due to unbounded loop

Summary

Context: LendingPool.sol#L307

The withdrawNFT function in the LendingPool contract is vulnerable to a potential Denial of Service (DoS) attack due to the unbounded loop in the process of finding and removing a specified tokenId from the user's deposited NFTs array. If the user.nftTokenIds array is large, the loop can exceed the gas limit, preventing users from withdrawing their NFTs, potentially causing a permanent blockage of their funds.

Vulnerability Details

In the depositNFT function, the contract allows users to add an NFT's tokenId to their user.nftTokenIds array. However, as the number of NFTs deposited by a user increases, the array becomes larger, and this may lead to an issue in the withdrawNFT function. Specifically, the unbounded loop in withdrawNFT that searches for a tokenId in the user.nftTokenIds array could exceed the gas limit if the array is too long.

function withdrawNFT(uint256 tokenId) external nonReentrant whenNotPaused {
if (isUnderLiquidation[msg.sender])
revert CannotWithdrawUnderLiquidation();
UserData storage user = userData[msg.sender];
if (!user.depositedNFTs[tokenId]) revert NFTNotDeposited();
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
// Check if withdrawal would leave user undercollateralized
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 nftValue = getNFTPrice(tokenId);
if (
collateralValue - nftValue <
userDebt.percentMul(liquidationThreshold)
) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
// Remove NFT from user's deposited NFTs
//@audit-issue unbounded loop (DOS)
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
if (user.nftTokenIds[i] == tokenId) {
user.nftTokenIds[i] = user.nftTokenIds[
user.nftTokenIds.length - 1
];
user.nftTokenIds.pop();
break;
}
}
user.depositedNFTs[tokenId] = false;
raacNFT.safeTransferFrom(address(this), msg.sender, tokenId);
emit NFTWithdrawn(msg.sender, tokenId);
}

Impact

Users who deposit too many NFTs into the LendingPool may experience issues withdrawing them later. If the user.nftTokenIds array grows too large, the withdrawNFT function may fail due to gas limitations, preventing users from withdrawing their NFTs. This could cause a denial of service for the affected user.

Tools Used

Manual review

Recommendations

To prevent the issue of exceeding gas limits due to large dynamic arrays, it is recommended to replace the use of the dynamic array user.nftTokenIds with a mapping variable to track which token IDs have been deposited. This way, the contract can efficiently check and manage deposited NFTs without the overhead of looping through an array.

For example, use a mapping like:

mapping(uint256 => bool) nftTokenIds;

When an NFT is deposited, set the corresponding token ID as true in the mapping:

nftTokenIds[tokenId] = true;

This eliminates the need for the for-loop that searches through the user.nftTokenIds array, improving gas efficiency and preventing denial of service (DOS) attacks due to exceeding the gas limit.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

LendingPool: Unbounded NFT array iteration in collateral valuation functions creates DoS risk, potentially blocking liquidations and critical operations

LightChaser L-36 and M-02 covers it.

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

LendingPool: Unbounded NFT array iteration in collateral valuation functions creates DoS risk, potentially blocking liquidations and critical operations

LightChaser L-36 and M-02 covers it.

Support

FAQs

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