Core Contracts

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

Critical Asset Handling Flaw in `LendingPool._depositIntoVault` Renders Core Protocol Operations Inoperable

Summary

A critical vulnerability exists in the LendingPool's vault integration (LendingPool._depositIntoVault) where reserve assets cannot be deposited to Curve due to missing asset transfers from the RToken contract. This breaks the protocol's liquidity rebalancing mechanism, rendering core functionality (deposits/withdrawals/borrowing) inoperable. The root cause is the incorrect assumption that reserve assets reside in the LendingPool rather than the RToken contract, leading to failed vault deposits when rebalancing liquidity buffers. This high-severity issue fundamentally disrupts protocol operations until resolved.

Vulnerability Details

The vulnerability exists in the liquidity rebalancing mechanism of the LendingPool contract. The LendingPool._depositIntoVault function (LendingPool.sol#L799-L803) attempts to deposit reserve assets directly from the LendingPool's balance without first transferring them from the RToken contract where they are actually held.

Key issues:

  1. Incorrect Asset Location: Reserve assets are stored in the RToken contract, not in the LendingPool itself

  2. Missing Transfer Step: The current implementation skips the critical step of transferring assets from RToken to LendingPool before vault deposition

  3. Failed State Transitions: This omission causes all operations dependent on liquidity rebalancing (LendingPool._rebalanceLiquidity) to fail due to insufficient balances

contract LendingPool is ILendingPool, Ownable, ReentrancyGuard, ERC721Holder, Pausable {
function _depositIntoVault(uint256 amount) internal {
@> IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
@> curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
function _rebalanceLiquidity() internal {
// if curve vault is not set, do nothing
if (address(curveVault) == address(0)) {
return;
}
uint256 totalDeposits = reserve.totalLiquidity; // Total liquidity in the system
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
@> uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
// Deposit excess into the Curve vault
@> _depositIntoVault(excess);
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
_withdrawFromVault(shortage);
}
emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);
}
}
library ReserveLibrary {
function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
// ...
// Transfer asset from caller to the RToken contract
@> IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender, // from
@> reserve.reserveRTokenAddress, // to
amount // amount
);
// Mint RToken to the depositor (scaling handled inside RToken)
(bool isFirstMint, uint256 amountScaled, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).mint(
address(this), // caller
depositor, // onBehalfOf
amount, // amount
reserve.liquidityIndex // index
);
// ...
}
}
contract RToken is ERC20, ERC20Permit, IRToken, Ownable {
function transferAsset(address user, uint256 amount) external override onlyReservePool {
IERC20(_assetAddress).safeTransfer(user, amount);
}
}

Impact

This vulnerability has critical protocol-wide consequence:

Complete Operational Failure:
All deposit/withdraw/borrow operations will revert when triggering liquidity rebalances, effectively freezing user funds and halting core protocol functionality.

Tools Used

Manual Review

Recommendations

Modify LendingPool._depositIntoVault to first transfer assets from RToken:

function _depositIntoVault(uint256 amount) internal {
+ 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.