A critical vulnerability exists in the BaseGauge
contract that allows users who have never staked any tokens to claim rewards. This issue arises because the reward calculation in the earned
function is based on the user's weight, which can be non-zero even if the user has not staked any tokens. The reward per token is calculated based on the total staked amount in the contract, but the earned
function does not verify whether the user has actually staked tokens. This allows any users (even with zero balance veToken) to claim rewards without staking, leading to an unfair distribution of rewards and potential exploitation of the protocol.
The earned
function calculates rewards based on the user's weight, which is derived from the getUserWeight
function.
The getUserWeight
function uses the _getBaseWeight
and _applyBoost
functions to determine the user's weight.
The _getBaseWeight
function retrieves the gauge weight for the contract,
And the _applyBoost
function applies a boost based on the user's balance of veToken
.
The boost is calculated in an out-of-scope file, but we can observe the following behavior in the calculateBoost
function:
Even when the veBalance
of the user is zero, the function returns params.minBoost
.
This means that when a user does not hold any veToken
, the _applyBoost
function will return (baseWeight * boostState.minBoost) / 1e18
.
baseWeight
and minBoost
do not depend on the user, this implies that even a user who does not hold or stake any veToken
can still claim free rewards from the protocol.
The reward per token (rewardPerTokenStored
) is calculated based on the total staked amount in the contract, as shown in the getRewardPerToken
function:
However, the earned
function does not check whether the user has staked any tokens. Instead, it relies solely on the user's weight, which can be non-zero even if the user has not staked any tokens.
Unauthorized Reward Claims: Users who have never staked or hold any tokens can claim rewards, leading to an unfair distribution of rewards and loss of funds for the protocol.
Economic Exploitation: Malicious users can exploit this vulnerability to drain rewards from the contract without contributing to the staking pool.
run in BaseGauge.test.js
before running the test add the user3 in the beginning
To fix this vulnerability, the contract should ensure that only users who have staked tokens are eligible to claim rewards.
To ensure that only users who have staked tokens are eligible to claim rewards, the earned
function should be modified to calculate rewards based on the user's staked balance (_balances[account]
) rather than their weight derived from veToken
holdings. This aligns with the fact that rewardPerToken
is calculated based on the total rewards distributed across the total staked supply (totalSupply
).
Here is the corrected earned
function:
balanceOf(account)
: This function retrieves the user's staked balance from the _balances
mapping. It ensures that rewards are only calculated for users who have actually staked tokens in the contract.
getRewardPerToken()
: This function calculates the reward per token based on the total rewards and the total staked supply (totalSupply
). By multiplying the user's staked balance by the difference between the current rewardPerToken
and the user's rewardPerTokenPaid
, we ensure that rewards are proportional to the user's contribution to the staking pool.
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.