Core Contracts

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

Unfair Liquidation Process Due to Grace Period Check in `closeLiquidation` function

Summary

The closeLiquidation function reverts if the current block.timestamp exceeds the liquidationStartTime[userAddress] + liquidationGracePeriod. This prevents users who have repaid their debt within the grace period from stopping the liquidation. As a result, the Stability Pool can call finalizeLiquidation, and the user's NFTs will be transferred to the Stability Pool even though the user has already repaid their debt. This is a serious flaw that unfairly penalizes users and undermines the fairness of the liquidation process.

Vulnerability Details

Root Cause:

  • The closeLiquidation function checks if the current block.timestamp is within the grace period:

if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}
  • If the grace period has expired, the function reverts, and the user cannot stop the liquidation.

  • The finalizeLiquidation function does not check if the user has repaid their debt. It simply transfers the user's NFTs to the Stability Pool if the grace period has expired.

Impact:

  • Unfair Penalization: Users who repay their debt within the grace period but are unable to call closeLiquidation due to the timestamp check will lose their NFTs.

  • Loss of Trust: Users may lose trust in the protocol if they are unfairly penalized despite repaying their debt.

Impact

  • Unfair Penalization: Users who repay their debt within the grace period but are unable to call closeLiquidation due to the timestamp check will lose their NFTs.

  • Loss of Trust: Users may lose trust in the protocol if they are unfairly penalized despite repaying their debt.

  • System Instability: The unfair liquidation process could disrupt the governance process and lead to instability in the system.

PoC

  1. A user's health factor falls below the liquidation threshold, and liquidation is initiated.

  2. The user repays their debt within the grace period.

  3. The user attempts to call closeLiquidation, but the function reverts because the grace period has expired.

  4. The Stability Pool calls finalizeLiquidation, and the user's NFTs are transferred to the Stability Pool even though the user has already repaid their debt.

Tools Used

Manual Review

Recommendations

Add Debt Repayment Check in finalizeLiquidation:

  • Ensure that the finalizeLiquidation function checks if the user's debt has been fully repaid before transferring their NFTs to the Stability Pool.

  • Example:

function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (userAddress == address(0)) revert InvalidAddress();
UserData storage user = userData[userAddress];
if (!user.underLiquidation) revert NotUnderLiquidation();
// Update reserve state
ReserveLibrary.updateReserveState(reserve, rateData);
// Check if the grace period has expired
if (block.timestamp <= user.liquidationStartTime + liquidationGracePeriod) {
revert GracePeriodNotExpired();
}
// Check if the user's debt has been fully repaid
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
if (userDebt <= DUST_THRESHOLD) revert DebtAlreadyRepaid();
// Finalize liquidation
user.underLiquidation = false;
user.liquidationStartTime = 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;
// Burn DebtTokens from the user
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
// Transfer reserve assets from Stability Pool to cover the debt
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
// Update user's scaled debt balance
user.scaledDebtBalance -= amountBurned;
reserve.totalUsage = newTotalSupply;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

A borrower can LendingPool::repay to avoid liquidation but might not be able to call LendingPool::closeLiquidation successfully due to grace period check, loses both funds and collateral

Support

FAQs

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

Give us feedback!