Core Contracts

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

Miscalculated liquidation threshold usage in borrow() and withdrawNFT()

Summary

The borrow() and withdrawNFT() function incorrectly calculates the collateral-to-debt ratio, causing malicious borrowing attempts to over**-collateralized positions to be accepted**.

Vulnerability Details

The liquidation threshold is defined as:

liquidationThreshold = debt * 10000 / collateral

However, the condition for borrowing:

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

This is mathematically incorrect, leading to incorrect liquidation risk evaluations.

Exploit Scenario

Assume: Collateral Value = 100 ETH, Debt Requested = 0 ETH, Liquidation Threshold = 80% (8000 in basis points)

Expected correct condition: 100 ETH * 8000 / 10000 = 80 ETH, So max borrow is 80 ETH.

But in fact, 100 ETH < 120 ETH * 8000 / 10000 = 96 ETH, False will lead to borrow can be allowed for 120 ETH.

This wrong logic is implied in withdrawNFT().

function withdrawNFT(uint256 tokenId) external nonReentrant whenNotPaused {
...
@> if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
...
}

Proof Of Code

Testcode is written in LendingPool.test.js

describe("Borrow and Repay", function () {
...
it("newspace poc test", async function () {
const borrowAmount = ethers.parseEther("120");
await lendingPool.connect(user1).borrow(borrowAmount);
const crvUSDBalance = await crvusd.balanceOf(user1.address);
expect(crvUSDBalance).to.equal(ethers.parseEther("1120"));
const debtBalance = await debtToken.balanceOf(user1.address);
expect(debtBalance).to.gte(borrowAmount);
});

POC shows that NFT Price is only 100 ETH, but user1 can borrow 120 ETH.

Impact

Borrow is allowed even with insufficient collateral.

Protocol will lose funds.

Tools Used

manual

Recommendations

Fix the borrowing condition. Be cautious with liquidation value can be 0 and userTotalDebt = 0, so >= must be correct.

- if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
+ if (collateralValue.percentMul(liquidationThreshold) >= userTotalDebt) {
revert NotEnoughCollateralToBorrow();
}
- if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
+ if ((collateralValue - nftValue).percentMul(liquidationThreshold) >= userDebt) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
Updates

Lead Judging Commences

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