Core Contracts

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

Incorrect Handling of Withdrawn Reserve Tokens During Liquidity Shortages

Summary

A vulnerability exists in the LendingPool.sol contract, specifically in the _withdrawFromVault() function. When reserve tokens are withdrawn from the Curve Vault to address a liquidity shortfall, the withdrawn tokens are incorrectly kept in LendingPool.sol rather than being transferred to RToken.sol. This causes liquidity checks (e.g., during withdrawals or borrow operations) to incorrectly revert transactions due to perceived insufficient liquidity in RToken.sol.

Vulnerability Details

When a shortfall in the reserve buffer is detected, the following logic is triggered in _rebalanceLiquidity():

LendingPool.sol#L786-L790

} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
_withdrawFromVault(shortage);
}

Similarly, sufficient liquidity is made sure available for withdrawals or borrowing via _ensureLiquidity():

LendingPool.sol#L762-L766

if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// Withdraw required amount from the Curve vault
_withdrawFromVault(requiredAmount);
}

When _withdrawFromVault() function is triggered,

LendingPool.sol#L809-L812

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

here's the problem:

  • The withdrawn reserve tokens are transferred to LendingPool.sol, address(this) instead of RToken.sol.

  • As a result, the RToken.sol balance does not reflect the updated liquidity, causing operations that depend on available liquidity checks to fail unnecessarily.

Impact

  • Users calling withdraw() will encounter DoS when RToken.burn() is attempting to make the following transfer:

RToken.sol#L178-L180

if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
  • Users calling borrow() will encounter DoS when attempting to make the following transfer:

LendingPool.sol#L355-L356

// Transfer borrowed amount to user
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
  • Additionally, users calling deposit() will have the withdrawn reserve assets tokens permanently stuck in Lending.sol as _rebalanceLiquidity() is also invoked at the end of its function logic.

Tools Used

Manual

Recommendations

Consider making the following refactoring:

LendingPool.sol#L809-L812

function _withdrawFromVault(uint256 amount) internal {
curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));
totalVaultDeposits -= amount;
+ // Transfer withdrawn tokens to RToken.sol
+ IERC20(reserve.reserveAssetAddress).safeTransfer(reserve.reserveRTokenAddress, amount);
}
Updates

Lead Judging Commences

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