Core Contracts

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

Incorrect Liquidity Handling When Withdrawing from Curve Vault via `LendingPool::withdraw`

Summary

When a user calls LendingPool::withdraw to withdraw more assets than are available in the reserveRToken contract, the system calls _withdrawFromVault via _ensureLiquidity to withdraw additional funds from the crvUSDvault. However, the retrieved assets are not routed to the reserveRToken contract as intended. Instead, they are sent to the LendingPool contract. As a result, when ReserveLibrary::withdraw is called, it fails due to insufficient funds in the reserveRToken contract.

Vulnerability Details

The issue occurs in LendingPool::withdraw when available liquidity in the reserveRToken contract is insufficient to cover the withdrawal. _ensureLiquidity tries to withdraw the missing amount from the crvUSDvault, but the assets are not routed to reserveRTokenAddress. This leaves the reserveRToken contract underfunded.

function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (withdrawalsPaused) revert WithdrawalsArePaused();
// Update the reserve state before the withdrawal
ReserveLibrary.updateReserveState(reserve, rateData);
// Ensure sufficient liquidity is available
@> _ensureLiquidity(amount);
// Perform the withdrawal through ReserveLibrary
(uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) = ReserveLibrary.withdraw(
reserve, // ReserveData storage
rateData, // ReserveRateData storage
@> amount, // Amount to withdraw
// @audit-issue _withdrawFromVault was called because the reserveRToken contract didn't have `amount` of assets in its balance.
// Since the withdrawn assets were not routed to the reserveRToken contract (and instead sent to the LendingPool contract),
// this transaction will fail due to insufficient funds in the reserveRToken contract.
msg.sender // Recipient
);
// Rebalance liquidity after withdrawal
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}
function _ensureLiquidity(uint256 amount) internal {
// if curve vault is not set, do nothing
if (address(curveVault) == address(0)) {
return;
}
uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// Withdraw required amount from the Curve vault
@> _withdrawFromVault(requiredAmount);
// @audit-notice if not enough assets in the reserveRToken, withdraw from curve vault
// to cover the required amount.
}
}
function _withdrawFromVault(uint256 amount) internal {
@> curveVault.withdraw(amount, address(this), msg.sender, 0, new address );
// @audit-issue the receiver of the assets (second argument) should be the reserveRToken contract
// rather than the LendingPool contract (address(this)).
// This results in the assets being sent to the LendingPool contract instead of the reserveRToken contract,
// leaving the reserveRToken contract underfunded and causing withdrawal failures.
}

Impact

Failed transactions due to insufficient liquidity in the reserveRToken contract.

Tools Used

Manual review

Recommendations

  1. Send Withdrawn Assets to reserveRTokenAddress

    • Modify _withdrawFromVault to route the assets to reserveRToken to ensure liquidity is correctly replenished:

    function _withdrawFromVault(uint256 amount) internal {
    curveVault.withdraw(amount, reserve.reserveRTokenAddress, address(this), 0, new address );
    }
Updates

Lead Judging Commences

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