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.
1.*Omission of Performance Fee Update:
The function computes the revenue split:
It updates the revenue shares for veRAAC holders:
However, the performanceFees
mapping is not updated, meaning the 20% performance fee is never recorded.
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:
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.
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.
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.
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.
Exploit Steps:
Step 1: Call the distributeRevenue
function with a non-zero amount.
Step 2: The function computes:
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:
Step 5: Verify that no reward tokens are transferred to the gauge and that the performanceFees
mapping remains unchanged.
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.
Manual review
Implement Actual Token Transfer:
Modify the _distributeToGauges
function to explicitly transfer reward tokens to each gauge before calling notifyRewardAmount
. For example:
This ensures that gauges actually receive the reward tokens corresponding to their share.
Update Performance Fees Accounting:
Update the distributeRevenue
function to record the performance fee by updating the performanceFees
mapping. For example:
(Or update the mapping as per the intended fee distribution logic.)
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.