Core Contracts

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

Incorrect Owner Parameter in Curve Vault Withdrawals leads to DoS in LendingPool and loss of funds.

Summary

The LendingPool contract's implementation of Curve vault withdrawals assumes incorrect ownership parameters, causing withdrawals to revert and leading to a Denial of Service in critical lending functions including borrowing, depositing, and withdrawing.

Vulnerability Details

Let's break down the flow:

  • When users deposit crvUSD into the protocol, they interact with the deposit() function in LendingPool

  • The crvUSD tokens are transferred to the RToken contract address (reserve.reserveRTokenAddress)

  • The excess liquidity above the buffer ratio is deposited into the Curve vault by the LendingPool contract which is the one which receives the curve vault shares.

Looking at _depositIntoVault, it is the LendinPool contract (address(this)) that received the curve vault shares.

function _depositIntoVault(uint256 amount) internal {
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this)); // @audit LendingPool receives the vault shares
totalVaultDeposits += amount;
}

Withdrawal Implementation Issue in _withdrawFromVault:

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

The critical error is in the withdraw() call to the Curve vault. Looking at ICurveCrvUSDVault interface:

function withdraw(
uint256 assets,
address receiver,
address owner, // @audit Must be the actual owner of vault shares
uint256 maxLoss,
address[] calldata strategies
) external returns (uint256 shares);

The implementation passes msg.sender as the owner parameter, but the actual owner of the vault shares is the LendingPool contract (address(this)), since it was the one that deposited and received the shares.

The incorrect withdrawal implementation affects functions such as _ensureLiquidity():

function _ensureLiquidity(uint256 amount) internal {
//...
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// @audit Will always revert
_withdrawFromVault(requiredAmount);
}
}

This function is indirectly / directly called in:

  • withdraw() - Lenders can't withdraw their deposits

  • borrow() - Borrowers can't borrow against their collateral

  • deposit() - Lenders can't deposit assets.

  • _rebalanceLiquidity() - Protocol can't stake crvUSD into curve vault.

PoC

  1. Alice deposits 1000 crvUSD into the LendingPool

  2. The LendingPool deposits 800 crvUSD (80%) into Curve vault, keeping 200 as buffer

  3. Bob tries to borrow 300 crvUSD

  4. borrow() calls _ensureLiquidity(300)

  5. Function attempts to withdraw 100 from vault but reverts because msg.sender (Bob) is not the owner of vault shares

  6. Bob's borrow transaction fails

Impact

  • Complete DoS of core LendingPool functions including borrowing, withdrawing, and depositing

  • User funds become locked

Tools Used

Manual review

Recommendations

Correct the withdrawal function to use the LendingPool contract as owner:

function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(
amount,
address(this),
address(this), // Fix: LendingPool is the owner
0,
new address[](0)
);
totalVaultDeposits -= amount;
}
Updates

Lead Judging Commences

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

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

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

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.