The function distributeRewards() allows users to trigger reward distribution manually, which introduces a front-running risk where users can manipulate emissions timing to their benefit.
The function does not verify when the last reward distribution occurred:
function distributeRewards(address gauge) external override nonReentrant whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (!gauges[gauge].isActive) revert GaugeNotActive();
uint256 reward = _calculateReward(gauge);
if (reward == 0) return;
IGauge(gauge).notifyRewardAmount(reward);
emit RewardDistributed(gauge, msg.sender, reward); }
Why is this a problem?
Users can monitor on-chain data to detect when rewards will be distributed.
Right before rewards are distributed, a user can stake more tokens into a gauge, increasing their share of emissions.
Since the contract does not track prior snapshots, the new staked tokens receive unfairly high rewards, diluting honest participants.
Example Attack
Attacker monitors mempool and waits for distributeRewards() to be called.
They quickly stake a large amount in a gauge right before the function executes.
When rewards are distributed, they receive disproportionate rewards.
After receiving rewards, they unstake their excess tokens.
The attack is repeated in every distribution cycle.
Attackers can drain emissions unfairly, reducing protocol efficiency.
Legitimate users earn fewer rewards, creating an incentive misalignment.
Staking System Manipulation:
Attackers can time their deposits and withdrawals to maximize reward capture.
This makes staking less predictable and fair.
Manual Code Review
Implement a Time-Based Reward Locking Mechanism
Introduce a mandatory waiting period before new stakes receive full rewards.
Fix: Add a Reward Lockup Period
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.