Core Contracts

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

LendingPool: User can still be liquidated even if he or someone else repaid his debt

Summary

The process of liquidation in LendingPool.sol:

  1. Anyone can call initiateLiquidation() if a user's health factor is below the threshold, it will trigger isUnderLiquidation[address(userA)] = true

  2. Then there is a period when a user can repay his debt, or another user can repay the debt by calling repayOnBehalf()

  3. Then the user needs to call closeLiquidation() to trigger isUnderLiquidation[address(userA)] = false and been removed from the state of liquidation

The issue here is that a userB can repay on behalf of userA, but the userA can still be liquidated as there is no check of his current debt in finalizeLiquidation()

A user can repay his debt but still be liquidated, and the issue is that there is no way for userB to call closeLiquidation() for userA as it checks only for msg.sender.

Vulnerability Details

There is a function repayOnBehalf() It allows a user to repay the debt of another one, but there is no way to cancel the liquidation process unless it's the user in liquidation who calls it. There could be gatekeeper services for users to subscribe to avoid any liquidation in case of a market move, but those services would be useless if another account cannot call closeLiquidation() on behalf of users. The presence of repayOnBehalf() shows that it's something who have been thought about but is not working in the end

  1. userA deposit nft, borrow X amount

  2. userA subscribes to external insurance against liquidation, let's call it userB

  3. userB sees that userA enter liquidation state because of market price movement

  4. userB repayOnBehalf() of userA

  5. userA does not have access to the internet at the moment and cannot closeLiquidation()

  6. userA gets liquidated even though his healthFactor > healthFactorLiquidationThreshold

Impact

A user can be unfairly liquidated and the function repayOnBehalf() will not work as it should, i.e avoid liquidation of certain users in case of big market movement.

Tools Used

Manual

Recommendations

  • it should be possible to call closeLiquidation() on behalf of a user

  • finalizeLiquidation() should check healthFactor :

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);
+ uint256 healthFactor = calculateHealthFactor(userAddress);
+ if (healthFactor > healthFactorLiquidationThreshold) revert HealthFactorOk();
isUnderLiquidation[userAddress] = false;
//...
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}
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.