Core Contracts

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

Incorrect asset receiver in curve vault withdraw function leads to failed user withdrawals in LendingPool.

Summary

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.

Vulnerability Details

The issue lies in the asset flow during withdrawals. Let's break down how the withdrawal process works:

  1. Users call withdraw() on LendingPool

  2. LendingPool checks if enough liquidity exists via _ensureLiquidity()

  3. If insufficient liquidity, _ensureLiquidity() calls _withdrawFromVault()

  4. _withdrawFromVault() withdraws crvUSD from Curve vault to LendingPool address:

function _withdrawFromVault(uint256 amount) internal {
// @audit the receiver is set to address(this)
// @audit means it is the LendingPool contract that will recive the crvUSD tokens
curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));
totalVaultDeposits -= amount;
}

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():

function burn(address from, address receiverOfUnderlying, uint256 amount, uint256 index) external override onlyReservePool returns (uint256, uint256, uint256) {
// ... other code ...
_burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
@> IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
// ... other code ...
}

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:

  1. LendingPool has the withdrawn crvUSD

  2. RToken tries to transfer crvUSD to user but has no balance

  3. The transfer fails due to insufficient balance

This breaks the core withdrawal functionality of the protocol.

PoC

  1. Alice deposits 100 crvUSD

  2. Protocol deposits 80 crvUSD to Curve vault (20% buffer)

  3. Alice tries to withdraw 90 crvUSD

  4. _ensureLiquidity() withdraws 70 crvUSD from vault to LendingPool

  5. RToken.burn() tries to transfer 90 crvUSD to Alice but fails as tokens are in LendingPool

Impact

Users cannot withdraw their deposited assets when withdrawal amount exceeds the buffer, effectively locking user funds hence DoS of the lending pool.

Tools Used

Manual Review

Recommendations

Modify _withdrawFromVault() to withdraw to RToken contract:

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