Core Contracts

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

Borrow, withdraw, deposit revert due to curve vault not having available liquidity or being paused.

Bug description

At the start of each call to borrow() and withdraw(), _ensureLiquidity() function is invoked, to check if the lending pool has enough liquidity, and if not to withdraw from the curve vault.

LendingPool.sol#L760-L766

uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress)
.balanceOf(reserve.reserveRTokenAddress);
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// Withdraw required amount from the Curve vault
_withdrawFromVault(requiredAmount);
}

At the end of each borrow/withdraw.deposit operation, _rebalanceLiquidity() is called. Depending on the desired and current buffer, the function will either deposit to or withdraw from the curve vault.

LendingPool.sol#L778-L790

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

Consider the following scenario:

  • Buffer ratio is set to 20%.

  • totalLiquidity is 100_000.

  • desiredBuffer is 20_000

Because current buffer is greater than desired buffer, 80_000 is deposited into the curve vault. Now let's say the curve vault got paused or does not have enough liquidity, which makes all deposit/withdraw operations inside it revert.

User comes and attempts to borrow 1000 assets of liquidity from the lending pool. Remember that the pool still has 20_000 assets of liquidity left after the deposit to the curve vault, so the check inside _ensureLiquidity passes without withdrawing additional liquidity. This operation will reduce current buffer from 20_000 to 19_000, making the second branch in _rebalanceLiquidity run, which will attempt to withdraw liquidity to fill the buffer. Since the vault is paused or might not have enough liquidity currently available, the transaction will revert, and even though the lending pool currently has enough funds to satisfy borrower's request, the transaction has no way of going through.

The same is true for withdraw operation. If user attempts to withdraw an amount that brings current buffer below the desired one, the call to _rebalanceLiquidity will fail if the curve vault is paused or does not have enough liquidity. As such withdraw will revert even though the LendingPool itself has enough liquidity to satisfy a request.

The problem is also true for deposit, if the vault is paused, the deposits may be DoS'ed. Also there's another scenario where deposits might be DoSed due to curve vault not having enough liquidity. Let's use the same setup provided above.
Remember there's 20_000 assets sitting in the pool. Let's say the buffer ratio was increased to 40% and currently all of curve's vault liquidity is unavailable due to being deposited into different strategies.
Now if user attempts to deposit a 1000, the totalLiquidity will become 101_000, and the desired buffer will evaluate to 101_000 * 40% = 40400. The current buffer is 21000, which is lower than desired one, so the pool will attempt to withdraw from the curve vault, which would result in a revert, as currently the vault is not liquid enough to withdraw 40400 - 21000 = 19400 assets.

Impact

DoS of borrowing/withdrawing operations if the amount borrowed or withdrawn brings current buffer below the desired one and curve vault is paused or does not have enough liquidity. This happens regardless of the fact that LendingPool itself has enough liquidity to satisfy a request.

Also a DoS of deposit operations, if the vault is paused or does not have available liquidity.

Recommended Mitigation

Wrap calls to _rebalanceLiquidity() in both withdraw and borrow into a try-catch block. This will work, because _ensureLiquidity will take care of ensuring that LendingPool has enough liquidity, if the LendingPool does not have enough liquidity, _ensureLiquidity will attempt to withdraw funds from the curve vault and it's okay to revert here as that would mean that there isn't enough liquidity in the system as a whole. But if _ensureLiquidity passes, there's no reason to enforce the _rebalanceLiquidity() call, if it fails it can always be called later.

Also wrap a call to _rebalanceLiquidity in deposit() function into a try-catch block.

Updates

Lead Judging Commences

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

LendingPool core operations revert if Curve vault is unavailable during rebalancing, even when sufficient liquidity exists in the pool

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

LendingPool core operations revert if Curve vault is unavailable during rebalancing, even when sufficient liquidity exists in the pool

Support

FAQs

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