Core Contracts

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

Core functions will be DoS'ed

Summary

When a user deposits, withdraws or borrows through the LendingPool the liquidity is rebalanced, by either depositing the difference of the desired buffer (which is 20% of all deposits) and the current buffer (crvUSD balance of the RToken contract), or withrdawing the shortage between the two buffers from a Curve crvUSD vault. However everytime assets are tried to be withdrawn from the Curve vault the call will revert.

Vulnerability Details

Everytime there is a deposit, withdraw or borrow call the internal LendingPool::_ensureLiquidity(), _rebalanceLiquidity() are invoked. Ensure liquidty is to withdraw the required assets from the Curve vault, rebalance liquidity also withdraws from the Curve vault, but only if the targeted buffer is more than the current buffer:

function _ensureLiquidity(uint256 amount) internal {
...
uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// Withdraw required amount from the Curve vault
@> _withdrawFromVault(requiredAmount);
}
}
function _rebalanceLiquidity() internal {
...
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
// Withdraw shortage from the Curve vault
@> _withdrawFromVault(shortage);
}

Now let's look at the problem part:

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

Now if we look at the vault's withdraw() implementation, address(this) is specified as receiver and msg.sender is specified as owner of the shares. We can see that it will try to burn the msg.sender's shares and transfer the underlying assets to address(this). In this case the caller is the msg.sender, which is the user, however the user doesn't hold any shares. The shares are minted to the Lending pool (address(this)):

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

As can be seen in the code above, address(this) is specified as the shares receiver. Here is a link to the Curve vault's deposit() function. Since all the shares are minted to the Lending Pool, that means all withrawals from the Curve vault will revert everytime.

Impact

  • Medium, DoS of core functions like deposit, withdraw, borrow

Tools Used

Manual Review

Recommendations

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

Lead Judging Commences

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