From 86d03812fce2a6d91c8210ed09b79c50b4c57105 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Tue, 10 Dec 2024 17:56:42 -0600 Subject: [PATCH] handle oracle overflow and rounding to zero for extreme values with cross rates (#1983) * handle oracle overflow and rounding to zero for extreme values with cross rates * remove empty branch --- x/oracle/abci.go | 6 ++++ x/oracle/abci_test.go | 60 ++++++++++++++++++++++++++++++++++++++++ x/oracle/types/ballot.go | 12 +++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/x/oracle/abci.go b/x/oracle/abci.go index f048093579..e070e89209 100755 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -85,6 +85,12 @@ func MidBlocker(ctx sdk.Context, k keeper.Keeper) { // Get weighted median of cross exchange rates exchangeRate := Tally(ctx, ballot, params.RewardBand, validatorClaimMap) + // if exchange rate is somehow 0, exclude it from ballot? + if exchangeRate.IsZero() { + // skip this denom + continue + } + // Transform into the original form base/quote if denom != referenceDenom { exchangeRate = exchangeRateRD.Quo(exchangeRate) diff --git a/x/oracle/abci_test.go b/x/oracle/abci_test.go index 250c1b3497..664ef5c275 100755 --- a/x/oracle/abci_test.go +++ b/x/oracle/abci_test.go @@ -672,3 +672,63 @@ func TestEndWindowClearExcessFeeds(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, len(response2.Actives)) } + +func TestOverflowAndDivByZero(t *testing.T) { + input, h := setup(t) + params := input.OracleKeeper.GetParams(input.Ctx) + params.Whitelist = types.DenomList{ + {Name: utils.MicroAtomDenom}, + {Name: utils.MicroEthDenom}, + } + input.OracleKeeper.SetParams(input.Ctx, params) + + // Set vote targets + input.OracleKeeper.ClearVoteTargets(input.Ctx) + input.OracleKeeper.SetVoteTarget(input.Ctx, utils.MicroAtomDenom) + input.OracleKeeper.SetVoteTarget(input.Ctx, utils.MicroEthDenom) + + // Test overflow case + overflowRate := sdk.MustNewDecFromStr("7896044618658097711785492504343953926634992332820282019728792003956564819967.999999999999999999") + smallRate := sdk.MustNewDecFromStr("0.000000000000000001") + overflowVote := sdk.DecCoins{ + sdk.NewDecCoinFromDec(utils.MicroAtomDenom, overflowRate), + sdk.NewDecCoinFromDec(utils.MicroEthDenom, smallRate), + } + makeAggregateVote(t, input, h, 0, overflowVote, 0) + makeAggregateVote(t, input, h, 0, overflowVote, 1) + makeAggregateVote(t, input, h, 0, overflowVote, 2) + + // This should not panic + oracle.MidBlocker(input.Ctx, input.OracleKeeper) + oracle.EndBlocker(input.Ctx, input.OracleKeeper) + + // Verify no exchange rates were set for overflowed one + rate, _, _, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) + require.NoError(t, err) + require.Equal(t, overflowRate, rate) + _, _, _, err = input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroEthDenom) + require.Error(t, err) + + input.Ctx = input.Ctx.WithBlockHeight(1) + + // Test divide by zero case + zeroVote := sdk.DecCoins{ + sdk.NewDecCoinFromDec(utils.MicroAtomDenom, smallRate), + sdk.NewDecCoinFromDec(utils.MicroEthDenom, overflowRate), + } + makeAggregateVote(t, input, h, 1, zeroVote, 0) + makeAggregateVote(t, input, h, 1, zeroVote, 1) + makeAggregateVote(t, input, h, 1, zeroVote, 2) + + // This should not panic + oracle.MidBlocker(input.Ctx, input.OracleKeeper) + oracle.EndBlocker(input.Ctx, input.OracleKeeper) + + // Verify no exchange rates were set for either case + rate, height, _, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) + require.NoError(t, err) + require.Equal(t, smallRate, rate) + require.Equal(t, int64(1), height.Int64()) + _, _, _, err = input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroEthDenom) + require.Error(t, err) +} diff --git a/x/oracle/types/ballot.go b/x/oracle/types/ballot.go index fd413a72c4..8abf577fb3 100755 --- a/x/oracle/types/ballot.go +++ b/x/oracle/types/ballot.go @@ -51,7 +51,17 @@ func (pb ExchangeRateBallot) ToCrossRate(bases map[string]sdk.Dec) (cb ExchangeR vote := pb[i] if exchangeRateRT, ok := bases[string(vote.Voter)]; ok && vote.ExchangeRate.IsPositive() { - vote.ExchangeRate = exchangeRateRT.Quo(vote.ExchangeRate) + // Quo will panic on overflow, so we wrap it in a defer/recover + func() { + defer func() { + if r := recover(); r != nil { + // if overflow, set exchange rate to 0 and power to 0 + vote.ExchangeRate = sdk.ZeroDec() + vote.Power = 0 + } + }() + vote.ExchangeRate = exchangeRateRT.Quo(vote.ExchangeRate) + }() } else { // If we can't get reference Sei exchange rate, we just convert the vote as abstain vote vote.ExchangeRate = sdk.ZeroDec()