Core Contracts

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

Liquidity buffer ratio invariant violation in LendingPool.sol

Summary

The _repay function in the LendingPool contract does not call _rebalanceLiquidity(), leading to an imbalance between the reserve liquidity buffer and the Curve Vault. This omission breaks the invariant for desired ratio between the pool and the vault.

Vulnerability Details

The _repay function processes debt repayments without rebalancing liquidity between the reserve and Curve Vault.

  • _rebalanceLiquidity() is only called in the borrow function, creating asymmetric behavior.

  • This breaks the expected invariant for ratio.

Impact

  1. Excess liquidity remains in the reserve instead of being deposited in the Curve Vault.

  2. The invariant ensuring 20% liquidity in the reserve and is violated.

  3. Liquidity is rebalanced when borrowing but not when repaying, leading to inconsistent liquidity conditions.

PoC

Prerequisites:

Liquidity buffer ratio: 20% (20_00 in basis points)

Borrowing calls _rebalanceLiquidity(), but repayment does not.

Scenario:

  1. A user borrows a large amount of reserve assets and _rebalanceLiquidity() moves excess liquidity to the Curve Vault.

  2. The same user repays the loan - Liquidity increases in the reserve, but _rebalanceLiquidity() is not called.

  3. Excess liquidity remains idle instead of being deposited back into the vault.

Tools Used

Manual review

Recommendations

Add _rebalanceLiquidity() at the end of _repay() to ensure liquidity remains balanced after repayments.

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);
+ _rebalanceLiquidity();
emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}
Updates

Lead Judging Commences

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

LendingPool::finalizeLiquidation or repay doesn't call _rebalanceLiquidity, leaving excess funds idle instead of depositing them in Curve vault for yield

Support

FAQs

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

Give us feedback!