Core Contracts

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

`_rebalanceLiquidity()` Missing in `_repay()` and `finalizeLiquidation()`

Summary

In LendingPool.sol, _rebalanceLiquidity() is invoked in deposit(), withdraw(), and borrow() to have excess reserve assets sent to curve vault for additionally leveraged yields, and vice versa, keeping 20% buffer of reserve.totalLiquidity. Such liquidity rebalancing is nonetheless missing in _repay() and finalizeLiquidation() when reserve.totalLiquidity has typically increased in value. This results in excess reserve assets idle in RToken.sol and miss out the on time opportunities leveraging curve yields.

Vulnerability Details

When _repay() or finalizeLiquidation() is called, transfer of reserve assets to reserve.reserveRTokenAddress is made:

LendingPool.sol#L421-L422

// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);

LendingPool.sol#L524-L525

// Transfer reserve assets from Stability Pool to cover the debt
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);

And then, ReserveLibrary.updateInterestRatesAndLiquidity() is invoked at the end of the function logic:

LendingPool.sol#L427-L428

LendingPool.sol#L531-L532

// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);

Evidently, amountScaled, the third input parameter, as matching to liquidityAdded, is going to be added to reserve.totalLiquidity as liquidityAdded.toUint128().

ReserveLibrary.sol#L199-L202

function updateInterestRatesAndLiquidity(ReserveData storage reserve,ReserveRateData storage rateData,uint256 liquidityAdded,uint256 liquidityTaken) internal {
// Update total liquidity
if (liquidityAdded > 0) {
reserve.totalLiquidity = reserve.totalLiquidity + liquidityAdded.toUint128();
}

So, rightfully, _rebalanceLiquidity() should have been called to keep desiredBuffer by sending excess to the curve vault, considering reserve.totalLiquidity has now increased by the delta of amountScaled:

LendingPool.sol#L778-L785

uint256 totalDeposits = reserve.totalLiquidity; // Total liquidity in the system
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
// Deposit excess into the Curve vault
_depositIntoVault(excess);

Not doing this is going to keep the excess reserve assets idling in RToken.sol instead having it sent to curve fault leveraging for extra yields.

Impact

Time is money. So, the missing liquidity rebalance in time is going to make the protocol lose out on yields deprived through the delta of time till the next trigger of _rebalanceLiquidity().

Tools Used

Manual

Recommendations

Consider making the following fix:

LendingPool.sol#L427-L428

// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
+ // Rebalance liquidity after repaying
+ _rebalanceLiquidity();

LendingPool.sol#L531-L532

// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
+ // Rebalance liquidity after finalizing liquidation
+ _rebalanceLiquidity();
Updates

Lead Judging Commences

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