Core Contracts

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

Incorrect Liquidation Closure and Finalization Logic

01. Relevant GitHub Links

02. Summary

In the LendingPool::closeLiquidation function, users are allowed to close their liquidation only if their total debt is below a fixed DUST_THRESHOLD. This design prevents users from closing liquidation even if they improve their collateral situation by adding more collateral or if the market value of their collateral increases. Conversely, if a user’s borrowed amount is below DUST_THRESHOLD and they enter liquidation, they can immediately close it without repaying debt or adding collateral.

A similar issue applies to finalizeLiquidation: once the grace period has passed, liquidation proceeds without re-evaluating whether the user’s collateral has become sufficient or whether some debt has been repaid. This results in users losing collateral even though they may have recovered a healthy health factor.

03. Vulnerability Details

LendingPool::closeLiquidation Logic

  • The function only checks whether the user’s debt is less than or equal to DUST_THRESHOLD.

  • If the debt is above DUST_THRESHOLD, the liquidation cannot be closed, even when the user’s health factor has improved in other ways.

  • Conversely, if a user’s borrowed amount is below DUST_THRESHOLD and they enter liquidation, they can immediately close it without repaying debt or adding collateral.

/**
* @notice Allows a user to repay their debt and close the liquidation within the grace period
*/
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);
}

finalizeLiquidation Logic

/**
* @notice Allows the Stability Pool to finalize the liquidation after the grace period has expired
* @param userAddress The address of the user being liquidated
*/
function finalizeLiquidation(
address userAddress
) external nonReentrant onlyStabilityPool {
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
if (
block.timestamp <=
@> liquidationStartTime[userAddress] + liquidationGracePeriod
) {
revert GracePeriodNotExpired();
}
UserData storage user = userData[userAddress];
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
// Transfer NFTs to Stability Pool
@> for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
raacNFT.transferFrom(address(this), stabilityPool, tokenId);
}
delete user.nftTokenIds;
...
  • After the grace period, it only checks whether the specified time has elapsed.

  • Even if the user’s collateral value is sufficient or the user has partially repaid enough debt to reach a healthy state, the contract proceeds with liquidation.

04. Impact

  • Users who wish to close liquidation by providing additional collateral or benefiting from increasing collateral value cannot stop liquidation if their debt exceeds DUST_THRESHOLD.

  • Users with very small borrow amounts can close liquidation immediately, ignoring the requirement to repay.

  • During finalization, valuable collateral may be liquidated despite the user potentially achieving a healthy collateral-to-debt ratio after the liquidation started.

05. Tools Used

Manual Code Review and Foundry

06. Recommended Mitigation

  1. Modify closeLiquidation to allow repayment of debt or addition of collateral to improve the user’s health factor rather than strictly checking DUST_THRESHOLD.

  2. Reassess user health factors during finalizeLiquidation. If the user’s new health factor is sufficient, do not seize all collateral.

  3. Ensure that the contract supports partial repayment or additional collateral deposits during the grace period, allowing users to exit liquidation more dynamically.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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!