Core Contracts

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

Liquidation execution fails to revalidate user's position leading to loss of funds

Summary

The finalizeLiquidation function in LendingPool executes liquidations based solely on time elapsed since initiation, without revalidating the user's current health factor. This allows liquidations to proceed even if a user has improved their position during the grace period, potentially leading to unnecessary loss of user funds(NFTs).

Vulnerability Details

The vulnerability exists in the finalizeLiquidation function in LendingPool:

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
// @audit - loss of funds if user deposited a NFT during the grace period
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;

The issue stems from several critical oversights:

  1. No Health Factor Revalidation:

  2. The function doesn't call calculateHealthFactor(userAddress) before executing the liquidation

  3. It ignores any improvements in the user's position during the grace period.

The LendingPoolallows a user that is under liquidation(set by calling the initiateLiquidation) to perform different actions such as:

  • Deposit additional NFTs (no restrictions while under liquidation)

  • Repay part or even the entire debt but do not call closeLiquidation.

  • Both actions could improve their health factor above the liquidation threshold.

Another check missing in the finalizeLiquidationfunction is: it accounts for all the user NFTs to pay the debt.

// @audit - loss of funds if user deposited a NFT during the grace period
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;

If the user has 4 NFTs, but 3 of them can cover his position, user will permanently lose the 4th NFT when he shouldn't even be liquidated.

Impact

  • Unfair liquidation of healthy positions.

  • Permanent loss of funds(NFTs) when unfair liquidations happen.

Tools Used

Manual Review

Recommendations

Add position revalidation before executing liquidation:

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
+ // Add health factor recheck
+ uint256 healthFactor = calculateHealthFactor(userAddress);
+ if (healthFactor >= healthFactorLiquidationThreshold) {
+ isUnderLiquidation[userAddress] = false;
+ liquidationStartTime[userAddress] = 0;
+ emit PositionNoLongerLiquidadatable(userAddress, healthFactor);
+ return;
+ }
// ... rest of the function
}

Additionally, the _repay function should not allow a repayment from a user that:

  • Is under liquidation

  • The grace period expired.

Reason: Users who remain unhealthy after the grace period ends cannot call repay to improve their health factor. This ensures they can be liquidated without the risk of front-running or DoS attacks that could block legitimate liquidations.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month 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.