Core Contracts

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

Repeated Reward Draining via Cyclic Deposits and Withdrawals in Stability Pool

Summary

This vulnerability in the Stability Pool allows users to drain RAAC rewards by repeatedly depositing and withdrawing RTokens. Since the contract does not track already claimed rewards, users can withdraw rewards multiple times by performing cyclic deposits and withdrawals, eventually draining the entire RAAC token balance.

Vulnerability Details

Iam explaining the flow of complete exploit.

1.User Deposits RToken

  • The user calls the StabilityPool::deposit function, transferring amount of RToken to the contract.

  • The contract mints an equivalent amount of deCRVUSD tokens based on the exchange rate.

  • The deposited amount is recorded in userDeposits[msg.sender].

  • StabilityPool::_mintRAACRewards() is called, which mints new RAAC rewards and sends to Stability Pool Contract.
    StabilityPool::deposit:

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

StabilityPool::_mintRAACRewards:

function _mintRAACRewards() internal {
if (address(raacMinter) != address(0)) {
@> raacMinter.tick();
}
}

2.Rewards Accumulate Over Time

  • As time passes, the protocol accumulates RAAC rewards inside the contract.

  • These rewards are meant to be distributed among users proportionally based on their deposits.

3.User Withdraws Deposited RToken and Claims Rewards

  • The user calls the withdraw function to withdraw their deposit.

  • The contract calculates the amount of RToken to be returned using calculateRcrvUSDAmount().

  • The contract also calculates RAAC rewards using calculateRaacRewards() which follows the formula: (totalRewards * userDeposit) / totalDeposits
    StabilityPool::withdraw:

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

StabilityPool::calculateRaacRewards:

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

4.User Repeats the Process to Drain RAAC Rewards

  • The user immediately redeposits the same amount of RToken.

  • Since calculateRaacRewards() does not track previously distributed rewards, the user can withdraw again and receive additional RAAC rewards.

  • By continuously repeating this deposit-withdraw cycle, the user can drain all RAAC rewards from the contract.

5.Consider two users who each deposit 20 RToken, making the total deposits 40. Over time, the contract accumulates 30 RAAC tokens as rewards. When the first user withdraws, they receive 15 RAAC (since they had 50% of the total deposits). If they redeposit and withdraw again, the remaining 15 RAAC is now split, so they get 7.5 more. By repeating this process, they can keep reducing the available rewards while continuously claiming a portion, eventually draining the entire RAAC balance from the contract.

Impact

  1. Full depletion of RAAC rewards: Malicious users can drain all rewards, leaving legitimate users with nothing.

  2. Unfair distribution of rewards: Users who execute this attack early can extract all rewards unfairly.

  3. Severe financial loss: The Stability Pool may lose all incentive mechanisms, damaging user trust.

Tools Used

Manual

Recommendations

  1. Track Already Claimed Rewards and also keep time lock sort of mechanism.

Updates

Lead Judging Commences

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

StabilityPool::calculateRaacRewards is vulnerable to just in time deposits

Support

FAQs

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