Core Contracts

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

Once initiateLiquidation() has been called, borrower can't closeLiquidation() post a partial repayment

Description & Impact

The only check inside closeLiquidation() is that the remaining userDebt is less than DUST_THRESHOLD of 1e6. So if the borrower makes a partial repayment which makes his debt healthy again, he still can't call closeLiquidation() and may be liquidated after the grace period:

File: contracts/core/pools/LendingPool/LendingPool.sol
465: /**
466: * @notice Allows a user to repay their debt and close the liquidation within the grace period
467: */
468: function closeLiquidation() external nonReentrant whenNotPaused {
469: address userAddress = msg.sender;
470:
471: if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
472:
473: // update state
474: ReserveLibrary.updateReserveState(reserve, rateData);
475:
476: if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
477: revert GracePeriodExpired();
478: }
479:
480: UserData storage user = userData[userAddress];
481:
482: uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
483:
484:@---> if (userDebt > DUST_THRESHOLD) revert DebtNotZero();
485:
486: isUnderLiquidation[userAddress] = false;
487: liquidationStartTime[userAddress] = 0;
488:
489: emit LiquidationClosed(userAddress);
490: }

Imagine:

  1. Borrowed value = 90.

  2. NFT collateral value when initiateLiquidation() is called by the liquidator on this debt = 100.

  3. Borrower repays 50. Outstanding debt = 40 which is healthy.

  4. Borrower can't call closeLiquidation() since it reverts on L484.

  5. StabilityPool calls finalizeLiquidation() after grace period and liquidates the borrower.

Mitigation

Add a check inside closeLiquidation() that the current debt state can be allowed if healthy:

File: contracts/core/pools/LendingPool/LendingPool.sol
465: /**
466: * @notice Allows a user to repay their debt and close the liquidation within the grace period
467: */
468: function closeLiquidation() external nonReentrant whenNotPaused {
469: address userAddress = msg.sender;
470:
471: if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
472:
473: // update state
474: ReserveLibrary.updateReserveState(reserve, rateData);
475:
476: if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
477: revert GracePeriodExpired();
478: }
479:
480: UserData storage user = userData[userAddress];
481:
482: uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
483:
- 484: if (userDebt > DUST_THRESHOLD) revert DebtNotZero();
+ 484: if (userDebt > DUST_THRESHOLD && calculateHealthFactor(userAddress) < healthFactorLiquidationThreshold) revert UnhealthyDebtExists();
485:
486: isUnderLiquidation[userAddress] = false;
487: liquidationStartTime[userAddress] = 0;
488:
489: emit LiquidationClosed(userAddress);
490: }
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.