Core Contracts

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

Depositor can unfairly earn the same as old staker

Summary

Depositors can unfairly earn rewards equivalent to long-term stakers because the earned function relies on veToken.balanceOf().

Vulnerability Details

  • The earned function calculates a depositor’s share of rewards and is invoked whenever the `updateReward modifier is called.

  • For new depositors, state.rewardPerTokenPaid is initialized to 0. This allows them to claim rewards starting from the full accumulated getRewardPerToken value (equivalent to the oldest depositor’s initial state).

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);
}
}
...
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

Suppose two depositors, A (old) and B (new), have identical getUserWeight.

For depositor B (new):
userStates[B].rewardPerTokenPaid = 0

For depositor A (old):
userStates[A].rewardPerTokenPaid = rewardPerTokenStored (updated value)
Both depositors end up receiving the same reward amount, which violates expectations.

  • the reason behind the issue is earned function rely on getUserWeight to calculate user commission from rewards. (which is the same as A user have during the first depositing by B user)

function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

Impact

  • loss of profit from staking by depositors.

Tools Used

Manual audit

Recommendations

To calculate user rewards accurately, you should likely reference _balances[account] to ensure the correct depositor’s balance is used.

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!