From c7aeaef7656bede40a5be1e52105c6d893d2dd34 Mon Sep 17 00:00:00 2001 From: matteyu Date: Sun, 5 Jul 2026 22:12:20 -0700 Subject: [PATCH] fix: Unweighted Oracle Standard Deviation Inflates Reward Band --- CHANGELOG.md | 1 + x/oracle/types/ballot.go | 16 +++++++++++----- x/oracle/types/ballot_test.go | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cffbd31f..a9882ece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixed +- Compute the oracle ballot `StandardDeviation` as a stake-weighted variance (weight each squared deviation by the vote's power and divide by total voting power) instead of an unweighted average divided by the vote count, aligning the reward-band width with the stake-weighted median and preventing a group of low-stake validators from inflating the deviation to widen the accepted vote window - Remove the forced minimum 1-unit-per-block reward release in `CalculateReward` and skip (instead of deactivating) sub-unit blocks in the rewards `BeginBlocker`, so the proportional share accumulates and the pool follows the configured schedule independent of block time (previously a 10-year, 1M-unit schedule drained in ~12 days at the 1s target block time and ~28 days at the current ~2.4s rate, regardless of the configured duration) - Reject `MsgEthereumTx` from being dispatched through the authz keeper (including when nested inside `authz.MsgExec`), closing an EVM ante bypass on message-router execution paths that skip the ante handler - Fix feegrant denomination bypass in the cosmos fee ante handler by converting the fee before consuming the grant, so `UseGrantedFees` is checked against the same coins later deducted (prevents a grantee from forcing the granter to pay in a non-granted fee-abstraction denom) diff --git a/x/oracle/types/ballot.go b/x/oracle/types/ballot.go index f30d5a24..553f8494 100644 --- a/x/oracle/types/ballot.go +++ b/x/oracle/types/ballot.go @@ -173,14 +173,20 @@ func (ex ExchangeRateBallot) StandardDeviation(median sdkMath.LegacyDec) (standa } }() + // Get the total voting power to weight the variance + totalPower := ex.Power() + if totalPower == 0 { + return sdkMath.LegacyZeroDec() + } + sum := sdkMath.LegacyZeroDec() for _, votes := range ex { - deviation := votes.ExchangeRate.Sub(median) // calculate the ex - median - sum = sum.Add(deviation.Mul(deviation)) // Calculate sum += (ex - median)^2 + deviation := votes.ExchangeRate.Sub(median) // calculate the ex - median + sum = sum.Add(deviation.Mul(deviation).MulInt64(votes.Power)) // Calculate sum += power * (ex - median)^2 } - // Divide the result by the number of ex - variance := sum.QuoInt64(int64(len(ex))) + // Divide the result by the total voting power + variance := sum.QuoInt64(totalPower) // Finally get the square root of variance standardDeviation, err := variance.ApproxSqrt() @@ -188,5 +194,5 @@ func (ex ExchangeRateBallot) StandardDeviation(median sdkMath.LegacyDec) (standa return sdkMath.LegacyZeroDec() } - return + return standardDeviation } diff --git a/x/oracle/types/ballot_test.go b/x/oracle/types/ballot_test.go index db07ff30..3144f81b 100644 --- a/x/oracle/types/ballot_test.go +++ b/x/oracle/types/ballot_test.go @@ -180,7 +180,7 @@ func TestStandardDeviation(t *testing.T) { median := ballot.WeightedMedianWithAssertion() deviation := ballot.StandardDeviation(median) - expected := sdkMath.LegacyNewDecWithPrec(1224745, 6) + expected := sdkMath.LegacyOneDec() // 2e-7 tolerance delta := sdkMath.LegacyNewDecWithPrec(2, 7)