Core Contracts

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

Incorrect Collateralization Checks in Borrow and WithdrawNFT Functions

Summary

The borrow and withdrawNFT functions use flawed collateralization checks, allowing users to borrow more than their collateral supports or withdraw NFTs, leaving them undercollateralized. This undermines the protocol’s safety mechanisms.

Vulnerability Details

Location:

  • borrow: Line if (collateralValue < userTotalDebt.percentMul(liquidationThreshold))

  • withdrawNFT: Line if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold))

    Problem:

  • In borrow, the check allows borrowing as long as collateralValue >= userTotalDebt * liquidationThreshold / 10000, but it should ensure userTotalDebt <= collateralValue * liquidationThreshold / 10000.

  • In withdrawNFT, the check fails to ensure the resulting health factor remains ≥ 1e18, allowing undercollateralization.

Attack Scenario:

  • Setup: User deposits an NFT worth 100 crvUSD, liquidationThreshold = 80_00 (80%), and borrows 80 crvUSD (usageIndex = 1e27).

  • Borrow Exploit:

    • Current debt = 80 crvUSD. User tries to borrow 20 more (total = 100 crvUSD).

    • Check: 100 < 100 * 0.8 = 80 (false, borrow allowed), but 100 * 0.8 = 80 < 100, so debt exceeds collateral threshold (should fail).

    • Result: User borrows 100 crvUSD with 100 crvUSD collateral, immediately liquidatable.

  • WithdrawNFT Exploit:

    • Debt = 80 crvUSD, collateral = 100 crvUSD. User withdraws the NFT (value 100).

    • Check: 100 - 100 = 0 < 80 * 0.8 = 64 (true, reverts), but if collateral drops to 0, health factor should be < 1e18 (should fail earlier).

    • Result: Incorrect logic allows unsafe withdrawals if misinterpreted.

The vulnerable code snippets :

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) { // Bug: Incorrect comparison
revert NotEnoughCollateralToBorrow();
}
// ...
}
function withdrawNFT(uint256 tokenId) external nonReentrant whenNotPaused {
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 nftValue = getNFTPrice(tokenId);
if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) { // Bug: Incorrect check
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
// ...
}

Impact

Undercollateralized Loans: Users can borrow beyond their collateral’s safe limit, risking instant liquidation or insolvency.
Protocol Losses: Liquidations may fail to recover enough value if collateral is insufficient, leading to losses.
Systemic Risk: Widespread undercollateralization undermines trust and stability.

Tools Used

Manual Review

Recommendations

Correct the collateralization check in borrow:

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
if (userTotalDebt > collateralValue.percentMul(liquidationThreshold)) { //@audit Fix: Ensure debt ≤ collateral * threshold
revert NotEnoughCollateralToBorrow();
}
// ...
}
  • Use health factor check in withdrawNFT:

function withdrawNFT(uint256 tokenId) external nonReentrant whenNotPaused {
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 nftValue = getNFTPrice(tokenId);
uint256 newCollateralValue = collateralValue - nftValue;
uint256 newCollateralThreshold = newCollateralValue.percentMul(liquidationThreshold);
uint256 newHealthFactor = userDebt == 0 ? type(uint256).max : (newCollateralThreshold * 1e18) / userDebt;
if (newHealthFactor < healthFactorLiquidationThreshold) { // Fix: Ensure health factor ≥ threshold
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
// ...
}
Updates

Lead Judging Commences

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