Core Contracts

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

Liquidity Rebalancing Issue Due to Incorrect Approval Source in Deposit Flow

Summary

An issue has been identified in the liquidity rebalancing mechanism, specifically within the LendingPool::_rebalanceLiquidity function. When excess liquidity exists beyond the buffer, LendingPool::_depositIntoVault is called to transfer the extra funds to the Curve vault. However, the approval for this transfer is granted from the lending pool instead of the RToken contract, which holds the actual funds. This misalignment could result in failed transactions and unintended behavior.

Vulnerability Details

  1. Deposit Flow:

  • A user deposits assets into the lending pool using the LendingPool::deposit function.

  • The ReserveLibrary.deposit function is called, updating the reserve state and minting RTokens and transferring the asset token to RToken contract.

  • _rebalanceLiquidity is invoked to ensure the buffer holds an optimal liquidity ratio.
    LendingPool::deposit:

function deposit(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// Update the reserve state before the deposit
ReserveLibrary.updateReserveState(reserve, rateData);
// Perform the deposit through ReserveLibrary
@> uint256 mintedAmount = ReserveLibrary.deposit(reserve, rateData, amount, msg.sender);
// Rebalance liquidity after deposit
@> _rebalanceLiquidity();
emit Deposit(msg.sender, amount, mintedAmount);
}

ReserveLibrary::deposit:

function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
if (amount < 1) revert InvalidAmount();
// Update reserve interests
updateReserveInterests(reserve, rateData);
// Transfer asset from caller to the RToken contract
@> IERC20(reserve.reserveAssetAddress).safeTransferFrom(
@> msg.sender, // from
@> reserve.reserveRTokenAddress, // to
@> amount // amount
@> );
// Mint RToken to the depositor (scaling handled inside RToken)
@> (bool isFirstMint, uint256 amountScaled, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).mint(
@> address(this), // caller
@> depositor, // onBehalfOf
@> amount, // amount
@> reserve.liquidityIndex // index
@> );
amountMinted = amountScaled;
// Update the total liquidity and interest rates
updateInterestRatesAndLiquidity(reserve, rateData, amount, 0);
emit Deposit(depositor, amount, amountMinted);
return amountMinted;
}

2.Rebalancing Liquidity:

  • The function checks the balance in the RToken contract to determine if excess funds exist.

  • If funds exceed the desired buffer, _depositIntoVault is triggered to move the excess into the vault.
    LendingPool::_rebalanceLiquidity:

function _rebalanceLiquidity() internal {
// if curve vault is not set, do nothing
if (address(curveVault) == address(0)) {
return;
}
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);
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
_withdrawFromVault(shortage);
}
emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);
}

3.Approval Issue in _depositIntoVault:

  • The function grants approval for the Curve vault to withdraw liquidity.

  • However, the approval is set from the lending pool's address, while the actual funds reside in the RToken contract.

  • This discrepancy may cause transaction failures, as the Curve vault will attempt to withdraw funds from an account that lacks the required balance.
    LendingPool::_depositIntoVault:

function _depositIntoVault(uint256 amount) internal {
@> IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}

Impact

  1. Transaction Reverts: If the lending pool does not hold the expected funds, the approval and deposit transaction may revert.

  2. Liquidity Management Failure: If the rebalancing process fails, excess liquidity may remain in the buffer, leading to inefficient fund utilization.

  3. User Experience Issues: Users may experience unexpected failures when depositing funds due to improper liquidity handling.

Tools Used

Manual

Recommendations

  1. Approve From Correct Source: Update _depositIntoVault to approve the Curve vault from the RToken contract instead of the lending pool.

Updates

Lead Judging Commences

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

LendingPool::_depositIntoVault and _withdrawFromVault don't transfer tokens between RToken and LendingPool, breaking Curve vault interactions

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

LendingPool::_depositIntoVault and _withdrawFromVault don't transfer tokens between RToken and LendingPool, breaking Curve vault interactions

Support

FAQs

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