Core Contracts

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

Incorrect Health Factor Calculation Enables Unauthorized Liquidations

Summary

The LendingPool contract allows liquidation of healthy positions due to precision loss in health factor calculations. When a borrower's position has a health factor above 1.0 (indicating safe collateralization), the contract still permits liquidation due to mathematical rounding errors in the division operations.

function calculateHealthFactor(address userAddress) public view returns (uint256) {
uint256 collateralValue = getUserCollateralValue(userAddress);
uint256 userDebt = getUserDebt(userAddress);
// Edge case: Returns max value when debt is near zero
if (userDebt < 1) return type(uint256).max;
// Uses percentMul for threshold but raw division for final calculation
uint256 collateralThreshold = collateralValue.percentMul(liquidationThreshold);
// Critical: Precision loss in division can make healthy positions appear unhealthy
// Should use WadRayMath.rayDiv for consistent precision
return (collateralThreshold * 1e18) / userDebt;
}

The key issue is in the final calculation where standard division is used instead of the protocol's WadRayMath library. This creates inconsistency since the rest of the protocol uses ray math (27 decimals) for precise calculations. The percentMul operation maintains precision, but it's lost in the final division step.

Vulnerability Details

The health factor calculation in LendingPool performs raw division without proper scaling, leading to precision loss that incorrectly flags healthy positions as eligible for liquidation.

Imagine a homeowner who has responsibly borrowed against their property, maintaining a healthy 120% collateralization ratio. Despite their prudent position, they could still face liquidation due to a subtle mathematical flaw in the LendingPool's health factor calculations.

The core issue lies in how the protocol determines borrower safety. When calculating the health factor, the LendingPool performs division operations that lose precision at crucial decimal points. This seemingly minor rounding error has major consequences, it can make a safe 1.2 health factor appear to be below the critical 1.0 threshold.

Here's how an opportunistic liquidator could exploit this:

They would scan for borrowers with health factors just above 1.0, targeting positions that should be completely safe. The liquidator calls initiateLiquidation() on these positions. Due to the precision loss in calculateHealthFactor(), the contract incorrectly flags these healthy positions as eligible for liquidation. The borrower's collateral gets liquidated at a discount, despite maintaining proper safety margins.

The real-world impact is substantial, borrowers who diligently maintained safe collateral ratios could lose their positions to liquidators. This is equivalent to a bank foreclosing on a mortgage that's current on payments, simply due to a spreadsheet rounding error.

Looking at these numbers.

  • Borrower's true health factor: 1.2 (120% collateralization)

  • Protocol's calculated value: 0.99999... (due to precision loss)

  • Result: Position liquidatable despite being 20% over-collateralized

This fundamentally undermines the protocol's risk management system and could affect any borrower maintaining a health factor between 1.0 and approximately 1.3.

Attack Flow

  1. Attacker identifies a borrower with health factor slightly above 1.0 (e.g. 1.2)

  2. Calls initiateLiquidation() on this position

  3. Due to precision loss, the health factor check passes

  4. The healthy position gets liquidated despite adequate collateralization

Impact

From LendingPool.sol

healthFactor = 1.2e18 // Above safe threshold
isUnderLiquidation[borrower] = true // Should be impossible

Attack Flow

  1. Attacker identifies a borrower with health factor slightly above 1.0 (e.g. 1.2)

  2. Calls initiateLiquidation() on this position

  3. Due to precision loss, the health factor check passes

  4. The healthy position gets liquidated despite adequate collateralization

This vulnerability enables premature liquidation of properly collateralized positions, potentially causing significant losses for borrowers who maintained adequate collateral ratios.

function calculateHealthFactor(address borrower) public view returns (uint256) {
// Precision loss in division leads to incorrect health factors
uint256 collateralValue = getUserCollateralValue(borrower);
uint256 debt = getUserDebt(borrower);
return (collateralValue * 1e18) / debt; // Vulnerable to rounding errors
}

Recommendations

Implement precise fixed-point arithmetic using WadRayMath

function calculateHealthFactor(address userAddress) public view returns (uint256) {
// Use WadRayMath for precise calculations
return collateralThreshold.rayDiv(userDebt); // Precise fixed-point division
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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