The LendingPool contract integrates a liquidity management mechanism that “tops up” available funds by withdrawing from a Curve crvUSD vault when the liquidity buffer falls short. This is done in the internal function _ensureLiquidity, which calls _withdrawFromVault if the reserve’s liquidity (held in the reserve RToken’s address) is insufficient. However, the implementation of _withdrawFromVault sends the withdrawn funds directly to the caller (msg.sender) rather than to the designated liquidity buffer (i.e. the RToken contract’s address). As a result, a malicious user could trigger a withdrawal operation to extract extra funds from the vault in addition to the normal withdrawal proceeds.
Intended Design:
The LendingPool is designed so that when a user requests a withdrawal, the contract first checks the liquidity available in the reserve (by querying the balance of the RToken contract).
– If the balance is insufficient, the system should “top up” the liquidity buffer by withdrawing the shortfall from the Curve vault. In a correct design, these funds should be deposited into the reserve (i.e. the RToken’s address), thereby increasing the buffer before proceeding with the withdrawal.
Faulty Implementation:
In the _ensureLiquidity function, if the available liquidity is less than the requested amount, the contract calculates the required shortfall and calls _withdrawFromVault(requiredAmount).
– The implementation of _withdrawFromVault is as follows:
Notice that the vault’s withdraw call is made with msg.sender as the recipient. Consequently, instead of replenishing the liquidity buffer (i.e. transferring funds to the reserve RToken’s address), the funds are sent directly to the user.
Attack Vector & Impact:
– A malicious user calling the withdraw function when the reserve’s liquidity is insufficient will trigger _ensureLiquidity.
– The vault will then withdraw the shortfall and send those funds directly to the user. Next, the standard ReserveLibrary withdrawal is executed—which also transfers funds from the reserve to the user. – As a result, the user ends up receiving both the extra funds from the vault and the normal withdrawal amount—effectively “double dipping” and draining funds from the vault. – This flaw could be exploited repeatedly, leading to a significant loss of assets from the vault and undermining the protocol’s overall capital efficiency and security.
– A mock ERC20 asset is deployed and minted to a simulated reserve (represented by an address).
– A mock Curve vault is deployed whose withdraw function sends funds directly to the recipient.
– A minimal mock LendingPool (integrating the flawed _ensureLiquidity) is deployed.
– >The user requests a withdrawal of 200 tokens while the reserve holds only 100 tokens.
– >The flawed _ensureLiquidity function withdraws the shortfall (100 tokens) directly to the user.
– >Then, the dummy ReserveLibrary withdrawal transfers 200 tokens from the reserve (simulated separately) to the user.
--->As a result, the user receives 300 tokens—demonstrating the double withdrawal vulnerability.
Fix:
Modify the _withdrawFromVault function so that funds withdrawn from the Curve vault are deposited into the liquidity buffer rather than sent directly to the caller. For example, change the recipient from msg.sender to the reserve’s RToken address:
This change would ensure that the liquidity buffer is replenished appropriately so that subsequent withdrawals draw funds only from the buffer.
– It prevents users from receiving extra funds directly from the vault and protects protocol liquidity.
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.