When users withdraw their assets from the lending pool, the protocol withdraws crvUSD from Curve vault to LendingPool
contract instead of RToken
contract, breaking the withdrawal flow since RToken
contract is the one responsible for transferring assets to users.
The issue lies in the asset flow during withdrawals. Let's break down how the withdrawal process works:
Users call withdraw()
on LendingPool
LendingPool
checks if enough liquidity exists via _ensureLiquidity()
If insufficient liquidity, _ensureLiquidity()
calls _withdrawFromVault()
_withdrawFromVault() withdraws crvUSD from Curve vault to LendingPool
address:
However this does not actually "ensure liquidity" since when withdrawing, i.e the flow in a nutshell;
LendingPool::withdraw() -> ReserveLibrary::withdraw() -> RToken::burn()
the actual asset transfer to users happens in RToken.burn()
:
The issue is that RToken
contract needs to have the crvUSD tokens to transfer to users, but the tokens are withdrawn to LendingPool
instead. This creates a mismatch where:
LendingPool
has the withdrawn crvUSD
RToken
tries to transfer crvUSD to user but has no balance
The transfer fails due to insufficient balance
This breaks the core withdrawal functionality of the protocol.
Alice deposits 100 crvUSD
Protocol deposits 80 crvUSD to Curve vault (20% buffer)
Alice tries to withdraw 90 crvUSD
_ensureLiquidity()
withdraws 70 crvUSD from vault to LendingPool
RToken.burn()
tries to transfer 90 crvUSD to Alice but fails as tokens are in LendingPool
Users cannot withdraw their deposited assets when withdrawal amount exceeds the buffer, effectively locking user funds hence DoS of the lending pool.
Manual Review
Modify _withdrawFromVault()
to withdraw to RToken
contract:
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.