Core Contracts

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

Users can borrow more than the collateral provided

Summary

The LendingPool::borrow allows users to borrow more crvUSD than the actual value of collateral provided.

Vulnerability Details

The LendingPool::borrow is designed to allow users to borrow crvUSD as per the collater provided as NFT via the LendingPool::depositNFT function.
However, the check that determines whether the borrow amount respects the liquidation threshold is incorrect.

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// . . . Rest of the code . . .
// Fetch user's total debt after borrowing
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
// Ensure the user has enough collateral to cover the new debt
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) { <<@ - // Incorrect logic
revert NotEnoughCollateralToBorrow();
}
// . . . Rest of the code . . .
}

The check evaluates upon the liquidation threshold percentage applied to the userTotalDebt, which is incorrect, thus allowing borrows greater than the actual collateral value provided.

Impact

  1. The protocol would become insolvent.

  2. Direct loss of funds for the liquidity providers / depositors.

Proof of concept

Add the following test case inside LendingPool.test.js file:

describe("Borrow Issue", 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 more than the collateral provided", async function () {
const userCollat = await lendingPool.getUserCollateralValue(user1.address);
// User's current collateral value is 100 crvUSD
expect(userCollat).to.equal(ethers.parseEther("100"));
// User borrows more than the collateral provided, i.e. 125 crvUSD
const borrowAmount = ethers.parseEther("125");
await lendingPool.connect(user1).borrow(borrowAmount);
const debtBalance = await debtToken.balanceOf(user1.address);
expect(debtBalance).to.equal(borrowAmount);
// check crvUSD balance
const crvUSDBalance = await crvusd.balanceOf(user1.address);
// User gains 125 crvUSD
expect(crvUSDBalance).to.equal(ethers.parseEther("1125"));
});
});

As we can observe, a 100 crvUSD valued collateral yeilded a 125 crvUSD borrow.

Tools Used

Manual Review
/
Hardhat

Recommendations

It is recommended to fix the check to apply liquidation threshold upon the collateral value, a fix for the same is suggested below:

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// . . . Rest of the code . . .
// Ensure the user has enough collateral to cover the new debt
if (userTotalDebt < collateralValue.percentMul(liquidationThreshold)) { <<@ - // Fixed logic
revert NotEnoughCollateralToBorrow();
}
// . . . Rest of the code . . .
}
Updates

Lead Judging Commences

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