Core Contracts

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

Incorrect Collateral Threshold Check in Borrow Function

Summary

The borrow function in the lending contract contains a logic flaw in how it verifies collateral sufficiency. Instead of using the actual collateral value to determine whether a user can borrow, the function incorrectly applies the liquidation threshold check against the user's debt. This allows borrowers to extract more funds than they should be entitled to, leading to potential insolvency risks for the lending protocol.

Vulnerability Details

The vulnerability exists in the following code segment:

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

The conditional check should ensure that collateralValue is sufficient to cover userTotalDebt based on the liquidation threshold. However, it erroneously uses userTotalDebt.percentMul(liquidationThreshold), which leads to an incorrect threshold calculation.

The issue arises because:

  1. liquidationThreshold is set to 80% (80_00 in basis points)

  2. The check compares if collateral value is less than (debt * 80%)

  3. This effectively allows users to borrow up to 125% of their collateral value (100/80 = 1.25)

For example, with a collateral value of 100 crvusd

  • Correct implementation should allow borrowing up to 80 USDC (80% of collateral)

  • Current implementation allows borrowing up to 125 USDC because:

    • 125 crvusd debt * 80% = 100 crvusd

    • 100 crvusd collateral >= 100 crvusd (debt * threshold)

    • The check passes incorrectly

The PoC demonstrates this by:

  1. Depositing an NFT worth 100 crvusd as collateral

  2. Successfully borrowing 125 crvusd

  3. Creating an undercollateralized position that should not be allowed

Impact

it allows users to borrow more than the protocol's intended limits

Users can mints NFTs and deposit it then borrow to gain the profit draining the lending pool.

Tools Used

Manual/Hardhat

##PoC

In LendingPool.test.js

describe("Borrow and Repay", function () {
beforeEach(async function () {
const depositAmount = ethers.parseEther("1000");
await crvusd.connect(user2).approve(lendingPool.target, depositAmount);
await lendingPool.connect(user2).deposit(depositAmount);
const tokenId = 1;
await raacNFT.connect(user1).approve(lendingPool.target, tokenId);
await lendingPool.connect(user1).depositNFT(tokenId);
});
it("should allow user to borrow crvUSD using NFT collateral", async function () {
const crvUSDBalanceBefore = await crvusd.balanceOf(user1.address);
console.log("crvUSDBalanceBefore", crvUSDBalanceBefore.toString());
const borrowAmount = ethers.parseEther("125");
await lendingPool.connect(user1).borrow(borrowAmount);
const user1CollateralValue = await lendingPool.getUserCollateralValue(user1.address);
expect(user1CollateralValue).to.equal(ethers.parseEther("100"));
const getNftPrice = await lendingPool.getNFTPrice(1);
console.log("getNftPrice", getNftPrice.toString());
const getUser1Debt = await lendingPool.getUserDebt(user1.address);
expect(getUser1Debt).to.equal(borrowAmount);
const crvUSDBalance = await crvusd.balanceOf(user1.address);
expect(crvUSDBalance).to.equal(BigInt(crvUSDBalanceBefore) + borrowAmount);
});
});

Recommendations

Compare against the maximum allowed borrow amount:

uint256 maxBorrowAmount = collateralValue.percentMul(liquidationThreshold);
if (userTotalDebt > maxBorrowAmount) {
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!