Skip to content

Commit e5078ec

Browse files
authored
Merge branch 'main' into fix/oracle-weighted-standard-deviation
2 parents ac6cf8a + ce58520 commit e5078ec

3 files changed

Lines changed: 82 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
### Fixed
1313

1414
- 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
15+
- 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
1516
- 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
1617
- 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)
1718
- 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

x/oracle/abci.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,24 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) error {
155155
)
156156
}
157157

158+
for _, ballot := range belowThresholdVoteMap {
159+
for _, vote := range ballot {
160+
voter := vote.Voter.String()
161+
claim, found := validatorClaimMap[voter]
162+
if !found {
163+
continue
164+
}
165+
claim.WinCount++
166+
claim.DidVote = true
167+
validatorClaimMap[voter] = claim
168+
}
169+
}
170+
171+
requiredWinCount := len(voteTargets) + len(belowThresholdVoteMap)
172+
158173
// Validate miss voting process
159174
for _, claim := range validatorClaimMap {
160-
if int(claim.WinCount) == len(voteTargets) {
175+
if int(claim.WinCount) == requiredWinCount {
161176
err = k.IncrementSuccessCount(ctx, claim.Recipient)
162177
if err != nil {
163178
return err

x/oracle/abci_test.go

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ func TestEndblocker(t *testing.T) {
503503
}
504504
})
505505

506-
t.Run("One denom below threshold - validators who voted for should not be penalized", func(t *testing.T) {
506+
t.Run("One denom below threshold - voters are not penalized but abstainers miss", func(t *testing.T) {
507507
// Reset blockchain state
508508
input, msgServer := SetUp(t)
509509
ctx := input.Ctx
@@ -559,18 +559,18 @@ func TestEndblocker(t *testing.T) {
559559
_, err = oracleKeeper.ExchangeRate.Get(ctx, utils.UsdcDenom)
560560
require.Error(t, err)
561561

562-
// Validators 1 and 2 voted for 3 out of 3 valid targets, their WinCount (3)
563-
// will match len(voteTargets) (3), so they get a success
562+
// Validators 1 and 2 skipped usdc, which received a vote and stayed an
563+
// active target, so their WinCount (3) falls short of the required count
564+
// (4) and they get a miss
564565
for i := 1; i < 3; i++ {
565566
counter, err := oracleKeeper.VotePenaltyCounter.Get(ctx, keeper.ValAddrs[i])
566567
require.NoError(t, err)
567-
require.EqualValues(t, uint64(0), counter.MissCount)
568+
require.EqualValues(t, uint64(1), counter.MissCount)
568569
require.EqualValues(t, uint64(0), counter.AbstainCount)
569570
}
570571

571-
// Validator 0 voted for all 4 denoms. Usdc went to belowThresholdVoteMap
572-
// and still runs through Tally, so validator 0 gets WinCount=4 and
573-
// should also get a success
572+
// Validator 0 voted for all 4 denoms, including the below-threshold usdc,
573+
// so its WinCount (4) matches the required count and it gets a success
574574
counter, err := oracleKeeper.VotePenaltyCounter.Get(ctx, keeper.ValAddrs[0])
575575
require.NoError(t, err)
576576
require.EqualValues(t, uint64(0), counter.MissCount)
@@ -632,12 +632,12 @@ func TestEndblocker(t *testing.T) {
632632
_, err = oracleKeeper.ExchangeRate.Get(ctx, utils.UsdcDenom)
633633
require.Error(t, err)
634634

635-
// Validators 1 and 2 voted correctly on all 3 above-threshold denoms
636-
// WinCount == 3 == len(voteTargets) → success
635+
// Validators 1 and 2 voted correctly on the 3 above-threshold denoms but
636+
// skipped usdc, which stayed an active target, so they miss
637637
for i := 1; i < 3; i++ {
638638
counter, err := oracleKeeper.VotePenaltyCounter.Get(ctx, keeper.ValAddrs[i])
639639
require.NoError(t, err)
640-
require.EqualValues(t, uint64(0), counter.MissCount)
640+
require.EqualValues(t, uint64(1), counter.MissCount)
641641
require.EqualValues(t, uint64(0), counter.AbstainCount)
642642
}
643643

@@ -648,4 +648,59 @@ func TestEndblocker(t *testing.T) {
648648
require.EqualValues(t, uint64(1), counter.MissCount)
649649
require.EqualValues(t, uint64(0), counter.AbstainCount)
650650
})
651+
652+
t.Run("Cartel abstains to push a denom below threshold - abstainers still miss", func(t *testing.T) {
653+
// Reset blockchain state
654+
input, msgServer := SetUp(t)
655+
ctx := input.Ctx
656+
oracleKeeper := input.OracleKeeper
657+
658+
err := oracleKeeper.VoteTarget.Clear(ctx, nil)
659+
require.NoError(t, err)
660+
err = oracleKeeper.VoteTarget.Set(ctx, utils.AtomDenom, types.Denom{Name: utils.AtomDenom})
661+
require.NoError(t, err)
662+
err = oracleKeeper.VoteTarget.Set(ctx, utils.EthDenom, types.Denom{Name: utils.EthDenom})
663+
require.NoError(t, err)
664+
err = oracleKeeper.VoteTarget.Set(ctx, utils.KiiDenom, types.Denom{Name: utils.KiiDenom})
665+
require.NoError(t, err)
666+
err = oracleKeeper.VoteTarget.Set(ctx, utils.UsdcDenom, types.Denom{Name: utils.UsdcDenom})
667+
require.NoError(t, err)
668+
669+
ctx = input.Ctx.WithBlockHeight(1)
670+
671+
fullRate := randomAExchangeRate.String() + utils.AtomDenom +
672+
"," + randomAExchangeRate.String() + utils.EthDenom +
673+
"," + randomAExchangeRate.String() + utils.KiiDenom +
674+
"," + randomAExchangeRate.String() + utils.UsdcDenom
675+
voteMsg := types.NewMsgAggregateExchangeRateVote(fullRate, keeper.Addrs[0], keeper.ValAddrs[0])
676+
_, err = msgServer.AggregateExchangeRateVote(ctx, voteMsg)
677+
require.NoError(t, err)
678+
679+
partialRate := randomAExchangeRate.String() + utils.AtomDenom +
680+
"," + randomAExchangeRate.String() + utils.EthDenom +
681+
"," + randomAExchangeRate.String() + utils.KiiDenom
682+
for i := 1; i < 3; i++ {
683+
voteMsg := types.NewMsgAggregateExchangeRateVote(partialRate, keeper.Addrs[i], keeper.ValAddrs[i])
684+
_, err := msgServer.AggregateExchangeRateVote(ctx, voteMsg)
685+
require.NoError(t, err)
686+
}
687+
688+
err = EndBlocker(ctx, oracleKeeper)
689+
require.NoError(t, err)
690+
691+
_, err = oracleKeeper.ExchangeRate.Get(ctx, utils.UsdcDenom)
692+
require.Error(t, err)
693+
694+
for i := 1; i < 3; i++ {
695+
counter, err := oracleKeeper.VotePenaltyCounter.Get(ctx, keeper.ValAddrs[i])
696+
require.NoError(t, err)
697+
require.EqualValues(t, uint64(1), counter.MissCount)
698+
require.EqualValues(t, uint64(0), counter.AbstainCount)
699+
}
700+
701+
counter, err := oracleKeeper.VotePenaltyCounter.Get(ctx, keeper.ValAddrs[0])
702+
require.NoError(t, err)
703+
require.EqualValues(t, uint64(0), counter.MissCount)
704+
require.EqualValues(t, uint64(0), counter.AbstainCount)
705+
})
651706
}

0 commit comments

Comments
 (0)