Core Contracts

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

Lost repayments as users potentially get liquidated nonetheless

Summary

The repayOnBehalf() function in the protocol allows users to repay debts for others without verifying whether the grace period for the borrower has expired. This oversight can lead to situations where a borrower, who has already passed their grace period and is set for liquidation, may still have their debt partially repaid by another user.

Consequently, the borrower can still be liquidated despite having their debt reduced, resulting in unnecessary losses for the repaying user.

Vulnerability Details

Notice that a user can only close liquidation within the grace period:

>> 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;

Within this period, the user can pay their debt to a point below or equal to DUST_THRESHOLD after which they can then remove themselves from the liquidation train.

However, note that any user can perform debt repayment for another user via repayOnBehalf(). Also the _repay() function called internally does not check that the borrower whose debt is being repayed is past their grace period.

function repayOnBehalf(uint256 amount, address onBehalfOf) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (!canPaybackDebt) revert PaybackDebtDisabled();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
_repay(amount, onBehalfOf);
}

Now notice that when liquidateBorrower() is invoked on a user, it will proceed as long as the userDebt is not 0.

>> if (userDebt == 0) revert InvalidAmount();
---SNIP---
// Call finalizeLiquidation on LendingPool
>> lendingPool.finalizeLiquidation(userAddress);

Imagne this scenario:

  • Alice has been set up for liquidation but since she doesn't have the funds to repay this debt within time, her grace period elapses.

  • Since liquidation may not happen immediately the grace period elapses, there is still a window untill the manager or owner calls the liquidateBorrower().

  • During this time, Bob calls repayOnBehalf(amount, Alice) and pays her debt to the DUST_THRESHOLD

  • The liquidateBorrower() is then called on Alice and since her userDebt is not 0, liquidation proceeds and all her NFTs are taken despite having her debt payed significantly by Bob.

As seen, Bob's action hasn't served any purpose here as Alice has been liquidated regardless.


One may argue that this is user error for repaying their debt past their grace time, but notice that this repayment was not done by the borrower herself. It was done by another user who could not have known the status of the borrower interms of grace period.


Impact

This leads to financial losses for users who attempt to assist others by repaying debts after the grace period has expired. It undermines the intended functionality of the liquidation process, as it allows for the possibility of liquidating a borrower who has had their debt reduced.

Tools Used

Manual Review

Recommendations

Implement a check in the repayOnBehalf() function to ensure that the grace period for the borrower has not expired before allowing any debt repayment. This will prevent users from inadvertently repaying debts that are no longer eligible for repayment.

function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
+ // @audit Check grace time
+ if (block.timestamp > liquidationStartTime[onBehalfOf] + liquidationGracePeriod) {
+ revert GracePeriodExpired();
+ }
UserData storage user = userData[onBehalfOf];
---SNIP---
}
Updates

Lead Judging Commences

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

Give us feedback!