Core Contracts

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

A flaw in the CurveVault integration leads to DoS of the borrow functionality

Summary

When a user borrows, LendingPool transfers the required crvusd from the reserve. If reserves are insufficient, it withdraws the shortfall from CurveVault. However, the borrowed crvusd is transferred from the reserve address, while the withdrawn funds from CurveVault are mistakenly sent to LendingPool instead of the reserve address.

Vulnerability Details

LendingPool::borrow calls _ensureLiquidity() to ensure sufficient liquidity for the borrower.

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

If liquidity is insufficient, _withdrawFromVault() withdraws the missing amount from CurveVault:

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

Here, the receiver parameter is set to address(this), meaning the withdrawn crvusd is sent to LendingPool instead of the reserve address.

The CurveVault::withdraw function sends tokens to the specified receiver, which in this case is incorrect (LendingPool).

Meanwhile, in LendingPool::borrow, crvusd is transferred from reserve.reserveRTokenAddress (not LendingPool) to the borrower. This mismatch causes DoS of the LendingPool::borrow as well as LendingPool::withdraw.

Impact

The flaw results in a Denial of Service (DoS) for borrowing and withdrawing.

Tools Used

VSCode, Manual Research

Recommendations

Fix the receiver argument in the CurveVault::withdraw call

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

Alternatively, transfer the withdrawn tokens to the reserve after withdrawal:

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

Lead Judging Commences

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

Give us feedback!