Core Contracts

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

Attacker can drain RAAC reward from stability pool

Summary

The user deposits rToken to stability pool and receive RAAC token as reward. Malicious user can drain RAAC rewards of the pool by invoking StabilityPool.withdraw() with tiny withdrawal amounts multiple times.

Vulnerability Details

In the StabilityPool.withdraw() function, it calculates the amount of rewards which user will receive with calculateRaacRewards(msg.sender).

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

In StabilityPool.calculateRaacRewards() function, it calculates the amount of reward with the total deposit amount of user, not withdrawal amount: (totalRewards * userDeposit) / totalDeposits. This allows user to receive reward for total deposit amount regardless to withdraw amount - deCRVUSDAmount.

Therefore, attacker can invoke withdraw() with tiny amount of deCRVUSDAmount multiple times and will receive full reward for total deposit amount every time.

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

Attack path:

  • Attacker deposits certain amount of rToken to stability pool.

  • Attacker invokes withdraw() with tiny withdrawal amount several times.

  • Each time, he will receive rewards for total deposit amount and will drain the reward of the pool.

Impact

The RAAC reward of the stability pool can be drained.

Tools Used

Manual Review

Recommendations

Add param withdraw amount to the StabilityPool.calculateRaacRewards() function and calculates the reward amount based on withdraw amount.

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);
+ uint256 raacRewards = calculateRaacRewards(msg.sender, deCRVUSDAmount);
if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
userDeposits[msg.sender] -= rcrvUSDAmount;
...
}
- function calculateRaacRewards(address user) public view returns (uint256) {
+ function calculateRaacRewards(address user, uint256 amount) public view returns (uint256) {
...
}
Updates

Lead Judging Commences

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