Core Contracts

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

LendingPool deposits to curveVault Fail because LendingPool does not have crvUSD to deposit

Summary

In LendingPool::depositIntoVaultThe LendingPool send crvUSDto the curve vault and receives shares in return.

function _depositIntoVault(uint256 amount) internal {
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;

However, the LendingPool does not have the crvUSD tokens within its contract, it has no balance to deposit into the vault. The crvUSD tokens are held in a different contract, RToken.

The LendingPoolcannot deposit into the curveVault becasue it does not have any or receive any crvUSD (reserveAsset) - All of the crvUSD/ reserveAsset is held within RToken.

Vulnerability Details

The intended functionality is to deposit some crvUSD / reserveAsset into the curveVaultand receive and store the shares in the LendingPool. The protocol then has 2 places where their reserves lie: RTokenand curveVault.

This is impossible with the current logic becasue the deposit to curveVault will never happen, because LendingPooldoes not have the crvUSD to deposit.

  • When a user deposits crvUSD to get minted RTokens in return, the crvUSD deposit is sent to RTokencontract, not the LendingPool.

// Transfer asset from caller to the RToken contract
IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender, // from
reserve.reserveRTokenAddress, // to
amount // amount
);
  • The crvUSD amount is then KEPT within the RToken contract. The crvUSD never gets sent to LendingPool. But it is intentionally that crvUSD is kept in RToken, that is intended.

  • But becasue of that, LendingPoolnever receives crvUSD and thus never has crvUSD to deposit into curveVault when attempting to do so :

    function _depositIntoVault(uint256 amount) internal {
    IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
    curveVault.deposit(amount, address(this));
    totalVaultDeposits += amount;

** The use of RTokencontract as the holder of all reserveAsset/crvUSD is prevelant throughout the codebase, where every transaction that involves the transfer of reserveAssetis facilitated by the LendingPoolbut never receives the reserve crvUSD, they are always transferred to RTokencontract. The LendingPooldoes not have crvUSD within its contract to deposit.

Another example of this, is in the finializeLiquidationfunction, crvUSD is not sent to the LendingPoolit is sent to RTokencontract. The LendingPoolonly facilitates the transfers of crvUSD but never holds any, they are always sent to and held in RToken.

// Transfer reserve assets from Stability Pool to cover the debt
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
** crvUSD is transferred to `RToken contract` -> crvUSD is never held in LendingPool

Impact

This is severe becasue crvUSD can never be deposited to curveVault by the LendingPool. The protocol intends to have their total amount of reserveAsset/crvUSD distributed amongst 2 different contracts : RTokenand curveVault.

When a user withdraws, The protocol ensures there is enough liquid reserveAsset to send to the user to complete the withdraw by :

  • Checking the total amount of reserveAssetheld within RToken

  • If that amount is not enough to fulfill the withdraw, the protocol redeems the remaining needed amount from the curveVault.

    function _ensureLiquidity(uint256 amount) internal {
    uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
    if (availableLiquidity < amount) {
    uint256 requiredAmount = amount - availableLiquidity;
    // Withdraw required amount from the Curve vault
    _withdrawFromVault(requiredAmount);
    }
    }
    function _withdrawFromVault(uint256 amount) internal {
    curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));
    totalVaultDeposits -= amount;

This process shows the intended distribution of the reserveAssetbeing held in RTokenand curveVault. And when it is necessary to retrieve more reserveAssetto the system for withdraws, the protocol can do this by redeeming crvUSDfrom the curveVaultby burning their shares (THAT THEY ONLY GET WHEN THE VAULT DEPOSIT SUCCEEDS).

This distribution is INTEGRAL to the protocol - BUT it can never be acheived because the LEndingPoolcan never make the deposit to the curveVault becasue all of the reserveTokensare held in RTokencontract.

The functionality of the protocol is broken by the inability of the LendingPoolto deposit crvUSD to the curveVault.

Tools Used

manual review

Recommendations

Becasue crvUSD. / reserveAssetis held only in the RTokencontract -> extend the functionality in _depositIntoVaultby adding that function and logic within the RTokencontract.

Place the modifier (already used within RToken) onlyReservePoolon the function, which ensures only the pool can call that function.

When depositing to curveVaultensure the receiver of the shares is LendingPool.

function _depositIntoVault(uint256 amount) external onlyReservePool return(amount) {
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(LendingPool);
return amount;

Update the totalVaultDepositswithin the LendingPool contract by adding the returned amount.

Updates

Lead Judging Commences

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