Core Contracts

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

Malicious Stakers can drain RAACToken rewards from the StabilityPool.

Summary

Stakers who stakes RToken in the StabilityPool receives DETokens which are 1:1 representation of their stake. The more they stake the more RAACTokens they can claim when its distrubuted to the StabilityPool. However, the current implementation of rewards claiming allows malicious stakers to drain the RAACToken rewards.

Vulnerability Details

How much a user is entitiled to gain is calculated in StabilityPool.sol::calculateRAACrewards(), which returns a portion of the RAACToken balance of the Pool proportional to the users staked amount / total staked amount.

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

The rewards are transferred to the users during the unstaking process in 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);
}

However a malicious users can simply withdraw 1 deToken again and again, and drain the rewards.

Lets walkthrough and example,

  1. The pool has 100e18 rTokens staked.

  2. The RAACToken balance of the pool is 1000e18.

  3. The malicious staker has 10e18 rTokens staked. (entitled to 10% of the rewards)

Attack Path.

  1. The malicious user calls withdraw() with 1 deCRVUSDAmount is an input.

  2. The calculateRAACrewards() will return 100e18 amount of RAACTokens.

  3. 1 deToken will be burned from the malicious user and he will gain 100e18 RAACTokens.

  4. Since there is no tracking of claimed rewards, nor the distributed rewards are tracked internally, he can just repeat this process to drain the whole RAACToken balance of the Pool.

Impact

Any stakers can drain RAAC rewards from the StabilityPool

Tools Used

Manual Review

Recommendations

Implement a reward tracking mechanism to track the distributed rewards internally, track the rewards claimed by the users, and prevent them from claiming their entitled amount twice.

Updates

Lead Judging Commences

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

StabilityPool::withdraw can be called with partial amounts, but it always send the full rewards

Support

FAQs

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

Give us feedback!