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
The inefficiency arises from the use of arrays and linear iteration to manage deposited NFTs. Key functions affected include:
withdrawNFT: Iterates over the user's deposited NFTs to find and remove a specific token ID.
getUserCollateralValue: Iterates over the user's deposited NFTs to calculate the total collateral value.
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.
withdrawNFTgetUserCollateralValuefinalizeLiquidationHigh 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.
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.
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.
Manual code review
To address these issues, the protocol should eliminate inefficient array iteration and adopt more gas-efficient data structures and processes. Recommended solutions include:
Optimized Data Structures:
Replace arrays with mappings to track deposited NFTs and their indices for O(1) lookups and removals.
Example:
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.
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.
Partial Liquidation:
Enable partial liquidation to ensure progress even if the full liquidation cannot be completed in one transaction.
LightChaser L-36 and M-02 covers it.
LightChaser L-36 and M-02 covers it.
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.