From 3ca259a2a231d5c2f0d67951bf76a13f82ce0e52 Mon Sep 17 00:00:00 2001 From: Unique-Divine Date: Tue, 5 Sep 2023 10:44:58 -0500 Subject: [PATCH] fix(oracle): #wip checkpoint for omit == abstain --- x/oracle/keeper/ballot.go | 14 +- x/oracle/keeper/msg_server_test.go | 2 +- x/oracle/keeper/reward_test.go | 2 +- x/oracle/keeper/slash_test.go | 101 ++++++++------ x/oracle/keeper/test_utils.go | 23 ++-- x/oracle/keeper/update_exchange_rates.go | 20 ++- x/oracle/keeper/update_exchange_rates_test.go | 130 ++++++++++-------- x/oracle/types/ballot.go | 11 ++ 8 files changed, 189 insertions(+), 114 deletions(-) diff --git a/x/oracle/keeper/ballot.go b/x/oracle/keeper/ballot.go index ee5aa0d3b..1e05387fd 100644 --- a/x/oracle/keeper/ballot.go +++ b/x/oracle/keeper/ballot.go @@ -22,7 +22,7 @@ import ( // asset pair is associated with its collection of ExchangeRateBallots. func (k Keeper) groupBallotsByPair( ctx sdk.Context, - validatorsPerformance types.ValidatorPerformances, + valPerfMap types.ValidatorPerformances, ) (pairBallotsMap map[asset.Pair]types.ExchangeRateBallots) { pairBallotsMap = map[asset.Pair]types.ExchangeRateBallots{} @@ -30,7 +30,7 @@ func (k Keeper) groupBallotsByPair( voterAddr, aggregateVote := value.Key, value.Value // organize ballot only for the active validators - if validatorPerformance, exists := validatorsPerformance[aggregateVote.Voter]; exists { + if validatorPerformance, exists := valPerfMap[aggregateVote.Voter]; exists { for _, exchangeRateTuple := range aggregateVote.ExchangeRateTuples { power := validatorPerformance.Power if !exchangeRateTuple.ExchangeRate.IsPositive() { @@ -147,7 +147,7 @@ func Tally( ballots types.ExchangeRateBallots, rewardBand sdk.Dec, validatorPerformances types.ValidatorPerformances, -) sdk.Dec { +) (sdk.Dec, types.ValidatorPerformances) { weightedMedian := ballots.WeightedMedianWithAssertion() standardDeviation := ballots.StandardDeviation(weightedMedian) rewardSpread := weightedMedian.Mul(rewardBand.QuoInt64(2)) @@ -162,20 +162,18 @@ func Tally( ballot.ExchangeRate.LTE(weightedMedian.Add(rewardSpread)) isAbstainVote := !ballot.ExchangeRate.IsPositive() isMiss := !(voteInsideSpread || isAbstainVote) - voterAddr := ballot.Voter.String() validatorPerformance := validatorPerformances[voterAddr] switch { - case isAbstainVote: - validatorPerformance.AbstainCount++ case voteInsideSpread: validatorPerformance.RewardWeight += ballot.Power validatorPerformance.WinCount++ case isMiss: validatorPerformance.MissCount++ + case isAbstainVote: + validatorPerformance.AbstainCount++ } validatorPerformances[voterAddr] = validatorPerformance } - - return weightedMedian + return weightedMedian, validatorPerformances } diff --git a/x/oracle/keeper/msg_server_test.go b/x/oracle/keeper/msg_server_test.go index 5337cab7a..f76d1deb3 100644 --- a/x/oracle/keeper/msg_server_test.go +++ b/x/oracle/keeper/msg_server_test.go @@ -17,7 +17,7 @@ func TestFeederDelegation(t *testing.T) { exchangeRates := types.ExchangeRateTuples{ { Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), - ExchangeRate: randomExchangeRate, + ExchangeRate: TEST_ERATE, }, } diff --git a/x/oracle/keeper/reward_test.go b/x/oracle/keeper/reward_test.go index c0861f6c8..19e0bc85b 100644 --- a/x/oracle/keeper/reward_test.go +++ b/x/oracle/keeper/reward_test.go @@ -40,7 +40,7 @@ func TestKeeperRewardsDistributionMultiVotePeriods(t *testing.T) { MakeAggregatePrevoteAndVote(t, fixture, msgServer, fixture.Ctx.BlockHeight(), types.ExchangeRateTuples{ { Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), - ExchangeRate: randomExchangeRate, + ExchangeRate: TEST_ERATE, }, }, valIndex) } diff --git a/x/oracle/keeper/slash_test.go b/x/oracle/keeper/slash_test.go index edf6e54eb..7c4d6c127 100644 --- a/x/oracle/keeper/slash_test.go +++ b/x/oracle/keeper/slash_test.go @@ -105,22 +105,22 @@ func TestInvalidVotesSlashing(t *testing.T) { // Account 1, govstable MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 0) // Account 2, govstable, miss vote MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Add(sdk.NewDec(100000000000000))}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Add(sdk.NewDec(100000000000000))}, }, 1) // Account 3, govstable MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 2) // Account 4, govstable MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 3) input.OracleKeeper.UpdateExchangeRates(input.Ctx) @@ -131,27 +131,27 @@ func TestInvalidVotesSlashing(t *testing.T) { } validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[1]) - require.Equal(t, stakingAmt, validator.GetBondedTokens()) + require.Equal(t, TEST_STAKING_AMT, validator.GetBondedTokens()) // one more miss vote will inccur ValAddrs[1] slashing // Account 1, govstable MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 0) // Account 2, govstable, miss vote MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Add(sdk.NewDec(100000000000000))}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Add(sdk.NewDec(100000000000000))}, }, 1) // Account 3, govstable MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 2) // Account 4, govstable MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 3) input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) @@ -160,49 +160,72 @@ func TestInvalidVotesSlashing(t *testing.T) { // input.OracleKeeper.UpdateExchangeRates(input.Ctx) validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[1]) - require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens()) + require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(TEST_STAKING_AMT).TruncateInt(), validator.GetBondedTokens()) } +// TestWhitelistSlashing: Creates a scenario where one valoper (valIdx 0) does +// not vote throughout an entire vote window, while valopers 1 and 2 do. func TestWhitelistSlashing(t *testing.T) { - input, h := Setup(t) + input, msgServer := Setup(t) votePeriodsPerWindow := sdk.NewDec(int64(input.OracleKeeper.SlashWindow(input.Ctx))).QuoInt64(int64(input.OracleKeeper.VotePeriod(input.Ctx))).TruncateInt64() - slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) + // slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + pair := asset.Registry.Pair(denoms.NIBI, denoms.NUSD) + priceVoteFromVal := func(valIdx int, block int64, erate sdk.Dec) { + MakeAggregatePrevoteAndVote(t, input, msgServer, block, + types.ExchangeRateTuples{{Pair: pair, ExchangeRate: erate}}, + valIdx) + } + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, pair) + perfs := input.OracleKeeper.UpdateExchangeRates(input.Ctx) + require.EqualValues(t, 0, perfs.GetTotalRewardWeight()) + + block := input.Ctx.BlockHeight() + 50 + priceVoteFromVal(0, block, TEST_ERATE) + priceVoteFromVal(1, block, TEST_ERATE) + input.Ctx = input.Ctx.WithBlockHeight(block + 100) + allowedMissPct := sdk.OneDec().Sub(minValidPerWindow) allowedMissPeriods := allowedMissPct.MulInt64(votePeriodsPerWindow). TruncateInt64() - for idxMissPeriod := uint64(0); idxMissPeriod < uint64(allowedMissPeriods); idxMissPeriod++ { - input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) - // Account 2, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1) - // Account 3, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) + // TODO - input.OracleKeeper.UpdateExchangeRates(input.Ctx) - // input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) - // input.OracleKeeper.UpdateExchangeRates(input.Ctx) - require.Equal(t, idxMissPeriod+1, input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[0], 0)) - } + t.Logf("For %v blocks, valoper0 does not vote, while 1 and 2 do.", allowedMissPeriods) + for idxMissPeriod := uint64(0); idxMissPeriod < uint64(allowedMissPeriods); idxMissPeriod++ { + block := input.Ctx.BlockHeight() + 1 + input.Ctx = input.Ctx.WithBlockHeight(block) - validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[0]) - require.Equal(t, stakingAmt, validator.GetBondedTokens()) + valIdx := 0 + priceVoteFromVal(valIdx+1, block, TEST_ERATE) + priceVoteFromVal(valIdx+2, block, TEST_ERATE) - // one more miss vote will inccur Account 1 slashing + // Valoper 0 votes outside band (miss) - // Account 2, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1) - // Account 3, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) + perfs := input.OracleKeeper.UpdateExchangeRates(input.Ctx) + missCount := input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[0], 0) + require.EqualValues(t, 0, missCount, perfs.String()) + } - input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) - input.OracleKeeper.UpdateExchangeRates(input.Ctx) - input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + t.Log("valoper0 should not be slashed") + validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[0]) + require.Equal(t, TEST_STAKING_AMT, validator.GetBondedTokens()) + + // // one more miss vote will inccur Account 1 slashing + // + // // Account 2, govstable + // MakeAggregatePrevoteAndVote(t, input, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1) + // // Account 3, govstable + // MakeAggregatePrevoteAndVote(t, input, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) + // + // input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) // input.OracleKeeper.UpdateExchangeRates(input.Ctx) - validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[0]) - require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens()) + // input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + // // input.OracleKeeper.UpdateExchangeRates(input.Ctx) + // validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[0]) + // require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens()) } func TestNotPassedBallotSlashing(t *testing.T) { @@ -221,7 +244,7 @@ func TestNotPassedBallotSlashing(t *testing.T) { input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) // Account 1, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0) + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, 0) input.OracleKeeper.UpdateExchangeRates(input.Ctx) input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) @@ -251,13 +274,13 @@ func TestAbstainSlashing(t *testing.T) { input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) // Account 1, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0) + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, 0) // Account 2, govstable, abstain vote MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: sdk.ZeroDec()}}, 1) // Account 3, govstable - MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, 2) input.OracleKeeper.UpdateExchangeRates(input.Ctx) input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) @@ -266,5 +289,5 @@ func TestAbstainSlashing(t *testing.T) { } validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[1]) - require.Equal(t, stakingAmt, validator.GetBondedTokens()) + require.Equal(t, TEST_STAKING_AMT, validator.GetBondedTokens()) } diff --git a/x/oracle/keeper/test_utils.go b/x/oracle/keeper/test_utils.go index c08bb1797..7e95f40a2 100644 --- a/x/oracle/keeper/test_utils.go +++ b/x/oracle/keeper/test_utils.go @@ -285,8 +285,8 @@ func AllocateRewards(t *testing.T, input TestFixture, rewards sdk.Coins, votePer } var ( - stakingAmt = sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction) - randomExchangeRate = sdk.NewDec(1700) + TEST_STAKING_AMT = sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction) + TEST_ERATE = sdk.NewDec(1700) ) func Setup(t *testing.T) (TestFixture, types.MsgServer) { @@ -304,22 +304,29 @@ func Setup(t *testing.T) (TestFixture, types.MsgServer) { sh := stakingkeeper.NewMsgServerImpl(&fixture.StakingKeeper) // Validator created - _, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], stakingAmt)) + _, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], TEST_STAKING_AMT)) require.NoError(t, err) - _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], stakingAmt)) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], TEST_STAKING_AMT)) require.NoError(t, err) - _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], stakingAmt)) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], TEST_STAKING_AMT)) require.NoError(t, err) - _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[3], ValPubKeys[3], stakingAmt)) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[3], ValPubKeys[3], TEST_STAKING_AMT)) require.NoError(t, err) - _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[4], ValPubKeys[4], stakingAmt)) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[4], ValPubKeys[4], TEST_STAKING_AMT)) require.NoError(t, err) staking.EndBlocker(fixture.Ctx, &fixture.StakingKeeper) return fixture, h } -func MakeAggregatePrevoteAndVote(t *testing.T, input TestFixture, msgServer types.MsgServer, height int64, rates types.ExchangeRateTuples, valIdx int) { +func MakeAggregatePrevoteAndVote( + t *testing.T, + input TestFixture, + msgServer types.MsgServer, + height int64, + rates types.ExchangeRateTuples, + valIdx int, +) { salt := "1" ratesStr, err := rates.ToString() require.NoError(t, err) diff --git a/x/oracle/keeper/update_exchange_rates.go b/x/oracle/keeper/update_exchange_rates.go index 369c3487b..31502eda3 100644 --- a/x/oracle/keeper/update_exchange_rates.go +++ b/x/oracle/keeper/update_exchange_rates.go @@ -12,7 +12,7 @@ import ( ) // UpdateExchangeRates updates the ExchangeRates, this is supposed to be executed on EndBlock. -func (k Keeper) UpdateExchangeRates(ctx sdk.Context) { +func (k Keeper) UpdateExchangeRates(ctx sdk.Context) types.ValidatorPerformances { k.Logger(ctx).Info("processing validator price votes") validatorPerformances := k.newValidatorPerformances(ctx) @@ -27,6 +27,8 @@ func (k Keeper) UpdateExchangeRates(ctx sdk.Context) { params, _ := k.Params.Get(ctx) k.clearVotesAndPreVotes(ctx, params.VotePeriod) k.updateWhitelist(ctx, params.Whitelist, whitelistedPairs) + k.registerAbstainsByOmission(ctx, len(params.Whitelist), validatorPerformances) + return validatorPerformances } // registerMissedVotes it parses all validators performance and increases the @@ -46,6 +48,20 @@ func (k Keeper) registerMissedVotes( } } +func (k Keeper) registerAbstainsByOmission( + ctx sdk.Context, + numMarkets int, + perfs types.ValidatorPerformances, +) { + for valAddr, perf := range perfs { + omitCount := int64(numMarkets) - (perf.WinCount + perf.AbstainCount + perf.MissCount) + if omitCount > 0 { + perf.AbstainCount += omitCount + perfs[valAddr] = perf + } + } +} + // countVotesAndUpdateExchangeRates processes the votes and updates the // ExchangeRates based on the results. func (k Keeper) countVotesAndUpdateExchangeRates( @@ -59,7 +75,7 @@ func (k Keeper) countVotesAndUpdateExchangeRates( orderedBallotsMap := omap.OrderedMap_Pair[types.ExchangeRateBallots](pairBallotsMap) for pair := range orderedBallotsMap.Range() { ballots := pairBallotsMap[pair] - exchangeRate := Tally(ballots, rewardBand, validatorPerformances) + exchangeRate, _ := Tally(ballots, rewardBand, validatorPerformances) k.SetPrice(ctx, pair, exchangeRate) diff --git a/x/oracle/keeper/update_exchange_rates_test.go b/x/oracle/keeper/update_exchange_rates_test.go index 2f169f1b4..87766b4ba 100644 --- a/x/oracle/keeper/update_exchange_rates_test.go +++ b/x/oracle/keeper/update_exchange_rates_test.go @@ -24,7 +24,7 @@ func TestOracleThreshold(t *testing.T) { exchangeRates := types.ExchangeRateTuples{ { Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), - ExchangeRate: randomExchangeRate, + ExchangeRate: TEST_ERATE, }, } exchangeRateStr, err := exchangeRates.ToString() @@ -68,12 +68,12 @@ func TestOracleThreshold(t *testing.T) { fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) rate, err := fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, exchangeRates[0].Pair) require.NoError(t, err) - assert.Equal(t, randomExchangeRate, rate.ExchangeRate) + assert.Equal(t, TEST_ERATE, rate.ExchangeRate) // Case 3. // Increase voting power of absent validator, exchange rate consensus fails val, _ := fixture.StakingKeeper.GetValidator(fixture.Ctx, ValAddrs[4]) - _, _ = fixture.StakingKeeper.Delegate(fixture.Ctx.WithBlockHeight(0), Addrs[4], stakingAmt.MulRaw(8), stakingtypes.Unbonded, val, false) + _, _ = fixture.StakingKeeper.Delegate(fixture.Ctx.WithBlockHeight(0), Addrs[4], TEST_STAKING_AMT.MulRaw(8), stakingtypes.Unbonded, val, false) for i := 0; i < 4; i++ { salt := fmt.Sprintf("%d", i) @@ -104,7 +104,7 @@ func TestResetExchangeRates(t *testing.T) { fixture.OracleKeeper.Params.Set(fixture.Ctx, params) // Post a price at block 1 - fixture.OracleKeeper.SetPrice(fixture.Ctx.WithBlockHeight(1), pair, randomExchangeRate) + fixture.OracleKeeper.SetPrice(fixture.Ctx.WithBlockHeight(1), pair, TEST_ERATE) // reset exchange rates at block 2 // Price should still be there because not expired yet @@ -121,7 +121,7 @@ func TestResetExchangeRates(t *testing.T) { // Post a price at block 69 // reset exchange rates at block 79 // Price should not be there anymore because expired - fixture.OracleKeeper.SetPrice(fixture.Ctx.WithBlockHeight(69), pair, randomExchangeRate) + fixture.OracleKeeper.SetPrice(fixture.Ctx.WithBlockHeight(69), pair, TEST_ERATE) fixture.OracleKeeper.resetExchangeRates(fixture.Ctx.WithBlockHeight(79), emptyBallot) _, err = fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, pair) @@ -153,7 +153,7 @@ func TestOracleTally(t *testing.T) { require.NoError(t, err1) require.NoError(t, err2) - power := stakingAmt.QuoRaw(int64(6)).Int64() + power := TEST_STAKING_AMT.QuoRaw(int64(6)).Int64() if decExchangeRate.IsZero() { power = int64(0) } @@ -207,11 +207,12 @@ func TestOracleTally(t *testing.T) { expectedValidatorClaimMap[key] = claim } - tallyMedian := Tally( + tallyMedian, perfs := Tally( ballot, fixture.OracleKeeper.RewardBand(fixture.Ctx), validatorClaimMap) assert.Equal(t, expectedValidatorClaimMap, validatorClaimMap) assert.Equal(t, tallyMedian.MulInt64(100).TruncateInt(), weightedMedian.MulInt64(100).TruncateInt()) + assert.NotEqualValues(t, 0, perfs.GetTotalRewardWeight(), perfs.String()) } func TestOracleRewardBand(t *testing.T) { @@ -228,26 +229,26 @@ func TestOracleRewardBand(t *testing.T) { } fixture.OracleKeeper.WhitelistedPairs.Insert(fixture.Ctx, asset.Registry.Pair(denoms.NIBI, denoms.NUSD)) - rewardSpread := randomExchangeRate.Mul(fixture.OracleKeeper.RewardBand(fixture.Ctx).QuoInt64(2)) + rewardSpread := TEST_ERATE.Mul(fixture.OracleKeeper.RewardBand(fixture.Ctx).QuoInt64(2)) // Account 1, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Sub(rewardSpread)}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Sub(rewardSpread)}, }, 0) // Account 2, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 1) // Account 3, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 2) // Account 4, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Add(rewardSpread)}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Add(rewardSpread)}, }, 3) fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) @@ -260,22 +261,22 @@ func TestOracleRewardBand(t *testing.T) { // Account 1 will miss the vote due to raward band condition // Account 1, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Sub(rewardSpread.Add(sdk.OneDec()))}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Sub(rewardSpread.Add(sdk.OneDec()))}, }, 0) // Account 2, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 1) // Account 3, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}, }, 2) // Account 4, nibi:nusd MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ - {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate.Add(rewardSpread)}, + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE.Add(rewardSpread)}, }, 3) fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) @@ -403,62 +404,81 @@ func TestWhitelistedPairs(t *testing.T) { params, err := fixture.OracleKeeper.Params.Get(fixture.Ctx) require.NoError(t, err) + t.Log("whitelist ONLY nibi:nusd") for _, p := range fixture.OracleKeeper.WhitelistedPairs.Iterate(fixture.Ctx, collections.Range[asset.Pair]{}).Keys() { fixture.OracleKeeper.WhitelistedPairs.Delete(fixture.Ctx, p) } fixture.OracleKeeper.WhitelistedPairs.Insert(fixture.Ctx, asset.Registry.Pair(denoms.NIBI, denoms.NUSD)) - // nibi:nusd - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 3) + t.Log("vote and prevote from all vals on nibi:nusd") + priceVoteFromVal := func(valIdx int, block int64) { + MakeAggregatePrevoteAndVote(t, fixture, msgServer, block, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: TEST_ERATE}}, valIdx) + } + block := int64(0) + priceVoteFromVal(0, block) + priceVoteFromVal(1, block) + priceVoteFromVal(2, block) + priceVoteFromVal(3, block) - // add btc:nusd for next vote period + t.Log("whitelist btc:nusd for next vote period") params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.NIBI, denoms.NUSD), asset.Registry.Pair(denoms.BTC, denoms.NUSD)} fixture.OracleKeeper.Params.Set(fixture.Ctx, params) fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) - // no missing current + t.Log("assert: no miss counts for all vals") assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[0], 0)) assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[1], 0)) assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[3], 0)) - // whitelisted pairs are {nibi:nusd, btc:nusd} - assert.Equal(t, []asset.Pair{asset.Registry.Pair(denoms.BTC, denoms.NUSD), asset.Registry.Pair(denoms.NIBI, denoms.NUSD)}, fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) + t.Log("whitelisted pairs are {nibi:nusd, btc:nusd}") + assert.Equal(t, + []asset.Pair{ + asset.Registry.Pair(denoms.BTC, denoms.NUSD), + asset.Registry.Pair(denoms.NIBI, denoms.NUSD)}, + fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) - // nibi:nusd, missing btc:nusd - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 3) + t.Log("vote from vals 0-3 on nibi:nusd (but not btc:nusd)") + priceVoteFromVal(0, block) + priceVoteFromVal(1, block) + priceVoteFromVal(2, block) + priceVoteFromVal(3, block) - // delete btc:nusd for next vote period + t.Log("delete btc:nusd for next vote period") params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.NIBI, denoms.NUSD)} fixture.OracleKeeper.Params.Set(fixture.Ctx, params) - fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) - - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[0], 0)) - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[1], 0)) - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[3], 0)) - - // btc:nusd must be deleted - assert.Equal(t, []asset.Pair{asset.Registry.Pair(denoms.NIBI, denoms.NUSD)}, fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) - require.False(t, fixture.OracleKeeper.WhitelistedPairs.Has(fixture.Ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD))) - - // nibi:nusd, no missing - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 0) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 1) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 2) - MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: randomExchangeRate}}, 3) - - fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) - - // validators keep miss counters from last vote period - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[0], 0)) - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[1], 0)) - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) - assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) + perfs := fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + t.Log("validators 0-3 all voted -> expect win") + for valIdx := 0; valIdx < 4; valIdx++ { + perf := perfs[ValAddrs[valIdx].String()] + assert.EqualValues(t, 1, perf.WinCount) + assert.EqualValues(t, 0, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) + } + t.Log("validators 4 didn't vote -> expect abstain") + perf := perfs[ValAddrs[4].String()] + assert.EqualValues(t, 0, perf.WinCount) + assert.EqualValues(t, 1, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) + + t.Log("btc:nusd must be deleted") + assert.Equal(t, []asset.Pair{asset.Registry.Pair(denoms.NIBI, denoms.NUSD)}, + fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) + require.False(t, fixture.OracleKeeper.WhitelistedPairs.Has( + fixture.Ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD))) + + t.Log("vote from vals 0-2 on nibi:nusd (same vote period)") + priceVoteFromVal(0, block) + priceVoteFromVal(1, block) + priceVoteFromVal(2, block) + perfs = fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + t.Log("Although validators 0-2 voted, it's for the same period -> expect abstains for everyone") + for valIdx := 0; valIdx < 5; valIdx++ { + perf := perfs[ValAddrs[valIdx].String()] + assert.EqualValues(t, 0, perf.WinCount) + assert.EqualValues(t, 1, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) + } } diff --git a/x/oracle/types/ballot.go b/x/oracle/types/ballot.go index 3851db302..9fa5098b7 100644 --- a/x/oracle/types/ballot.go +++ b/x/oracle/types/ballot.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" "math" "sort" @@ -207,3 +208,13 @@ func (vp ValidatorPerformances) GetTotalRewardWeight() int64 { return totalRewardWeight } + +func (vp ValidatorPerformances) String() string { + jsonBz, _ := json.Marshal(vp) + return string(jsonBz) +} + +func (vp ValidatorPerformance) String() string { + jsonBz, _ := json.Marshal(vp) + return string(jsonBz) +}