The withdraw function in the LendingPool contract fails to properly ensure liquidity before processing user withdrawals. The _withdrawFromVault function transfers funds from the Curve vault to the LendingPool contract instead of the reserveRTokenAddress. However, since the actual withdrawal mechanism relies on the balance of reserveRTokenAddress, the required liquidity is never made available for user withdrawals. As a result, the withdraw function fails, potentially reverting the transaction and causing a Denial of Service (DoS) for users attempting to withdraw their funds.
Withdraw Function Calls _ensureLiquidity
When a user calls the withdraw function, the contract first attempts to ensure sufficient liquidity:
The _ensureLiquidity function checks the available liquidity in reserveRTokenAddress:
If there is insufficient liquidity, _withdrawFromVault is called to withdraw assets from the Curve vault.
#2. _withdrawFromVault Transfers Funds to LendingPool Instead of reserveRTokenAddress
The withdrawal transfers funds to address(this), which is the LendingPool contract.
However, for withdrawals to succeed, liquidity must be made available in reserveRTokenAddress, not LendingPool.
Since the liquidity is never transferred to reserveRTokenAddress, _ensureLiquidity fails to truly ensure liquidity for withdrawals.
The Withdraw Function Fails
Since the liquidity check relies on reserveRTokenAddress, the balance remains insufficient even after _withdrawFromVault. This results in a failed transaction, causing the user’s withdrawal to revert.
Scenario Setup:
User deposits assets into LendingPool.
A portion of assets is stored in the Curve vault.
The user attempts to withdraw their deposit.
Exploit Steps:
Call withdraw(amount).
withdraw calls _ensureLiquidity(amount).
_ensureLiquidity(amount) checks liquidity in reserveRTokenAddress and finds it insufficient.
_withdrawFromVault(amount) is called, which transfers funds to LendingPool instead of reserveRTokenAddress.
Liquidity remains unavailable for withdrawal.
The transaction fails and reverts.
Expected Result:
The transaction should succeed, allowing the user to withdraw their assets.
Actual Result:
The transaction fails due to insufficient liquidity in reserveRTokenAddress.
The user is unable to withdraw funds.
User Withdrawals Fail (Denial of Service - DoS)
When users attempt to withdraw funds, the contract will attempt to ensure liquidity by calling _withdrawFromVault.
The function incorrectly transfers liquidity to LendingPool instead of reserveRTokenAddress.
As a result, the withdraw function will fail, leading to transaction reverts.
Users will be unable to withdraw their assets, causing a complete Denial of Service (DoS) on withdrawals.
Permanent Liquidity Locking Risk
The liquidity remains in the LendingPool contract rather than reserveRTokenAddress.
Unless manually corrected by an admin or governance function, liquidity will remain locked and unavailable for withdrawals indefinitely.
This can result in funds being permanently inaccessible to users.
Transfer Withdrawn Liquidity to reserveRTokenAddress
Modify _withdrawFromVault to ensure liquidity is sent to reserveRTokenAddress instead of LendingPool:
This ensures that liquidity is properly allocated for user withdrawals.
Introduce Fallback Mechanism
Implement a fallback function to manually transfer liquidity from LendingPool to reserveRTokenAddress in case of failures.
This would allow administrators to recover locked liquidity without requiring contract upgrades.
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.