Core Contracts

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

LendingPool cannot deposit assets into vault due to incorrect assets location

Summary

The LendingPool::_depositIntoVault() function attempts to deposit reserve assets into Curve's vault directly from the LendingPool contract, but the assets are actually held in the RToken contract (reserve.reserveRTokenAddress), making deposits impossible.

Vulnerability Details

In _depositIntoVault, the function tries to deposit assets into Curve's vault:

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

However, the reserve assets are stored in the RToken contract (reserve.reserveRTokenAddress) not in the LendingPool contract itself. This means the deposit will fail as the LendingPool has no assets to deposit, breaking the core deposit functionality of the protocol.

This issue occurs because the deposit flow in the deposit() function transfers assets to the RToken contract, but the vault integration expects the assets to be in the LendingPool contract.

Impact

  • Users cannot deposit assets into the protocol as the vault deposit will fail

  • Core protocol functionality is completely broken

Tools Used

Manual review

Proof of Concept

Create a new Mock CurveVault contract that inherits from ERC4626 in the file contracts/mocks/core/vaults/CurveVaultMock.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ICurveCrvUSDVault} from "../../../interfaces/curve/ICurveCrvUSDVault.sol";
contract CurveVaultMock is ERC4626 {
bool public depositEnabled;
constructor(
address _asset
) ERC4626(IERC20(_asset)) ERC20("Mock Curve Vault", "mcrvVault") {
depositEnabled = true;
}
function enableDeposits(bool _depositEnabled) external {
depositEnabled = _depositEnabled;
}
/**
* @notice Withdraws assets from the vault
* @param amount The amount of assets to withdraw
* @param owner The owner of the shares
* @param recipient The address to receive the assets
* @param maxLoss The maximum acceptable loss (not used in mock)
* @param strategies Array of strategies (not used in mock)
*/
function withdraw(
uint256 amount,
address owner,
address recipient,
uint256 maxLoss,
address[] calldata strategies
) external returns (uint256) {
// Mock implementation ignores maxLoss and strategies parameters
return super.withdraw(amount, recipient, owner);
}
/**
* @notice Returns the maximum amount that can be deposited
* @param owner The address to check deposit limit for
*/
function maxDeposit(
address owner
) public view override(ERC4626) returns (uint256) {
if (!depositEnabled) return 0;
return super.maxDeposit(owner);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual override returns (uint256) {
// Mock implementation ignores maxLoss and strategies parameters
return super.withdraw(assets, receiver, owner);
}
}

Add the following test case to the test/unit/core/pools/LendingPool/LendingPool.test.js file:

describe("Curve Vault", function () {
let curveVault;
beforeEach(async function () {
const CurveVault = await ethers.getContractFactory("CurveVaultMock");
curveVault = await CurveVault.deploy(crvusd.target);
await lendingPool.setCurveVault(curveVault.target);
});
it("Cannot deposit into vault because assets are in the RToken not the lending pool", async function () {
const depositAmount = ethers.parseEther("100");
await expect(lendingPool.connect(user1).deposit(depositAmount))
.to.be.revertedWithCustomError(crvusd, "ERC20InsufficientBalance");
});
});

Recommendations

Transfer the assets from RToken to LendingPool before attempting vault deposit:

function _depositIntoVault(uint256 amount) internal {
+ // Transfer assets from RToken to LendingPool first
+ IRToken(reserve.reserveRTokenAddress).transferAsset(address(this), amount);
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
Updates

Lead Judging Commences

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