Core Contracts

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

Small loans against low-priced NFT collateral can never be liquidated

Description & Impact

The only check inside closeLiquidation() is that the userDebt is less than DUST_THRESHOLD of 1e6. So if the NFT put up as collateral is a low-priced one and the debt is less than DUST_THRESHOLD, the borrower can always call closeLiquidation() within the grace period and escape liquidation repeatedly.

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. NFT collateral value = 1.1e6

  2. Borrowed = 1e6. This (1 / 1.1 = 90.9%) is more than the liquidation threshold of 80%.

  3. Liquidator calls initiateLiquidation().

  4. Borrower simply calls closeLiquidation() within the grace period.

Mitigation

Add a check that any remaining debt has to be 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();
- 485:
+ 485: require (calculateHealthFactor(userAddress) >= healthFactorLiquidationThreshold, "remaining debt is unhealthy");
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
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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