Core Contracts

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

`LendingPool:_withdrawFromVault` will fail because we are using `msg.sender` as owner instead of `address(this)`.

Summary

when lending pool doesn’t have a sufficient amount for user withdrawal, it will try to get some token back from curve vault, using _withdrawFromVault but this function sending msg.sender instead of lendingPool as owner of the shares and this will revert because user doesn't have any shares to burn.

Vulnerability Details

When user deposit crvUsd to lending pool, some percentage that crvUSD is deposited to curve vault for extra yield. The deposit is handled by _rebalanceLiquidity which is called from deposit

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

if you look at the depositIntoVault the second parameter in curveVault:deposit is the recipient of shares, which as you see is set to address(this) or lendingPool itself.

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

Now when a user tries to withdraw their crvUSD from lending pool the _ensureLiquidity is called to make sure there is enough liquidity in the contract if not then it should be withdrawn from curveVault

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L249

/**
* @notice Allows a user to withdraw reserve assets by burning RTokens
* @param amount The amount of reserve assets to withdraw
*/
function withdraw(
uint256 amount
) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (withdrawalsPaused) revert WithdrawalsArePaused();
// Update the reserve state before the withdrawal
ReserveLibrary.updateReserveState(reserve, rateData);
// Ensure sufficient liquidity is available
-@> _ensureLiquidity(amount);
// Perform the withdrawal through ReserveLibrary
(
uint256 amountWithdrawn,
uint256 amountScaled,
uint256 amountUnderlying
) = ReserveLibrary.withdraw(
reserve, // ReserveData storage
rateData, // ReserveRateData storage
amount, // Amount to withdraw
msg.sender // Recipient
);
// Rebalance liquidity after withdrawal
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}

_ensureLiquidity will call _withdrawFromVault if contract doesn’t have enough to cover the withdraw request

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

/**
* @notice Internal function to withdraw liquidity from the Curve vault
* @param amount The amount to withdraw
*/
function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(
amount,
address(this),
msg.sender,
0,
new address[](0)
);
totalVaultDeposits -= amount;
}

third parameter of the curveVault:withdraw function is the owner of the shares, we can see the code is providing msg.sender as an owner, which is wrong because we know from the deposit flow that shares are owned by the lending pool, not the msg.sender.

When a curveVault tries to check for allowance of token to lendingPool from user then the transaction will revert.

impact

_withdrawFromVault will revert because user don’t have that many shares, even if he does have shares from is external deposit to curveVault, because he has not approved the lendingPool to use that, the transaction will revert.

The worst case is that if the user has approved his own shares to the lending pool then he will lose it.

Recommendation

user address(this) as a owner when withdrawing instead of msg.sender

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_withdrawFromVault incorrectly uses msg.sender instead of address(this) as the owner parameter, causing vault withdrawals to fail

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_withdrawFromVault incorrectly uses msg.sender instead of address(this) as the owner parameter, causing vault withdrawals to fail

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!