Core Contracts

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

Solvent Users can be liquidated if the HealthFactorLiquidationThreshold is changed

Summary

Solvent Users can be liquidated if the HealthFactorLiquidationThreshold is changed

Vulnerability Details

The function calculateHealthFactor shown below is a function used to calculate a user's health factor by comparing the helath factor to the liquidation threshold.

[code](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L538-L555)

/**
* @notice Calculates the user's health factor
* @param userAddress The address of the user
* @return The health factor (in RAY)
*/
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;
}

This function is then used in the function inititateliquidation to determine if a user can be liquidated.

[code](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L443-L464)

/**
* @notice Allows anyone to initiate the liquidation process if a user's health factor is below threshold
* @param userAddress The address of the user to liquidate
*/
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);
}

An issue arises in the case that a user was deemed insolvent after an inititateliquidation call. An owner, acting non maliciously, can change the liquidation threshold. When this threshold is changed, a user that was previously insolvent, can become solvent again. However, this change is not reflected as the calculateHealthFactor function is only called through initiateliquidation while other functions throughout the contract only fetches the outdated mapping.

This locks the user out of 2 functions.

  1. the Borrow function as if only fetches the mapping, without recalculating the new healthfactor

  2. The withdrawNFT function for the same reasons

Repay also doesn't change the user 's status

Impact

A user who was previously insolvent cannot become solvent if the healthfactor threshold is changed and cannot change their status

Tools Used

Manual Review

Recommendations

Instead of fetching the isUnderLiquidation mapping, call the function calculatehealthfactor

Updates

Lead Judging Commences

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

Appeal created

minusone Submitter
7 months ago
inallhonesty Lead Judge
7 months ago
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.

Give us feedback!