Summary
The BaseGauge contract implements time-weighted reward distribution to stakers but it is not used when users claim rewards.
Vulnerability Details
From RAAC documentation:
Boost multipliers affect reward earnings
Time-weighted averages prevent manipulation
To claim rewards the user has to call getReward function.
function getReward() external virtual nonReentrant whenNotPaused updateReward(msg.sender) {
if (block.timestamp - lastClaimTime[msg.sender] < MIN_CLAIM_INTERVAL) {
revert ClaimTooFrequent();
}
lastClaimTime[msg.sender] = block.timestamp;
UserState storage state = userStates[msg.sender];
uint256 reward = state.rewards;
if (reward > 0) {
state.rewards = 0;
uint256 balance = rewardToken.balanceOf(address(this));
if (reward > balance) {
revert InsufficientBalance();
}
rewardToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
This function has updateReward modifier which calculates rewards for user. As we can see the created periods are not used in rewards calculation.
modifier updateReward(address account) {
_updateReward(account);
_;
}
function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
state.rewards = earned(account);
state.rewardPerTokenPaid = rewardPerTokenStored;
state.lastUpdateTime = block.timestamp;
emit RewardUpdated(account, state.rewards);
}
}
Rewards are calculated based on time staked and rewardPerTokenStored but not on created periods.
Impact
Rewards should be calculated based on created periods however they are distributed based on time staked and rewardPerTokenStored.
Tools Used
Manual Review, Hardhat
Recommendations
Change the rewards calculation function so that rewards are based on implemented periods.