Core Contracts

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

Incorrect liquidity transfer causes withdrawal failure in LendingPool

Summary

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.

Vulnerability Details

  1. Withdraw Function Calls _ensureLiquidity
    When a user calls the withdraw function, the contract first attempts to ensure sufficient liquidity:

_ensureLiquidity(amount);

The _ensureLiquidity function checks the available liquidity in reserveRTokenAddress:

uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.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

function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(amount, address(this), msg.sender, 0, new address );
totalVaultDeposits -= amount;
}
  • 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.

  1. 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.

Proof-of-Concept (PoC)

Scenario Setup:

  1. User deposits assets into LendingPool.

  2. A portion of assets is stored in the Curve vault.

  3. The user attempts to withdraw their deposit.

Exploit Steps:

  1. Call withdraw(amount).

  2. withdraw calls _ensureLiquidity(amount).

  3. _ensureLiquidity(amount) checks liquidity in reserveRTokenAddress and finds it insufficient.

  4. _withdrawFromVault(amount) is called, which transfers funds to LendingPool instead of reserveRTokenAddress.

  5. Liquidity remains unavailable for withdrawal.

  6. 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.

Impact

  1. 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.

  1. 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.

Tools Used

Recommendations

Transfer Withdrawn Liquidity to reserveRTokenAddress
Modify _withdrawFromVault to ensure liquidity is sent to reserveRTokenAddress instead of LendingPool:

function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(amount, reserve.reserveRTokenAddress, msg.sender, 0, new address );
totalVaultDeposits -= amount;
}

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.

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 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 6 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.