Core Contracts

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

RToken transfer function locks user deposits in StabilityPool

Summary

The StabilityPool.sol code assumes the current implementation of the transfer() function in RToken.sol behaves like a regular ERC20 transfer function which is not the case. This assumption leads to permanently locking funds from lenders who have deposited in the StabilityPool.

Vulnerability Details

The function withdraw() converts a user's DETokens 1:1 with RTokens and then sends the RTokens to the user via rToken.safeTransfer(msg.sender, rcrvUSDAmount);

function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
_update();
if (deToken.balanceOf(msg.sender) < deCRVUSDAmount) revert InsufficientBalance();
uint256 rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount);
uint256 raacRewards = calculateRaacRewards(msg.sender);
if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
userDeposits[msg.sender] -= rcrvUSDAmount;
if (userDeposits[msg.sender] == 0) {
delete userDeposits[msg.sender];
}
deToken.burn(msg.sender, deCRVUSDAmount);
rToken.safeTransfer(msg.sender, rcrvUSDAmount);
if (raacRewards > 0) {
raacToken.safeTransfer(msg.sender, raacRewards);
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}

There is an issue with how the transfer() function in RToken.sol is defined.

function transfer(address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
return super.transfer(recipient, scaledAmount);
}

The function first descales the amount by ILendingPool(_reservePool).getNormalizedIncome() which returns reserve.liquidityIndex.

Example:
reserve.liquidityIndex = 4

  1. A user has deposited 100 RTokensand has received 100 DETokens.

  2. The user wishes to withdraw all of his tokens and calls withdraw(100)

  3. rToken.safeTransfer(user, 100); is called => the transfer function divides the amount (100) by the index (4): 100 / 4 = 25 RTokens, 25 tokens are send back to the user.

Impact

It is impossible for users to withdraw all of their deposits in the StabilityPool leading to a loss of funds for the users.

Tools Used

manual review

Recommendations

Remove the logic for descaling the provided amount from the overridden transfer() function. Make it behave like a regular ERC20 function.

Updates

Lead Judging Commences

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

StabilityPool's userDeposits mapping doesn't update with DEToken transfers or interest accrual, and this combined with RToken transfers causes fund loss and permanent lockup

Support

FAQs

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