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.