Core Contracts

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

Double accounting in `StabilityPool` and `deToken` may lead to locked funds

Summary

A double, desynchronized deposit accounting implementation in StabilityPool and deToken may lead to users losses and locked rTokens.

Vulnerability Details

On StabilityPool::deposit the amount deposited is minted as deToken and saved in userDeposit mapping.

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

On withdraw the opposite is done: burning of deToken and subtracting from userDeposits

function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
...
@> userDeposits[msg.sender] -= rcrvUSDAmount;
if (userDeposits[msg.sender] == 0) {
delete userDeposits[msg.sender];
}
@> deToken.burn(msg.sender, deCRVUSDAmount);
rToken.safeTransfer(msg.sender, rcrvUSDAmount);

The only purpose of userDeposits mapping is to be used to calculated users RAAC rewards:

function calculateRaacRewards(address user) public view returns (uint256) {
@> uint256 userDeposit = userDeposits[user];
uint256 totalDeposits = deToken.totalSupply();
uint256 totalRewards = raacToken.balanceOf(address(this));
if (totalDeposits < 1e6) return 0;
@> return (totalRewards * userDeposit) / totalDeposits;
}

deToken is intended to be transferable. There's no locking mechanism added to transfer / transferFrom/ burn.

An user may obtain deToken from other means than depositing to StabilityPool. He may think he can redeem deToken for rToken at any time and that he accrue RAAC rewards and decide to hold the token.

When he calls withdraw to get back rToken and raacRewards, the transaction reverts with underflow panic error because his userDeposits amount is 0.

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;

Impact

Some deToken holders may not be able to withdraw their rToken and accumulated rewards.

Tools Used

Recommendations

Remove userDeposits mapping. Calculate raac rewards using user's balanceOf deToken instead.

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.