Core Contracts

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

Unfair Reward Allocation in `calculateRaacRewards` Affects Withdrawals and Reward Claims

Description

The calculateRaacRewards function is used in both withdraw and getPendingRewards to determine a user's RAAC token rewards. However, it fails to account for deposit timing, allowing users to manipulate withdrawals to maximize rewards unfairly.

Affected Code

Function:calculateRaacRewards

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/StabilityPool/StabilityPool.sol#L246-L259

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

Usage Context

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/StabilityPool/StabilityPool.sol#L220-L244

/**
* @notice Allows a user to withdraw their rToken and RAAC rewards.
* @param deCRVUSDAmount Amount of deToken to redeem.
*/
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);
}
  • withdraw(uint256 deCRVUSDAmount): Calls calculateRaacRewards(msg.sender) before transferring RAAC rewards to the user. A user can deposit shortly before withdrawal to increase their reward share.

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/StabilityPool/StabilityPool.sol#L261-L268

/**
* @notice Gets the pending RAAC rewards for a user.
* @param user Address of the user.
* @return Amount of pending RAAC rewards.
*/
function getPendingRewards(address user) external view returns (uint256) {
return calculateRaacRewards(user);
}
  • getPendingRewards(address user): Relies on calculateRaacRewards(user) to show pending rewards, which can be misleading due to the lack of deposit duration tracking.

Vulnerability Details

  • Exploitable Reward Calculation: Users can deposit large amounts right before calling withdraw to claim disproportionately high RAAC rewards, then withdraw immediately.

  • No Reward Vesting or Duration Tracking: Rewards are allocated solely based on the snapshot of deposits at the time of calculation, rather than over a fair duration.

  • Unfair to Long-Term Depositors: Users who maintain deposits for a longer time may receive fewer rewards compared to those who game the system by depositing just before withdrawal.

Tools Used

Manual Review

Recommended Mitigation Steps

  1. Implement Time-Weighted Rewards: Modify calculateRaacRewards to account for how long a deposit has been in the pool, rather than just the deposit size at the time of calculation.

  2. Introduce a Vesting Period: Ensure rewards accrue gradually over time, preventing instant reward claims from short-term deposits.

Updates

Lead Judging Commences

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

Give us feedback!