Core Contracts

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

Users can repay debt after grace period expiry

Summary

The _repay function allows users to repay their debt at any time, even after they have entered liquidation. If the grace period has expired but finalizeLiquidation has not yet been executed, a user can repay their debt and potentially avoid liquidation. This creates a race condition where users can exploit the delay between the expiration of the grace period and the execution of liquidation.

Vulnerability Details

  • The finalizeLiquidation function reverts if called before the grace period expires.

  • The _repay function does not check if the user is under liquidation or if their grace period has expired.

  • This allows users to repay their debt after the grace period expires but before liquidation is finalized, potentially preventing the liquidation from occurring.

Code Snippet

[contracts/core/pools/LendingPool/LendingPool.sol]
398 function _repay(uint256 amount, address onBehalfOf) internal {
399 if (amount == 0) revert InvalidAmount();
400 if (onBehalfOf == address(0)) revert AddressCannotBeZero();
401
402 UserData storage user = userData[onBehalfOf];
403
404 // Update reserve state before repayment
405 ReserveLibrary.updateReserveState(reserve, rateData);
406
407 // Calculate the user's debt (for the onBehalfOf address)
408 uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
409 uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
410
411 // If amount is greater than userDebt, cap it at userDebt
412 uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
413
414 uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
415
416 // Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
417 // is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
418 (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
419 IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
420
421 // Transfer reserve assets from the caller (msg.sender) to the reserve
422 IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
423
424 reserve.totalUsage = newTotalSupply;
425 user.scaledDebtBalance -= amountBurned;
426
427 // Update liquidity and interest rates
428 ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
429
430 emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
431 }

Impact

Users can escape liquidation after the grace period by repaying their debt before finalizeLiquidation is executed.

Recommendations

Add a check in _repay function to prevent repayments after the grace period has expired:

+ if (isUnderLiquidation[onBehalfOf] && block.timestamp > liquidationStartTime[onBehalfOf] + liquidationGracePeriod) {
+ revert CannotRepayAfterGracePeriod();
+ }
Updates

Lead Judging Commences

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