The earned
function is intended to calculate the rewards a user has accrued based on the amount of tokens they have staked. However, it relies on the getUserWeight
function to determine the user's weight. Instead of reflecting the user's individual staked tokens, getUserWeight
returns the gauge’s weight as provided by the GaugeController. This means rewards are calculated based on a gauge-wide value (influenced by external voting) rather than on the user's actual stake, leading to incorrect and unfair reward distribution.
What Went Wrong:
The earned
function is defined as follows:
It uses getUserWeight(account) to determine the weight for reward calculation. However, getUserWeight is implemented as:
And _getBaseWeight returns:
This chain of function calls retrieves the gauge’s weight from the GaugeController, rather than a weight calculated from the individual user’s staked tokens. As a result, rewards are not distributed based on each user's actual stake, but instead are tied to the overall gauge weight determined by external voting.
Why It Matters:
The intended design is to distribute rewards in proportion to the tokens staked by each user, potentially modified by a boost factor. By using the gauge’s weight—which is uniform for all users of that gauge—the reward calculation fails to differentiate between users with different staking amounts. This flaw undermines the fairness of the reward mechanism and can lead to significant discrepancies in reward distribution.
Unfair Reward Distribution:
Users receive rewards based on the gauge’s weight rather than their individual stake. This can result in users with small stakes earning rewards comparable to those with larger stakes, or rewards being determined entirely by external gauge voting.
Governance Manipulation Risk:
Since the gauge’s weight is influenced by voting in the GaugeController, malicious actors could manipulate gauge weights to unfairly affect reward distribution, regardless of the actual staked amounts.
Economic Imbalance:
The discrepancy between intended and actual reward allocation can lead to an economic imbalance, potentially reducing trust in the protocol and affecting user participation.
Manual Code Review: We thoroughly examined the reward calculation logic in the BaseGauge contract, focusing on how earned, getUserWeight, and _getBaseWeight are implemented and interact with the GaugeController.
Incorporate User-Specific Stake Data:
Modify the getUserWeight function (or introduce a new function) to calculate the user's weight based on their actual staked tokens (e.g., using data from the user's balance in the gauge) combined with any boost factors, rather than solely relying on the gauge's weight.
Revise Reward Calculation Logic:
Ensure that the earned function reflects rewards in direct proportion to the user’s individual stake. For example, use the user’s staking balance (from a mapping such as _balances) to compute their share of the rewards.
Comprehensive Testing:
Develop unit and integration tests that simulate different staking scenarios to verify that rewards are accurately and fairly distributed according to individual user stakes.
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.