Core Contracts

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

Paused Repayments With enabled Liquidation Vulnerability in `LiquidationPool.sol`

Summary

A vulnerability stems from the LendingPool contract, where the protocol can enter a state in which payments are paused while liquidations remain partially enabled. The repay and repayOnBehalf functions are disabled when the contract is paused, this may prevent borrowers from paying their debt to improve their health factor. However the finalizeLiquidation lacks a pause check, allowing the StabilityPool to liquidate positions that were initiated before the pause. This creates an unfair scenario where borrowers are unable to mitigate liquidation risk, yet liquidations are still ongoing during the contracts paused state. This vulnerability undermines the fairness and integrity of the lending pool.

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L496-L536
@> function repay(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
_repay(amount, msg.sender);
}
//@audit: This disables repayments when contracts state is paused.
/**
* @notice Allows a user to repay borrowed reserve assets on behalf of another user
* @param amount The amount to repay
* @param onBehalfOf The address of the user whose debt is being repaid
*/
@> function repayOnBehalf(uint256 amount, address onBehalfOf) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (!canPaybackDebt) revert PaybackDebtDisabled();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
_repay(amount, onBehalfOf);
}
//@audit:q This disables repayments unbehalf of others when contracts state is paused?
//answer: yes!
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (withdrawalsPaused) revert WithdrawalsArePaused(); // Ensure liquidations are paused when repayments are paused
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);
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
// Transfer NFTs to Stability Pool
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
raacNFT.transferFrom(address(this), stabilityPool, tokenId);
//!@audit-issue: Area of concern!
}
delete user.nftTokenIds;
// Burn DebtTokens from the user
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
// Transfer reserve assets from Stability Pool to cover the debt
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
// Update user's scaled debt balance
user.scaledDebtBalance -= amountBurned;
reserve.totalUsage = newTotalSupply;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}

This function lacks the whenNotPaused modifier allowing the StabilityPool to execute this function even when the contract is paused, provided the position was marked for liquidation beforehand.

stabilityPool.liquidateBorrower

function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
// ... calls lendingPool.finalizeLiquidation(userAddress) ...
}

It uses whenNotPaused as well but this applies to the stability pool's pause state and not the lending pools's. If the StabilityPool is unpaused it can call finalizeLiquidation regardless of the lending pool's state.

Impact

Borrowers are unfairly penalized as they cannot repay debt to avoid liquidation during a pause, yet their collateral remains at risk. this could lead to unnecessary liquidations and loss of assets.

The lack of equitable treatment may discourage user's participation, damaging the protocol's reputation and adoption.

Borrowers unable to pay during a pause may suffer disproportionate losses if collateral value fluctuates, while liquidator via the StabilityPool without borrower recourse.

Tools Used

Manual review

Recommendations

To address this issue and ensure the fairness of borrowers in the LiquidityPool modify the finalizeLiquidation function to enforce a consistent pause state.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unfair Liquidation As Repayment / closeLiquidation Paused While Liquidations Enabled

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unfair Liquidation As Repayment / closeLiquidation Paused While Liquidations Enabled

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.