Core Contracts

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

missing check in LendingPool :: repay can lead to A user still able to repay and still get liquidated

[H-2] missing check in LendingPool :: repay can lead to A user still able to repay and still get liquidated

Description: In closeLiquidation() a user can still be able to repay and close the liquidation if still in liquidationGracePeriod , there is a check that prevent a user from been able to close the liquidation if block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod

if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}

The check is fine but there are few issue here

  1. user can repay when the grace period is still on and forgets to call closeLiquidation and when the grace period finishes he can not close the liquidation again

  2. secondly user can still repay even when the grace period time has passed, meaning that user can still repay there Reserve dept and wont be able to close the liquidation .

Impact: With this user will lost both Reserve token and his NFT collateral , which is not right,

Proof of Concept:

* Imagine A user have total NFT worth more than 2 million dollars
* now he can borrow close to 1.5 million dollars or more
* lets assumes he borrows that
* now with time because of selling some of his NFT and some other stuffs that can lead his account to decrease his health factor
* now lets assume his health factor went below threshold
* and now someone initiate liquidation with his address as parameter
* now his account is underLiquidation but is still yet within grace period
* because the dept he have to pay is quite much it will take him sometime to gather it
* lets say he gathered the money and repaid even before graceperiod is over
* now he repaid in the right time but just that he forgot to call closeLiquidation on time
* and now after some block the grace period finally elaspe
* and he remebered and called the closeLiquidation , then this function will revert , because this is a whole seperate function and do not check when user paid his dept
* with this the user can lose both his 1.5 million reserve paid and his NFT

Code snippets

repay function

function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
UserData storage user = userData[onBehalfOf];
// Update reserve state before repayment
ReserveLibrary.updateReserveState(reserve, rateData);
// Calculate the user's debt (for the onBehalfOf address)
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
// If amount is greater than userDebt, cap it at userDebt
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
// is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned; // @check if this is correct // Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}

closeLiquidation()


/**
* @notice Allows a user to repay their debt and close the liquidation within the grace period */
* function closeLiquidation() external nonReentrant whenNotPaused {
address userAddress = msg.sender;
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}
UserData storage user = userData[userAddress];
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
if (userDebt > DUST_THRESHOLD) revert DebtNotZero();
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
emit LiquidationClosed(userAddress);
}

Recommended Mitigation:

Since users will be able to repay when they are still under liquidation and Grace Period has not Expired yet , is neccessary to implement a logic that will stop a user from repaying when Grace Period has Expired

fix this check in repay

// First check if user is under liquidation
if (isUnderLiquidation[msg.sender]) {
// If under liquidation, check grace period
if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}
}
With this check in repay the user wont be able to repay again when Grace Period has Expired

This check will only solve half of the problem

Now if the user actually paid before the grace period but forgets to call closeLiquidation then he will pass this check

So my second recommendation is to introduce a new function that will repay and close the liquidation at once if the user is under liquidation

function repayAndCloseLiquidation

Then this function will now contain all the checks neccessary

Updates

Lead Judging Commences

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