Core Contracts

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

NFT Liquidation Failure Due to Gas Limit

SUMMARY

If a user deposits a large number of NFTs over time, the methods used to iterate over their NFTs—whether to calculate collateral value or withdraw a specific NFT—become highly inefficient. This inefficiency leads to prohibitively high gas costs for legitimate users and can even prevent the protocol from liquidating defaulted borrowers. Specifically, when a borrower fails to repay their loan and is meant to be liquidated, the stability pool may be unable to finalize the liquidation because the transaction can exceed Ethereum's block gas limit. This occurs because transferring a large number of NFTs in a single transaction becomes prohibitively expensive.

Affected Code: LendingPool::finalizeLiquidation


VULNERABILITY DETAILS

The inefficiency arises from the use of arrays and linear iteration to manage deposited NFTs. Key functions affected include:

  1. withdrawNFT: Iterates over the user's deposited NFTs to find and remove a specific token ID.

  2. getUserCollateralValue: Iterates over the user's deposited NFTs to calculate the total collateral value.

  3. finalizeLiquidation: Iterates over the user's deposited NFTs to transfer them to the stability pool.

For users with hundreds of NFTs, these iterations consume significant gas, making withdrawals and collateral value calculations expensive. In the case of liquidation, the gas cost can exceed Ethereum's block gas limit (~30M gas), rendering the transaction infeasible.

This issue can arise from legitimate users, as described in the documentation. For example, the depositor of the RAAC NFT could be a real estate company with a high portfolio, leading to a large number of deposited NFTs. Alternatively, an attacker could intentionally deposit a large number of NFTs to exploit the system. This vulnerability is exacerbated by the fact that there are no bounds on the number of NFTs that can be minted and deposited by an individual or entity.

Affected Code

withdrawNFT

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;
}
}

getUserCollateralValue

for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
uint256 price = getNFTPrice(tokenId);
totalValue += price;
}

finalizeLiquidation

for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
raacNFT.transferFrom(address(this), stabilityPool, tokenId);
}

IMPACT

How It Can Happen

  1. High Gas Costs for Legitimate Users:

    • A user with hundreds of deposited NFTs attempts to withdraw a specific NFT or calculate their collateral value.

    • The transaction consumes excessive gas due to array iteration, making it expensive or infeasible.

  2. Liquidation Failure:

    • A borrower with hundreds of NFTs defaults on their loan.

    • The stability pool attempts to liquidate the borrower by calling finalizeLiquidation.

    • The transaction exceeds Ethereum's block gas limit due to the large number of NFTs being transferred, causing the liquidation to fail.

  3. Systemic Risks:

    • Locked collateral and unrecoverable debt accumulate in the protocol.

    • Legitimate users are discouraged from using the platform due to high gas costs.

    • The protocol's financial stability is compromised if liquidation failures become widespread.


TOOLS USED

  • Manual code review


RECOMMENDATIONS

To address these issues, the protocol should eliminate inefficient array iteration and adopt more gas-efficient data structures and processes. Recommended solutions include:

  1. Optimized Data Structures:

    • Replace arrays with mappings to track deposited NFTs and their indices for O(1) lookups and removals.

    • Example:

      struct UserData {
      mapping(uint256 => uint256) nftIndex; // Mapping from tokenId to its index in the array
      uint256[] nftTokenIds; // Array of deposited NFT token IDs
      mapping(uint256 => bool) depositedNFTs; // Mapping to track if an NFT is deposited
      uint256 totalCollateralValue; // Cached total collateral value
      }
  2. Cached Collateral Value:

    • Store the total collateral value in the UserData struct and update it during deposits and withdrawals.

    • This eliminates the need for iteration in getUserCollateralValue.

  3. Batch Processing:

    • Allow NFTs to be liquidated or withdrawn in batches over multiple transactions.

    • Example: Implement a batchWithdrawNFT function that processes a limited number of NFTs per transaction.

  4. Partial Liquidation:

    • Enable partial liquidation to ensure progress even if the full liquidation cannot be completed in one transaction.

Updates

Lead Judging Commences

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

Give us feedback!