Core Contracts

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

High Gas Consumption DoS Risk Due to Unbounded NFT Array Iteration in LendingPool

Summary

Within the LendingPool contract, certain functions rely on iterating over a potentially large array of deposited NFT token IDs. Each iteration requires computing or fetching the price of each NFT, resulting in gas costs that scale with the array’s size. In extreme cases—such as when a malicious user deposits hundreds or thousands of low-value NFTs—essential processes like liquidation checks or collateral valuation become prohibitively expensive or impossible, effectively leading to a Denial of Service. This vulnerability compromises the reliability of the entire protocol and can block or severely hinder normal user operations.

Vulnerability Details

The core issue arises from functions within the LendingPool contract that iterate over an array storing all deposited NFT token IDs (user.nftTokenIds). If a user deposits a large number of NFTs, any function dependent on that array (e.g., getUserCollateralValue, calculateHealthFactor, initiateLiquidation) will iterate through each token ID, incurring a gas cost proportional to the size of the array. In a worst-case scenario—such as a malicious actor depositing thousands of low-value NFTs—this can become prohibitively expensive or impossible to execute, effectively leading to a Denial of Service. This situation not only disrupts normal operations for legitimate users but also poses a strategic attack vector that can degrade the protocol’s overall performance and accessibility.

Impact

  1. Denial of Service: Excessive gas usage when iterating through a large array of NFTs can make critical operations—such as liquidation checks, collateral valuation, or health factor calculations—unfeasibly expensive or impossible to execute, halting normal protocol functionality.

  2. User Experience Degradation: Even under moderately large arrays of NFTs, legitimate users may face significantly higher transaction costs, reducing usability and discouraging participation in the protocol.

  3. Attack Vector for Malicious Actors: By depositing large numbers of low-value NFTs, an attacker can intentionally inflate gas costs and disrupt essential operations, affecting other users’ ability to manage their positions or withdraw funds in a timely manner.

Proof of Concept

The functions iterating over user.nftTokenIds can become extremely gas-costly if a user deposits a large number of NFTs. This overconsumption of gas can lead to a Denial of Service scenario, where any function that checks collateral value or initiates liquidation becomes prohibitively expensive or impossible to execute.

Code Analysis

Below is a brief snippet highlighting the potential issue of iterating over the NFT array:

function getUserCollateralValue(address userAddress) public view returns (uint256) {
// ... Preliminary code ...
// (!) Iterates over the entire user.nftTokenIds array
uint256 totalValue = 0;
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
uint256 price = getNFTPrice(tokenId);
totalValue += price;
}
return totalValue;
}
function initiateLiquidation(address userAddress) external nonReentrant whenNotPaused {
// ... Preliminary code ...
// (!) Ultimately depends on functions like calculateHealthFactor
uint256 healthFactor = calculateHealthFactor(userAddress);
// ... Rest of the code ...
}
function calculateHealthFactor(address userAddress) public view returns (uint256) {
// (!) Calls getUserCollateralValue, which iterates over the entire array
uint256 collateralValue = getUserCollateralValue(userAddress);
uint256 userDebt = getUserDebt(userAddress);
// ... Subsequent code ...
}

( ! ) The issue arises because if user.nftTokenIds.length is large, these loops can consume excessive gas, leading to a potential Denial of Service or prohibitive costs for critical operations like liquidation or collateral valuation.

Explanation

  • Reason for the finding: By storing all NFT IDs in a single array and iterating through it, each call to getUserCollateralValue, calculateHealthFactor, or any function that relies on them could become very gas-intensive if the array is large.

  • Potential Impact:

    • A malicious user could deposit numerous low-value NFTs to overload and block operations.

    • Gas block limits might prevent execution if the array is too large.

    • User experience deteriorates due to high gas fees.

Vulnerable Scenario

  1. A user repeatedly calls depositNFT, depositing hundreds or thousands of NFTs (e.g., very low-value NFTs).

  2. The array user.nftTokenIds grows excessively.

  3. When the protocol (or any other user) calls calculateHealthFactor, initiateLiquidation, or getUserCollateralValue on this user, the loop processes the entire NFT collection.

  4. The gas cost surges significantly, potentially causing transaction failures or consuming most of the block gas limit, resulting in a Denial of Service.

Test and Result

This test mints 200 NFTs for user1, each assigned a valid price of 10 crvUSD, and deposits them into the LendingPool. By calling getUserCollateralValue and calculateHealthFactor, we confirm the contract iterates through the large NFT array (showing a total collateral value of 2000 crvUSD and a maximum health factor). The test completes in over 4 minutes, demonstrating that iterating over a large array of NFTs significantly increases gas consumption, reinforcing the Denial of Service risk.

  • Add the following test to test/unit/core/pools/LendingPool/LendingPool.test.js

Note: Run this test only if you have at least 4 minutes available.

describe("Large NFT Array DoS Tests", function () {
it("should incur excessive gas costs when iterating over a large NFT array", async function () {
// Increase the test timeout to allow multiple transactions (up to 5 minutes)
this.timeout(300000); // 300,000 ms = 5 minutes
// Precondition: user1 already holds crvUSD and can deposit NFTs.
// Here, we mint several NFTs for user1 with consecutive IDs:
const numNFTs = 200; // Example: 200 NFTs
for (let i = 2; i < numNFTs + 2; i++) {
// Assign a valid price for the NFT with tokenId 'i' (set to 10 crvUSD)
await raacHousePrices.setHousePrice(i, ethers.parseEther("10"));
// Ensure user1 has enough crvUSD to mint the NFT (10 crvUSD)
await token.mint(user1.address, ethers.parseEther("10"));
// Approve the RAACNFT contract to spend those 10 crvUSD
await token
.connect(user1)
.approve(raacNFT.target, ethers.parseEther("10"));
// Mint the NFT with tokenId 'i' at the cost of 10 crvUSD
await raacNFT.connect(user1).mint(i, ethers.parseEther("10"));
// Approve the LendingPool to manage the newly minted NFT
await raacNFT.connect(user1).approve(lendingPool.target, i);
// Finally, deposit the NFT into the LendingPool
await lendingPool.connect(user1).depositNFT(i);
}
// Execute the functions that iterate over the entire NFT array
const collateralValue = await lendingPool.getUserCollateralValue(
user1.address
);
expect(collateralValue).to.be.gt(0);
const healthFactor = await lendingPool.calculateHealthFactor(user1.address);
expect(healthFactor).to.be.gt(0);
// Log for a clear visual confirmation of the vulnerability
console.log("Collateral Value:", collateralValue.toString());
console.log("Health Factor:", healthFactor.toString());
console.log(
"WARNING: High gas consumption when iterating over a large NFT array indicates a potential DoS vulnerability."
);
});
});
LendingPool
Large NFT Array DoS Tests
Collateral Value: 2000000000000000000000
Health Factor: 115792089237316195423570985008687907853269984665640564039457584007913129639935
WARNING: High gas consumption when iterating over a large NFT array indicates a potential DoS vulnerability.
✔ should incur excessive gas costs when iterating over a large NFT array (236675ms)
1 passing (4m)

Confirmation

Iterating over a large array directly scales with gas consumption. Without limiting the number of NFTs a user can deposit or implementing partial/batch calculations, the functions iterating over user.nftTokenIds can become excessively expensive or even impossible to run, confirming the Denial of Service risk.

Tools Used

Manual Code Review: A comprehensive examination of the contract source code was performed, with particular attention to loops and operations that could lead to high gas consumption.

Recommendations

  • Limit the Number of NFTs per User: Enforce a maximum cap on deposited NFTs to prevent runaway loop iterations.

  • Implement Alternative Data Structures: Use more efficient, non-iterative lookups (e.g., mappings) to avoid costly loops for collateral calculations.

Updates

Lead Judging Commences

inallhonesty Lead Judge 5 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 5 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.