Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

_withdrawFromVault() can temporarely DOS withdrawals if _ensureLiqudity() is needed

Vulnerability Details

This problem arises from the LendingPool::_withdrawFromVault() function. The 2nd argument is wrong and will make the tx always revert.

As per the interface provided ICurveCrvUSDVault, see here:

/**
* @notice Withdraws assets from the vault
* @param assets Amount of assets to withdraw
@> * @param receiver Address to receive the assets // 🟢👁️
* @param owner Owner of the shares
* @param maxLoss Maximum acceptable loss in basis points
* @param strategies Optional specific strategies to withdraw from
* @return shares Amount of shares burned
*/
function withdraw(
uint256 assets,
address receiver,
address owner,
uint256 maxLoss,
address[] calldata strategies
) external returns (uint256 shares);

But when withdrawing lending pool passes address(this) as the owner argument instead of the RToken.address. This will make the tx revert as when RToken tries to transfer the funds to the user it won't have enough because the funds have been withdrawn to the LendingPool and not to the RToken where actually all funds deposited are stored.

Proof Of Concept

Let's showcase a detailed flow of a withdrawal that requires to withdraw from the CRV vault.

_ensureLiquidity() detects that a _withdrawFromVault() is needed and transfers the funds to the LendingPool.

Call flow: LendingPool::withdraw() -> _ensureLiquidity() -> _withdrawFromVault()

Links: Here -> Here -> Here.

As you can see after this the next step is withdraw from reserve library here. That calls burn from RToken here.

Burn transfers amount from RToken to the receiver here.

As we could see in the _ensureLiquidity() function, the withdrawal only triggers if amount was too big and required for funds to be transferred.

So the transfer from the RToken will revert because the balance of the RToken contract is smaller than the amount, because if _ensureLiquidity() is triggered, that means there were not enough available liquidity deposited.

But as said, then the receiver of the withdrawn from CRV tokens is the LendingPool and not the RToken contract. Yet the eventual transfer happens on the RToken as we saw here. Note that there is no transfer from LendingPool to RToken contract before the burn function call in this flow either, see again here.

Impact

_withdrawFromVault() is incorrectly implemented due to the receiver argument. Temporarely DOSing vital funcitons if they require withdrawing from CRV vaults, functions like LendingPool::withdraw() or LendingPool::borrow(). They both call _withdrawFromVault() form _ensureLiquidity().

Recommendations

At _withdrawFromVault() the receiver arg should be RToken.address.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_depositIntoVault and _withdrawFromVault don't transfer tokens between RToken and LendingPool, breaking Curve vault interactions

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_depositIntoVault and _withdrawFromVault don't transfer tokens between RToken and LendingPool, breaking Curve vault interactions

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.