diff --git a/CHANGELOG.md b/CHANGELOG.md index dc06a057..94e21f93 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 - Close an oracle slashing bypass in the `EndBlocker` where validators were scored against the post-filtered `voteTargets` map: a denom that received votes but was pushed below the vote threshold (e.g. by a coordinated group abstaining) was dropped from the scoring denominator, letting the abstainers avoid miss penalties. Participation is now scored against the configured targets that received votes (passing targets plus below-threshold targets), crediting validators that voted on a below-threshold target while counting abstention on it as a miss; targets that received no votes at all are still excluded so a legitimately unpriceable denom cannot mass-slash the validator set - Allow EIP-7702 delegated EOAs to send direct EVM transactions by exempting delegation-designator code from the externally-owned-account-only check in `VerifyIfAccountExists`, so accounts that delegate via `SetCodeTx` can still manage (and revoke) their own delegation without a sponsored transaction - 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) 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)