Core Contracts

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

Incorrect Health Factor Check in Liquidation Closure Leading to Unnecessary Debt Repayment

Summary

The closeLiquidation function in the LendingPool contract incorrectly requires users to repay nearly their entire debt to exit liquidation, even when partial repayment would restore their health factor above the liquidation threshold. This flaw forces users into unnecessary full repayments or liquidation, leading to potential loss of funds.

Vulnerability Details

The closeLiquidation function checks if the user's remaining debt is below a dust threshold (DUST_THRESHOLD = 1e6). However, this check does not consider the user's updated health factor after partial repayment. A user may repay enough to become solvent (health factor ≥ liquidation threshold) but still have debt exceeding the dust threshold, causing the function to revert unnecessarily. The correct logic should verify the health factor instead of debt magnitude.

function closeLiquidation() external nonReentrant whenNotPaused {
// ... existing checks ...
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
if (userDebt > DUST_THRESHOLD) revert DebtNotZero(); // Incorrect check
// ... update state ...
}

POC

  1. User deposits an NFT valued at 100e18 crvUSD.

  2. User borrows 50e18 crvUSD (within 80% LTV).

  3. NFT value drops to 60e18 crvUSD.

  4. Health Factor Below Threshold:

    • Collateral Threshold = 60e18 * 80% = 48e18.

    • Health Factor = (48e18 * 1e18) / 50e18 = 0.96e18 (< 1e18 threshold).

    • Liquidation is initiated.

  5. Partial Repayment: User repays 10e18 crvUSD, reducing debt to 40e18.

    • New Health Factor = (48e18 * 1e18) / 40e18 = 1.2e18 (≥ 1e18 threshold).

  6. Attempt Closure: User calls closeLiquidation.

    • Current Code: Reverts with DebtNotZero (debt = 40e18 > 1e6).

    • Expected Behavior: Liquidation should close as health factor is healthy.

Impact

  • Unnecessary Liquidations: Users who could become solvent via partial repayment are forced into full repayment or liquidation.

  • Financial Loss: Users may lose collateral due to avoidable liquidations or incur higher costs repaying more than needed.

Tools Used

Manual Review

Recommendations

Replace the debt check with a health factor check:

function closeLiquidation() external nonReentrant whenNotPaused {
// ... existing checks ...
// Recalculate health factor after state updates
uint256 healthFactor = calculateHealthFactor(msg.sender);
if (healthFactor < healthFactorLiquidationThreshold) {
revert HealthFactorStillTooLow();
}
// Optionally check for dust to prevent negligible debt
if (userDebt > DUST_THRESHOLD) {
revert DebtAboveDustThreshold();
}
// ... update state ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::finalizeLiquidation() never checks if debt is still unhealthy

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.