Core Contracts

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

Users can't withdraw total deposited amount of rToken in stability pool

Summary

The users deposit rToken to stability pool and receive RAAC reward while withdrawing. Althouth RToken.transferfrom() function uses normalized amount as its param, both StabilityPool.deposit() and StabilityPool.withdraw() use scaled amount of rToken for deposit and withdraw. Therefore, depositors can't withdraw total deposited rToken in the pool.

Vulnerability Details

Both StabilityPool.deposit() and StabilityPool.withdraw() use scaled amount of rToken for deposit and withdraw.

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
@> rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
@> userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}
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);
}

However, RToken.transferfrom() receives normalized amount as its param. Therefore, the scaled amount of the deposited amount at withdraw timestamp will be less than the amount at deposit timestamp and users can't withdraw total deposited rToken in the pool.

function transferFrom(address sender, address recipient, uint256 amount) public override(ERC20, IERC20) returns (bool) {
uint256 scaledAmount = amount.rayDiv(_liquidityIndex);
return super.transferFrom(sender, recipient, scaledAmount);
}

Impact

The users can't withdraw total deposited amount of rToken in stability pool.

Tools Used

Manual Review

Recommendations

Use normalized amount for deposit() and withdraw() in stability pool.

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
+ lendingPool.updateState();
+ normalizedAmount = amount.rayMul(lendingPool.getNormalizedIncome());
+ rToken.safeTransferFrom(msg.sender, address(this), normalizedAmount);
- rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}
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);
+ lendingPool.updateState();
+ normalizedAmount = rcrvUSDAmount.rayMul(lendingPool.getNormalizedIncome());
+ rToken.safeTransfer(msg.sender, normalizedAmount);
- rToken.safeTransfer(msg.sender, rcrvUSDAmount);
if (raacRewards > 0) {
raacToken.safeTransfer(msg.sender, raacRewards);
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}
Updates

Lead Judging Commences

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