Core Contracts

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

Incorrect Curve Vault Withdrawal Implementation

Summary

The withdraw function allows users to withdraw reserve assets from the protocol, the function checks if there are enough reserve assets in the protocol by calling _ensureLiquidity, if the reserve asset balance is not up to the amount required, it attempts to withdraw the amount needed from curve vault by calling _withdrawFromVault. However, Curve vault withdrawal parameters are incorrectly configured.

This misconfiguration could lead to users being unable to withdraw funds.

Vulnerability Details

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);
.....
}

The withdraw above is called by the user to withdraw amount of reserve assets by burning Rtokens. The function calls _ensureLiquidity which checks the available liquidity in Rtokenscontract. If there's insufficient liquidity, it attempts to withdraw from curve.

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);
}
}

The main issue is in _withdrawFromVault;

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

Curve Vault Interface

According to Curve documentation:

Vault.withdraw(
assets: uint256,
receiver: address = msg.sender,
owner: address = msg.sender
) -> uint256

Analysis

  1. Incorrect Receiver Address

    • Current: address(this) (the protocol contract)

    • Expected: reserve.reserveRTokenAddress (the RToken contract)

    • Impact: Tokens are being withdrawn to the protocol contract instead of the RToken contract where they should be available for user withdrawals

  2. Incorrect Owner Address

    • Current: msg.sender (the user requesting withdrawal)

    • Expected: address(this) (the protocol contract)

    • Impact: Incorrect permission structure as the protocol contract is the one who deposits into the vault and owns the shares to be burnt.

    Also, The implementation includes two additional parameters (uint256 maxLoss and
    address[] calldata strategies) that aren't part of the Curve vault interface. This suggests possible confusion with a different vault implementation.

Impact

The transactions will fail when users attempt to withdraw reserve assets and the protocol doesn't have enough liquidity and attempts to withdraw from the vault.

Tools Used

Manual

Recommendations

The fix below should be implemented

function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(
amount,
reserve.reserveRTokenAddress, // Correct receiver
address(this) // Correct shares owner
);
totalVaultDeposits -= amount;
}
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

LendingPool::_withdrawFromVault incorrectly uses msg.sender instead of address(this) as the owner parameter, causing vault withdrawals to fail

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

LendingPool::_withdrawFromVault incorrectly uses msg.sender instead of address(this) as the owner parameter, causing vault withdrawals to fail

Support

FAQs

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