Core Contracts

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

Lack of Lower Bound Validation on Liquidation Threshold Leads to Mass Liquidation Risk

Summary

The LendingPool contract's setParameter function allows the owner to set the liquidationThreshold without enforcing a minimum value. If the admin sets the liquidationThreshold too low (e.g., below 80_00 or even 0), the liquidation condition may be triggered for all users—even those with healthy collateral—resulting in mass liquidations and significant user losses.

Vulnerability Details

The vulnerability stems from the fact that the setParameter function only checks that the newValue does not exceed 100_00 but does not enforce a lower bound. Without a lower bound, an Owner might set the liquidationThreshold to an unsafe value—such as 0 or any value below a safe minimum (for example, below 80_00). This unsafe setting could cause the collateral condition to be unrealistic.

Consider this snippet of the setParameter function:

function setParameter(OwnerParameter param, uint256 newValue) external override onlyOwner {
if (param == OwnerParameter.LiquidationThreshold) {
require(newValue <= 100_00, "Invalid liquidation threshold");
liquidationThreshold = newValue;
emit LiquidationParametersUpdated(liquidationThreshold, healthFactorLiquidationThreshold, liquidationGracePeriod);
}
//...
}

How the Vulnerability Impacts the Liquidation Process

  1. Effect on Collateral Calculation:

    Without a lower bound, an overly low liquidationThreshold can cause the computation of the collateral threshold to become unrealistic. In the calculateHealthFactor function, the collateral threshold is calculated:

    function calculateHealthFactor(address userAddress) public view returns (uint256) {
    uint256 collateralValue = getUserCollateralValue(userAddress);
    uint256 userDebt = getUserDebt(userAddress);
    if (userDebt < 1) return type(uint256).max;
    uint256 collateralThreshold = collateralValue.percentMul(liquidationThreshold);
    return (collateralThreshold * 1e18) / userDebt;
    }

    When liquidationThreshold is set too low, collateralThreshold becomes so low that almost any debt will make the computed health factor fall below the required threshold. As a result, users could be incorrectly flagged for liquidation.

  2. Initiating Liquidation:

    The initiateLiquidation function triggers liquidation when the calculated health factor is below a certain threshold:

    function initiateLiquidation(address userAddress) external nonReentrant whenNotPaused {
    if (isUnderLiquidation[userAddress]) revert UserAlreadyUnderLiquidation();
    // update state
    ReserveLibrary.updateReserveState(reserve, rateData);
    UserData storage user = userData[userAddress];
    uint256 healthFactor = calculateHealthFactor(userAddress);
    if (healthFactor >= healthFactorLiquidationThreshold) revert HealthFactorTooLow();
    isUnderLiquidation[userAddress] = true;
    liquidationStartTime[userAddress] = block.timestamp;
    emit LiquidationInitiated(msg.sender, userAddress);
    }

    With a too-low threshold, even users with reasonable collateral may end up being put under liquidation.

  3. Closing Liquidation:

    After initiating liquidation, if the admin resets the liquidationThreshold back to a normal value, users have a grace period (defined by liquidationGracePeriod) to call closeLiquidation and resolve their liquidation status. However, the function requires that a user's debt is reduced such that:

    function closeLiquidation() external nonReentrant whenNotPaused {
    address userAddress = msg.sender;
    if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
    // update state
    ReserveLibrary.updateReserveState(reserve, rateData);
    if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
    revert GracePeriodExpired();
    }
    UserData storage user = userData[userAddress];
    uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
    if (userDebt > DUST_THRESHOLD) revert DebtNotZero();
    isUnderLiquidation[userAddress] = false;
    liquidationStartTime[userAddress] = 0;
    emit LiquidationClosed(userAddress);
    }

    This situation creates a potential problem: even during the grace period, if the user's debt (userDebt) remains above the DUST_THRESHOLD, the liquidation cannot be closed. This may leave users in a perpetual liquidation state, even if the administration corrects the threshold after initiating liquidation.

Impact

  • Setting the liquidationThreshold too low can trigger unintended liquidations across the platform, causing severe losses for users.

Tools Used

Manual Review

Recommendations

Implement a lower bound check in the setParameter function to ensure that the liquidationThreshold remains within a safe range. For example, enforce that the newValue is not lower than 80_00 (or another protocol-determined minimum):

function setParameter(OwnerParameter param, uint256 newValue) external override onlyOwner {
if (param == OwnerParameter.LiquidationThreshold) {
- require(newValue <= 100_00, "Invalid liquidation threshold");
+ require(newValue >= 80_00 && newValue <= 100_00, "Invalid liquidation threshold");
liquidationThreshold = newValue;
emit LiquidationParametersUpdated(liquidationThreshold, healthFactorLiquidationThreshold, liquidationGracePeriod);
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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