Core Contracts

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

Unstaked Users Can Earn Rewards Due to Improper Boost Calculation

Summary

The BaseGauge contract contains a vulnerability where users can earn rewards without staking, minting, or performing any legitimate action. This issue arises from how the _applyBoost function calculates the boost, leading to unintended reward distributions even to users with no stake.

Affected code: BaseGauge::earned

Vulnerability Details

The earned function calculates rewards using the getUserWeight function, which in turn calls _applyBoost. The boost calculation incorrectly applies the minBoost value to users with zero balance. Since minBoost is set to 1e18, the calculation still applies a base weight, resulting in rewards being distributed to non-stakers.

Since boost is always at least minBoost, users without stake can still receive rewards, as getUserWeight returns a nonzero value.

Reward Calculation Flow

The earned function calls getUserWeight to determine the user's weight in the reward distribution.

getUserWeight calculates the base weight and applies a boost using the _applyBoost function.

The _applyBoost function checks the user's balance of veTokens. Since the balance is zero, it calculates the boost using the calculateBoost function.

The calculateBoost function returns the minBoost value (1e18) when the user's balance is zero, ensuring that the base weight is applied without any reduction.

Impact of minBoost:

Since minBoost is set to 1e18, the base weight is multiplied by 1e18 and divided by 1e18 in the _applyBoost function, effectively returning the base weight unchanged.

This means that even users with no staked tokens receive rewards based on the base weight of the gauge.

POC

Paste the following into BaseGauge.test.js to demonstrate the issue:

describe("Reward Distribution", () => {
beforeEach(async () => {
// Setup rewards
await rewardToken.mint(await baseGauge.getAddress(), ethers.parseEther("10000"));
// Set emission cap before notifying rewards
await baseGauge.setEmission(ethers.parseEther("10000"));
// Set initial weights and vote to enable rewards
await gaugeController.connect(user1).vote(await baseGauge.getAddress(), 5000);
// Stake tokens for user1
await rewardToken.mint(user1.address, ethers.parseEther("1000"));
await rewardToken.connect(user1).approve(await baseGauge.getAddress(), ethers.parseEther("1000"));
await baseGauge.connect(user1).stake(ethers.parseEther("1000"));
// Notify rewards after staking
await baseGauge.notifyRewardAmount(ethers.parseEther("1000"));
// Move time forward by 7 days to allow rewards to accrue
await time.increase(7 * DAY);
});
it.only("should calculate earned rewards", async () => {
const user3 = (await ethers.getSigners())[3];
const earned2 = await baseGauge.earned(user3.address);
const earned = await baseGauge.earned(user1.address);
expect(earned).to.be.gt(0);
console.log("total supply", (await baseGauge.totalSupply()).toString());
console.log("user 1 earned", earned.toString());
console.log("user 3 earned", earned2.toString());
});
});

Impact

  • Users can receive rewards without participating in the staking process.

  • This can lead to an unfair distribution of rewards and financial losses for legitimate stakers.

  • Attackers can repeatedly claim rewards with different accounts without any stake, leading to fund depletion.

Tools Used

  • Manual Review

Recommendations

  1. Ensure Base Weight is Nonzero: Modify _applyBoost to only apply a boost if the user's balance is greater than zero.

  2. Require Staking Before Earning: Enforce a check in getUserWeight that prevents non-stakers from receiving rewards.

  3. Explicitly Verify Stake Existence: Add validation in earned to confirm that the user has an actual stake before calculating rewards.

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

Support

FAQs

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

Give us feedback!