Core Contracts

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

Gauge Reward Distribution Exploit (Front-Running Risk) in GaugeController.sol

Summary

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.

Vulnerability Details

Issue: Unrestricted Reward Distribution Timing

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

    1. Attacker monitors mempool and waits for distributeRewards() to be called.

    2. They quickly stake a large amount in a gauge right before the function executes.

    3. When rewards are distributed, they receive disproportionate rewards.

    4. After receiving rewards, they unstake their excess tokens.

    5. The attack is repeated in every distribution cycle.

Impact: High

  • 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.

Tools Used

Manual Code Review

Recommendations

  1. Implement a Time-Based Reward Locking Mechanism

  • Introduce a mandatory waiting period before new stakes receive full rewards.

    Fix: Add a Reward Lockup Period

    uint256 public constant REWARD\_LOCK\_PERIOD = 7 days;
    mapping(address => uint256) public lastStakeTime
    function stake(address gauge, uint256 amount) external nonReentrant whenNotPaused {
    require(amount > 0, "Cannot stake zero");
    lastStakeTime[msg.sender] = block.timestamp;
    // Transfer and record stake
    IERC20(veRAACToken).safeTransferFrom(msg.sender, address(this), amount);
    emit Staked(msg.sender, gauge, amount);
    }
    2. Modify distributeRewards() to Ignore Recently Staked Users:
    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;
    // Ensure stakers within lock period do not receive rewards
    for (uint256 i = 0; i < stakers[gauge].length; i++) {
    if (block.timestamp - lastStakeTime[stakers[gauge][i]] < REWARD_LOCK_PERIOD) {
    continue; // Skip new stakers
    }
    IGauge(gauge).notifyRewardAmount(reward);
    }
    emit RewardDistributed(gauge, msg.sender, reward);
    }
Updates

Lead Judging Commences

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

GaugeController's distributeRewards lacks time-tracking, allowing attackers to repeatedly distribute full period rewards until hitting emission caps

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

GaugeController's distributeRewards lacks time-tracking, allowing attackers to repeatedly distribute full period rewards until hitting emission caps

Support

FAQs

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