[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
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
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];
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned;
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();
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
if (isUnderLiquidation[msg.sender]) {
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