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.
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.
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.
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.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.