in baseguage, a user's rewards are updated through the updateReward()
function every time the stake, withdraw, getreward, votedirection, or checkpoint functions are called. within the updateReward()
function, the earned()
function is called to calculate rewards, and the value of earned()
is used later when withdrawing rewards. however, due to Gauge manipulation, an attacker can freeze the rewards so that they are no longer updated.
the _updateReward()
function calls the earned() function to update the user's rewards.
the reward calculation subtracts the rewardpertokenpaid from getRewardPerToken()
value then multiplies the difference by the getUserWeight()
function, and adds the result to the existing rewards.
the getUserWeight()
function returns the value returned by the _applyBoost()
function. in other words, if the _applyBoost()
function always returns 0, then the getUserWeight()
function always returns 0
in the first line of the _applyboost() function, if baseweight is 0, it returns 0. (_getBaseWeight
should returns 0)
the _getBaseWeight()
function retrieves the weight value of the current contract not the user using IGaugeController's getGaugeWeight()
function
GaugeController's getGaugeWeight()
function returns gauges[gauge].weight. then, if an attacker can set this value to 0, they could force all users' reward updates to halt.
in the _updateGaugeWeight()
function, the gauges[gauge].weight value is updated. the important point is that while the maximum value of newweight is verified, the minimum value is not. therefore, by setting the denominator to 0, the additional value can be forced to 0 (it can be manipulated to 0-0).
we don't need to care newWeight part because the denominator is 0. we need to force the value of oldgaugeweight - (oldweight * votingpower / weight_precision)
to 0. usergaugevotes[msg.sender][gauge] * veraactoken.balanceof(msg.sender) / 10000
must equal oldGaugeWeight
. this ensures that when subtracted, the result is 0.
however, userGaugeVotes[msg.sender][gauge]
value is initially 0. in other words, we need to first call the vote()
function to assign an arbitrary value to userGaugeVotes[msg.sender][gauge]
.
and before calling the second vote() function, assign the value of X
that satisfies the above expression to votingpower. and when the vote()
function is called, the value of (oldweight * votingpower) / 10000
becomes equal to oldGaugeWeight
, so subtracting the two results in 0. since the value of voingPower
is the current user's balance of veraactoken, an attacker can manipulate it. if that's the case, then in baseguage, when the user's reward is updated, it will always be set to 0.
WEIGHT_PRECISION = 10000
veRAACToken.balanceOf(attacker) = 1000
userGaugeVotes[attacker][BaseGauge] = 0
gauges[BaseGauge].weight = 10000
call ⇒ vote(BaseGauge, 4000)
newWeight = 4000
votingPower = veRAACToken.balanceOf(attacker) = 1000
oldWeight = userGaugeVotes[attacker][BaseGauge] = 0
userGaugeVotes[attacker][BaseGauge] = 1000 (the updated value is used in the next vote() function call)
oldGaugeWeight = gauges[BaseGauge].weight = 10000
gauges[BaseGauge].weight = 10400
4000 * X / 10000 = 10400, Mint the attacker's veRAACToken balance by X amount so X should be 26000
call ⇒ vote(BaseGauge, 0)
newWeight = 0
votingPower = veRAACToken.balanceOf(attacker) = 26000
oldWeight = userGaugeVotes[attacker][gauge] = 4000
userGaugeVotes[attacker][BaseGauge] = 0 (the updated value is used in the next vote() function call)
oldGaugeWeight = gauges[BaseGauge].weight = 10400
gauges[BaseGauge].weight = 0
since getUserWeight(account) now always returns 0, the reward is not updated.
an attacker can set the gauge's value to 0, freezing the reward at 0.=
code review
when setting a new gauge, the minimum value is checked. it must be greater than 0. also, must we really allow attackers to manipulate this gauge at will? any attackers can manipulate this value to change the rewards
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.