Core Contracts

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

Revenue distribution function fails to update performance fees and transfer actual rewards in GaugeController

Summary

The distributeRevenue function is intended to split incoming revenue between veRAACToken holders (80%) and performance fees (20%) and then distribute rewards to gauges based on their type. However, the function fails to update the performanceFees mapping for the performance fee portion, and the internal _distributeToGauges function merely calls notifyRewardAmount—which only updates gauge state—without transferring any reward tokens. As a result, gauges receive misleading state updates while the reward tokens remain locked in the GaugeController contract, and the performance fee allocation remains untracked.

Vulnerability Details

1.*Omission of Performance Fee Update:

  • The function computes the revenue split:

    uint256 veRAACShare = amount * 80 / 100; // 80% to veRAAC holders
    uint256 performanceShare = amount * 20 / 100; // 20% performance fee
  • It updates the revenue shares for veRAAC holders:

    revenueShares[gaugeType] += veRAACShare;
  • However, the performanceFees mapping is not updated, meaning the 20% performance fee is never recorded.

  1. Failure to Transfer Actual Reward Tokens:

    • The revenue split intended for gauges is distributed via the _distributeToGauges function. Within this function, rewards are allocated by calculating each gauge’s share and then calling:

      IGauge(gauge).notifyRewardAmount(gaugeShare);
    • The notifyRewardAmount function only updates the gauge’s internal state (e.g., reward rate, period state) and does not perform any token transfer. Therefore, while gauges appear to have been credited with rewards, the actual reward tokens remain in the GaugeController contract.

  2. Misleading Behavior:

    • Although the function’s comments state that it "Calculates and transfers rewards based on gauge weight," the implementation only modifies internal state without transferring tokens.

    • This discrepancy results in gauges showing updated reward amounts on-chain while no tokens are actually made available for distribution.

Impact

  • False Reward Allocation:
    Gauges may display an updated reward state, suggesting that rewards have been allocated, but the reward tokens remain untransferred in the GaugeController contract. Users expecting rewards may be misled.

  • Untracked Performance Fees:
    The failure to update the performanceFees mapping means that the performance fee portion of the revenue (20%) is not accounted for, potentially leading to financial discrepancies and misallocation of funds.

Proof-of-Concept (PoC)

  1. Scenario Setup:

    • Deploy the GaugeController contract along with its dependencies, including the reward token, veRAACToken, and a gauge contract implementing the IGauge interface.

    • Ensure the gauge is registered (i.e., isGauge(gauge) returns true) and marked as active.

  2. Exploit Steps:

    • Step 1: Call the distributeRevenue function with a non-zero amount.

    • Step 2: The function computes:

      uint256 veRAACShare = amount * 80 / 100;
      uint256 performanceShare = amount * 20 / 100;
    • Step 3: The function updates revenueShares[gaugeType] with the veRAACShare but does not update performanceFees.

    • Step 4: _distributeToGauges is invoked, iterating over active gauges and calling:

      IGauge(gauge).notifyRewardAmount(gaugeShare);
    • Step 5: Verify that no reward tokens are transferred to the gauge and that the performanceFees mapping remains unchanged.

  3. Observation:

    • The gauge’s internal state indicates it has been notified of a reward amount.

    • A check of token balances reveals that the reward tokens remain in the GaugeController contract.

    • The performance fee portion is untracked, confirming the vulnerability.

Tools Used

Manual review

Recommendations

  1. Implement Actual Token Transfer:

    Modify the _distributeToGauges function to explicitly transfer reward tokens to each gauge before calling notifyRewardAmount. For example:

    for (uint256 i = 0; i < _gaugeList.length; i++) {
    address gauge = _gaugeList[i];
    if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
    uint256 gaugeShare = (amount * gaugeWeights[i]) / totalTypeWeight;
    if (gaugeShare > 0) {
    // Transfer reward tokens to the gauge
    rewardToken.safeTransfer(gauge, gaugeShare);
    // Notify the gauge of the transferred reward
    IGauge(gauge).notifyRewardAmount(gaugeShare);
    }
    }
    }

    This ensures that gauges actually receive the reward tokens corresponding to their share.

  2. Update Performance Fees Accounting:

    Update the distributeRevenue function to record the performance fee by updating the performanceFees mapping. For example:

    performanceFees[msg.sender] += performanceShare;

    (Or update the mapping as per the intended fee distribution logic.)

Updates

Lead Judging Commences

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

GaugeController notifies gauges of rewards without transferring tokens in both distributeRewards and _distributeToGauges functions, breaking reward distribution

GaugeController.distributeRevenue calculates 20% performance fee but never transfers or allocates it to any recipient, causing loss of funds

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

GaugeController notifies gauges of rewards without transferring tokens in both distributeRewards and _distributeToGauges functions, breaking reward distribution

GaugeController.distributeRevenue calculates 20% performance fee but never transfers or allocates it to any recipient, causing loss of funds

Support

FAQs

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