The Lending pool is designed to interact with Yearn V3 vault (Curve vault), which serves as a buffer mechanism for rebalancing liquidity. If the buffer exceeds the targeted buffer, it deposits the excess amount in to a Curve vault and in case of shortage it withdraws the needed amount + any earned yield. However the earned yield can never be withdrawn or utilized within the protocol.
Everytime the liquidity needs to be optimized there is a deposit or withdraw from the Curve vault:
Notice that the deposits and withdrawals to/from the vault are tracked by the totalVaultDeposits
variable. The problem is that this variable will prevent withdrawing more than deposited, due to an underflow revert, which means the earning yield will be locked in the vault and can never be utilized within the protocol.
Consider the following scenario:
Alice deposits 1000
tokens -> desiredBuffer
(200) < currentBuffer
(1000) -> excess
amount = 800
(will be deposited into the Curve vault) -> 200
to RToken
totalVaultDeposits
+= 800
Bob deposits 1000
tokens -> desiredBuffer
(400) < currentBuffer
(1200) -> excess
amount = 800
totalVaultDeposits
+= 1600
-> 200
to RToken
Curve vault will have balance of 1600
crvUSD, let's say it earns yield of 10
tokens, so new balance = 1610
Now when a user decides to withdraw or borrow some amount, first _ensureLiquidity
is called to provide from the Curve vault the required amount.
Now let's say that a borrower wants to borrow 2010
tokens:
availableLiquidity
will be 400
requiredAmount
= 2010 - 400 = 1610
that means totalVaultDeposits
(1600
) -= 1610
, here it reverts
The call will revert with underflow error, even though the protocol actually has this balance and can provide the selected amount
Overall the totalVaultDeposits
variable doesn't allow the gained yield to be withdrawn from the vault, hence it will result in stuck yield, hence loss of yield.
2nd example is:
Essentially the protocol allows users to specify any amount they wish for withdrawals of crvUSD
tokens from the protocol.
And if he specifies more than he has deposited, the required amount will be withdrawn from the Curve as can be seen above from the code snippets (but the protocol will not let him to take out more than deposited, because this amount will be capped to his actual deposits later on)
That means by specifiying large values, he can withdraw all of the deposits in the Curve vault (i assume this is intentional design, so the earned yield from the vault can also be used, acting as buffer helper mechanism)
However the call will again revert if the required amount will be greater than totalVaultDeposits
.
Here is a coded POC, demonstrating the issue (i'm using Foundry for tests):
Install Foundry
Run forge init --force
in the terminal
In order to run the test successfully there are 2 other issues that must be fixed
one of them is fixed in the test file itself (it's also mentioned there)
second one is to refactor the code of LendingPool::_withdrawFromVault()
Paste the following file in the test folder and run forge test --mt testX
The earned yield can't be withdrawn, when interacting with the withdraw or borrow functions, hence results in loss of yield.
Manual Review
Currently the totalVaultDeposits
variable is not used in any core functionality. It only provides information about the token balances of the Curve vault, but this can be checked from the Curve vault itself. My recommendation is to remove the totalVaultDeposits
variable.
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.