Core Contracts

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

Improper update of rewards will allow new staker to immediately be eligible for rewards

Summary

The RAAC protocol has a staking feature through the gauges it provides. With every call, the rewards for a user who has staked are updated through the updateReward modifier.

The way a gauge accrues rewards is by keeping track of all the rewardPerToken accrued. If the current rewardPerToken is 10e18, whenever a new staker joins, 10e18 should be assigned to their state.rewardPerTokenPaid.

This ensures that gauges accurately track how many rewards have been accrued for each individual user.

However, a problem arises when a user makes their very first stake.


Vulnerability Details

For a new user, rewards are calculated first without taking into account the current rewardPerToken. This will make them immediatily eligible for rewards. Also if a user stakes four years after the contract is deployed, they will accrue all the rewards emitted during this entire timespan.

function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
state.rewards = earned(account); // This is done first, so rewardPerTokenPaid will be 0
state.rewardPerTokenPaid = rewardPerTokenStored;
state.lastUpdateTime = block.timestamp;
emit RewardUpdated(account, state.rewards);
}
}
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
@> (getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18 // @audit - rewardPerTokenPaid will be zero for the first staker
) + userStates[account].rewards;
}

Impact

  • Inflated user rewards – Users who stake late can unfairly claim rewards they did not earn.

  • Loss for the protocol – This results in unintended reward distribution, effectively draining funds.


Tools Used

  • Manual review


Recommendations

Add an if statement to check whether this is the user's first stake (state.rewardPerTokenPaid == 0).

  • If it is, calculate earned using the new rewardPerTokenStored.

  • If it is not, proceed with the current implementation.

Updates

Lead Judging Commences

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

BaseGauge::earned calculates rewards using getUserWeight instead of staked balances, potentially allowing users to claim rewards by gaining weight without proper reward checkpoint updates

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

BaseGauge::earned calculates rewards using getUserWeight instead of staked balances, potentially allowing users to claim rewards by gaining weight without proper reward checkpoint updates

Appeal created

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

BaseGauge::_updateReward calculates rewards before updating rewardPerTokenPaid, allowing new stakers to instantly claim accumulated rewards as if they had staked since contract deployment

Support

FAQs

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

Give us feedback!