When a user deposits crvUSD
, the contract mints RTokens
and calls _rebalanceLiquidity
to split the funds between the contract and the Curve vault based on a liquidity buffer ratio.When a user requests a withdrawal, _ensureLiquidity
checks if the RToken contract holds enough liquidity. If not, it calculates a requiredAmount
and attempts to withdraw that from the Curve vault. After this, the withdrawal process continues in the ReserveLibrary.withdraw
function which eventually calls RToken.burn
.If a user submits an excessively high withdrawal request (e.g., using uint256.max
), the RToken.burn
function detects that the requested amount exceeds the actual user balance and adjusts it down accordingly. However, by this point, the LendingPool
has already initiated a vault withdrawal based on the unadjusted, inflated amount. This discrepancy can lead to unnecessarily large withdrawals from the vault, wasting gas and causing potential performance issues.
When a user deposits crvUSD
and mints RTokens
, the _rebalanceLiquidity
function calculates, based on the liquidityBufferRatio
, the portion of crvUSD
that should remain in the RToken
contract, while the remaining liquidity is deposited into the Curve vault.
So when a user wants to withdraw, the _ensureLiquidity
function is first called to ensure that the required amount
of liquidity is available. If there is not enough availableLiquidity
, the function will withdraw the necessary funds from the vault.
The problem here is that if a user passes the full amount or sets amount = uint256.max
, the logic in the RToken contract checks if the amount
is greater than userBalance
. If so, it adjusts amount
to match the actual userBalance
instead of reverting. However, this can cause an issue in the LendingPool
, as it will attempt to withdraw the full amount from _withdrawFromVault(requiredAmount);
.
User Input Handling:
When a user initiates a withdrawal with an amount exceeding their RToken balance (e.g., uint256.max
), the RToken.burn
function adjusts the amount to their actual balance. However, this adjustment happens after the LendingPool
has already prepared liquidity based on the original (unadjusted) amount.
This leads to the LendingPool
potentially over-withdrawing from the Curve vault.
User Action: Calls LendingPool.withdraw(uint256 amount)
.
Step 1: _ensureLiquidity(amount)
is invoked:
Checks if the RToken contract has enough liquidity (availableLiquidity
).
If not, calculates requiredAmount = amount - availableLiquidity
and withdraws it from the Curve vault.
Step 2: ReserveLibrary.withdraw
calls RToken.burn
:
The burn
function checks the user's RToken balance.
If amount > userBalance
, it reduces amount
to userBalance
.
User Balance: 100 RTokens.
Withdrawal Request: amount = uint256.max
.
Available Liquidity: 50 crvUSD in RToken contract.
Step 1 (_ensureLiquidity
):
requiredAmount = uint256.max - 50
(a massive value).
Attempts to withdraw this from the Curve vault.
Step 2 (burn
):
Adjusts amount
to 100 (user's actual balance).
Withdraws 100 crvUSD from the RToken contract.
Result:
If the vault has sufficient liquidity, the RToken contract ends up with:
The vault may process large withdrawals/deposits
due to incorrect requiredAmount
, wasting gas and increasing latency.
Manual Review
In LendingPool.withdraw
, query the user's RToken balance upfront:
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.