Core Contracts

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

`_depositIntoVault` and `_withdrawFromVault` won't work because there are no tokens inside the contract

Summary

_depositIntoVault and _withdrawFromVault won't work because there are no tokens inside the contract

Vulnerability Details

When we have some funds inside the contract we call _depositIntoVault to deposit them to crv, so they will act as liquidity and provide an APY. Where we can clearly see that this contract is gonna be the receiver of the LP.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L799

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

We can also withdraw them if we need to, where we are the ones receiving the real token

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L809

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

The issue we face is inside withdraw where if we want to withdraw but there are not enough funds we call _ensureLiquidity which withdraws from the crv vault to this contract.

function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (withdrawalsPaused) revert WithdrawalsArePaused();
// Update the reserve state before the withdrawal
ReserveLibrary.updateReserveState(reserve, rateData);
_ensureLiquidity(amount);

However if you are familiar with how withdraw you will know that withdraw itself doesn't send us any funds, but the rToken does inside it's burn:

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/tokens/RToken.sol#L154

function burn( ... ) external override onlyReservePool returns (uint256, uint256, uint256) {
// ...
_burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amount, totalSupply(), amount);
}

If the rToken doesn't have enough funds we would withdraw from the curve pool, but we would withdraw not to rToken which sends us tokens, but to the pool. This means that the pool will still be insufficient.

Note that we still don't send tokens to the pool and so the pool won't be able to deposit them in the first place due to deposit sending all of the tokens to reserveRTokenAddress, leaving the pool empty with nothing to deposit.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/libraries/pools/ReserveLibrary.sol#L323

function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
if (amount < 1) revert InvalidAmount();
// Update reserve interests
updateReserveInterests(reserve, rateData);
IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender, // from
reserve.reserveRTokenAddress, // to
amount // amount
);

Impact

Tools Used

Manual review

Recommendations

Move both functions to rToken so it will be able to deposit them and will be able to withdraw the LP from the pool in case it needs more base tokens.

Updates

Lead Judging Commences

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