Core Contracts

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

Stakers who stake at the end of the period receive the same rewards as those who stake at the beginning of the period

Summary

Users have the ability to stake some staking tokens in the Gauge contracts to be eligible for rewards. The problem is that late stakers are rewarded the same as early stakers leaving no incentive for locking tokens for an extended period of time. Users will just stake at the end of the period to claim the maximum amount of rewards.

Vulnerability Details

Users can stake tokens through the stake function in BaseGauge to be eligible for rewards. The problem is that late stakers and early stakers can earn the same amount of rewards. If we look in the earned function, it only uses the current reward per token amount. It does not keep a snapshot of when the staker staked within the period. They can come in and stake when reward per token is at its maximum amount at the end of the period and instantly be eligible for all rewards.

/**
* @notice Calculates earned rewards for account
* @param account Address to calculate earnings for
* @return Amount of rewards earned
*/
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

Impact

No incentive for staking longer durations

POC

The following demonstrates one staker staking at the beginning of the period and another right at the end. They both end up earning the same amount of rewards.

Add the following test to BaseGauge.test.js and run npx hardhat test test/unit/core/governance/gauges/BaseGauge.test.js

it("should demonstrate equal rewards for early and late stakers", async () => {
// User1 stakes at the start of the period
await baseGauge.connect(user1).stake(ethers.parseEther("100"));
// Move to almost end of period (6.9 days)
await time.increase(6.9 * DAY);
// User2 stakes same amount near end of period
await baseGauge.connect(user2).stake(ethers.parseEther("100"));
// Move past minimum claim interval
await time.increase(1.2 * DAY);
// Both users claim rewards
await baseGauge.connect(user1).getReward();
await baseGauge.connect(user2).getReward();
// Get final reward balances
const user1Rewards = await rewardToken.balanceOf(user1.address);
const user2Rewards = await rewardToken.balanceOf(user2.address);
// Calculate reward difference (should be significant but isn't)
const rewardDiff = user1Rewards - user2Rewards;
console.log("User1 rewards:", ethers.formatEther(user1Rewards));
console.log("User2 rewards:", ethers.formatEther(user2Rewards));
console.log("Reward difference:", ethers.formatEther(rewardDiff));
// Despite User1 staking for 6.9 days longer, rewards are nearly identical
expect(rewardDiff).to.be.lt(ethers.parseEther("0.1")); // Difference is negligible
});
});

Receive the following output

Reward Distribution Vulnerability
User1 rewards: 900.000000000000107142
User2 rewards: 900.000000000000107142
Reward difference: 0.0

Tools Used

Manual Review

Recommendations

Create snapshots for stakers and reward based on staking duration

Updates

Lead Judging Commences

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

BaseGauge reward system can be gamed through repeated stake/withdraw cycles without minimum staking periods, allowing users to earn disproportionate rewards vs long-term stakers

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

BaseGauge reward system can be gamed through repeated stake/withdraw cycles without minimum staking periods, allowing users to earn disproportionate rewards vs long-term stakers

Support

FAQs

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