Large rewardSpread due to miscalculation
Description
The oracle module uses the rewardSpread to check if the price data from the validator is within an acceptable range from the chosen price.
func Tally(ballots types.ExchangeRateBallots, rewardBand sdk.Dec, validatorPerformances types.ValidatorPerformances) sdk.Dec {
sort.Sort(ballots)
weightedMedian := ballots.WeightedMedianWithAssertion()
standardDeviation := ballots.StandardDeviation(weightedMedian)
rewardSpread := weightedMedian.Mul(rewardBand.QuoInt64(2))
if standardDeviation.GT(rewardSpread) {
rewardSpread = standardDeviationsum := sdk.ZeroDec()
for _, v := range pb {
deviation := v.ExchangeRate.Sub(median)
sum = sum.Add(deviation.Mul(deviation))
}The standard deviation for the ballots is used directly as the rewardSpread if it is greater than the calculated rewardSpread.
if standardDeviation.GT(rewardSpread) {
rewardSpread = standardDeviationThe StandardDeviation function, however, does not ignore negative votes. This could allow a malicious validator to submit abstaining votes with very large negative values and increase the rewardSpread.
Impact
Two malicious validators could collude to repeatedly submit prices outside the acceptable price band. They can do this without being slashed due to rewardSpread having a very high value. If eventually the attacker succeeds in publishing an invalid price, they could profit by liquidating strong postions through the perp module.
Recommendations
Abstained votes should be ignored when calculating the standard deviation for the ballots.
Remediation
This issue has been acknowledged by Nibiru, and a fix was implemented in commit 908571f0↗.