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 = standardDeviation
sum := 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 = standardDeviation
The 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↗.