From 543d1d2736e9870d69e677a7f54766f86baa6ff2 Mon Sep 17 00:00:00 2001 From: yys Date: Sun, 17 Nov 2019 17:21:11 +0900 Subject: [PATCH] [Feature] oracle refactor & add oracle slashing (#265) * storing changes to tally, abvi, and keeper * Revert "storing changes to tally, abvi, and keeper" This reverts commit 70445a82ca4a2d9d3bda0a0dedfb2f19f9afcde5. * stylistic refactor done - not building * Refactored oracle module. changing variable names Price references changed to ExhangeRate * Refactored rewards to be given out only for luna removed claimpool, as it is no longer being used * Implement oracle slashing * update client code for miss count querying * align comment & change DistributionPeriod to DistributionWindow & do swagger update * jail the validator after oracle slashing * * extract slashing from the keeper * reduce tally cost by computing vote power at organizing * split abstain vote for each denom --- app/export.go | 17 +- app/sim_test.go | 2 +- client/lcd/swagger-ui/swagger.yaml | 76 +++-- docs/specifications/market.md | 2 +- docs/specifications/oracle.md | 22 +- .../{upgrade-gaiad.sh => upgrade-terrad.sh} | 0 x/market/handler_test.go | 2 +- x/market/internal/keeper/keeper_test.go | 2 +- x/market/internal/keeper/querier_test.go | 2 +- x/market/internal/keeper/swap.go | 4 +- x/market/internal/keeper/swap_test.go | 6 +- x/market/internal/types/expected_keepers.go | 2 +- x/market/test_utils.go | 2 +- x/oracle/abci.go | 120 +++++--- x/oracle/abci_test.go | 270 ++++++++++++------ x/oracle/alias.go | 107 +++---- x/oracle/client/cli/query.go | 67 ++++- x/oracle/client/cli/tx.go | 56 ++-- x/oracle/client/rest/query.go | 49 +++- x/oracle/client/rest/rest.go | 1 - x/oracle/client/rest/tx.go | 22 +- x/oracle/genesis.go | 64 +++-- x/oracle/genesis_test.go | 14 +- x/oracle/handler.go | 46 +-- x/oracle/handler_test.go | 60 ++-- x/oracle/internal/keeper/ballot.go | 34 +++ x/oracle/internal/keeper/ballot_test.go | 63 ++++ x/oracle/internal/keeper/keeper.go | 226 ++++++++------- x/oracle/internal/keeper/keeper_test.go | 236 +++++++++------ x/oracle/internal/keeper/params.go | 26 +- x/oracle/internal/keeper/querier.go | 75 +++-- x/oracle/internal/keeper/querier_test.go | 75 ++--- x/oracle/internal/keeper/reward.go | 79 ++--- x/oracle/internal/keeper/reward_test.go | 14 +- x/oracle/internal/types/ballot.go | 57 ++-- x/oracle/internal/types/ballot_test.go | 157 ++++------ x/oracle/internal/types/claim.go | 19 ++ x/oracle/internal/types/claimpool.go | 46 --- x/oracle/internal/types/claimpool_test.go | 23 -- x/oracle/internal/types/codec.go | 6 +- x/oracle/internal/types/denom.go | 2 +- x/oracle/internal/types/errors.go | 30 +- x/oracle/internal/types/events.go | 18 +- x/oracle/internal/types/expected_keeper.go | 3 + x/oracle/internal/types/genesis.go | 39 +-- x/oracle/internal/types/keys.go | 20 +- x/oracle/internal/types/msgs.go | 118 ++++---- x/oracle/internal/types/msgs_test.go | 12 +- x/oracle/internal/types/params.go | 62 ++-- x/oracle/internal/types/params_test.go | 22 +- x/oracle/internal/types/querier.go | 32 ++- x/oracle/internal/types/test_utils.go | 17 +- x/oracle/internal/types/vote.go | 71 +++-- x/oracle/simulation/msgs.go | 10 +- x/oracle/slash_test.go | 33 +++ x/oracle/slashing.go | 35 +++ x/oracle/tally.go | 32 +-- x/oracle/test_utils.go | 4 +- x/treasury/internal/keeper/indicator_test.go | 70 ++++- x/treasury/internal/keeper/policy_test.go | 4 +- x/treasury/internal/keeper/seigniorage.go | 15 +- .../internal/keeper/seigniorage_test.go | 6 +- 62 files changed, 1698 insertions(+), 1108 deletions(-) rename networks/{upgrade-gaiad.sh => upgrade-terrad.sh} (100%) create mode 100644 x/oracle/internal/keeper/ballot.go create mode 100644 x/oracle/internal/keeper/ballot_test.go create mode 100644 x/oracle/internal/types/claim.go delete mode 100644 x/oracle/internal/types/claimpool.go delete mode 100644 x/oracle/internal/types/claimpool_test.go create mode 100644 x/oracle/slash_test.go create mode 100644 x/oracle/slashing.go diff --git a/app/export.go b/app/export.go index 3b0e7499a..0e6249d7f 100644 --- a/app/export.go +++ b/app/export.go @@ -168,21 +168,26 @@ func (app *TerraApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []s /* Handle oracle state. */ // Clear all prevotes - app.oracleKeeper.IteratePrevotes(ctx, func(prevote oracle.PricePrevote) (stop bool) { - app.oracleKeeper.DeletePrevote(ctx, prevote) + app.oracleKeeper.IterateExchangeRatePrevotes(ctx, func(prevote oracle.ExchangeRatePrevote) (stop bool) { + app.oracleKeeper.DeleteExchangeRatePrevote(ctx, prevote) return false }) // Clear all votes - app.oracleKeeper.IterateVotes(ctx, func(vote oracle.PriceVote) (stop bool) { - app.oracleKeeper.DeleteVote(ctx, vote) + app.oracleKeeper.IterateExchangeRateVotes(ctx, func(vote oracle.ExchangeRateVote) (stop bool) { + app.oracleKeeper.DeleteExchangeRateVote(ctx, vote) return false }) // Clear all prices - app.oracleKeeper.IterateLunaPrices(ctx, func(denom string, price sdk.Dec) bool { - app.oracleKeeper.DeletePrice(ctx, denom) + app.oracleKeeper.IterateLunaExchangeRates(ctx, func(denom string, _ sdk.Dec) bool { + app.oracleKeeper.DeleteLunaExchangeRate(ctx, denom) + return false + }) + + app.oracleKeeper.IterateMissCounters(ctx, func(operator sdk.ValAddress, _ int64) bool { + app.oracleKeeper.SetMissCounter(ctx, operator, 0) return false }) diff --git a/app/sim_test.go b/app/sim_test.go index 80fbdb7d8..f6d75ccb8 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -427,7 +427,7 @@ func testAndRunTxs(app *TerraApp) []simulation.WeightedOperation { }) return v }(nil), - oraclesim.SimulateMsgDelegateFeederPermission(app.oracleKeeper), + oraclesim.SimulateMsgDelegateFeedConsent(app.oracleKeeper), }, { func(_ *rand.Rand) int { diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index b23b81d46..a2f427d38 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -2140,7 +2140,7 @@ paths: description: Internal Server Error /oracle/denoms/{denom}/votes: post: - summary: Generate oracle price vote message containing price and salt for an prevote + summary: Generate oracle exchange rate vote message containing exchange rate and salt for an prevote tags: - Oracle produces: @@ -2165,7 +2165,7 @@ paths: 500: description: Internal Server Error get: - summary: Request to get the currently unelected outstanding price oracle vote + summary: Request to get the currently unelected outstanding exchange rate oracle vote tags: - Oracle produces: @@ -2182,14 +2182,14 @@ paths: schema: type: array items: - $ref: "#/definitions/PriceVote" + $ref: "#/definitions/ExchangeRateVote" 400: description: Bad Request 500: description: Internal Server Error /oracle/denoms/{denom}/votes/{validator}: get: - summary: Request to get the currently unelected outstanding price oracle vote + summary: Request to get the currently unelected outstanding exchange rate oracle vote tags: - Oracle produces: @@ -2210,14 +2210,14 @@ paths: schema: type: array items: - $ref: "#/definitions/PriceVote" + $ref: "#/definitions/ExchangeRateVote" 400: description: Bad Request 500: description: Internal Server Error /oracle/voters/{validator}/votes: get: - summary: Request to get the currently outstanding price oracle votes of a validator + summary: Request to get the currently outstanding exchange rate oracle votes of a validator tags: - Oracle produces: @@ -2233,14 +2233,14 @@ paths: schema: type: array items: - $ref: "#/definitions/PriceVote" + $ref: "#/definitions/ExchangeRateVote" 400: description: Bad Request 500: description: Internal Server Error /oracle/denoms/{denom}/prevotes: post: - summary: Generate oracle price prevote message containing hash of an vote + summary: Generate oracle exchange rate prevote message containing hash of an vote tags: - Oracle produces: @@ -2265,7 +2265,7 @@ paths: 500: description: Internal Server Error get: - summary: Request to get the currently outstanding price oracle prevote + summary: Request to get the currently outstanding exchange rate oracle prevote tags: - Oracle produces: @@ -2282,14 +2282,14 @@ paths: schema: type: array items: - $ref: "#/definitions/PricePrevote" + $ref: "#/definitions/ExchangeRatePrevote" 400: description: Bad Request 500: description: Internal Server Error /oracle/denoms/{denom}/prevotes/{validator}: get: - summary: Request to get the currently outstanding price oracle prevote + summary: Request to get the currently outstanding exchange rate oracle prevote tags: - Oracle produces: @@ -2310,14 +2310,14 @@ paths: schema: type: array items: - $ref: "#/definitions/PricePrevote" + $ref: "#/definitions/ExchangeRatePrevote" 400: description: Bad Request 500: description: Internal Server Error /oracle/voters/{validator}/prevotes: get: - summary: Request to get the currently outstanding price oracle prevotes of a validator + summary: Request to get the currently outstanding exchange rate oracle prevotes of a validator tags: - Oracle produces: @@ -2333,14 +2333,14 @@ paths: schema: type: array items: - $ref: "#/definitions/PricePrevote" + $ref: "#/definitions/ExchangeRatePrevote" 400: description: Bad Request 500: description: Internal Server Error - /oracle/denoms/{denom}/price: + /oracle/denoms/{denom}/exchange_rate: get: - summary: Get the current effective price in Luna for the asset + summary: Get the current effective exchange rate in Luna for the asset tags: - Oracle produces: @@ -2353,7 +2353,7 @@ paths: type: string responses: 200: - description: current price of denom i.e. "1000.0" + description: current exchange rate of denom i.e. "1000.0" schema: type: number example: "1872.000000000000000000" @@ -2361,9 +2361,9 @@ paths: description: Bad Request 500: description: Internal Server Error - /oracle/denoms/prices: + /oracle/denoms/exchange_rates: get: - summary: Get all activated denom coins + summary: Get all activated exchange rates tags: - Oracle produces: @@ -2445,6 +2445,30 @@ paths: description: Bad Request 500: description: Internal Server Error + /oracle/voters/{validator}/miss: + get: + summary: Get the number of vote periods missed in this oracle slash window. + tags: + - Oracle + produces: + - application/json + parameters: + - in: path + name: validator + description: oracle operator + required: true + type: string + responses: + 200: + description: OK + schema: + type: number + format: integer + example: "100" + 400: + description: Bad Request + 500: + description: Internal Server Error /oracle/parameters: get: summary: Get oracle params @@ -3314,10 +3338,10 @@ definitions: properties: base_req: $ref: "#/definitions/BaseReq" - price: + exchange_rate: type: number example: "1000.0" - description: "price of Luna in denom currency is to make provte hash; this field is required to submit prevote in case absense of hash" + description: "exchange rate of Luna in denom currency is to make provte hash; this field is required to submit prevote in case absense of hash" salt: type: string example: "abcd" @@ -3333,10 +3357,10 @@ definitions: properties: base_req: $ref: "#/definitions/BaseReq" - price: + exchange_rate: type: number example: "1000.0" - description: "proof price of Luna in denom currency was used to make prevote hash; initial prevote does not require this field" + description: "proof exchange rate of Luna in denom currency was used to make prevote hash; initial prevote does not require this field" salt: type: string example: "abcd" @@ -3350,10 +3374,10 @@ definitions: $ref: "#/definitions/BaseReq" feeder: $ref: "#/definitions/Address" - PriceVote: + ExchangeRateVote: type: object properties: - price: + exchange_rate: type: number example: "0.01241" denom: @@ -3361,7 +3385,7 @@ definitions: example: "ukrw" voter: $ref: "#/definitions/ValidatorAddress" - PricePrevote: + ExchangeRatePrevote: type: object properties: hash: diff --git a/docs/specifications/market.md b/docs/specifications/market.md index e9b0350c2..e85d0485c 100644 --- a/docs/specifications/market.md +++ b/docs/specifications/market.md @@ -6,7 +6,7 @@ The Market module facilitates the atomic swaps among terra currencies and luna. The market module adopts uni-swap modle to facilitate swaps between all terra currencies that have an active exchange rate with Luna registered with the Oracle module and Luna. -* A user can swap SDT \(TerraSDR\) and UST \(TerraUSD\) at the exchange rate registered with the oracle. For example, if Luna<>SDT exchange rate returned by `GetLunaPrice` by the oracle is 10, and Luna<>KRT exchange rate is 10,000, a swapping 1 SDT will return 1000 KRT. +* A user can swap SDT \(TerraSDR\) and UST \(TerraUSD\) at the exchange rate registered with the oracle. For example, if Luna<>SDT exchange rate returned by `GetLunaExchangeRate` by the oracle is 10, and Luna<>KRT exchange rate is 10,000, a swapping 1 SDT will return 1000 KRT. * A user cap swap any of the Terra currencies for Luna at the oracle exchange rate. Using the same exchange rates in the above example, a user can swap 1 SDT for 0.1 Luna, or 0.1 Luna for 1 SDT. ## Safety mechanisms for Luna swaps diff --git a/docs/specifications/oracle.md b/docs/specifications/oracle.md index c094c5279..674bd9d04 100644 --- a/docs/specifications/oracle.md +++ b/docs/specifications/oracle.md @@ -9,8 +9,8 @@ The objective of the oracle module is to get accurate exchange rates of Luna wit In order to get fair exchange rates, the oracle operates in the following way: * Let P = {P1, P2, ...} be a time series split up by `params.VotePeriod`, currently 1 minute. In each P, validators must submit two votes: - * A `MsgPricePrevote`, containing the SHA256 hash of the exchange rate of Luna is with respect to a Terra peg. For example, in order to support swaps for Terra currencies pegged to KRW, USD, SDR, three prevotes must be submitted each containing the uluna<>ukrw, uluna<>uusd, and uluna<>usdr exchange rates. - * A `MsgPriceVote`, containing the salt used to create the hash for the prevote submitted in P-1. + * A `MsgExchangeRatePrevote`, containing the SHA256 hash of the exchange rate of Luna is with respect to a Terra peg. For example, in order to support swaps for Terra currencies pegged to KRW, USD, SDR, three prevotes must be submitted each containing the uluna<>ukrw, uluna<>uusd, and uluna<>usdr exchange rates. + * A `MsgExchangeRateVote`, containing the salt used to create the hash for the prevote submitted in P-1. * At the end of each P, votes submitted are tallied. * The submitted salt of each vote is used to verify consistency with the prevote submitted by the validator in P-1. If the validator has not submitted a prevote, or the SHA256 resulting from the salt does not match the hash from the prevote, the vote is dropped. * For each currency, if the total voting power of submitted votes exceeds 50%, a weighted median price of the vote is taken and is record on-chain as the effective exchange rate for Luna w.r.t. said currency for P+1. @@ -31,10 +31,10 @@ Effectively this scheme forces the voter to commit to a firm price submission be ### Submit a prevote ```go -// MsgPricePrevote - struct for prevoting on the PriceVote. +// MsgExchangeRatePrevote - struct for prevoting on the ExchangeRateVote. // The purpose of prevote is to hide vote price with hash // which is formatted as hex string in SHA256("salt:price:denom:voter") -type MsgPricePrevote struct { +type MsgExchangeRatePrevote struct { Hash string `json:"hash"` // hex string Denom string `json:"denom"` Feeder sdk.AccAddress `json:"feeder"` @@ -42,7 +42,7 @@ type MsgPricePrevote struct { } ``` -The `MsgPricePrevote` is just the submission of the leading 20 bytes of the SHA256 hex string run over a string containing the metadata of the actual `MsgPriceVote` to follow in the next period. The string is of the format: `salt:price:denom:voter`. Note that since in the subsequent `MsgPriceVote` the salt will have to be revealed, the salt used must be regenerated for each prevote submission. +The `MsgExchangeRatePrevote` is just the submission of the leading 20 bytes of the SHA256 hex string run over a string containing the metadata of the actual `MsgExchangeRateVote` to follow in the next period. The string is of the format: `salt:price:denom:voter`. Note that since in the subsequent `MsgExchangeRateVote` the salt will have to be revealed, the salt used must be regenerated for each prevote submission. `Denom` is the denomination of the currency for which the vote is being cast. For example, if the voter wishes to submit a prevote for the usd, then the correct `Denom` is `uusd`. @@ -55,10 +55,10 @@ The price used in the hash must be the open market price of Luna, w.r.t. to the ### Submit a vote ```go -// MsgPriceVote - struct for voting on the price of Luna denominated in various Terra assets. +// MsgExchangeRateVote - struct for voting on the price of Luna denominated in various Terra assets. // For example, if the validator believes that the effective price of Luna in USD is 10.39, that's // what the price field would be, and if 1213.34 for KRW, same. -type MsgPriceVote struct { +type MsgExchangeRateVote struct { Price sdk.Dec `json:"price"` // the effective price of Luna in {Denom} Salt string `json:"salt"` Denom string `json:"denom"` @@ -67,12 +67,12 @@ type MsgPriceVote struct { } ``` -The `MsgPriceVote` contains the actual price vote. The `Salt` parameter must match the salt used to create the prevote, otherwise the voter cannot be rewarded. +The `MsgExchangeRateVote` contains the actual price vote. The `Salt` parameter must match the salt used to create the prevote, otherwise the voter cannot be rewarded. ### Delegate voting rights to another key -Validators may also elect to delegate voting rights to another key to prevent the block signing key from being kept online. To do so, they must submit a `MsgDelegateFeederPermission`, delegating their oracle voting rights to a `FeedDelegate`, which in turn sign `MsgPricePrevote` and `MsgPriceVote` on behalf of the validator. +Validators may also elect to delegate voting rights to another key to prevent the block signing key from being kept online. To do so, they must submit a `NewMsgDelegateFeedConsent`, delegating their oracle voting rights to a `Delegatee`, which in turn sign `MsgExchangeRatePrevote` and `MsgExchangeRateVote` on behalf of the validator. {% hint style="info" %} Make sure to populate the delegate address with some coins by which to pay fees. @@ -82,11 +82,11 @@ Make sure to populate the delegate address with some coins by which to pay fees. // MsgDelegateFeederPermission - struct for delegating oracle voting rights to another address. type MsgDelegateFeederPermission struct { Operator sdk.ValAddress `json:"operator"` - FeedDelegate sdk.AccAddress `json:"feed_delegate"` + Delegatee sdk.AccAddress `json:"delegatee"` } ``` -The `Operator` field contains the operator address of the validator. The `FeedDelegate` field is the address of the delegate account that will be submitting price related votes and prevotes on behalf of the `Operator`. +The `Operator` field contains the operator address of the validator. The `Delegatee` field is the address of the delegate account that will be submitting price related votes and prevotes on behalf of the `Operator`. ## Parameters diff --git a/networks/upgrade-gaiad.sh b/networks/upgrade-terrad.sh similarity index 100% rename from networks/upgrade-gaiad.sh rename to networks/upgrade-terrad.sh diff --git a/x/market/handler_test.go b/x/market/handler_test.go index ef18f4854..4e34dbabe 100644 --- a/x/market/handler_test.go +++ b/x/market/handler_test.go @@ -40,7 +40,7 @@ func TestSwapMsg(t *testing.T) { afterTerraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) diff := beforeTerraPoolDelta.Sub(afterTerraPoolDelta) - price, _ := input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroSDRDenom) + price, _ := input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroSDRDenom) require.Equal(t, price.MulInt(amt), diff.Abs()) swapMsg = NewMsgSwap(keeper.Addrs[0], offerCoin, core.MicroLunaDenom) diff --git a/x/market/internal/keeper/keeper_test.go b/x/market/internal/keeper/keeper_test.go index fa6cbdaee..6693a3b2f 100644 --- a/x/market/internal/keeper/keeper_test.go +++ b/x/market/internal/keeper/keeper_test.go @@ -26,7 +26,7 @@ func TestTerraPoolDeltaUpdate(t *testing.T) { // each pools move towards base pool func TestReplenishPools(t *testing.T) { input := CreateTestInput(t) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, sdk.OneDec()) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, sdk.OneDec()) basePool := input.MarketKeeper.BasePool(input.Ctx) terraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) diff --git a/x/market/internal/keeper/querier_test.go b/x/market/internal/keeper/querier_test.go index c510d7b57..f516afbe8 100644 --- a/x/market/internal/keeper/querier_test.go +++ b/x/market/internal/keeper/querier_test.go @@ -49,7 +49,7 @@ func TestQuerySwap(t *testing.T) { input := CreateTestInput(t) price := sdk.NewDecWithPrec(17, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, price) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, price) querier := NewQuerier(input.MarketKeeper) var err error diff --git a/x/market/internal/keeper/swap.go b/x/market/internal/keeper/swap.go index 659e7c7be..ac71af7b5 100644 --- a/x/market/internal/keeper/swap.go +++ b/x/market/internal/keeper/swap.go @@ -119,12 +119,12 @@ func (k Keeper) ComputeInternalSwap(ctx sdk.Context, offerCoin sdk.DecCoin, askD return offerCoin, nil } - offerRate, err := k.oracleKeeper.GetLunaPrice(ctx, offerCoin.Denom) + offerRate, err := k.oracleKeeper.GetLunaExchangeRate(ctx, offerCoin.Denom) if err != nil { return sdk.DecCoin{}, types.ErrNoEffectivePrice(types.DefaultCodespace, offerCoin.Denom) } - askRate, err := k.oracleKeeper.GetLunaPrice(ctx, askDenom) + askRate, err := k.oracleKeeper.GetLunaExchangeRate(ctx, askDenom) if err != nil { return sdk.DecCoin{}, types.ErrNoEffectivePrice(types.DefaultCodespace, askDenom) } diff --git a/x/market/internal/keeper/swap_test.go b/x/market/internal/keeper/swap_test.go index 48f8c75e4..410234016 100644 --- a/x/market/internal/keeper/swap_test.go +++ b/x/market/internal/keeper/swap_test.go @@ -14,7 +14,7 @@ func TestApplySwapToPool(t *testing.T) { input := CreateTestInput(t) lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) offerCoin := sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(1000)) askCoin := sdk.NewDecCoin(core.MicroSDRDenom, sdk.NewInt(1700)) @@ -47,7 +47,7 @@ func TestComputeSwap(t *testing.T) { // Set Oracle Price lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) for i := 0; i < 100; i++ { swapAmountInSDR := lunaPriceInSDR.MulInt64(rand.Int63()%10000 + 2).TruncateInt() @@ -69,7 +69,7 @@ func TestComputeInternalSwap(t *testing.T) { // Set Oracle Price lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) for i := 0; i < 100; i++ { offerCoin := sdk.NewDecCoin(core.MicroSDRDenom, lunaPriceInSDR.MulInt64(rand.Int63()+1).TruncateInt()) diff --git a/x/market/internal/types/expected_keepers.go b/x/market/internal/types/expected_keepers.go index edb736edb..69844fd13 100644 --- a/x/market/internal/types/expected_keepers.go +++ b/x/market/internal/types/expected_keepers.go @@ -19,5 +19,5 @@ type SupplyKeeper interface { // OracleKeeper defines expected oracle keeper type OracleKeeper interface { - GetLunaPrice(ctx sdk.Context, denom string) (price sdk.Dec, err sdk.Error) + GetLunaExchangeRate(ctx sdk.Context, denom string) (price sdk.Dec, err sdk.Error) } diff --git a/x/market/test_utils.go b/x/market/test_utils.go index 32ffb975e..97845ce41 100644 --- a/x/market/test_utils.go +++ b/x/market/test_utils.go @@ -21,7 +21,7 @@ func setup(t *testing.T) (keeper.TestInput, sdk.Handler) { input := keeper.CreateTestInput(t) params := input.MarketKeeper.GetParams(input.Ctx) input.MarketKeeper.SetParams(input.Ctx, params) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, randomPrice) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, randomPrice) h := NewHandler(input.MarketKeeper) return input, h diff --git a/x/oracle/abci.go b/x/oracle/abci.go index e16dfad27..c4667a001 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -4,6 +4,7 @@ import ( "github.com/terra-project/core/x/oracle/internal/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/exported" core "github.com/terra-project/core/types" ) @@ -17,77 +18,116 @@ func EndBlocker(ctx sdk.Context, k Keeper) { return } - actives := k.GetActiveDenoms(ctx) - votes := k.CollectVotes(ctx) + // Build valid votes counter and winner map over all validators in active set + validVotesCounterMap := make(map[string]int64) + winnerMap := make(map[string]types.Claim) + k.StakingKeeper.IterateValidators(ctx, func(index int64, validator exported.ValidatorI) bool { - // Clear prices - for _, activeDenom := range actives { - k.DeletePrice(ctx, activeDenom) - } + // Exclude not bonded vaildator or jailed validators from tallying + if validator.IsBonded() && !validator.IsJailed() { + valAddr := validator.GetOperator() + validVotesCounterMap[valAddr.String()] = int64(0) + winnerMap[valAddr.String()] = types.NewClaim(0, valAddr) + } + + return false + }) - // Changes whitelist array to map for the fast lookup - whitelistMap := make(map[string]bool) + whitelist := make(map[string]bool) for _, denom := range k.Whitelist(ctx) { - whitelistMap[denom] = true + whitelist[denom] = true + } + + // Clear exchange rates + for denom := range whitelist { + k.DeleteLunaExchangeRate(ctx, denom) } - // Iterate through votes and update prices; drop if not enough votes have been achieved. - claimMap := make(map[string]types.Claim) - for denom, ballot := range votes { + // Organize votes to ballot by denom + // NOTE: **Filter out inative or jailed validators** + // NOTE: **Make abstain votes to have zero vote power** + voteMap := k.OrganizeBallotByDenom(ctx) + + // Iterate through ballots and update exchange rates; drop if not enough votes have been achieved. + for denom, ballot := range voteMap { - // Check whitelist; if denom is not exists or exists but the ballot is not passed, then skip - if _, exists := whitelistMap[denom]; !exists || !ballotIsPassing(ctx, ballot, k) { + // If denom is not in the whitelist, or the ballot for it has failed, then skip + if _, exists := whitelist[denom]; !exists || !ballotIsPassing(ctx, ballot, k) { continue } - // Get weighted median prices, and faithful respondants - mod, ballotWinners := tally(ctx, ballot, k) + // Get weighted median exchange rates, and faithful respondants + ballotMedian, ballotWinningClaims := tally(ctx, ballot, params.RewardBand) + + // Set the exchange rate + k.SetLunaExchangeRate(ctx, denom, ballotMedian) // Collect claims of ballot winners - for _, winner := range ballotWinners { - key := winner.Recipient.String() - claim, exists := claimMap[key] - if exists { - claim.Weight += winner.Weight - claimMap[key] = claim - } else { - claimMap[key] = winner - } + for _, ballotWinningClaim := range ballotWinningClaims { + key := ballotWinningClaim.Recipient.String() + + // Update claim + prevClaim := winnerMap[key] + prevClaim.Weight += ballotWinningClaim.Weight + winnerMap[key] = prevClaim + + // Increase valid votes counter + validVotesCounterMap[key]++ } - // Set price to the store - k.SetLunaPrice(ctx, denom, mod) + // Emit abci events ctx.EventManager().EmitEvent( - sdk.NewEvent(types.EventTypePriceUpdate, + sdk.NewEvent(types.EventTypeExchangeRateUpdate, sdk.NewAttribute(types.AttributeKeyDenom, denom), - sdk.NewAttribute(types.AttributeKeyPrice, mod.String()), + sdk.NewAttribute(types.AttributeKeyExchangeRate, ballotMedian.String()), ), ) } - // Convert map to array - var claimPool types.ClaimPool - for _, claim := range claimMap { - claimPool = append(claimPool, claim) + //--------------------------- + // Do miss counting & slashing + + whitelistLen := int64(len(whitelist)) + for operatorBechAddr, count := range validVotesCounterMap { + // Skip abstain & valid voters + if count == whitelistLen { + continue + } + + // Increase miss counter + operator, _ := sdk.ValAddressFromBech32(operatorBechAddr) // error never occur + k.SetMissCounter(ctx, operator, k.GetMissCounter(ctx, operator)+1) + } + + // Do slash who did miss voting over threshold and + // reset miss counters of all validators at the last block of slash window + if core.IsPeriodLastBlock(ctx, params.VotePeriod*params.SlashWindow) { + SlashAndResetMissCounters(ctx, k) } // Distribute rewards to ballot winners - k.RewardBallotWinners(ctx, claimPool) + k.RewardBallotWinners(ctx, winnerMap) + + // Clear the ballot + clearBallots(k, ctx, params) + return +} + +// clearBallots clears all tallied prevotes and votes from the store +func clearBallots(k Keeper, ctx sdk.Context, params Params) { // Clear all prevotes - k.IteratePrevotes(ctx, func(prevote PricePrevote) (stop bool) { + k.IterateExchangeRatePrevotes(ctx, func(prevote types.ExchangeRatePrevote) (stop bool) { if ctx.BlockHeight() > prevote.SubmitBlock+params.VotePeriod { - k.DeletePrevote(ctx, prevote) + k.DeleteExchangeRatePrevote(ctx, prevote) } return false }) // Clear all votes - k.IterateVotes(ctx, func(vote PriceVote) (stop bool) { - k.DeleteVote(ctx, vote) + k.IterateExchangeRateVotes(ctx, func(vote types.ExchangeRateVote) (stop bool) { + k.DeleteExchangeRateVote(ctx, vote) return false }) - - return } diff --git a/x/oracle/abci_test.go b/x/oracle/abci_test.go index 0851780bd..1332e0064 100644 --- a/x/oracle/abci_test.go +++ b/x/oracle/abci_test.go @@ -17,88 +17,88 @@ func TestOracleThreshold(t *testing.T) { input, h := setup(t) // Less than the threshold signs, msg fails - // Prevote without price + // Prevote without exchange rate salt := "1" - bz, err := VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err := VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.NoError(t, err) - prevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg := NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res := h(input.Ctx.WithBlockHeight(0), prevoteMsg) require.True(t, res.IsOK()) // Vote and new Prevote - voteMsg := NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + voteMsg := NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.True(t, res.IsOK()) EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) - _, err = input.OracleKeeper.GetLunaPrice(input.Ctx.WithBlockHeight(1), core.MicroSDRDenom) + _, err = input.OracleKeeper.GetLunaExchangeRate(input.Ctx.WithBlockHeight(1), core.MicroSDRDenom) require.NotNil(t, err) // More than the threshold signs, msg succeeds salt = "1" - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) h(input.Ctx.WithBlockHeight(0), prevoteMsg) - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) h(input.Ctx.WithBlockHeight(1), voteMsg) salt = "2" - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[1]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[1]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) h(input.Ctx.WithBlockHeight(0), prevoteMsg) - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) h(input.Ctx.WithBlockHeight(1), voteMsg) salt = "3" - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[2]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[2]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) h(input.Ctx.WithBlockHeight(0), prevoteMsg) - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) h(input.Ctx.WithBlockHeight(1), voteMsg) EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) - price, err := input.OracleKeeper.GetLunaPrice(input.Ctx.WithBlockHeight(1), core.MicroSDRDenom) + rate, err := input.OracleKeeper.GetLunaExchangeRate(input.Ctx.WithBlockHeight(1), core.MicroSDRDenom) require.Nil(t, err) - require.Equal(t, randomPrice, price) + require.Equal(t, randomExchangeRate, rate) val, _ := input.StakingKeeper.GetValidator(input.Ctx, keeper.ValAddrs[2]) input.StakingKeeper.Delegate(input.Ctx.WithBlockHeight(0), keeper.Addrs[2], stakingAmt.MulRaw(3), sdk.Unbonded, val, false) salt = "1" - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) h(input.Ctx.WithBlockHeight(0), prevoteMsg) - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) h(input.Ctx.WithBlockHeight(1), voteMsg) salt = "2" - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[1]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[1]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) h(input.Ctx.WithBlockHeight(0), prevoteMsg) - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) h(input.Ctx.WithBlockHeight(1), voteMsg) EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) - price, err = input.OracleKeeper.GetLunaPrice(input.Ctx.WithBlockHeight(1), core.MicroSDRDenom) + rate, err = input.OracleKeeper.GetLunaExchangeRate(input.Ctx.WithBlockHeight(1), core.MicroSDRDenom) require.NotNil(t, err) } @@ -107,100 +107,100 @@ func TestOracleMultiVote(t *testing.T) { // Less than the threshold signs, msg fails salt := "1" - bz, err := VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err := VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.NoError(t, err) - prevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg := NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res := h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[1]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[1]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[2]) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[2]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - bz, err = VoteHash(salt, anotherRandomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err = VoteHash(salt, anotherRandomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - bz, err = VoteHash(salt, anotherRandomPrice, core.MicroSDRDenom, keeper.ValAddrs[1]) + bz, err = VoteHash(salt, anotherRandomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[1]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - bz, err = VoteHash(salt, anotherRandomPrice, core.MicroSDRDenom, keeper.ValAddrs[2]) + bz, err = VoteHash(salt, anotherRandomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[2]) require.NoError(t, err) - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - // Reveal Price + // Reveal ExchangeRate input.Ctx = input.Ctx.WithBlockHeight(1) - voteMsg := NewMsgPriceVote(anotherRandomPrice, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + voteMsg := NewMsgExchangeRateVote(anotherRandomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx, voteMsg) require.True(t, res.IsOK()) - voteMsg = NewMsgPriceVote(anotherRandomPrice, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) + voteMsg = NewMsgExchangeRateVote(anotherRandomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[1]) res = h(input.Ctx, voteMsg) require.True(t, res.IsOK()) - voteMsg = NewMsgPriceVote(anotherRandomPrice, salt, core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) + voteMsg = NewMsgExchangeRateVote(anotherRandomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[2]) res = h(input.Ctx, voteMsg) require.True(t, res.IsOK()) EndBlocker(input.Ctx, input.OracleKeeper) - price, err := input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroSDRDenom) + rate, err := input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroSDRDenom) require.Nil(t, err) - require.Equal(t, price, anotherRandomPrice) + require.Equal(t, rate, anotherRandomExchangeRate) } func TestOracleDrop(t *testing.T) { input, h := setup(t) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, randomPrice) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, randomExchangeRate) // Account 1, KRW - makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomPrice, 0) + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) // Immediately swap halt after an illiquid oracle vote EndBlocker(input.Ctx, input.OracleKeeper) - _, err := input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroKRWDenom) + _, err := input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroKRWDenom) require.NotNil(t, err) } func TestOracleTally(t *testing.T) { input, _ := setup(t) - ballot := PriceBallot{} - prices, valAddrs, stakingKeeper := types.GenerateRandomTestCase() + ballot := ExchangeRateBallot{} + rates, valAddrs, stakingKeeper := types.GenerateRandomTestCase() input.OracleKeeper.StakingKeeper = stakingKeeper h := NewHandler(input.OracleKeeper) - for i, price := range prices { + for i, rate := range rates { - decPrice := sdk.NewDecWithPrec(int64(price*math.Pow10(keeper.OracleDecPrecision)), int64(keeper.OracleDecPrecision)) + decExchangeRate := sdk.NewDecWithPrec(int64(rate*math.Pow10(keeper.OracleDecPrecision)), int64(keeper.OracleDecPrecision)) salt := string(i) - bz, err := VoteHash(salt, decPrice, core.MicroSDRDenom, valAddrs[i]) + bz, err := VoteHash(salt, decExchangeRate, core.MicroSDRDenom, valAddrs[i]) require.NoError(t, err) - prevoteMsg := NewMsgPricePrevote( + prevoteMsg := NewMsgExchangeRatePrevote( hex.EncodeToString(bz), core.MicroSDRDenom, sdk.AccAddress(valAddrs[i]), @@ -210,8 +210,8 @@ func TestOracleTally(t *testing.T) { res := h(input.Ctx.WithBlockHeight(0), prevoteMsg) require.True(t, res.IsOK()) - voteMsg := NewMsgPriceVote( - decPrice, + voteMsg := NewMsgExchangeRateVote( + decExchangeRate, salt, core.MicroSDRDenom, sdk.AccAddress(valAddrs[i]), @@ -221,7 +221,7 @@ func TestOracleTally(t *testing.T) { res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.True(t, res.IsOK()) - vote := NewPriceVote(decPrice, core.MicroSDRDenom, valAddrs[i]) + vote := NewVoteForTally(NewExchangeRateVote(decExchangeRate, core.MicroSDRDenom, valAddrs[i]), stakingAmt.QuoRaw(core.MicroUnit).Int64()) ballot = append(ballot, vote) // change power of every three validator @@ -231,8 +231,8 @@ func TestOracleTally(t *testing.T) { } rewardees := []sdk.AccAddress{} - weightedMedian := ballot.WeightedMedian(input.Ctx, stakingKeeper) - standardDeviation := ballot.StandardDeviation(input.Ctx, stakingKeeper) + weightedMedian := ballot.WeightedMedian() + standardDeviation := ballot.StandardDeviation() maxSpread := input.OracleKeeper.RewardBand(input.Ctx).QuoInt64(2) if standardDeviation.GT(maxSpread) { @@ -240,12 +240,12 @@ func TestOracleTally(t *testing.T) { } for _, vote := range ballot { - if vote.Price.GTE(weightedMedian.Sub(maxSpread)) && vote.Price.LTE(weightedMedian.Add(maxSpread)) { + if vote.ExchangeRate.GTE(weightedMedian.Sub(maxSpread)) && vote.ExchangeRate.LTE(weightedMedian.Add(maxSpread)) { rewardees = append(rewardees, sdk.AccAddress(vote.Voter)) } } - tallyMedian, ballotWinner := tally(input.Ctx, ballot, input.OracleKeeper) + tallyMedian, ballotWinner := tally(input.Ctx, ballot, input.OracleKeeper.RewardBand(input.Ctx)) require.Equal(t, len(rewardees), len(ballotWinner)) require.Equal(t, tallyMedian.MulInt64(100).TruncateInt(), weightedMedian.MulInt64(100).TruncateInt()) @@ -256,7 +256,7 @@ func TestOracleTallyTiming(t *testing.T) { // all the keeper.Addrs vote for the block ... not last period block yet, so tally fails for i := range keeper.Addrs { - makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomPrice, i) + makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomExchangeRate, i) } params := input.OracleKeeper.GetParams(input.Ctx) @@ -265,13 +265,13 @@ func TestOracleTallyTiming(t *testing.T) { require.Equal(t, 0, int(input.Ctx.BlockHeight())) EndBlocker(input.Ctx, input.OracleKeeper) - _, err := input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroSDRDenom) + _, err := input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroSDRDenom) require.Error(t, err) input.Ctx = input.Ctx.WithBlockHeight(params.VotePeriod - 1) EndBlocker(input.Ctx, input.OracleKeeper) - _, err = input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroSDRDenom) + _, err = input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroSDRDenom) require.NoError(t, err) } @@ -279,76 +279,178 @@ func TestOracleRewardDistribution(t *testing.T) { input, h := setup(t) // Account 1, SDR - makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomPrice, 0) + makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomExchangeRate, 0) // Account 2, SDR - makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomPrice, 1) + makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomExchangeRate, 1) rewardsAmt := sdk.NewInt(100000000) moduleAcc := input.SupplyKeeper.GetModuleAccount(input.Ctx.WithBlockHeight(1), ModuleName) - err := moduleAcc.SetCoins(sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, rewardsAmt))) + err := moduleAcc.SetCoins(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, rewardsAmt))) require.NoError(t, err) input.SupplyKeeper.SetModuleAccount(input.Ctx.WithBlockHeight(1), moduleAcc) EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) - votePeriod := input.OracleKeeper.VotePeriod(input.Ctx) - rewardDistributionPeriod := input.OracleKeeper.RewardDistributionPeriod(input.Ctx) - expectedRewardAmt := sdk.NewDecFromInt(rewardsAmt.QuoRaw(2)).MulInt64(votePeriod).QuoInt64(rewardDistributionPeriod).TruncateInt() + rewardDistributionWindow := input.OracleKeeper.RewardDistributionWindow(input.Ctx) + expectedRewardAmt := sdk.NewDecFromInt(rewardsAmt.QuoRaw(2)).QuoInt64(rewardDistributionWindow).TruncateInt() rewards := input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), keeper.ValAddrs[0]) - require.Equal(t, expectedRewardAmt, rewards.AmountOf(core.MicroSDRDenom).TruncateInt()) + require.Equal(t, expectedRewardAmt, rewards.AmountOf(core.MicroLunaDenom).TruncateInt()) rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), keeper.ValAddrs[1]) - require.Equal(t, expectedRewardAmt, rewards.AmountOf(core.MicroSDRDenom).TruncateInt()) + require.Equal(t, expectedRewardAmt, rewards.AmountOf(core.MicroLunaDenom).TruncateInt()) } func TestOracleMultiRewardDistribution(t *testing.T) { input, h := setup(t) // Account 1, SDR - makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomPrice, 0) + makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomExchangeRate, 0) // Account 1, KRW - makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomPrice, 0) + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) // Account 2, SDR - makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomPrice, 1) + makePrevoteAndVote(t, input, h, 0, core.MicroSDRDenom, randomExchangeRate, 1) // Account 3, KRW - makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomPrice, 2) + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 2) rewardAmt := sdk.NewInt(100000000) moduleAcc := input.SupplyKeeper.GetModuleAccount(input.Ctx.WithBlockHeight(1), ModuleName) - err := moduleAcc.SetCoins(sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, rewardAmt))) + err := moduleAcc.SetCoins(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, rewardAmt))) require.NoError(t, err) input.SupplyKeeper.SetModuleAccount(input.Ctx.WithBlockHeight(1), moduleAcc) EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) - votePeriod := input.OracleKeeper.VotePeriod(input.Ctx) - rewardDistributedPeriod := input.OracleKeeper.RewardDistributionPeriod(input.Ctx) - expectedRewardAmt := sdk.NewDecFromInt(rewardAmt.QuoRaw(2)).MulInt64(votePeriod).QuoInt64(rewardDistributedPeriod).TruncateInt() - expectedRewardAmt2 := sdk.NewDecFromInt(rewardAmt.QuoRaw(4)).MulInt64(votePeriod).QuoInt64(rewardDistributedPeriod).TruncateInt() + rewardDistributedWindow := input.OracleKeeper.RewardDistributionWindow(input.Ctx) + expectedRewardAmt := sdk.NewDecFromInt(rewardAmt.QuoRaw(2)).QuoInt64(rewardDistributedWindow).TruncateInt() + expectedRewardAmt2 := sdk.NewDecFromInt(rewardAmt.QuoRaw(4)).QuoInt64(rewardDistributedWindow).TruncateInt() rewards := input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), keeper.ValAddrs[0]) - require.Equal(t, expectedRewardAmt, rewards.AmountOf(core.MicroSDRDenom).TruncateInt()) + require.Equal(t, expectedRewardAmt, rewards.AmountOf(core.MicroLunaDenom).TruncateInt()) rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), keeper.ValAddrs[1]) - require.Equal(t, expectedRewardAmt2, rewards.AmountOf(core.MicroSDRDenom).TruncateInt()) + require.Equal(t, expectedRewardAmt2, rewards.AmountOf(core.MicroLunaDenom).TruncateInt()) rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), keeper.ValAddrs[2]) - require.Equal(t, expectedRewardAmt2, rewards.AmountOf(core.MicroSDRDenom).TruncateInt()) + require.Equal(t, expectedRewardAmt2, rewards.AmountOf(core.MicroLunaDenom).TruncateInt()) } -func makePrevoteAndVote(t *testing.T, input keeper.TestInput, h sdk.Handler, height int64, denom string, price sdk.Dec, idx int) { +func TestInvalidVotesSlashing(t *testing.T) { + input, h := setup(t) + params := input.OracleKeeper.GetParams(input.Ctx) + params.Whitelist = types.DenomList{core.MicroKRWDenom} + input.OracleKeeper.SetParams(input.Ctx, params) + + slashWindow := input.OracleKeeper.SlashWindow(input.Ctx) + slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) + minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + + for i := int64(0); i < sdk.OneDec().Sub(minValidPerWindow).MulInt64(slashWindow).TruncateInt64(); i++ { + input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) + + // Account 1, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) + + // Account 2, KRW, miss vote + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate.Add(sdk.NewDec(100000000000000)), 1) + + // Account 3, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 2) + + EndBlocker(input.Ctx, input.OracleKeeper) + require.Equal(t, i+1, input.OracleKeeper.GetMissCounter(input.Ctx, keeper.ValAddrs[1])) + } + + validator := input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[1]) + require.Equal(t, stakingAmt, validator.GetBondedTokens()) + + // one more miss vote will inccur ValAddrs[1] slashing + // Account 1, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) + + // Account 2, KRW, miss vote + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate.Add(sdk.NewDec(100000000000000)), 1) + + // Account 3, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 2) + + input.Ctx = input.Ctx.WithBlockHeight(slashWindow - 1) + EndBlocker(input.Ctx, input.OracleKeeper) + validator = input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[1]) + require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens()) +} + +func TestWhitelistSlashing(t *testing.T) { + input, h := setup(t) + + slashWindow := input.OracleKeeper.SlashWindow(input.Ctx) + slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) + minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + + for i := int64(0); i < sdk.OneDec().Sub(minValidPerWindow).MulInt64(slashWindow).TruncateInt64(); i++ { + input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) + + // Account 1, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) + + EndBlocker(input.Ctx, input.OracleKeeper) + require.Equal(t, i+1, input.OracleKeeper.GetMissCounter(input.Ctx, keeper.ValAddrs[0])) + } + + validator := input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[0]) + require.Equal(t, stakingAmt, validator.GetBondedTokens()) + + // one more miss vote will inccur ValAddrs[1] slashing + // Account 1, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) + + input.Ctx = input.Ctx.WithBlockHeight(slashWindow - 1) + EndBlocker(input.Ctx, input.OracleKeeper) + validator = input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[0]) + require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens()) +} + +func TestAbstainSlashing(t *testing.T) { + input, h := setup(t) + params := input.OracleKeeper.GetParams(input.Ctx) + params.Whitelist = types.DenomList{core.MicroKRWDenom} + input.OracleKeeper.SetParams(input.Ctx, params) + + slashWindow := input.OracleKeeper.SlashWindow(input.Ctx) + minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + + for i := int64(0); i <= sdk.OneDec().Sub(minValidPerWindow).MulInt64(slashWindow).TruncateInt64(); i++ { + input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) + + // Account 1, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 0) + + // Account 2, KRW, miss vote + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, sdk.ZeroDec(), 1) + + // Account 3, KRW + makePrevoteAndVote(t, input, h, 0, core.MicroKRWDenom, randomExchangeRate, 2) + + EndBlocker(input.Ctx, input.OracleKeeper) + require.Equal(t, int64(0), input.OracleKeeper.GetMissCounter(input.Ctx, keeper.ValAddrs[1])) + } + + validator := input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[1]) + require.Equal(t, stakingAmt, validator.GetBondedTokens()) +} + +func makePrevoteAndVote(t *testing.T, input keeper.TestInput, h sdk.Handler, height int64, denom string, rate sdk.Dec, idx int) { // Account 1, SDR salt := "1" - bz, err := VoteHash(salt, price, denom, keeper.ValAddrs[idx]) + bz, err := VoteHash(salt, rate, denom, keeper.ValAddrs[idx]) require.NoError(t, err) - prevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), denom, keeper.Addrs[idx], keeper.ValAddrs[idx]) + prevoteMsg := NewMsgExchangeRatePrevote(hex.EncodeToString(bz), denom, keeper.Addrs[idx], keeper.ValAddrs[idx]) res := h(input.Ctx.WithBlockHeight(height), prevoteMsg) require.True(t, res.IsOK()) - voteMsg := NewMsgPriceVote(price, salt, denom, keeper.Addrs[idx], keeper.ValAddrs[idx]) + voteMsg := NewMsgExchangeRateVote(rate, salt, denom, keeper.Addrs[idx], keeper.ValAddrs[idx]) res = h(input.Ctx.WithBlockHeight(height+1), voteMsg) require.True(t, res.IsOK()) } diff --git a/x/oracle/alias.go b/x/oracle/alias.go index 2f9775483..fa1680b14 100644 --- a/x/oracle/alias.go +++ b/x/oracle/alias.go @@ -11,40 +11,44 @@ import ( ) const ( - DefaultCodespace = types.DefaultCodespace - CodeUnknownDenom = types.CodeUnknownDenom - CodeInvalidPrice = types.CodeInvalidPrice - CodeVoterNotValidator = types.CodeVoterNotValidator - CodeInvalidVote = types.CodeInvalidVote - CodeNoVotingPermission = types.CodeNoVotingPermission - CodeInvalidHashLength = types.CodeInvalidHashLength - CodeInvalidPrevote = types.CodeInvalidPrevote - CodeVerificationFailed = types.CodeVerificationFailed - CodeNotRevealPeriod = types.CodeNotRevealPeriod - CodeInvalidSaltLength = types.CodeInvalidSaltLength - CodeInvalidMsgFormat = types.CodeInvalidMsgFormat - ModuleName = types.ModuleName - StoreKey = types.StoreKey - RouterKey = types.RouterKey - QuerierRoute = types.QuerierRoute - DefaultParamspace = types.DefaultParamspace - DefaultVotePeriod = types.DefaultVotePeriod - DefaultVotesWindow = types.DefaultVotesWindow - QueryParameters = types.QueryParameters - QueryPrice = types.QueryPrice - QueryActives = types.QueryActives - QueryPrevotes = types.QueryPrevotes - QueryVotes = types.QueryVotes - QueryFeederDelegation = types.QueryFeederDelegation + DefaultCodespace = types.DefaultCodespace + CodeUnknownDenom = types.CodeUnknownDenom + CodeInvalidExchangeRate = types.CodeInvalidExchangeRate + CodeVoterNotValidator = types.CodeVoterNotValidator + CodeInvalidVote = types.CodeInvalidVote + CodeNoVotingPermission = types.CodeNoVotingPermission + CodeInvalidHashLength = types.CodeInvalidHashLength + CodeInvalidPrevote = types.CodeInvalidPrevote + CodeVerificationFailed = types.CodeVerificationFailed + CodeNotRevealPeriod = types.CodeNotRevealPeriod + CodeInvalidSaltLength = types.CodeInvalidSaltLength + CodeInvalidMsgFormat = types.CodeInvalidMsgFormat + ModuleName = types.ModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + DefaultParamspace = types.DefaultParamspace + DefaultVotePeriod = types.DefaultVotePeriod + DefaultSlashWindow = types.DefaultSlashWindow + DefaultRewardDistributionWindow = types.DefaultRewardDistributionWindow + QueryParameters = types.QueryParameters + QueryExchangeRate = types.QueryExchangeRate + QueryExchangeRates = types.QueryExchangeRates + QueryActives = types.QueryActives + QueryPrevotes = types.QueryPrevotes + QueryVotes = types.QueryVotes + QueryFeederDelegation = types.QueryFeederDelegation + QueryMissCounter = types.QueryMissCounter ) var ( // functions aliases + NewVoteForTally = types.NewVoteForTally NewClaim = types.NewClaim RegisterCodec = types.RegisterCodec ErrInvalidHashLength = types.ErrInvalidHashLength ErrUnknownDenomination = types.ErrUnknownDenomination - ErrInvalidPrice = types.ErrInvalidPrice + ErrInvalidExchangeRate = types.ErrInvalidExchangeRate ErrVerificationFailed = types.ErrVerificationFailed ErrNoPrevote = types.ErrNoPrevote ErrNoVote = types.ErrNoVote @@ -54,21 +58,23 @@ var ( NewGenesisState = types.NewGenesisState DefaultGenesisState = types.DefaultGenesisState ValidateGenesis = types.ValidateGenesis - GetPrevoteKey = types.GetPrevoteKey + GetExchangeRatePrevoteKey = types.GetExchangeRatePrevoteKey GetVoteKey = types.GetVoteKey - GetPriceKey = types.GetPriceKey + GetExchangeRateKey = types.GetExchangeRateKey GetFeederDelegationKey = types.GetFeederDelegationKey - NewMsgPricePrevote = types.NewMsgPricePrevote - NewMsgPriceVote = types.NewMsgPriceVote - NewMsgDelegateFeederPermission = types.NewMsgDelegateFeederPermission + GetMissCounterKey = types.GetMissCounterKey + NewMsgExchangeRatePrevote = types.NewMsgExchangeRatePrevote + NewMsgExchangeRateVote = types.NewMsgExchangeRateVote + NewMsgDelegateFeedConsent = types.NewMsgDelegateFeedConsent DefaultParams = types.DefaultParams - NewQueryPriceParams = types.NewQueryPriceParams + NewQueryExchangeRateParams = types.NewQueryExchangeRateParams NewQueryPrevotesParams = types.NewQueryPrevotesParams NewQueryVotesParams = types.NewQueryVotesParams NewQueryFeederDelegationParams = types.NewQueryFeederDelegationParams - NewPricePrevote = types.NewPricePrevote + NewQueryMissCounterParams = types.NewQueryMissCounterParams + NewExchangeRatePrevote = types.NewExchangeRatePrevote VoteHash = types.VoteHash - NewPriceVote = types.NewPriceVote + NewExchangeRateVote = types.NewExchangeRateVote NewKeeper = keeper.NewKeeper ParamKeyTable = keeper.ParamKeyTable NewQuerier = keeper.NewQuerier @@ -77,40 +83,45 @@ var ( ModuleCdc = types.ModuleCdc PrevoteKey = types.PrevoteKey VoteKey = types.VoteKey - PriceKey = types.PriceKey + ExchangeRateKey = types.ExchangeRateKey FeederDelegationKey = types.FeederDelegationKey + MissCounterKey = types.MissCounterKey ParamStoreKeyVotePeriod = types.ParamStoreKeyVotePeriod ParamStoreKeyVoteThreshold = types.ParamStoreKeyVoteThreshold ParamStoreKeyRewardBand = types.ParamStoreKeyRewardBand - ParamStoreKeyRewardDistributionPeriod = types.ParamStoreKeyRewardDistributionPeriod + ParamStoreKeyRewardDistributionWindow = types.ParamStoreKeyRewardDistributionWindow ParamStoreKeyWhitelist = types.ParamStoreKeyWhitelist + ParamStoreKeySlashFraction = types.ParamStoreKeySlashFraction + ParamStoreKeySlashWindow = types.ParamStoreKeySlashWindow + ParamStoreKeyMinValidPerWindow = types.ParamStoreKeyMinValidPerWindow DefaultVoteThreshold = types.DefaultVoteThreshold DefaultRewardBand = types.DefaultRewardBand - DefaultRewardDistributionPeriod = types.DefaultRewardDistributionPeriod - DefaultMinValidVotesPerWindow = types.DefaultMinValidVotesPerWindow DefaultWhitelist = types.DefaultWhitelist + DefaultSlashFraction = types.DefaultSlashFraction + DefaultMinValidPerWindow = types.DefaultMinValidPerWindow ) type ( - PriceBallot = types.PriceBallot + VoteForTally = types.VoteForTally + ExchangeRateBallot = types.ExchangeRateBallot Claim = types.Claim - ClaimPool = types.ClaimPool DenomList = types.DenomList StakingKeeper = types.StakingKeeper DistributionKeeper = types.DistributionKeeper SupplyKeeper = types.SupplyKeeper GenesisState = types.GenesisState - MsgPricePrevote = types.MsgPricePrevote - MsgPriceVote = types.MsgPriceVote - MsgDelegateFeederPermission = types.MsgDelegateFeederPermission + MsgExchangeRatePrevote = types.MsgExchangeRatePrevote + MsgExchangeRateVote = types.MsgExchangeRateVote + MsgDelegateFeedConsent = types.MsgDelegateFeedConsent Params = types.Params - QueryPriceParams = types.QueryPriceParams + QueryExchangeRateParams = types.QueryExchangeRateParams QueryPrevotesParams = types.QueryPrevotesParams QueryVotesParams = types.QueryVotesParams QueryFeederDelegationParams = types.QueryFeederDelegationParams - PricePrevote = types.PricePrevote - PricePrevotes = types.PricePrevotes - PriceVote = types.PriceVote - PriceVotes = types.PriceVotes + QueryMissCounterParams = types.QueryMissCounterParams + ExchangeRatePrevote = types.ExchangeRatePrevote + ExchangeRatePrevotes = types.ExchangeRatePrevotes + ExchangeRateVote = types.ExchangeRateVote + ExchangeRateVotes = types.ExchangeRateVotes Keeper = keeper.Keeper ) diff --git a/x/oracle/client/cli/query.go b/x/oracle/client/cli/query.go index 595a94121..bded70095 100644 --- a/x/oracle/client/cli/query.go +++ b/x/oracle/client/cli/query.go @@ -23,48 +23,49 @@ func GetQueryCmd(cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } oracleQueryCmd.AddCommand(client.GetCommands( - GetCmdQueryPrice(cdc), + GetCmdQueryExchangeRate(cdc), GetCmdQueryVotes(cdc), GetCmdQueryPrevotes(cdc), GetCmdQueryActive(cdc), GetCmdQueryParams(cdc), GetCmdQueryFeederDelegation(cdc), + GetCmdQueryMissCounter(cdc), )...) return oracleQueryCmd } -// GetCmdQueryPrice implements the query price command. -func GetCmdQueryPrice(cdc *codec.Codec) *cobra.Command { +// GetCmdQueryExchangeRate implements the query rate command. +func GetCmdQueryExchangeRate(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "price [denom]", + Use: "exchange-rate [denom]", Args: cobra.ExactArgs(1), Short: "Query the current Luna exchange rate w.r.t an asset", Long: strings.TrimSpace(` Query the current exchange rate of Luna with an asset. You can find the current list of active denoms by running: terracli query oracle active -$ terracli query oracle price ukrw +$ terracli query oracle exchange-rate ukrw `), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) denom := args[0] - params := types.NewQueryPriceParams(denom) + params := types.NewQueryExchangeRateParams(denom) bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { return err } - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryPrice), bz) + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryExchangeRate), bz) if err != nil { return err } - var price sdk.Dec - cdc.MustUnmarshalJSON(res, &price) - return cliCtx.PrintOutput(price) + var rate sdk.Dec + cdc.MustUnmarshalJSON(res, &rate) + return cliCtx.PrintOutput(rate) }, } return cmd @@ -140,7 +141,7 @@ returns oracle votes submitted by the validator for the denom uusd return err } - var matchingVotes types.PriceVotes + var matchingVotes types.ExchangeRateVotes cdc.MustUnmarshalJSON(res, &matchingVotes) return cliCtx.PrintOutput(matchingVotes) @@ -192,7 +193,7 @@ returns oracle prevotes submitted by the validator for denom uusd return err } - var matchingPrevotes types.PricePrevotes + var matchingPrevotes types.ExchangeRatePrevotes cdc.MustUnmarshalJSON(res, &matchingPrevotes) return cliCtx.PrintOutput(matchingPrevotes) @@ -228,7 +229,7 @@ func GetCmdQueryParams(cdc *codec.Codec) *cobra.Command { // GetCmdQueryFeederDelegation implements the query feeder delegation command func GetCmdQueryFeederDelegation(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "feeder-delegation [validator]", + Use: "feeder [validator]", Args: cobra.ExactArgs(1), Short: "Query the oracle feeder delegate account", Long: strings.TrimSpace(` @@ -264,3 +265,43 @@ $ terracli query oracle feeder terravaloper... return cmd } + +// GetCmdQueryMissCounter implements the query miss counter of the validator command +func GetCmdQueryMissCounter(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "miss [validator]", + Args: cobra.ExactArgs(1), + Short: "Query the # of the miss count", + Long: strings.TrimSpace(` +Query the # of vote periods missed in this oracle slash window. + +$ terracli query oracle miss terravaloper... +`), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + valString := args[0] + validator, err := sdk.ValAddressFromBech32(valString) + if err != nil { + return err + } + + params := types.NewQueryMissCounterParams(validator) + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryMissCounter), bz) + if err != nil { + return err + } + + var missCounter int64 + cdc.MustUnmarshalJSON(res, &missCounter) + return cliCtx.PrintOutput(sdk.NewInt(missCounter)) + }, + } + + return cmd +} diff --git a/x/oracle/client/cli/tx.go b/x/oracle/client/cli/tx.go index 09791c4a2..24a4fd9d1 100644 --- a/x/oracle/client/cli/tx.go +++ b/x/oracle/client/cli/tx.go @@ -30,29 +30,29 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { } oracleTxCmd.AddCommand(client.PostCommands( - GetCmdPricePrevote(cdc), - GetCmdPriceVote(cdc), + GetCmdExchangeRatePrevote(cdc), + GetCmdExchangeRateVote(cdc), GetCmdDelegateFeederPermission(cdc), )...) return oracleTxCmd } -// GetCmdPricePrevote will create a pricePrevote tx and sign it with the given key. -func GetCmdPricePrevote(cdc *codec.Codec) *cobra.Command { +// GetCmdExchangeRatePrevote will create a exchangeRatePrevote tx and sign it with the given key. +func GetCmdExchangeRatePrevote(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "prevote [salt] [price] [validator]", + Use: "prevote [salt] [exchange_rate] [validator]", Args: cobra.RangeArgs(2, 3), - Short: "Submit an oracle prevote for the price of Luna", + Short: "Submit an oracle prevote for the exchange rate of Luna", Long: strings.TrimSpace(` -Submit an oracle prevote for the price of Luna denominated in the input denom. -The purpose of prevote is to hide vote price with hash which is formatted -as hex string in SHA256("salt:price:denom:voter") +Submit an oracle prevote for the exchange rate of Luna denominated in the input denom. +The purpose of prevote is to hide vote exchnage rate with hash which is formatted +as hex string in SHA256("salt:exchange_rate:denom:voter") # Prevote $ terracli tx oracle prevote 1234 8888.0ukrw -where "ukrw" is the denominating currency, and "8888.0" is the price of micro Luna in micro KRW from the voter's point of view. +where "ukrw" is the denominating currency, and "8888.0" is the exchange rate of micro Luna in micro KRW from the voter's point of view. If voting from a voting delegate, set "validator" to the address of the validator to vote on behalf of: $ terracli tx oracle prevote 1234 8888.0ukrw terravaloper1... @@ -63,15 +63,15 @@ $ terracli tx oracle prevote 1234 8888.0ukrw terravaloper1... cliCtx := context.NewCLIContext().WithCodec(cdc) salt := args[0] - price, err := sdk.ParseDecCoin(args[1]) + rate, err := sdk.ParseDecCoin(args[1]) if err != nil { - return fmt.Errorf("given price {%s} is not a valid format; price should be formatted as DecCoin", price) + return fmt.Errorf("given exchange_rate {%s} is not a valid format; exchange_rate should be formatted as DecCoin", rate) } // Get from address voter := cliCtx.GetFromAddress() - denom := price.Denom - amount := price.Amount + denom := rate.Denom + amount := rate.Amount // By default the voter is voting on behalf of itself validator := sdk.ValAddress(voter) @@ -92,7 +92,7 @@ $ terracli tx oracle prevote 1234 8888.0ukrw terravaloper1... hash := hex.EncodeToString(hashBytes) - msg := types.NewMsgPricePrevote(hash, denom, voter, validator) + msg := types.NewMsgExchangeRatePrevote(hash, denom, voter, validator) err = msg.ValidateBasic() if err != nil { return err @@ -105,18 +105,18 @@ $ terracli tx oracle prevote 1234 8888.0ukrw terravaloper1... return cmd } -// GetCmdPriceVote will create a priceVote tx and sign it with the given key. -func GetCmdPriceVote(cdc *codec.Codec) *cobra.Command { +// GetCmdExchangeRateVote will create a exchangeRateVote tx and sign it with the given key. +func GetCmdExchangeRateVote(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "vote [salt] [price] [validator]", + Use: "vote [salt] [exchange_rate] [validator]", Args: cobra.RangeArgs(2, 3), - Short: "Submit an oracle vote for the price of Luna", + Short: "Submit an oracle vote for the exchange_rate of Luna", Long: strings.TrimSpace(` -Submit a vote for the price of Luna denominated in the input denom. Companion to a prevote submitted in the previous vote period. +Submit a vote for the exchange_rate of Luna w.r.t the input denom. Companion to a prevote submitted in the previous vote period. $ terracli tx oracle vote 1234 8890.0ukrw -where "ukrw" is the denominating currency, and "8890.0" is the price of micro Luna in micro KRW from the voter's point of view. +where "ukrw" is the denominating currency, and "8890.0" is the exchange rate of micro Luna in micro KRW from the voter's point of view. "salt" should match the salt used to generate the SHA256 hex in the associated pre-vote. @@ -129,15 +129,15 @@ $ terracli tx oracle vote 1234 8890.0ukrw terravaloper1.... cliCtx := context.NewCLIContext().WithCodec(cdc) salt := args[0] - price, err := sdk.ParseDecCoin(args[1]) + rate, err := sdk.ParseDecCoin(args[1]) if err != nil { - return fmt.Errorf("given price {%s} is not a valid format; price should be formatted as DecCoin", price) + return fmt.Errorf("given exchange_rate {%s} is not a valid format; exchange rate should be formatted as DecCoin", rate) } // Get from address voter := cliCtx.GetFromAddress() - denom := price.Denom - amount := price.Amount + denom := rate.Denom + amount := rate.Amount // By default the voter is voting on behalf of itself validator := sdk.ValAddress(voter) @@ -151,7 +151,7 @@ $ terracli tx oracle vote 1234 8890.0ukrw terravaloper1.... validator = parsedVal } - msg := types.NewMsgPriceVote(amount, salt, denom, voter, validator) + msg := types.NewMsgExchangeRateVote(amount, salt, denom, voter, validator) err = msg.ValidateBasic() if err != nil { return err @@ -171,7 +171,7 @@ func GetCmdDelegateFeederPermission(cdc *codec.Codec) *cobra.Command { Args: cobra.ExactArgs(1), Short: "Delegate the permission to vote for the oracle to an address", Long: strings.TrimSpace(` -Delegate the permission to vote for the oracle to an address. +Delegate the permission to submit exchange rate votes for the oracle to an address. Delegation can keep your validator operator key offline and use a separate replaceable key online. @@ -196,7 +196,7 @@ where "terra1..." is the address you want to delegate your voting rights to. return err } - msg := types.NewMsgDelegateFeederPermission(validator, feeder) + msg := types.NewMsgDelegateFeedConsent(validator, feeder) err = msg.ValidateBasic() if err != nil { return err diff --git a/x/oracle/client/rest/query.go b/x/oracle/client/rest/query.go index 4b4d1382d..490c48e18 100644 --- a/x/oracle/client/rest/query.go +++ b/x/oracle/client/rest/query.go @@ -19,11 +19,12 @@ func registerQueryRoute(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc(fmt.Sprintf("/oracle/denoms/{%s}/votes", RestDenom), queryVotesHandlerFunction(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/oracle/denoms/{%s}/votes/{%s}", RestDenom, RestVoter), queryVotesHandlerFunction(cliCtx)).Methods("GET") r.HandleFunc("/oracle/voter/{%s}/votes", queryVoterVotesHandlerFunction(cliCtx)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/oracle/denoms/{%s}/price", RestDenom), queryPriceHandlerFunction(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/oracle/denoms/{%s}/exchange_rate", RestDenom), queryExchangeRateHandlerFunction(cliCtx)).Methods("GET") r.HandleFunc("/oracle/denoms/actives", queryActivesHandlerFunction(cliCtx)).Methods("GET") - r.HandleFunc("/oracle/denoms/prices", queryPricesHandlerFunction(cliCtx)).Methods("GET") + r.HandleFunc("/oracle/denoms/exchange_rates", queryExchangeRatesHandlerFunction(cliCtx)).Methods("GET") r.HandleFunc("/oracle/parameters", queryParamsHandlerFn(cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/oracle/voters/{%s}/feeder", RestVoter), queryFeederDelegationHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/oracle/voters/{%s}/miss", RestVoter), queryMissHandlerFn(cliCtx)).Methods("GET") } func queryVotesHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { @@ -109,7 +110,7 @@ func queryPrevotesHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { } } -func queryPriceHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { +func queryExchangeRateHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { @@ -119,14 +120,14 @@ func queryPriceHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { vars := mux.Vars(r) denom := vars[RestDenom] - params := types.NewQueryPriceParams(denom) + params := types.NewQueryExchangeRateParams(denom) bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryPrice), bz) + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryExchangeRate), bz) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -137,14 +138,14 @@ func queryPriceHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { } } -func queryPricesHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { +func queryExchangeRatesHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryPrices), nil) + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryExchangeRates), nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return @@ -286,3 +287,37 @@ func queryFeederDelegationHandlerFn(cliCtx context.CLIContext) http.HandlerFunc rest.PostProcessResponse(w, cliCtx, res) } } + +func queryMissHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + + vars := mux.Vars(r) + voter := vars[RestVoter] + + validator, err := sdk.ValAddressFromBech32(voter) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := types.NewQueryMissCounterParams(validator) + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryMissCounter), bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/x/oracle/client/rest/rest.go b/x/oracle/client/rest/rest.go index 971c17187..b2c4bb20c 100644 --- a/x/oracle/client/rest/rest.go +++ b/x/oracle/client/rest/rest.go @@ -9,7 +9,6 @@ import ( const ( RestDenom = "denom" RestVoter = "voter" - RestPrice = "price" ) // RegisterRoutes registers oracle-related REST handlers to a router diff --git a/x/oracle/client/rest/tx.go b/x/oracle/client/rest/tx.go index 216d10c94..7785100c8 100644 --- a/x/oracle/client/rest/tx.go +++ b/x/oracle/client/rest/tx.go @@ -25,9 +25,9 @@ func resgisterTxRoute(cliCtx context.CLIContext, r *mux.Router) { type PrevoteReq struct { BaseReq rest.BaseReq `json:"base_req"` - Hash string `json:"hash"` - Price sdk.Dec `json:"price"` - Salt string `json:"salt"` + Hash string `json:"hash"` + ExchangeRate sdk.Dec `json:"exchange_rate"` + Salt string `json:"salt"` Validator string `json:"validator"` } @@ -66,9 +66,9 @@ func submitPrevoteHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { } } - // If hash is not given, then retrieve hash from price and salt - if len(req.Hash) == 0 && (!req.Price.Equal(sdk.ZeroDec()) && len(req.Salt) > 0) { - hashBytes, err := types.VoteHash(req.Salt, req.Price, denom, valAddress) + // If hash is not given, then retrieve hash from exchange_rate and salt + if len(req.Hash) == 0 && (!req.ExchangeRate.Equal(sdk.ZeroDec()) && len(req.Salt) > 0) { + hashBytes, err := types.VoteHash(req.Salt, req.ExchangeRate, denom, valAddress) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return @@ -78,7 +78,7 @@ func submitPrevoteHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { } // create the message - msg := types.NewMsgPricePrevote(req.Hash, denom, fromAddress, valAddress) + msg := types.NewMsgExchangeRatePrevote(req.Hash, denom, fromAddress, valAddress) err = msg.ValidateBasic() if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) @@ -93,8 +93,8 @@ func submitPrevoteHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { type VoteReq struct { BaseReq rest.BaseReq `json:"base_req"` - Price sdk.Dec `json:"price"` - Salt string `json:"salt"` + ExchangeRate sdk.Dec `json:"exchange_rate"` + Salt string `json:"salt"` Validator string `json:"validator"` } @@ -134,7 +134,7 @@ func submitVoteHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { } // create the message - msg := types.NewMsgPriceVote(req.Price, req.Salt, denom, fromAddress, valAddress) + msg := types.NewMsgExchangeRateVote(req.ExchangeRate, req.Salt, denom, fromAddress, valAddress) err = msg.ValidateBasic() if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) @@ -194,7 +194,7 @@ func submitDelegateHandlerFunction(cliCtx context.CLIContext) http.HandlerFunc { } // create the message - msg := types.NewMsgDelegateFeederPermission(valAddress, feeder) + msg := types.NewMsgDelegateFeedConsent(valAddress, feeder) err = msg.ValidateBasic() if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) diff --git a/x/oracle/genesis.go b/x/oracle/genesis.go index 6959822f4..2397e6317 100644 --- a/x/oracle/genesis.go +++ b/x/oracle/genesis.go @@ -12,19 +12,19 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { if err != nil { panic(err) } - keeper.SetFeedDelegate(ctx, delegator, delegatee) + keeper.SetOracleDelegate(ctx, delegator, delegatee) } - for _, prevote := range data.PricePrevotes { - keeper.AddPrevote(ctx, prevote) + for _, prevote := range data.ExchangeRatePrevotes { + keeper.AddExchangeRatePrevote(ctx, prevote) } - for _, vote := range data.PriceVotes { - keeper.AddVote(ctx, vote) + for _, vote := range data.ExchangeRateVotes { + keeper.AddExchangeRateVote(ctx, vote) } - for denom, price := range data.Prices { - keeper.SetLunaPrice(ctx, denom, price) + for denom, rate := range data.ExchangeRates { + keeper.SetLunaExchangeRate(ctx, denom, rate) } for delegatorBechAddr, delegatee := range data.FeederDelegations { @@ -32,19 +32,27 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { if err != nil { panic(err) } - keeper.SetFeedDelegate(ctx, delegator, delegatee) + keeper.SetOracleDelegate(ctx, delegator, delegatee) } - for _, prevote := range data.PricePrevotes { - keeper.AddPrevote(ctx, prevote) + for _, prevote := range data.ExchangeRatePrevotes { + keeper.AddExchangeRatePrevote(ctx, prevote) } - for _, vote := range data.PriceVotes { - keeper.AddVote(ctx, vote) + for _, vote := range data.ExchangeRateVotes { + keeper.AddExchangeRateVote(ctx, vote) } - for denom, price := range data.Prices { - keeper.SetLunaPrice(ctx, denom, price) + for denom, rate := range data.ExchangeRates { + keeper.SetLunaExchangeRate(ctx, denom, rate) + } + + for operatorBechAddr, missCounter := range data.MissCounters { + operator, err := sdk.ValAddressFromBech32(operatorBechAddr) + if err != nil { + panic(err) + } + keeper.SetMissCounter(ctx, operator, missCounter) } keeper.SetParams(ctx, data.Params) @@ -56,29 +64,35 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { params := keeper.GetParams(ctx) feederDelegations := make(map[string]sdk.AccAddress) - keeper.IterateFeederDelegations(ctx, func(delegator sdk.ValAddress, delegatee sdk.AccAddress) (stop bool) { + keeper.IterateOracleDelegates(ctx, func(delegator sdk.ValAddress, delegatee sdk.AccAddress) (stop bool) { bechAddr := delegator.String() feederDelegations[bechAddr] = delegatee return false }) - var pricePrevotes []PricePrevote - keeper.IteratePrevotes(ctx, func(prevote PricePrevote) (stop bool) { - pricePrevotes = append(pricePrevotes, prevote) + var exchangeRatePrevotes []ExchangeRatePrevote + keeper.IterateExchangeRatePrevotes(ctx, func(prevote ExchangeRatePrevote) (stop bool) { + exchangeRatePrevotes = append(exchangeRatePrevotes, prevote) + return false + }) + + var exchangeRateVotes []ExchangeRateVote + keeper.IterateExchangeRateVotes(ctx, func(vote ExchangeRateVote) (stop bool) { + exchangeRateVotes = append(exchangeRateVotes, vote) return false }) - var priceVotes []PriceVote - keeper.IterateVotes(ctx, func(vote PriceVote) (stop bool) { - priceVotes = append(priceVotes, vote) + rates := make(map[string]sdk.Dec) + keeper.IterateLunaExchangeRates(ctx, func(denom string, rate sdk.Dec) (stop bool) { + rates[denom] = rate return false }) - prices := make(map[string]sdk.Dec) - keeper.IterateLunaPrices(ctx, func(denom string, price sdk.Dec) bool { - prices[denom] = price + missCounters := make(map[string]int64) + keeper.IterateMissCounters(ctx, func(operator sdk.ValAddress, missCounter int64) (stop bool) { + missCounters[operator.String()] = missCounter return false }) - return NewGenesisState(params, pricePrevotes, priceVotes, prices, feederDelegations) + return NewGenesisState(params, exchangeRatePrevotes, exchangeRateVotes, rates, feederDelegations, missCounters) } diff --git a/x/oracle/genesis_test.go b/x/oracle/genesis_test.go index abb8ca18b..3ed708e70 100644 --- a/x/oracle/genesis_test.go +++ b/x/oracle/genesis_test.go @@ -13,16 +13,16 @@ import ( func TestExportInitGenesis(t *testing.T) { input, h := setup(t) - makePrevoteAndVote(t, input, h, 1, core.MicroSDRDenom, randomPrice, 0) - makePrevoteAndVote(t, input, h, 1, core.MicroSDRDenom, randomPrice, 1) - makePrevoteAndVote(t, input, h, 1, core.MicroSDRDenom, randomPrice.MulInt64(10000), 2) + makePrevoteAndVote(t, input, h, 1, core.MicroSDRDenom, randomExchangeRate, 0) + makePrevoteAndVote(t, input, h, 1, core.MicroSDRDenom, randomExchangeRate, 1) + makePrevoteAndVote(t, input, h, 1, core.MicroSDRDenom, randomExchangeRate.MulInt64(10000), 2) EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) - input.OracleKeeper.SetFeedDelegate(input.Ctx, keeper.ValAddrs[0], keeper.Addrs[1]) - input.OracleKeeper.AddPrevote(input.Ctx, NewPricePrevote("1234", "denom", sdk.ValAddress{}, int64(2))) - input.OracleKeeper.AddVote(input.Ctx, NewPriceVote(sdk.NewDec(1), "denom", sdk.ValAddress{})) - input.OracleKeeper.SetLunaPrice(input.Ctx, "denom", sdk.NewDec(123)) + input.OracleKeeper.SetOracleDelegate(input.Ctx, keeper.ValAddrs[0], keeper.Addrs[1]) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, NewExchangeRatePrevote("1234", "denom", sdk.ValAddress{}, int64(2))) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, NewExchangeRateVote(sdk.NewDec(1), "denom", sdk.ValAddress{})) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, "denom", sdk.NewDec(123)) genesis := ExportGenesis(input.Ctx, input.OracleKeeper) newInput := keeper.CreateTestInput(t) diff --git a/x/oracle/handler.go b/x/oracle/handler.go index ef5e268ff..d6092d76c 100644 --- a/x/oracle/handler.go +++ b/x/oracle/handler.go @@ -15,12 +15,12 @@ import ( func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { - case MsgPricePrevote: - return handleMsgPricePrevote(ctx, k, msg) - case MsgPriceVote: - return handleMsgPriceVote(ctx, k, msg) - case MsgDelegateFeederPermission: - return handleMsgDelegateFeederPermission(ctx, k, msg) + case MsgExchangeRatePrevote: + return handleMsgExchangeRatePrevote(ctx, k, msg) + case MsgExchangeRateVote: + return handleMsgExchangeRateVote(ctx, k, msg) + case MsgDelegateFeedConsent: + return handleMsgDelegateFeedConsent(ctx, k, msg) default: errMsg := fmt.Sprintf("Unrecognized oracle message type: %T", msg) return sdk.ErrUnknownRequest(errMsg).Result() @@ -28,10 +28,10 @@ func NewHandler(k Keeper) sdk.Handler { } } -// handleMsgPricePrevote handles a MsgPricePrevote -func handleMsgPricePrevote(ctx sdk.Context, keeper Keeper, ppm MsgPricePrevote) sdk.Result { +// handleMsgExchangeRatePrevote handles a MsgExchangeRatePrevote +func handleMsgExchangeRatePrevote(ctx sdk.Context, keeper Keeper, ppm MsgExchangeRatePrevote) sdk.Result { if !ppm.Feeder.Equals(ppm.Validator) { - delegate := keeper.GetFeedDelegate(ctx, ppm.Validator) + delegate := keeper.GetOracleDelegate(ctx, ppm.Validator) if !delegate.Equals(ppm.Feeder) { return ErrNoVotingPermission(keeper.Codespace(), ppm.Feeder, ppm.Validator).Result() } @@ -43,8 +43,8 @@ func handleMsgPricePrevote(ctx sdk.Context, keeper Keeper, ppm MsgPricePrevote) return staking.ErrNoValidatorFound(keeper.Codespace()).Result() } - prevote := NewPricePrevote(ppm.Hash, ppm.Denom, ppm.Validator, ctx.BlockHeight()) - keeper.AddPrevote(ctx, prevote) + prevote := NewExchangeRatePrevote(ppm.Hash, ppm.Denom, ppm.Validator, ctx.BlockHeight()) + keeper.AddExchangeRatePrevote(ctx, prevote) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( @@ -62,10 +62,10 @@ func handleMsgPricePrevote(ctx sdk.Context, keeper Keeper, ppm MsgPricePrevote) return sdk.Result{Events: ctx.EventManager().Events()} } -// handleMsgPriceVote handles a MsgPriceVote -func handleMsgPriceVote(ctx sdk.Context, keeper Keeper, pvm MsgPriceVote) sdk.Result { +// handleMsgExchangeRateVote handles a MsgExchangeRateVote +func handleMsgExchangeRateVote(ctx sdk.Context, keeper Keeper, pvm MsgExchangeRateVote) sdk.Result { if !pvm.Feeder.Equals(pvm.Validator) { - delegate := keeper.GetFeedDelegate(ctx, pvm.Validator) + delegate := keeper.GetOracleDelegate(ctx, pvm.Validator) if !delegate.Equals(pvm.Feeder) { return ErrNoVotingPermission(keeper.Codespace(), pvm.Feeder, pvm.Validator).Result() } @@ -80,7 +80,7 @@ func handleMsgPriceVote(ctx sdk.Context, keeper Keeper, pvm MsgPriceVote) sdk.Re params := keeper.GetParams(ctx) // Get prevote - prevote, err := keeper.GetPrevote(ctx, pvm.Denom, pvm.Validator) + prevote, err := keeper.GetExchangeRatePrevote(ctx, pvm.Denom, pvm.Validator) if err != nil { return ErrNoPrevote(keeper.Codespace(), pvm.Validator, pvm.Denom).Result() } @@ -90,9 +90,9 @@ func handleMsgPriceVote(ctx sdk.Context, keeper Keeper, pvm MsgPriceVote) sdk.Re return ErrNotRevealPeriod(keeper.Codespace()).Result() } - // If there is an prevote, we verify a price with prevote hash and move prevote to vote with given price + // If there is an prevote, we verify a exchange rate with prevote hash and move prevote to vote with given exchange rate bz, _ := hex.DecodeString(prevote.Hash) // prevote hash - bz2, err2 := VoteHash(pvm.Salt, pvm.Price, prevote.Denom, prevote.Voter) + bz2, err2 := VoteHash(pvm.Salt, pvm.ExchangeRate, prevote.Denom, prevote.Voter) if err2 != nil { return ErrVerificationFailed(keeper.Codespace(), bz, []byte{}).Result() } @@ -102,9 +102,9 @@ func handleMsgPriceVote(ctx sdk.Context, keeper Keeper, pvm MsgPriceVote) sdk.Re } // Add the vote to the store - vote := NewPriceVote(pvm.Price, prevote.Denom, prevote.Voter) - keeper.DeletePrevote(ctx, prevote) - keeper.AddVote(ctx, vote) + vote := NewExchangeRateVote(pvm.ExchangeRate, prevote.Denom, prevote.Voter) + keeper.DeleteExchangeRatePrevote(ctx, prevote) + keeper.AddExchangeRateVote(ctx, vote) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( @@ -122,8 +122,8 @@ func handleMsgPriceVote(ctx sdk.Context, keeper Keeper, pvm MsgPriceVote) sdk.Re return sdk.Result{Events: ctx.EventManager().Events()} } -// handleMsgDelegateFeederPermission handles a MsgDelegateFeederPermission -func handleMsgDelegateFeederPermission(ctx sdk.Context, keeper Keeper, dfpm MsgDelegateFeederPermission) sdk.Result { +// handleMsgDelegateFeedConsent handles a MsgDelegateFeedConsent +func handleMsgDelegateFeedConsent(ctx sdk.Context, keeper Keeper, dfpm MsgDelegateFeedConsent) sdk.Result { signer := dfpm.Operator // Check the delegator is a validator @@ -133,7 +133,7 @@ func handleMsgDelegateFeederPermission(ctx sdk.Context, keeper Keeper, dfpm MsgD } // Set the delegation - keeper.SetFeedDelegate(ctx, signer, dfpm.Delegatee) + keeper.SetOracleDelegate(ctx, signer, dfpm.Delegatee) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( diff --git a/x/oracle/handler_test.go b/x/oracle/handler_test.go index eeb225fee..979bcf538 100644 --- a/x/oracle/handler_test.go +++ b/x/oracle/handler_test.go @@ -22,26 +22,26 @@ func TestOracleFilters(t *testing.T) { res := h(input.Ctx, bankMsg) require.False(t, res.IsOK()) - // Case 2: Normal MsgPricePrevote submission goes through + // Case 2: Normal MsgExchangeRatePrevote submission goes through salt := "1" - bz, err := VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err := VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.Nil(t, err) - prevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg := NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) - // // Case 3: Normal MsgPriceVote submission goes through keeper.keeper.Addrs - voteMsg := NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + // // Case 3: Normal MsgExchangeRateVote submission goes through keeper.keeper.Addrs + voteMsg := NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.True(t, res.IsOK()) // Case 4: a non-validator sending an oracle message fails _, addrs := mock.GeneratePrivKeyAddressPairs(1) salt = "2" - bz, err = VoteHash(salt, randomPrice, core.MicroSDRDenom, sdk.ValAddress(addrs[0])) + bz, err = VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, sdk.ValAddress(addrs[0])) require.Nil(t, err) - prevoteMsg = NewMsgPricePrevote("", core.MicroSDRDenom, addrs[0], sdk.ValAddress(addrs[0])) + prevoteMsg = NewMsgExchangeRatePrevote("", core.MicroSDRDenom, addrs[0], sdk.ValAddress(addrs[0])) res = h(input.Ctx, prevoteMsg) require.False(t, res.IsOK()) } @@ -50,27 +50,27 @@ func TestPrevoteCheck(t *testing.T) { input, h := setup(t) salt := "1" - bz, err := VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err := VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.Nil(t, err) - pricePrevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) - res := h(input.Ctx, pricePrevoteMsg) + exchangeRatePrevoteMsg := NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + res := h(input.Ctx, exchangeRatePrevoteMsg) require.True(t, res.IsOK()) - // Invalid price reveal period - priceVoteMsg := NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, sdk.AccAddress(keeper.Addrs[0]), keeper.ValAddrs[0]) - res = h(input.Ctx, priceVoteMsg) + // Invalid exchange rate reveal period + exchangeRateVoteMsg := NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, sdk.AccAddress(keeper.Addrs[0]), keeper.ValAddrs[0]) + res = h(input.Ctx, exchangeRateVoteMsg) require.False(t, res.IsOK()) input.Ctx = input.Ctx.WithBlockHeight(2) - priceVoteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, sdk.AccAddress(keeper.Addrs[0]), keeper.ValAddrs[0]) - res = h(input.Ctx, priceVoteMsg) + exchangeRateVoteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, sdk.AccAddress(keeper.Addrs[0]), keeper.ValAddrs[0]) + res = h(input.Ctx, exchangeRateVoteMsg) require.False(t, res.IsOK()) - // valid price reveal submission + // valid exchange rate reveal submission input.Ctx = input.Ctx.WithBlockHeight(1) - priceVoteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, sdk.AccAddress(keeper.Addrs[0]), keeper.ValAddrs[0]) - res = h(input.Ctx, priceVoteMsg) + exchangeRateVoteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, sdk.AccAddress(keeper.Addrs[0]), keeper.ValAddrs[0]) + res = h(input.Ctx, exchangeRateVoteMsg) require.True(t, res.IsOK()) } @@ -79,55 +79,55 @@ func TestFeederDelegation(t *testing.T) { input, h := setup(t) salt := "1" - bz, err := VoteHash(salt, randomPrice, core.MicroSDRDenom, keeper.ValAddrs[0]) + bz, err := VoteHash(salt, randomExchangeRate, core.MicroSDRDenom, keeper.ValAddrs[0]) require.Nil(t, err) // Case 1: empty message - bankMsg := MsgDelegateFeederPermission{} + bankMsg := MsgDelegateFeedConsent{} res := h(input.Ctx, bankMsg) require.False(t, res.IsOK()) // Case 2: Normal Prevote - without delegation - prevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + prevoteMsg := NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) // Case 2.1: Normal Prevote - with delegation fails - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) res = h(input.Ctx, prevoteMsg) require.False(t, res.IsOK()) // Case 2.2: Normal Vote - without delegation - voteMsg := NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) + voteMsg := NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[0], keeper.ValAddrs[0]) res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.True(t, res.IsOK()) // Case 2.3: Normal Vote - with delegation fails - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.False(t, res.IsOK()) - // Case 3: Normal MsgDelegateFeederPermission succeeds - msg := NewMsgDelegateFeederPermission(keeper.ValAddrs[0], keeper.Addrs[1]) + // Case 3: Normal MsgDelegateFeedConsent succeeds + msg := NewMsgDelegateFeedConsent(keeper.ValAddrs[0], keeper.Addrs[1]) res = h(input.Ctx, msg) require.True(t, res.IsOK()) // Case 4.1: Normal Prevote - without delegation fails - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[0]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[0]) res = h(input.Ctx, prevoteMsg) require.False(t, res.IsOK()) // Case 4.2: Normal Prevote - with delegation succeeds - prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) + prevoteMsg = NewMsgExchangeRatePrevote(hex.EncodeToString(bz), core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) res = h(input.Ctx, prevoteMsg) require.True(t, res.IsOK()) // Case 4.3: Normal Vote - without delegation fails - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[0]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[2], keeper.ValAddrs[0]) res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.False(t, res.IsOK()) // Case 4.4: Normal Vote - with delegation succeeds - voteMsg = NewMsgPriceVote(randomPrice, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) + voteMsg = NewMsgExchangeRateVote(randomExchangeRate, salt, core.MicroSDRDenom, keeper.Addrs[1], keeper.ValAddrs[0]) res = h(input.Ctx.WithBlockHeight(1), voteMsg) require.True(t, res.IsOK()) } diff --git a/x/oracle/internal/keeper/ballot.go b/x/oracle/internal/keeper/ballot.go new file mode 100644 index 000000000..b3649defe --- /dev/null +++ b/x/oracle/internal/keeper/ballot.go @@ -0,0 +1,34 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/terra-project/core/x/oracle/internal/types" +) + +// OrganizeBallotByDenom collects all oracle votes for the period, categorized by the votes' denom parameter +func (k Keeper) OrganizeBallotByDenom(ctx sdk.Context) (votes map[string]types.ExchangeRateBallot) { + votes = map[string]types.ExchangeRateBallot{} + handler := func(vote types.ExchangeRateVote) (stop bool) { + validator := k.StakingKeeper.Validator(ctx, vote.Voter) + + // organize ballot only for the active validators + if validator != nil && validator.IsBonded() && !validator.IsJailed() { + power := validator.GetConsensusPower() + if !vote.ExchangeRate.IsPositive() { + // Make the power of abstain vote zero + power = 0 + } + + votes[vote.Denom] = append(votes[vote.Denom], + types.NewVoteForTally( + vote, + power, + ), + ) + } + + return false + } + k.IterateExchangeRateVotes(ctx, handler) + return +} diff --git a/x/oracle/internal/keeper/ballot_test.go b/x/oracle/internal/keeper/ballot_test.go new file mode 100644 index 000000000..a85139244 --- /dev/null +++ b/x/oracle/internal/keeper/ballot_test.go @@ -0,0 +1,63 @@ +package keeper + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/require" + + core "github.com/terra-project/core/types" + "github.com/terra-project/core/x/oracle/internal/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +func TestOrganize(t *testing.T) { + input := CreateTestInput(t) + + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + sh := staking.NewHandler(input.StakingKeeper) + ctx := input.Ctx + + // Validator created + got := sh(ctx, NewTestMsgCreateValidator(ValAddrs[0], PubKeys[0], amt)) + require.True(t, got.IsOK()) + got = sh(ctx, NewTestMsgCreateValidator(ValAddrs[1], PubKeys[1], amt)) + require.True(t, got.IsOK()) + got = sh(ctx, NewTestMsgCreateValidator(ValAddrs[2], PubKeys[2], amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, input.StakingKeeper) + + sdrBallot := types.ExchangeRateBallot{ + types.NewVoteForTally(types.NewExchangeRateVote(sdk.NewDec(17), core.MicroSDRDenom, ValAddrs[0]), power), + types.NewVoteForTally(types.NewExchangeRateVote(sdk.NewDec(10), core.MicroSDRDenom, ValAddrs[1]), power), + types.NewVoteForTally(types.NewExchangeRateVote(sdk.NewDec(6), core.MicroSDRDenom, ValAddrs[2]), power), + } + krwBallot := types.ExchangeRateBallot{ + types.NewVoteForTally(types.NewExchangeRateVote(sdk.NewDec(1000), core.MicroKRWDenom, ValAddrs[0]), power), + types.NewVoteForTally(types.NewExchangeRateVote(sdk.NewDec(1300), core.MicroKRWDenom, ValAddrs[1]), power), + types.NewVoteForTally(types.NewExchangeRateVote(sdk.NewDec(2000), core.MicroKRWDenom, ValAddrs[2]), power), + } + + for _, vote := range sdrBallot { + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote.ExchangeRateVote) + } + for _, vote := range krwBallot { + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote.ExchangeRateVote) + } + + // oranize votes by denom + ballotMap := input.OracleKeeper.OrganizeBallotByDenom(input.Ctx) + + // sort each ballot for comparison + sort.Sort(sdrBallot) + sort.Sort(krwBallot) + sort.Sort(ballotMap[core.MicroSDRDenom]) + sort.Sort(ballotMap[core.MicroKRWDenom]) + + require.Equal(t, sdrBallot, ballotMap[core.MicroSDRDenom]) + require.Equal(t, krwBallot, ballotMap[core.MicroKRWDenom]) + +} diff --git a/x/oracle/internal/keeper/keeper.go b/x/oracle/internal/keeper/keeper.go index 0b0af9e22..59498697e 100644 --- a/x/oracle/internal/keeper/keeper.go +++ b/x/oracle/internal/keeper/keeper.go @@ -57,15 +57,40 @@ func (k Keeper) Codespace() sdk.CodespaceType { } //----------------------------------- -// Prevote logic +// ExchangeRatePrevote logic -// IteratePrevotes iterates rate over prevotes in the store -func (k Keeper) IteratePrevotes(ctx sdk.Context, handler func(prevote types.PricePrevote) (stop bool)) { +// GetExchangeRatePrevote retrieves an oracle prevote from the store +func (k Keeper) GetExchangeRatePrevote(ctx sdk.Context, denom string, voter sdk.ValAddress) (prevote types.ExchangeRatePrevote, err sdk.Error) { + store := ctx.KVStore(k.storeKey) + b := store.Get(types.GetExchangeRatePrevoteKey(denom, voter)) + if b == nil { + err = types.ErrNoPrevote(k.codespace, voter, denom) + return + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &prevote) + return +} + +// AddExchangeRatePrevote adds an oracle prevote to the store +func (k Keeper) AddExchangeRatePrevote(ctx sdk.Context, prevote types.ExchangeRatePrevote) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(prevote) + store.Set(types.GetExchangeRatePrevoteKey(prevote.Denom, prevote.Voter), bz) +} + +// DeleteExchangeRatePrevote deletes an oracle prevote from the store +func (k Keeper) DeleteExchangeRatePrevote(ctx sdk.Context, prevote types.ExchangeRatePrevote) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.GetExchangeRatePrevoteKey(prevote.Denom, prevote.Voter)) +} + +// IterateExchangeRatePrevotes iterates rate over prevotes in the store +func (k Keeper) IterateExchangeRatePrevotes(ctx sdk.Context, handler func(prevote types.ExchangeRatePrevote) (stop bool)) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, types.PrevoteKey) defer iter.Close() for ; iter.Valid(); iter.Next() { - var prevote types.PricePrevote + var prevote types.ExchangeRatePrevote k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &prevote) if handler(prevote) { break @@ -73,13 +98,13 @@ func (k Keeper) IteratePrevotes(ctx sdk.Context, handler func(prevote types.Pric } } -// iteratePrevotesWithPrefix iterates over prevotes in the store with given prefix -func (k Keeper) iteratePrevotesWithPrefix(ctx sdk.Context, prefix []byte, handler func(vote types.PricePrevote) (stop bool)) { +// iterateExchangeRatePrevotesWithPrefix iterates over prevotes in the store with given prefix +func (k Keeper) iterateExchangeRatePrevotesWithPrefix(ctx sdk.Context, prefix []byte, handler func(vote types.ExchangeRatePrevote) (stop bool)) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, prefix) defer iter.Close() for ; iter.Valid(); iter.Next() { - var prevote types.PricePrevote + var prevote types.ExchangeRatePrevote k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &prevote) if handler(prevote) { break @@ -88,27 +113,15 @@ func (k Keeper) iteratePrevotesWithPrefix(ctx sdk.Context, prefix []byte, handle } //----------------------------------- -// Votes logic - -// CollectVotes collects all oracle votes for the period, categorized by the votes' denom parameter -func (k Keeper) CollectVotes(ctx sdk.Context) (votes map[string]types.PriceBallot) { - votes = map[string]types.PriceBallot{} - handler := func(vote types.PriceVote) (stop bool) { - votes[vote.Denom] = append(votes[vote.Denom], vote) - return false - } - k.IterateVotes(ctx, handler) +// ExchangeRateVotes logic - return -} - -// IterateVotes iterates over votes in the store -func (k Keeper) IterateVotes(ctx sdk.Context, handler func(vote types.PriceVote) (stop bool)) { +// IterateExchangeRateVotes iterates over votes in the store +func (k Keeper) IterateExchangeRateVotes(ctx sdk.Context, handler func(vote types.ExchangeRateVote) (stop bool)) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, types.VoteKey) defer iter.Close() for ; iter.Valid(); iter.Next() { - var vote types.PriceVote + var vote types.ExchangeRateVote k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vote) if handler(vote) { break @@ -116,13 +129,13 @@ func (k Keeper) IterateVotes(ctx sdk.Context, handler func(vote types.PriceVote) } } -// Iterate over votes in the store -func (k Keeper) iterateVotesWithPrefix(ctx sdk.Context, prefix []byte, handler func(vote types.PriceVote) (stop bool)) { +// Iterate over oracle votes in the store +func (k Keeper) iterateExchangeRateVotesWithPrefix(ctx sdk.Context, prefix []byte, handler func(vote types.ExchangeRateVote) (stop bool)) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, prefix) defer iter.Close() for ; iter.Valid(); iter.Next() { - var vote types.PriceVote + var vote types.ExchangeRateVote k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vote) if handler(vote) { break @@ -130,33 +143,8 @@ func (k Keeper) iterateVotesWithPrefix(ctx sdk.Context, prefix []byte, handler f } } -// GetPrevote retrieves a prevote from the store -func (k Keeper) GetPrevote(ctx sdk.Context, denom string, voter sdk.ValAddress) (prevote types.PricePrevote, err sdk.Error) { - store := ctx.KVStore(k.storeKey) - b := store.Get(types.GetPrevoteKey(denom, voter)) - if b == nil { - err = types.ErrNoPrevote(k.codespace, voter, denom) - return - } - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &prevote) - return -} - -// AddPrevote adds a prevote to the store -func (k Keeper) AddPrevote(ctx sdk.Context, prevote types.PricePrevote) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(prevote) - store.Set(types.GetPrevoteKey(prevote.Denom, prevote.Voter), bz) -} - -// DeletePrevote deletes a prevote from the store -func (k Keeper) DeletePrevote(ctx sdk.Context, prevote types.PricePrevote) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.GetPrevoteKey(prevote.Denom, prevote.Voter)) -} - -// Retrieves a vote from the store -func (k Keeper) getVote(ctx sdk.Context, denom string, voter sdk.ValAddress) (vote types.PriceVote, err sdk.Error) { +// Retrieves an oracle vote from the store +func (k Keeper) getExchangeRateVote(ctx sdk.Context, denom string, voter sdk.ValAddress) (vote types.ExchangeRateVote, err sdk.Error) { store := ctx.KVStore(k.storeKey) b := store.Get(types.GetVoteKey(denom, voter)) if b == nil { @@ -167,94 +155,70 @@ func (k Keeper) getVote(ctx sdk.Context, denom string, voter sdk.ValAddress) (vo return } -// AddVote adds a vote to the store -func (k Keeper) AddVote(ctx sdk.Context, vote types.PriceVote) { +// AddExchangeRateVote adds an oracle vote to the store +func (k Keeper) AddExchangeRateVote(ctx sdk.Context, vote types.ExchangeRateVote) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(vote) store.Set(types.GetVoteKey(vote.Denom, vote.Voter), bz) } -// DeleteVote deletes a vote from the store -func (k Keeper) DeleteVote(ctx sdk.Context, vote types.PriceVote) { +// DeleteExchangeRateVote deletes an oracle vote from the store +func (k Keeper) DeleteExchangeRateVote(ctx sdk.Context, vote types.ExchangeRateVote) { store := ctx.KVStore(k.storeKey) store.Delete(types.GetVoteKey(vote.Denom, vote.Voter)) } //----------------------------------- -// Price logic +// ExchangeRate logic -// GetLunaPrice gets the consensus exchange rate of Luna denominated in the denom asset from the store. -func (k Keeper) GetLunaPrice(ctx sdk.Context, denom string) (price sdk.Dec, err sdk.Error) { +// GetLunaExchangeRate gets the consensus exchange rate of Luna denominated in the denom asset from the store. +func (k Keeper) GetLunaExchangeRate(ctx sdk.Context, denom string) (exchangeRate sdk.Dec, err sdk.Error) { if denom == core.MicroLunaDenom { return sdk.OneDec(), nil } store := ctx.KVStore(k.storeKey) - b := store.Get(types.GetPriceKey(denom)) + b := store.Get(types.GetExchangeRateKey(denom)) if b == nil { return sdk.ZeroDec(), types.ErrUnknownDenomination(k.codespace, denom) } - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &price) + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &exchangeRate) return } -// SetLunaPrice sets the consensus exchange rate of Luna denominated in the denom asset to the store. -func (k Keeper) SetLunaPrice(ctx sdk.Context, denom string, price sdk.Dec) { +// SetLunaExchangeRate sets the consensus exchange rate of Luna denominated in the denom asset to the store. +func (k Keeper) SetLunaExchangeRate(ctx sdk.Context, denom string, exchangeRate sdk.Dec) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(exchangeRate) + store.Set(types.GetExchangeRateKey(denom), bz) +} + +// DeleteLunaExchangeRate deletes the consensus exchange rate of Luna denominated in the denom asset from the store. +func (k Keeper) DeleteLunaExchangeRate(ctx sdk.Context, denom string) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(price) - store.Set(types.GetPriceKey(denom), bz) + store.Delete(types.GetExchangeRateKey(denom)) } -// IterateLunaPrices iterates over luna prices in the store -func (k Keeper) IterateLunaPrices(ctx sdk.Context, handler func(denom string, price sdk.Dec) (stop bool)) { +// IterateLunaExchangeRates iterates over luna rates in the store +func (k Keeper) IterateLunaExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate sdk.Dec) (stop bool)) { store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, types.PriceKey) + iter := sdk.KVStorePrefixIterator(store, types.ExchangeRateKey) defer iter.Close() for ; iter.Valid(); iter.Next() { - denom := string(iter.Key()[len(types.PriceKey):]) - var price sdk.Dec - k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &price) - if handler(denom, price) { + denom := string(iter.Key()[len(types.ExchangeRateKey):]) + var exchangeRate sdk.Dec + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &exchangeRate) + if handler(denom, exchangeRate) { break } } } -// DeletePrice deletes the consensus exchange rate of Luna denominated in the denom asset from the store. -func (k Keeper) DeletePrice(ctx sdk.Context, denom string) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.GetPriceKey(denom)) -} - -// GetActiveDenoms returns all active oracle asset denoms from the store -func (k Keeper) GetActiveDenoms(ctx sdk.Context) (denoms types.DenomList) { - denoms = types.DenomList{} - - k.IterateLunaPrices(ctx, func(denom string, _ sdk.Dec) bool { - denoms = append(denoms, denom) - return false - }) - - return -} - -// GetLunaPrices returns all active oracle asset prices in sdk.DecCoins format from the store -func (k Keeper) GetLunaPrices(ctx sdk.Context) (prices sdk.DecCoins) { - prices = sdk.DecCoins{} - - k.IterateLunaPrices(ctx, func(denom string, price sdk.Dec) bool { - prices = append(prices, sdk.NewDecCoinFromDec(denom, price)) - return false - }) - - return -} - //----------------------------------- -// Feeder delegation logic +// Oracle delegation logic -// GetFeedDelegate gets the account address that the feeder right was delegated to by the validator operator. -func (k Keeper) GetFeedDelegate(ctx sdk.Context, operator sdk.ValAddress) (delegate sdk.AccAddress) { +// GetOracleDelegate gets the account address that the validator operator delegated oracle vote rights to +func (k Keeper) GetOracleDelegate(ctx sdk.Context, operator sdk.ValAddress) (delegate sdk.AccAddress) { store := ctx.KVStore(k.storeKey) b := store.Get(types.GetFeederDelegationKey(operator)) if b == nil { @@ -265,16 +229,15 @@ func (k Keeper) GetFeedDelegate(ctx sdk.Context, operator sdk.ValAddress) (deleg return } -// SetFeedDelegate sets the account address that the feeder right was delegated to by the validator operator. -func (k Keeper) SetFeedDelegate(ctx sdk.Context, operator sdk.ValAddress, delegatedFeeder sdk.AccAddress) { +// SetOracleDelegate sets the account address that the validator operator delegated oracle vote rights to +func (k Keeper) SetOracleDelegate(ctx sdk.Context, operator sdk.ValAddress, delegatedFeeder sdk.AccAddress) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(delegatedFeeder) store.Set(types.GetFeederDelegationKey(operator), bz) } -// IterateFeederDelegations iterates over the feeder delegations -// and performs a callback function -func (k Keeper) IterateFeederDelegations(ctx sdk.Context, +// IterateOracleDelegates iterates over the feed delegates and performs a callback function. +func (k Keeper) IterateOracleDelegates(ctx sdk.Context, handler func(delegator sdk.ValAddress, delegatee sdk.AccAddress) (stop bool)) { store := ctx.KVStore(k.storeKey) @@ -300,3 +263,44 @@ func (k Keeper) getRewardPool(ctx sdk.Context) sdk.Coins { acc := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName) return acc.GetCoins() } + +//----------------------------------- +// Miss counter logic + +// GetMissCounter retrives the # of vote periods missed in this oracle slash window +func (k Keeper) GetMissCounter(ctx sdk.Context, operator sdk.ValAddress) (missCounter int64) { + store := ctx.KVStore(k.storeKey) + b := store.Get(types.GetMissCounterKey(operator)) + if b == nil { + // By default the counter is zero + return 0 + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &missCounter) + return +} + +// SetMissCounter updates the # of vote periods missed in this oracle slash window +func (k Keeper) SetMissCounter(ctx sdk.Context, operator sdk.ValAddress, missCounter int64) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(missCounter) + store.Set(types.GetMissCounterKey(operator), bz) +} + +// IterateMissCounters iterates over the miss counters and performs a callback function. +func (k Keeper) IterateMissCounters(ctx sdk.Context, + handler func(operator sdk.ValAddress, missCounter int64) (stop bool)) { + + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.MissCounterKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + operator := sdk.ValAddress(iter.Key()[len(types.MissCounterKey):]) + + var missCounter int64 + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &missCounter) + + if handler(operator, missCounter) { + break + } + } +} diff --git a/x/oracle/internal/keeper/keeper_test.go b/x/oracle/internal/keeper/keeper_test.go index 190393315..c8cc680a6 100644 --- a/x/oracle/internal/keeper/keeper_test.go +++ b/x/oracle/internal/keeper/keeper_test.go @@ -15,30 +15,30 @@ import ( func TestPrevoteAddDelete(t *testing.T) { input := CreateTestInput(t) - prevote := types.NewPricePrevote("", core.MicroSDRDenom, sdk.ValAddress(Addrs[0]), 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote) + prevote := types.NewExchangeRatePrevote("", core.MicroSDRDenom, sdk.ValAddress(Addrs[0]), 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote) - KPrevote, err := input.OracleKeeper.GetPrevote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + KPrevote, err := input.OracleKeeper.GetExchangeRatePrevote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) require.NoError(t, err) require.Equal(t, prevote, KPrevote) - input.OracleKeeper.DeletePrevote(input.Ctx, prevote) - _, err = input.OracleKeeper.GetPrevote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + input.OracleKeeper.DeleteExchangeRatePrevote(input.Ctx, prevote) + _, err = input.OracleKeeper.GetExchangeRatePrevote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) require.Error(t, err) } func TestPrevoteIterate(t *testing.T) { input := CreateTestInput(t) - prevote1 := types.NewPricePrevote("", core.MicroSDRDenom, sdk.ValAddress(Addrs[0]), 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote1) + prevote1 := types.NewExchangeRatePrevote("", core.MicroSDRDenom, sdk.ValAddress(Addrs[0]), 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote1) - prevote2 := types.NewPricePrevote("", core.MicroSDRDenom, sdk.ValAddress(Addrs[1]), 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote2) + prevote2 := types.NewExchangeRatePrevote("", core.MicroSDRDenom, sdk.ValAddress(Addrs[1]), 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote2) i := 0 bigger := bytes.Compare(Addrs[0], Addrs[1]) - input.OracleKeeper.IteratePrevotes(input.Ctx, func(p types.PricePrevote) (stop bool) { + input.OracleKeeper.IterateExchangeRatePrevotes(input.Ctx, func(p types.ExchangeRatePrevote) (stop bool) { if (i == 0 && bigger == -1) || (i == 1 && bigger == 1) { require.Equal(t, prevote1, p) } else { @@ -49,10 +49,10 @@ func TestPrevoteIterate(t *testing.T) { return false }) - prevote3 := types.NewPricePrevote("", core.MicroLunaDenom, sdk.ValAddress(Addrs[2]), 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote3) + prevote3 := types.NewExchangeRatePrevote("", core.MicroLunaDenom, sdk.ValAddress(Addrs[2]), 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote3) - input.OracleKeeper.iteratePrevotesWithPrefix(input.Ctx, types.GetPrevoteKey(core.MicroLunaDenom, sdk.ValAddress{}), func(p types.PricePrevote) (stop bool) { + input.OracleKeeper.iterateExchangeRatePrevotesWithPrefix(input.Ctx, types.GetExchangeRatePrevoteKey(core.MicroLunaDenom, sdk.ValAddress{}), func(p types.ExchangeRatePrevote) (stop bool) { require.Equal(t, prevote3, p) return false }) @@ -61,32 +61,32 @@ func TestPrevoteIterate(t *testing.T) { func TestVoteAddDelete(t *testing.T) { input := CreateTestInput(t) - price := sdk.NewDec(1700) - vote := types.NewPriceVote(price, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) - input.OracleKeeper.AddVote(input.Ctx, vote) + rate := sdk.NewDec(1700) + vote := types.NewExchangeRateVote(rate, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote) - KVote, err := input.OracleKeeper.getVote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + KVote, err := input.OracleKeeper.getExchangeRateVote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) require.NoError(t, err) require.Equal(t, vote, KVote) - input.OracleKeeper.DeleteVote(input.Ctx, vote) - _, err = input.OracleKeeper.getVote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + input.OracleKeeper.DeleteExchangeRateVote(input.Ctx, vote) + _, err = input.OracleKeeper.getExchangeRateVote(input.Ctx, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) require.Error(t, err) } func TestVoteIterate(t *testing.T) { input := CreateTestInput(t) - price := sdk.NewDec(1700) - vote1 := types.NewPriceVote(price, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) - input.OracleKeeper.AddVote(input.Ctx, vote1) + rate := sdk.NewDec(1700) + vote1 := types.NewExchangeRateVote(rate, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote1) - vote2 := types.NewPriceVote(price, core.MicroSDRDenom, sdk.ValAddress(Addrs[1])) - input.OracleKeeper.AddVote(input.Ctx, vote2) + vote2 := types.NewExchangeRateVote(rate, core.MicroSDRDenom, sdk.ValAddress(Addrs[1])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote2) i := 0 bigger := bytes.Compare(Addrs[0], Addrs[1]) - input.OracleKeeper.IterateVotes(input.Ctx, func(p types.PriceVote) (stop bool) { + input.OracleKeeper.IterateExchangeRateVotes(input.Ctx, func(p types.ExchangeRateVote) (stop bool) { if (i == 0 && bigger == -1) || (i == 1 && bigger == 1) { require.Equal(t, vote1, p) } else { @@ -97,10 +97,10 @@ func TestVoteIterate(t *testing.T) { return false }) - vote3 := types.NewPriceVote(price, core.MicroLunaDenom, sdk.ValAddress(Addrs[2])) - input.OracleKeeper.AddVote(input.Ctx, vote3) + vote3 := types.NewExchangeRateVote(rate, core.MicroLunaDenom, sdk.ValAddress(Addrs[2])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote3) - input.OracleKeeper.iterateVotesWithPrefix(input.Ctx, types.GetVoteKey(core.MicroLunaDenom, sdk.ValAddress{}), func(p types.PriceVote) (stop bool) { + input.OracleKeeper.iterateExchangeRateVotesWithPrefix(input.Ctx, types.GetVoteKey(core.MicroLunaDenom, sdk.ValAddress{}), func(p types.ExchangeRateVote) (stop bool) { require.Equal(t, vote3, p) return false }) @@ -109,20 +109,20 @@ func TestVoteIterate(t *testing.T) { func TestVoteCollect(t *testing.T) { input := CreateTestInput(t) - price := sdk.NewDec(1700) - vote1 := types.NewPriceVote(price, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) - input.OracleKeeper.AddVote(input.Ctx, vote1) + rate := sdk.NewDec(1700) + vote1 := types.NewExchangeRateVote(rate, core.MicroSDRDenom, sdk.ValAddress(Addrs[0])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote1) - vote2 := types.NewPriceVote(price, core.MicroSDRDenom, sdk.ValAddress(Addrs[1])) - input.OracleKeeper.AddVote(input.Ctx, vote2) + vote2 := types.NewExchangeRateVote(rate, core.MicroSDRDenom, sdk.ValAddress(Addrs[1])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote2) - vote3 := types.NewPriceVote(price, core.MicroLunaDenom, sdk.ValAddress(Addrs[0])) - input.OracleKeeper.AddVote(input.Ctx, vote3) + vote3 := types.NewExchangeRateVote(rate, core.MicroLunaDenom, sdk.ValAddress(Addrs[0])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote3) - vote4 := types.NewPriceVote(price, core.MicroLunaDenom, sdk.ValAddress(Addrs[1])) - input.OracleKeeper.AddVote(input.Ctx, vote4) + vote4 := types.NewExchangeRateVote(rate, core.MicroLunaDenom, sdk.ValAddress(Addrs[1])) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote4) - collectedVotes := input.OracleKeeper.CollectVotes(input.Ctx) + collectedVotes := input.OracleKeeper.OrganizeBallotByDenom(input.Ctx) pb1 := collectedVotes[core.MicroSDRDenom] pb2 := collectedVotes[core.MicroLunaDenom] @@ -145,66 +145,72 @@ func TestVoteCollect(t *testing.T) { } } -func TestPrice(t *testing.T) { +func TestExchangeRate(t *testing.T) { input := CreateTestInput(t) - cnyPrice := sdk.NewDecWithPrec(839, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - gbpPrice := sdk.NewDecWithPrec(4995, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - krwPrice := sdk.NewDecWithPrec(2838, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - lunaPrice := sdk.NewDecWithPrec(3282384, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + cnyExchangeRate := sdk.NewDecWithPrec(839, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + gbpExchangeRate := sdk.NewDecWithPrec(4995, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + krwExchangeRate := sdk.NewDecWithPrec(2838, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + lunaExchangeRate := sdk.NewDecWithPrec(3282384, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - // Set & get prices - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroCNYDenom, cnyPrice) - price, err := input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroCNYDenom) + // Set & get rates + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroCNYDenom, cnyExchangeRate) + rate, err := input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroCNYDenom) require.NoError(t, err) - require.Equal(t, cnyPrice, price) + require.Equal(t, cnyExchangeRate, rate) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroGBPDenom, gbpPrice) - price, err = input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroGBPDenom) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroGBPDenom, gbpExchangeRate) + rate, err = input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroGBPDenom) require.NoError(t, err) - require.Equal(t, gbpPrice, price) + require.Equal(t, gbpExchangeRate, rate) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, krwPrice) - price, err = input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroKRWDenom) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, krwExchangeRate) + rate, err = input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroKRWDenom) require.NoError(t, err) - require.Equal(t, krwPrice, price) + require.Equal(t, krwExchangeRate, rate) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroLunaDenom, lunaPrice) - price, _ = input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroLunaDenom) - require.Equal(t, sdk.OneDec(), price) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroLunaDenom, lunaExchangeRate) + rate, _ = input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroLunaDenom) + require.Equal(t, sdk.OneDec(), rate) - input.OracleKeeper.DeletePrice(input.Ctx, core.MicroKRWDenom) - _, err = input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroKRWDenom) + input.OracleKeeper.DeleteLunaExchangeRate(input.Ctx, core.MicroKRWDenom) + _, err = input.OracleKeeper.GetLunaExchangeRate(input.Ctx, core.MicroKRWDenom) require.Error(t, err) - lunaPrices := input.OracleKeeper.GetLunaPrices(input.Ctx) - require.True(t, len(lunaPrices) == 3) + numExchangeRates := 0 + handler := func(denom string, exchangeRate sdk.Dec) (stop bool) { + numExchangeRates = numExchangeRates + 1 + return false + } + input.OracleKeeper.IterateLunaExchangeRates(input.Ctx, handler) + + require.True(t, numExchangeRates == 3) } -func TestIterateLunaPrices(t *testing.T) { +func TestIterateLunaExchangeRates(t *testing.T) { input := CreateTestInput(t) - cnyPrice := sdk.NewDecWithPrec(839, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - gbpPrice := sdk.NewDecWithPrec(4995, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - krwPrice := sdk.NewDecWithPrec(2838, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - lunaPrice := sdk.NewDecWithPrec(3282384, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + cnyExchangeRate := sdk.NewDecWithPrec(839, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + gbpExchangeRate := sdk.NewDecWithPrec(4995, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + krwExchangeRate := sdk.NewDecWithPrec(2838, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) + lunaExchangeRate := sdk.NewDecWithPrec(3282384, int64(OracleDecPrecision)).MulInt64(core.MicroUnit) - // Set & get prices - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroCNYDenom, cnyPrice) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroGBPDenom, gbpPrice) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, krwPrice) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroLunaDenom, lunaPrice) + // Set & get rates + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroCNYDenom, cnyExchangeRate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroGBPDenom, gbpExchangeRate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, krwExchangeRate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroLunaDenom, lunaExchangeRate) - input.OracleKeeper.IterateLunaPrices(input.Ctx, func(denom string, price sdk.Dec) (stop bool) { + input.OracleKeeper.IterateLunaExchangeRates(input.Ctx, func(denom string, rate sdk.Dec) (stop bool) { switch denom { case core.MicroCNYDenom: - require.Equal(t, cnyPrice, price) + require.Equal(t, cnyExchangeRate, rate) case core.MicroGBPDenom: - require.Equal(t, gbpPrice, price) + require.Equal(t, gbpExchangeRate, rate) case core.MicroKRWDenom: - require.Equal(t, krwPrice, price) + require.Equal(t, krwExchangeRate, rate) case core.MicroLunaDenom: - require.Equal(t, lunaPrice, price) + require.Equal(t, lunaExchangeRate, rate) } return false }) @@ -239,53 +245,101 @@ func TestParams(t *testing.T) { votePeriod := int64(10) voteThreshold := sdk.NewDecWithPrec(1, 10) oracleRewardBand := sdk.NewDecWithPrec(1, 2) - rewardDistributionPeriod := int64(10000000000000) + rewardDistributionWindow := int64(10000000000000) + slashFraction := sdk.NewDecWithPrec(1, 2) + slashWindow := int64(1000) + minValidPerWindow := sdk.NewDecWithPrec(1, 4) + whilelist := types.DenomList{ + core.MicroSDRDenom, + core.MicroKRWDenom, + } // Should really test validateParams, but skipping because obvious newParams := types.Params{ VotePeriod: votePeriod, VoteThreshold: voteThreshold, RewardBand: oracleRewardBand, - RewardDistributionPeriod: rewardDistributionPeriod, + RewardDistributionWindow: rewardDistributionWindow, + Whitelist: whilelist, + SlashFraction: slashFraction, + SlashWindow: slashWindow, + MinValidPerWindow: minValidPerWindow, } input.OracleKeeper.SetParams(input.Ctx, newParams) storedParams := input.OracleKeeper.GetParams(input.Ctx) require.NotNil(t, storedParams) - require.Equal(t, newParams, storedParams) + require.Equal(t, storedParams, newParams) } func TestFeederDelegation(t *testing.T) { input := CreateTestInput(t) // Test default getters and setters - delegate := input.OracleKeeper.GetFeedDelegate(input.Ctx, ValAddrs[0]) - require.Equal(t, delegate, Addrs[0]) + delegate := input.OracleKeeper.GetOracleDelegate(input.Ctx, ValAddrs[0]) + require.Equal(t, Addrs[0], delegate) - input.OracleKeeper.SetFeedDelegate(input.Ctx, ValAddrs[0], Addrs[1]) - delegate = input.OracleKeeper.GetFeedDelegate(input.Ctx, ValAddrs[0]) - require.Equal(t, delegate, Addrs[1]) + input.OracleKeeper.SetOracleDelegate(input.Ctx, ValAddrs[0], Addrs[1]) + delegate = input.OracleKeeper.GetOracleDelegate(input.Ctx, ValAddrs[0]) + require.Equal(t, Addrs[1], delegate) } func TestIterateFeederDelegations(t *testing.T) { input := CreateTestInput(t) // Test default getters and setters - delegate := input.OracleKeeper.GetFeedDelegate(input.Ctx, ValAddrs[0]) - require.Equal(t, delegate, Addrs[0]) + delegate := input.OracleKeeper.GetOracleDelegate(input.Ctx, ValAddrs[0]) + require.Equal(t, Addrs[0], delegate) - input.OracleKeeper.SetFeedDelegate(input.Ctx, ValAddrs[0], Addrs[1]) + input.OracleKeeper.SetOracleDelegate(input.Ctx, ValAddrs[0], Addrs[1]) var delegators []sdk.ValAddress var delegatees []sdk.AccAddress - input.OracleKeeper.IterateFeederDelegations(input.Ctx, func(delegator sdk.ValAddress, delegatee sdk.AccAddress) (stop bool) { + input.OracleKeeper.IterateOracleDelegates(input.Ctx, func(delegator sdk.ValAddress, delegatee sdk.AccAddress) (stop bool) { delegators = append(delegators, delegator) delegatees = append(delegatees, delegatee) return false }) - require.Equal(t, len(delegators), 1) - require.Equal(t, len(delegatees), 1) - require.Equal(t, delegators[0], ValAddrs[0]) - require.Equal(t, delegatees[0], Addrs[1]) + require.Equal(t, 1, len(delegators)) + require.Equal(t, 1, len(delegatees)) + require.Equal(t, ValAddrs[0], delegators[0]) + require.Equal(t, Addrs[1], delegatees[0]) +} + +func TestMissCounter(t *testing.T) { + input := CreateTestInput(t) + + // Test default getters and setters + counter := input.OracleKeeper.GetMissCounter(input.Ctx, ValAddrs[0]) + require.Equal(t, int64(0), counter) + + missCounter := int64(10) + input.OracleKeeper.SetMissCounter(input.Ctx, ValAddrs[0], missCounter) + counter = input.OracleKeeper.GetMissCounter(input.Ctx, ValAddrs[0]) + require.Equal(t, missCounter, counter) +} + +func TestIterateMissCounters(t *testing.T) { + input := CreateTestInput(t) + + // Test default getters and setters + counter := input.OracleKeeper.GetMissCounter(input.Ctx, ValAddrs[0]) + require.Equal(t, int64(0), counter) + + missCounter := int64(10) + input.OracleKeeper.SetMissCounter(input.Ctx, ValAddrs[1], missCounter) + + var operators []sdk.ValAddress + var missCounters []int64 + input.OracleKeeper.IterateMissCounters(input.Ctx, func(delegator sdk.ValAddress, missCounter int64) (stop bool) { + operators = append(operators, delegator) + missCounters = append(missCounters, missCounter) + return false + }) + + require.Equal(t, 1, len(operators)) + require.Equal(t, 1, len(missCounters)) + require.Equal(t, ValAddrs[1], operators[0]) + require.Equal(t, missCounter, missCounters[0]) } diff --git a/x/oracle/internal/keeper/params.go b/x/oracle/internal/keeper/params.go index a05874482..6e7a29a63 100644 --- a/x/oracle/internal/keeper/params.go +++ b/x/oracle/internal/keeper/params.go @@ -23,15 +23,15 @@ func (k Keeper) VoteThreshold(ctx sdk.Context) (res sdk.Dec) { return } -// RewardBand returns the ratio of allowable price error that a validator can be rewared +// RewardBand returns the ratio of allowable exchange rate error that a validator can be rewared func (k Keeper) RewardBand(ctx sdk.Context) (res sdk.Dec) { k.paramSpace.Get(ctx, types.ParamStoreKeyRewardBand, &res) return } -// RewardDistributionPeriod returns the number of blocks of the the period during which seigiornage reward comes in and then is distributed. -func (k Keeper) RewardDistributionPeriod(ctx sdk.Context) (res int64) { - k.paramSpace.Get(ctx, types.ParamStoreKeyRewardDistributionPeriod, &res) +// RewardDistributionWindow returns the number of vote periods during which seigiornage reward comes in and then is distributed. +func (k Keeper) RewardDistributionWindow(ctx sdk.Context) (res int64) { + k.paramSpace.Get(ctx, types.ParamStoreKeyRewardDistributionWindow, &res) return } @@ -41,6 +41,24 @@ func (k Keeper) Whitelist(ctx sdk.Context) (res types.DenomList) { return } +// SlashFraction returns oracle voting penalty rate +func (k Keeper) SlashFraction(ctx sdk.Context) (res sdk.Dec) { + k.paramSpace.Get(ctx, types.ParamStoreKeySlashFraction, &res) + return +} + +// SlashWindow returns # of vote period for oracle slashing +func (k Keeper) SlashWindow(ctx sdk.Context) (res int64) { + k.paramSpace.Get(ctx, types.ParamStoreKeySlashWindow, &res) + return +} + +// MinValidPerWindow returns oracle slashing threshold +func (k Keeper) MinValidPerWindow(ctx sdk.Context) (res sdk.Dec) { + k.paramSpace.Get(ctx, types.ParamStoreKeyMinValidPerWindow, &res) + return +} + // GetParams returns the total set of oracle parameters. func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { k.paramSpace.GetParamSet(ctx, ¶ms) diff --git a/x/oracle/internal/keeper/querier.go b/x/oracle/internal/keeper/querier.go index 96c7eb04e..adde86fa6 100644 --- a/x/oracle/internal/keeper/querier.go +++ b/x/oracle/internal/keeper/querier.go @@ -13,10 +13,10 @@ import ( func NewQuerier(keeper Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { switch path[0] { - case types.QueryPrice: - return queryPrice(ctx, req, keeper) - case types.QueryPrices: - return queryPrices(ctx, keeper) + case types.QueryExchangeRate: + return queryExchangeRate(ctx, req, keeper) + case types.QueryExchangeRates: + return queryExchangeRates(ctx, keeper) case types.QueryActives: return queryActives(ctx, keeper) case types.QueryVotes: @@ -27,25 +27,27 @@ func NewQuerier(keeper Keeper) sdk.Querier { return queryParameters(ctx, keeper) case types.QueryFeederDelegation: return queryFeederDelegation(ctx, req, keeper) + case types.QueryMissCounter: + return queryMissCounter(ctx, req, keeper) default: return nil, sdk.ErrUnknownRequest("unknown oracle query endpoint") } } } -func queryPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params types.QueryPriceParams +func queryExchangeRate(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + var params types.QueryExchangeRateParams err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } - price, err := keeper.GetLunaPrice(ctx, params.Denom) + rate, err := keeper.GetLunaExchangeRate(ctx, params.Denom) if err != nil { return nil, types.ErrUnknownDenomination(types.DefaultCodespace, params.Denom) } - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, price) + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, rate) if err2 != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } @@ -53,10 +55,15 @@ func queryPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, return bz, nil } -func queryPrices(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { - prices := keeper.GetLunaPrices(ctx) +func queryExchangeRates(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { + var rates sdk.DecCoins - bz, err := codec.MarshalJSONIndent(keeper.cdc, prices) + keeper.IterateLunaExchangeRates(ctx, func(denom string, rate sdk.Dec) (stop bool) { + rates = append(rates, sdk.NewDecCoinFromDec(denom, rate)) + return false + }) + + bz, err := codec.MarshalJSONIndent(keeper.cdc, rates) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } @@ -65,7 +72,12 @@ func queryPrices(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { } func queryActives(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { - denoms := keeper.GetActiveDenoms(ctx) + denoms := []string{} + + keeper.IterateLunaExchangeRates(ctx, func(denom string, rate sdk.Dec) (stop bool) { + denoms = append(denoms, denom) + return false + }) bz, err := codec.MarshalJSONIndent(keeper.cdc, denoms) if err != nil { @@ -82,11 +94,11 @@ func queryVotes(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - filteredVotes := types.PriceVotes{} + filteredVotes := types.ExchangeRateVotes{} // collects all votes without filter prefix := types.VoteKey - handler := func(vote types.PriceVote) (stop bool) { + handler := func(vote types.ExchangeRateVote) (stop bool) { filteredVotes = append(filteredVotes, vote) return false } @@ -97,7 +109,7 @@ func queryVotes(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, } else if len(params.Denom) != 0 { prefix = types.GetVoteKey(params.Denom, sdk.ValAddress{}) } else if !params.Voter.Empty() { - handler = func(vote types.PriceVote) (stop bool) { + handler = func(vote types.ExchangeRateVote) (stop bool) { if vote.Voter.Equals(params.Voter) { filteredVotes = append(filteredVotes, vote) @@ -107,7 +119,7 @@ func queryVotes(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, } } - keeper.iterateVotesWithPrefix(ctx, prefix, handler) + keeper.iterateExchangeRateVotesWithPrefix(ctx, prefix, handler) bz, err := codec.MarshalJSONIndent(keeper.cdc, filteredVotes) if err != nil { @@ -123,22 +135,22 @@ func queryPrevotes(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byt return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - filteredPrevotes := types.PricePrevotes{} + filteredPrevotes := types.ExchangeRatePrevotes{} - // collects all votes without filter + // collects all prevotes without filter prefix := types.PrevoteKey - handler := func(prevote types.PricePrevote) (stop bool) { + handler := func(prevote types.ExchangeRatePrevote) (stop bool) { filteredPrevotes = append(filteredPrevotes, prevote) return false } // applies filter if len(params.Denom) != 0 && !params.Voter.Empty() { - prefix = types.GetPrevoteKey(params.Denom, params.Voter) + prefix = types.GetExchangeRatePrevoteKey(params.Denom, params.Voter) } else if len(params.Denom) != 0 { - prefix = types.GetPrevoteKey(params.Denom, sdk.ValAddress{}) + prefix = types.GetExchangeRatePrevoteKey(params.Denom, sdk.ValAddress{}) } else if !params.Voter.Empty() { - handler = func(prevote types.PricePrevote) (stop bool) { + handler = func(prevote types.ExchangeRatePrevote) (stop bool) { if prevote.Voter.Equals(params.Voter) { filteredPrevotes = append(filteredPrevotes, prevote) @@ -148,7 +160,7 @@ func queryPrevotes(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byt } } - keeper.iteratePrevotesWithPrefix(ctx, prefix, handler) + keeper.iterateExchangeRatePrevotesWithPrefix(ctx, prefix, handler) bz, err := codec.MarshalJSONIndent(keeper.cdc, filteredPrevotes) if err != nil { @@ -172,10 +184,25 @@ func queryFeederDelegation(ctx sdk.Context, req abci.RequestQuery, keeper Keeper return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - delegatee := keeper.GetFeedDelegate(ctx, params.Validator) + delegatee := keeper.GetOracleDelegate(ctx, params.Validator) bz, err := codec.MarshalJSONIndent(keeper.cdc, delegatee) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } return bz, nil } + +func queryMissCounter(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { + var params types.QueryMissCounterParams + err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + + missCounter := keeper.GetMissCounter(ctx, params.Validator) + bz, err := codec.MarshalJSONIndent(keeper.cdc, missCounter) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} diff --git a/x/oracle/internal/keeper/querier_test.go b/x/oracle/internal/keeper/querier_test.go index ece50fc27..f481cff07 100644 --- a/x/oracle/internal/keeper/querier_test.go +++ b/x/oracle/internal/keeper/querier_test.go @@ -46,12 +46,12 @@ func TestQueryPrevotes(t *testing.T) { input := CreateTestInput(t) querier := NewQuerier(input.OracleKeeper) - prevote1 := types.NewPricePrevote("", core.MicroSDRDenom, ValAddrs[0], 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote1) - prevote2 := types.NewPricePrevote("", core.MicroSDRDenom, ValAddrs[1], 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote2) - prevote3 := types.NewPricePrevote("", core.MicroLunaDenom, ValAddrs[2], 0) - input.OracleKeeper.AddPrevote(input.Ctx, prevote3) + prevote1 := types.NewExchangeRatePrevote("", core.MicroSDRDenom, ValAddrs[0], 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote1) + prevote2 := types.NewExchangeRatePrevote("", core.MicroSDRDenom, ValAddrs[1], 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote2) + prevote3 := types.NewExchangeRatePrevote("", core.MicroLunaDenom, ValAddrs[2], 0) + input.OracleKeeper.AddExchangeRatePrevote(input.Ctx, prevote3) // voter denom both query params queryParams := types.NewQueryPrevotesParams(ValAddrs[0], core.MicroSDRDenom) @@ -66,7 +66,7 @@ func TestQueryPrevotes(t *testing.T) { res, err := querier(input.Ctx, []string{types.QueryPrevotes}, req) require.NoError(t, err) - var filteredPrevotes types.PricePrevotes + var filteredPrevotes types.ExchangeRatePrevotes err = cdc.UnmarshalJSON(res, &filteredPrevotes) require.NoError(t, err) require.Equal(t, 1, len(filteredPrevotes)) @@ -114,12 +114,12 @@ func TestQueryVotes(t *testing.T) { input := CreateTestInput(t) querier := NewQuerier(input.OracleKeeper) - vote1 := types.NewPriceVote(sdk.NewDec(1700), core.MicroSDRDenom, ValAddrs[0]) - input.OracleKeeper.AddVote(input.Ctx, vote1) - vote2 := types.NewPriceVote(sdk.NewDec(1700), core.MicroSDRDenom, ValAddrs[1]) - input.OracleKeeper.AddVote(input.Ctx, vote2) - vote3 := types.NewPriceVote(sdk.NewDec(1700), core.MicroLunaDenom, ValAddrs[2]) - input.OracleKeeper.AddVote(input.Ctx, vote3) + vote1 := types.NewExchangeRateVote(sdk.NewDec(1700), core.MicroSDRDenom, ValAddrs[0]) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote1) + vote2 := types.NewExchangeRateVote(sdk.NewDec(1700), core.MicroSDRDenom, ValAddrs[1]) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote2) + vote3 := types.NewExchangeRateVote(sdk.NewDec(1700), core.MicroLunaDenom, ValAddrs[2]) + input.OracleKeeper.AddExchangeRateVote(input.Ctx, vote3) // voter denom both query params queryParams := types.NewQueryVotesParams(ValAddrs[0], core.MicroSDRDenom) @@ -134,7 +134,7 @@ func TestQueryVotes(t *testing.T) { res, err := querier(input.Ctx, []string{types.QueryVotes}, req) require.NoError(t, err) - var filteredVotes types.PriceVotes + var filteredVotes types.ExchangeRateVotes err = cdc.UnmarshalJSON(res, &filteredVotes) require.NoError(t, err) require.Equal(t, 1, len(filteredVotes)) @@ -177,16 +177,16 @@ func TestQueryVotes(t *testing.T) { require.Equal(t, vote3, filteredVotes[0]) } -func TestQueryPrice(t *testing.T) { +func TestQueryExchangeRate(t *testing.T) { cdc := codec.New() input := CreateTestInput(t) querier := NewQuerier(input.OracleKeeper) - price := sdk.NewDec(1700) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, price) + rate := sdk.NewDec(1700) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, rate) // denom query params - queryParams := types.NewQueryPriceParams(core.MicroSDRDenom) + queryParams := types.NewQueryExchangeRateParams(core.MicroSDRDenom) bz, err := cdc.MarshalJSON(queryParams) require.NoError(t, err) @@ -195,34 +195,34 @@ func TestQueryPrice(t *testing.T) { Data: bz, } - res, err := querier(input.Ctx, []string{types.QueryPrice}, req) + res, err := querier(input.Ctx, []string{types.QueryExchangeRate}, req) require.NoError(t, err) - var rprice sdk.Dec - err = cdc.UnmarshalJSON(res, &rprice) + var rrate sdk.Dec + err = cdc.UnmarshalJSON(res, &rrate) require.NoError(t, err) - require.Equal(t, price, rprice) + require.Equal(t, rate, rrate) } -func TestQueryPrices(t *testing.T) { +func TestQueryExchangeRates(t *testing.T) { cdc := codec.New() input := CreateTestInput(t) querier := NewQuerier(input.OracleKeeper) - price := sdk.NewDec(1700) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, price) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroUSDDenom, price) + rate := sdk.NewDec(1700) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, rate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroUSDDenom, rate) - res, err := querier(input.Ctx, []string{types.QueryPrices}, abci.RequestQuery{}) + res, err := querier(input.Ctx, []string{types.QueryExchangeRates}, abci.RequestQuery{}) require.NoError(t, err) - var rprice sdk.DecCoins - err2 := cdc.UnmarshalJSON(res, &rprice) + var rrate sdk.DecCoins + err2 := cdc.UnmarshalJSON(res, &rrate) require.NoError(t, err2) require.Equal(t, sdk.DecCoins{ - sdk.NewDecCoinFromDec(core.MicroSDRDenom, price), - sdk.NewDecCoinFromDec(core.MicroUSDDenom, price), - }, rprice) + sdk.NewDecCoinFromDec(core.MicroSDRDenom, rate), + sdk.NewDecCoinFromDec(core.MicroUSDDenom, rate), + }, rrate) } func TestQueryActives(t *testing.T) { @@ -230,10 +230,10 @@ func TestQueryActives(t *testing.T) { input := CreateTestInput(t) querier := NewQuerier(input.OracleKeeper) - price := sdk.NewDec(1700) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, price) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, price) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroUSDDenom, price) + rate := sdk.NewDec(1700) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, rate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, rate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroUSDDenom, rate) res, err := querier(input.Ctx, []string{types.QueryActives}, abci.RequestQuery{}) require.NoError(t, err) @@ -243,6 +243,7 @@ func TestQueryActives(t *testing.T) { core.MicroSDRDenom, core.MicroUSDDenom, } + var denoms types.DenomList err2 := cdc.UnmarshalJSON(res, &denoms) require.NoError(t, err2) @@ -254,7 +255,7 @@ func TestQueryFeederDelegation(t *testing.T) { input := CreateTestInput(t) querier := NewQuerier(input.OracleKeeper) - input.OracleKeeper.SetFeedDelegate(input.Ctx, ValAddrs[0], Addrs[1]) + input.OracleKeeper.SetOracleDelegate(input.Ctx, ValAddrs[0], Addrs[1]) queryParams := types.NewQueryFeederDelegationParams(ValAddrs[0]) bz, err := cdc.MarshalJSON(queryParams) diff --git a/x/oracle/internal/keeper/reward.go b/x/oracle/internal/keeper/reward.go index a9527be95..07ae8b195 100644 --- a/x/oracle/internal/keeper/reward.go +++ b/x/oracle/internal/keeper/reward.go @@ -5,52 +5,57 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + core "github.com/terra-project/core/types" "github.com/terra-project/core/x/oracle/internal/types" ) // RewardBallotWinners implements // at the end of every VotePeriod, we give out portion of seigniorage reward(reward-weight) to the // oracle voters that voted faithfully. -func (k Keeper) RewardBallotWinners(ctx sdk.Context, ballotWinners types.ClaimPool) { - // Sum weight of the claimpool - prevBallotWeightSum := int64(0) +func (k Keeper) RewardBallotWinners(ctx sdk.Context, ballotWinners map[string]types.Claim) { + // Sum weight of the claims + ballotPowerSum := int64(0) for _, winner := range ballotWinners { - prevBallotWeightSum += winner.Weight + ballotPowerSum += winner.Weight } - if prevBallotWeightSum != 0 { - rewardPool := k.getRewardPool(ctx) - if !rewardPool.Empty() { - // rewardCoin = (oraclePool / rewardDistributionPeriod) * votePeriod - rewardDistributionPeriod := k.RewardDistributionPeriod(ctx) - votePeriod := k.VotePeriod(ctx) - - // Dole out rewards - var distributedReward sdk.Coins - for _, winner := range ballotWinners { - rewardCoins := sdk.NewCoins() - rewardeeVal := k.StakingKeeper.Validator(ctx, winner.Recipient) - - for _, poolCoin := range rewardPool { - // The amount of the coin will be distributed in this vote period - totalRewardAmt := sdk.NewDecFromInt(poolCoin.Amount).MulInt64(votePeriod).QuoInt64(rewardDistributionPeriod) - // Reflects contribution - rewardAmt := totalRewardAmt.QuoInt64(prevBallotWeightSum).MulInt64(winner.Weight).TruncateInt() - rewardCoins = append(rewardCoins, sdk.NewCoin(poolCoin.Denom, rewardAmt)) - } - - // In case absence of the validator, we just skip distribution - if rewardeeVal != nil { - k.distrKeeper.AllocateTokensToValidator(ctx, rewardeeVal, sdk.NewDecCoins(rewardCoins)) - distributedReward = distributedReward.Add(rewardCoins) - } - } - - // Move distributed reward to distribution module - err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.distrName, distributedReward) - if err != nil { - panic(fmt.Sprintf("[oracle] Failed to send coins to distribution module %s", err.Error())) - } + // Exit if the ballot is empty + if ballotPowerSum == 0 { + return + } + + rewardPool := k.getRewardPool(ctx) + + // return if there's no rewards to give out + if rewardPool.Empty() { + return + } + + // rewardCoin = oraclePool / rewardDistributionWindow + periodRewards := sdk.NewDecFromInt(rewardPool.AmountOf(core.MicroLunaDenom)). + QuoInt64(k.RewardDistributionWindow(ctx)) + + // Dole out rewards + var distributedReward sdk.Coins + for _, winner := range ballotWinners { + rewardCoins := sdk.NewCoins() + rewardeeVal := k.StakingKeeper.Validator(ctx, winner.Recipient) + + // Reflects contribution + rewardAmt := periodRewards.QuoInt64(ballotPowerSum).MulInt64(winner.Weight).TruncateInt() + rewardCoins = append(rewardCoins, sdk.NewCoin(core.MicroLunaDenom, rewardAmt)) + + // In case absence of the validator, we just skip distribution + if rewardeeVal != nil { + k.distrKeeper.AllocateTokensToValidator(ctx, rewardeeVal, sdk.NewDecCoins(rewardCoins)) + distributedReward = distributedReward.Add(rewardCoins) } } + + // Move distributed reward to distribution module + err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.distrName, distributedReward) + if err != nil { + panic(fmt.Sprintf("[oracle] Failed to send coins to distribution module %s", err.Error())) + } + } diff --git a/x/oracle/internal/keeper/reward_test.go b/x/oracle/internal/keeper/reward_test.go index 633513c63..c9d227498 100644 --- a/x/oracle/internal/keeper/reward_test.go +++ b/x/oracle/internal/keeper/reward_test.go @@ -43,7 +43,10 @@ func TestRewardBallotWinners(t *testing.T) { // Add claim pools claim := types.NewClaim(10, addr) claim2 := types.NewClaim(20, addr1) - claimPool := types.ClaimPool{claim, claim2} + claims := map[string]types.Claim{ + addr.String(): claim, + addr1.String(): claim2, + } // Prepare reward pool givingAmt := sdk.NewCoins(sdk.NewInt64Coin(core.MicroLunaDenom, 3000000)) @@ -52,16 +55,15 @@ func TestRewardBallotWinners(t *testing.T) { require.NoError(t, err) input.SupplyKeeper.SetModuleAccount(ctx, acc) - votePeriod := input.OracleKeeper.VotePeriod(input.Ctx) - rewardDistributionPeriod := input.OracleKeeper.RewardDistributionPeriod(input.Ctx) - input.OracleKeeper.RewardBallotWinners(ctx, claimPool) + rewardDistributionWindow := input.OracleKeeper.RewardDistributionWindow(input.Ctx) + input.OracleKeeper.RewardBallotWinners(ctx, claims) outstandingRewardsDec := input.DistrKeeper.GetValidatorOutstandingRewards(ctx, addr) outstandingRewards, _ := outstandingRewardsDec.TruncateDecimal() - require.Equal(t, sdk.NewDecFromInt(givingAmt.AmountOf(core.MicroLunaDenom)).QuoInt64(rewardDistributionPeriod).MulInt64(votePeriod).QuoInt64(3).TruncateInt(), + require.Equal(t, sdk.NewDecFromInt(givingAmt.AmountOf(core.MicroLunaDenom)).QuoInt64(rewardDistributionWindow).QuoInt64(3).TruncateInt(), outstandingRewards.AmountOf(core.MicroLunaDenom)) outstandingRewardsDec1 := input.DistrKeeper.GetValidatorOutstandingRewards(ctx, addr1) outstandingRewards1, _ := outstandingRewardsDec1.TruncateDecimal() - require.Equal(t, sdk.NewDecFromInt(givingAmt.AmountOf(core.MicroLunaDenom)).QuoInt64(rewardDistributionPeriod).MulInt64(votePeriod).QuoInt64(3).MulInt64(2).TruncateInt(), + require.Equal(t, sdk.NewDecFromInt(givingAmt.AmountOf(core.MicroLunaDenom)).QuoInt64(rewardDistributionWindow).QuoInt64(3).MulInt64(2).TruncateInt(), outstandingRewards1.AmountOf(core.MicroLunaDenom)) } diff --git a/x/oracle/internal/types/ballot.go b/x/oracle/internal/types/ballot.go index 4b1ee4b80..fb34fe7bf 100644 --- a/x/oracle/internal/types/ballot.go +++ b/x/oracle/internal/types/ballot.go @@ -9,22 +9,36 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// PriceBallot is a convinience wrapper around a PriceVote slice -type PriceBallot []PriceVote +// VoteForTally is a convinience wrapper to reduct redundant lookup cost +type VoteForTally struct { + ExchangeRateVote + Power int64 +} + +// NewVoteForTally returns a new VoteForTally instance +func NewVoteForTally(vote ExchangeRateVote, power int64) VoteForTally { + return VoteForTally{ + vote, + power, + } +} + +// ExchangeRateBallot is a convinience wrapper around a ExchangeRateVote slice +type ExchangeRateBallot []VoteForTally // Power returns the total amount of voting power in the ballot -func (pb PriceBallot) Power(ctx sdk.Context, sk StakingKeeper) int64 { +func (pb ExchangeRateBallot) Power() int64 { totalPower := int64(0) for _, vote := range pb { - totalPower += vote.getPower(ctx, sk) + totalPower += vote.Power } return totalPower } -// WeightedMedian returns the median weighted by the power of the PriceVote. -func (pb PriceBallot) WeightedMedian(ctx sdk.Context, sk StakingKeeper) sdk.Dec { - totalPower := pb.Power(ctx, sk) +// WeightedMedian returns the median weighted by the power of the ExchangeRateVote. +func (pb ExchangeRateBallot) WeightedMedian() sdk.Dec { + totalPower := pb.Power() if pb.Len() > 0 { if !sort.IsSorted(pb) { sort.Sort(pb) @@ -32,28 +46,28 @@ func (pb PriceBallot) WeightedMedian(ctx sdk.Context, sk StakingKeeper) sdk.Dec pivot := int64(0) for _, v := range pb { - votePower := v.getPower(ctx, sk) + votePower := v.Power pivot += votePower if pivot >= (totalPower / 2) { - return v.Price + return v.ExchangeRate } } } return sdk.ZeroDec() } -// StandardDeviation returns the standard deviation by the power of the PriceVote. -func (pb PriceBallot) StandardDeviation(ctx sdk.Context, sk StakingKeeper) (standardDeviation sdk.Dec) { +// StandardDeviation returns the standard deviation by the power of the ExchangeRateVote. +func (pb ExchangeRateBallot) StandardDeviation() (standardDeviation sdk.Dec) { if len(pb) == 0 { return sdk.ZeroDec() } - median := pb.WeightedMedian(ctx, sk) + median := pb.WeightedMedian() sum := sdk.ZeroDec() for _, v := range pb { - deviation := v.Price.Sub(median) + deviation := v.ExchangeRate.Sub(median) sum = sum.Add(deviation.Mul(deviation)) } @@ -67,26 +81,17 @@ func (pb PriceBallot) StandardDeviation(ctx sdk.Context, sk StakingKeeper) (stan } // Len implements sort.Interface -func (pb PriceBallot) Len() int { +func (pb ExchangeRateBallot) Len() int { return len(pb) } // Less reports whether the element with // index i should sort before the element with index j. -func (pb PriceBallot) Less(i, j int) bool { - return pb[i].Price.LTE(pb[j].Price) +func (pb ExchangeRateBallot) Less(i, j int) bool { + return pb[i].ExchangeRate.LTE(pb[j].ExchangeRate) } // Swap implements sort.Interface. -func (pb PriceBallot) Swap(i, j int) { +func (pb ExchangeRateBallot) Swap(i, j int) { pb[i], pb[j] = pb[j], pb[i] } - -// String implements fmt.Stringer interface -func (pb PriceBallot) String() (out string) { - out = fmt.Sprintf("PriceBallot of %d votes\n", pb.Len()) - for _, pv := range pb { - out += fmt.Sprintf("\n %s", pv.String()) - } - return -} diff --git a/x/oracle/internal/types/ballot_test.go b/x/oracle/internal/types/ballot_test.go index f32dde972..9a45a5314 100644 --- a/x/oracle/internal/types/ballot_test.go +++ b/x/oracle/internal/types/ballot_test.go @@ -33,87 +33,105 @@ func TestPBPower(t *testing.T) { ctx := sdk.NewContext(nil, abci.Header{}, false, nil) _, valAccAddrs, sk := GenerateRandomTestCase() - pb := PriceBallot{} + pb := ExchangeRateBallot{} ballotPower := int64(0) for i := 0; i < len(sk.Validators()); i++ { - vote := NewPriceVote(sdk.ZeroDec(), core.MicroSDRDenom, sdk.ValAddress(valAccAddrs[i])) + power := sk.Validator(ctx, sdk.ValAddress(valAccAddrs[i])).GetConsensusPower() + vote := NewVoteForTally( + NewExchangeRateVote( + sdk.ZeroDec(), + core.MicroSDRDenom, + sdk.ValAddress(valAccAddrs[i]), + ), + power, + ) + pb = append(pb, vote) - valPower := vote.getPower(ctx, sk) - require.NotEqual(t, int64(0), valPower) + require.NotEqual(t, int64(0), vote.Power) - ballotPower += valPower + ballotPower += vote.Power } - require.Equal(t, ballotPower, pb.Power(ctx, sk)) + require.Equal(t, ballotPower, pb.Power()) // Mix in a fake validator, the total power should not have changed. pubKey := secp256k1.GenPrivKey().PubKey() faceValAddr := sdk.ValAddress(pubKey.Address()) - fakeVote := NewPriceVote(sdk.OneDec(), core.MicroSDRDenom, faceValAddr) + fakeVote := NewVoteForTally( + NewExchangeRateVote( + sdk.OneDec(), + core.MicroSDRDenom, + faceValAddr, + ), + 0, + ) + pb = append(pb, fakeVote) - require.Equal(t, ballotPower, pb.Power(ctx, sk)) + require.Equal(t, ballotPower, pb.Power()) } func TestPBWeightedMedian(t *testing.T) { tests := []struct { - inputs []float64 + inputs []int64 weights []int64 isValidator []bool median sdk.Dec }{ { // Supermajority one number - []float64{1.0, 2.0, 10.0, 100000.0}, + []int64{1, 2, 10, 100000}, []int64{1, 1, 100, 1}, []bool{true, true, true, true}, - sdk.NewDecWithPrec(10, 0), + sdk.NewDec(10), }, { // Adding fake validator doesn't change outcome - []float64{1.0, 2.0, 10.0, 100000.0, 10000000000}, + []int64{1, 2, 10, 100000, 10000000000}, []int64{1, 1, 100, 1, 10000}, []bool{true, true, true, true, false}, - sdk.NewDecWithPrec(10, 0), + sdk.NewDec(10), }, { // Tie votes - []float64{1.0, 2.0, 3.0, 4.0}, + []int64{1, 2, 3, 4}, []int64{1, 100, 100, 1}, []bool{true, true, true, true}, - sdk.NewDecWithPrec(2, 0), + sdk.NewDec(2), }, { // No votes - []float64{}, + []int64{}, []int64{}, []bool{true, true, true, true}, - sdk.NewDecWithPrec(0, 0), + sdk.NewDec(0), }, } - var mockValset []MockValidator - base := math.Pow10(oracleDecPrecision) for _, tc := range tests { - pb := PriceBallot{} + pb := ExchangeRateBallot{} for i, input := range tc.inputs { valAddr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) power := tc.weights[i] - mockVal := NewMockValidator(valAddr, power) - - if tc.isValidator[i] { - mockValset = append(mockValset, mockVal) + if !tc.isValidator[i] { + power = 0 } - vote := NewPriceVote(sdk.NewDecWithPrec(int64(input*base), int64(oracleDecPrecision)), core.MicroSDRDenom, valAddr) + + vote := NewVoteForTally( + NewExchangeRateVote( + sdk.NewDec(int64(input)), + core.MicroSDRDenom, + valAddr, + ), + power, + ) + pb = append(pb, vote) } - sk := NewDummyStakingKeeper(mockValset) - - ctx := sdk.NewContext(nil, abci.Header{}, false, nil) - require.Equal(t, tc.median, pb.WeightedMedian(ctx, sk)) + require.Equal(t, tc.median, pb.WeightedMedian()) } } @@ -154,82 +172,29 @@ func TestPBStandardDeviation(t *testing.T) { }, } - var mockValset []MockValidator base := math.Pow10(oracleDecPrecision) for _, tc := range tests { - pb := PriceBallot{} + pb := ExchangeRateBallot{} for i, input := range tc.inputs { valAddr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) power := tc.weights[i] - mockVal := NewMockValidator(valAddr, power) - - if tc.isValidator[i] { - mockValset = append(mockValset, mockVal) + if !tc.isValidator[i] { + power = 0 } - vote := NewPriceVote(sdk.NewDecWithPrec(int64(input*base), int64(oracleDecPrecision)), core.MicroSDRDenom, valAddr) + + vote := NewVoteForTally( + NewExchangeRateVote( + sdk.NewDecWithPrec(int64(input*base), int64(oracleDecPrecision)), + core.MicroSDRDenom, + valAddr, + ), + power, + ) + pb = append(pb, vote) } - sk := NewDummyStakingKeeper(mockValset) - - ctx := sdk.NewContext(nil, abci.Header{}, false, nil) - require.Equal(t, tc.standardDeviation, pb.StandardDeviation(ctx, sk)) + require.Equal(t, tc.standardDeviation, pb.StandardDeviation()) } } - -func TestString(t *testing.T) { - pb := PriceBallot{} - require.Equal(t, "PriceBallot of 0 votes\n", pb.String()) - - vote := NewPriceVote(sdk.NewDecWithPrec(int64(1123400), int64(oracleDecPrecision)), core.MicroSDRDenom, sdk.ValAddress{}) - pb = append(pb, vote) - require.Equal(t, "PriceBallot of 1 votes\n\n PriceVote\n\tDenom: usdr, \n\tVoter: , \n\tPrice: 1.123400000000000000", pb.String()) -} - -// func TestPBTally(t *testing.T) { -// _, addrs :=mock.GeneratePrivKeyAddressPairs(3) -// tests := []struct { -// inputs []float64 -// weights []int64 -// rewardees []sdk.AccAddress -// }{ -// { -// // Supermajority one number -// []float64{1.0, 2.0, 10.0, 100000.0}, -// []int64{1, 1, 100, 1}, -// []sdk.AccAddress{addrs[2]}, -// }, -// { -// // Tie votes -// []float64{1.0, 2.0, 3.0, 4.0}, -// []int64{1, 100, 100, 1}, -// []sdk.AccAddress{addrs[1]}, -// }, -// { -// // No votes -// []float64{}, -// []int64{}, -// []sdk.AccAddress{}, -// }, - -// { -// // Lots of random votes -// []float64{1.0, 78.48, 78.11, 79.0}, -// []int64{1, 51, 79, 33}, -// []sdk.AccAddress{addrs[1], addrs[2], addrs[3]}, -// }, -// } - -// for _, tc := range tests { -// pb := PriceBallot{} -// for i, input := range tc.inputs { -// vote := NewPriceVote(sdk.NewDecWithPrec(int64(input*100), 2), "", -// sdk.NewInt(tc.weights[i]), addrs[i]) -// pb = append(pb, vote) -// } - -// _, rewardees := pb.Tally() -// require.Equal(t, len(tc.rewardees), len(rewardees)) -// } -// } diff --git a/x/oracle/internal/types/claim.go b/x/oracle/internal/types/claim.go new file mode 100644 index 000000000..20a68c8a8 --- /dev/null +++ b/x/oracle/internal/types/claim.go @@ -0,0 +1,19 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Claim is an interface that directs its rewards to an attached bank account. +type Claim struct { + Weight int64 `json:"weight"` + Recipient sdk.ValAddress `json:"recipient"` +} + +// NewClaim generates a Claim instance. +func NewClaim(weight int64, recipient sdk.ValAddress) Claim { + return Claim{ + Weight: weight, + Recipient: recipient, + } +} diff --git a/x/oracle/internal/types/claimpool.go b/x/oracle/internal/types/claimpool.go deleted file mode 100644 index 59a98c545..000000000 --- a/x/oracle/internal/types/claimpool.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Claim is an interface that directs its rewards to an attached bank account. -type Claim struct { - Weight int64 `json:"weight"` - Recipient sdk.ValAddress `json:"recipient"` -} - -// NewClaim generates a Claim instance. -func NewClaim(weight int64, recipient sdk.ValAddress) Claim { - return Claim{ - Weight: weight, - Recipient: recipient, - } -} - -// ClaimPool is a list of Claims -type ClaimPool []Claim - -// Sort sorts the ClaimPool -func (cp ClaimPool) Sort() ClaimPool { - sortBuf := map[string]Claim{} - - for _, claim := range cp { - addrStr := claim.Recipient.String() - if val, ok := sortBuf[addrStr]; ok { - val.Weight = val.Weight + claim.Weight - sortBuf[addrStr] = val - } else { - sortBuf[addrStr] = claim - } - } - - i := 0 - cp = make([]Claim, len(sortBuf)) - for _, claim := range sortBuf { - cp[i] = claim - i++ - } - - return cp -} diff --git a/x/oracle/internal/types/claimpool_test.go b/x/oracle/internal/types/claimpool_test.go deleted file mode 100644 index 806505790..000000000 --- a/x/oracle/internal/types/claimpool_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package types - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/stretchr/testify/require" -) - -func TestClaimPoolSort(t *testing.T) { - _, addrs, _, _ := mock.CreateGenAccounts(2, sdk.Coins{}) - - claim1 := NewClaim(1, sdk.ValAddress(addrs[0])) - claim2 := NewClaim(2, sdk.ValAddress(addrs[0])) - claim3 := NewClaim(3, sdk.ValAddress(addrs[1])) - - claimPool := ClaimPool{claim1, claim2, claim3} - claimPool = claimPool.Sort() - - require.Equal(t, 2, len(claimPool)) - require.Equal(t, int64(3), claimPool[0].Weight) -} diff --git a/x/oracle/internal/types/codec.go b/x/oracle/internal/types/codec.go index b4c675bf7..91875463a 100644 --- a/x/oracle/internal/types/codec.go +++ b/x/oracle/internal/types/codec.go @@ -9,9 +9,9 @@ var ModuleCdc = codec.New() // RegisterCodec registers concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgPriceVote{}, "oracle/MsgPriceVote", nil) - cdc.RegisterConcrete(MsgPricePrevote{}, "oracle/MsgPricePrevote", nil) - cdc.RegisterConcrete(MsgDelegateFeederPermission{}, "oracle/MsgDelegateFeederPermission", nil) + cdc.RegisterConcrete(MsgExchangeRateVote{}, "oracle/MsgExchangeRateVote", nil) + cdc.RegisterConcrete(MsgExchangeRatePrevote{}, "oracle/MsgExchangeRatePrevote", nil) + cdc.RegisterConcrete(MsgDelegateFeedConsent{}, "oracle/MsgDelegateFeedConsent", nil) } func init() { diff --git a/x/oracle/internal/types/denom.go b/x/oracle/internal/types/denom.go index 56db8ea62..125f37e9e 100644 --- a/x/oracle/internal/types/denom.go +++ b/x/oracle/internal/types/denom.go @@ -9,6 +9,6 @@ type DenomList []string // String implements fmt.Stringer interface func (dl DenomList) String() (out string) { - out = strings.Join(dl, "\n") + strings.Join(dl, "\n") return } diff --git a/x/oracle/internal/types/errors.go b/x/oracle/internal/types/errors.go index bd3c773a0..53d2fdee5 100644 --- a/x/oracle/internal/types/errors.go +++ b/x/oracle/internal/types/errors.go @@ -13,17 +13,17 @@ type codeType = sdk.CodeType const ( DefaultCodespace sdk.CodespaceType = "oracle" - CodeUnknownDenom codeType = 1 - CodeInvalidPrice codeType = 2 - CodeVoterNotValidator codeType = 3 - CodeInvalidVote codeType = 4 - CodeNoVotingPermission codeType = 5 - CodeInvalidHashLength codeType = 6 - CodeInvalidPrevote codeType = 7 - CodeVerificationFailed codeType = 8 - CodeNotRevealPeriod codeType = 9 - CodeInvalidSaltLength codeType = 10 - CodeInvalidMsgFormat codeType = 11 + CodeUnknownDenom codeType = 1 + CodeInvalidExchangeRate codeType = 2 + CodeVoterNotValidator codeType = 3 + CodeInvalidVote codeType = 4 + CodeNoVotingPermission codeType = 5 + CodeInvalidHashLength codeType = 6 + CodeInvalidPrevote codeType = 7 + CodeVerificationFailed codeType = 8 + CodeNotRevealPeriod codeType = 9 + CodeInvalidSaltLength codeType = 10 + CodeInvalidMsgFormat codeType = 11 ) // ---------------------------------------- @@ -39,9 +39,9 @@ func ErrUnknownDenomination(codespace sdk.CodespaceType, denom string) sdk.Error return sdk.NewError(codespace, CodeUnknownDenom, fmt.Sprintf("The denom is not known: %s", denom)) } -// ErrInvalidPrice called when the price submitted is not valid -func ErrInvalidPrice(codespace sdk.CodespaceType, price sdk.Dec) sdk.Error { - return sdk.NewError(codespace, CodeInvalidPrice, fmt.Sprintf("Price is invalid: %s", price.String())) +// ErrInvalidExchangeRate called when the rate submitted is not valid +func ErrInvalidExchangeRate(codespace sdk.CodespaceType, rate sdk.Dec) sdk.Error { + return sdk.NewError(codespace, CodeInvalidExchangeRate, fmt.Sprintf("ExchangeRate is invalid: %s", rate.String())) } // ErrVerificationFailed called when the given prevote has different hash from the retrieved one @@ -64,7 +64,7 @@ func ErrNoVotingPermission(codespace sdk.CodespaceType, feeder sdk.AccAddress, o return sdk.NewError(codespace, CodeNoVotingPermission, fmt.Sprintf("Feeder %s not permitted to vote on behalf of: %s", feeder.String(), operator.String())) } -// ErrNotRevealPeriod called when the feeder submit price reveal vote in wrong period. +// ErrNotRevealPeriod called when the feeder submit rate reveal vote in wrong period. func ErrNotRevealPeriod(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeNotRevealPeriod, fmt.Sprintf("Now is not proper reveal period.")) } diff --git a/x/oracle/internal/types/events.go b/x/oracle/internal/types/events.go index e0e09ac49..f6ee72eef 100644 --- a/x/oracle/internal/types/events.go +++ b/x/oracle/internal/types/events.go @@ -3,16 +3,16 @@ package types // Oracle module event types const ( - EventTypePriceUpdate = "price_update" - EventTypePrevote = "prevote" - EventTypeVote = "vote" - EventTypeFeedDeleate = "feed_delegate" + EventTypeExchangeRateUpdate = "exchange_rate_update" + EventTypePrevote = "prevote" + EventTypeVote = "vote" + EventTypeFeedDeleate = "feed_delegate" - AttributeKeyDenom = "denom" - AttributeKeyVoter = "voter" - AttributeKeyPrice = "price" - AttributeKeyOperator = "operator" - AttributeKeyFeeder = "feeder" + AttributeKeyDenom = "denom" + AttributeKeyVoter = "voter" + AttributeKeyExchangeRate = "exchange_rate" + AttributeKeyOperator = "operator" + AttributeKeyFeeder = "feeder" AttributeValueCategory = ModuleName ) diff --git a/x/oracle/internal/types/expected_keeper.go b/x/oracle/internal/types/expected_keeper.go index 69f0fdcf6..a27d45d09 100644 --- a/x/oracle/internal/types/expected_keeper.go +++ b/x/oracle/internal/types/expected_keeper.go @@ -10,6 +10,9 @@ import ( type StakingKeeper interface { Validator(ctx sdk.Context, address sdk.ValAddress) stakingexported.ValidatorI // get validator by operator address; nil when validator not found TotalBondedTokens(sdk.Context) sdk.Int // total bonded tokens within the validator set + Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction + Jail(sdk.Context, sdk.ConsAddress) // jail a validator + IterateValidators(sdk.Context, func(index int64, validator stakingexported.ValidatorI) (stop bool)) } // DistributionKeeper is expected keeper for distribution module diff --git a/x/oracle/internal/types/genesis.go b/x/oracle/internal/types/genesis.go index a5ef29917..15b1e2bc2 100644 --- a/x/oracle/internal/types/genesis.go +++ b/x/oracle/internal/types/genesis.go @@ -8,37 +8,40 @@ import ( // GenesisState - all oracle state that must be provided at genesis type GenesisState struct { - Params Params `json:"params" yaml:"params"` - FeederDelegations map[string]sdk.AccAddress `json:"feeder_delegations" yaml:"feeder_delegations"` - Prices map[string]sdk.Dec `json:"prices" yaml:"prices"` - PricePrevotes []PricePrevote `json:"price_prevotes" yaml:"price_prevotes"` - PriceVotes []PriceVote `json:"price_votes" yaml:"price_votes"` + Params Params `json:"params" yaml:"params"` + FeederDelegations map[string]sdk.AccAddress `json:"feeder_delegations" yaml:"feeder_delegations"` + ExchangeRates map[string]sdk.Dec `json:"exchange_rates" yaml:"exchange_rates"` + ExchangeRatePrevotes []ExchangeRatePrevote `json:"exchange_rate_prevotes" yaml:"exchange_rate_prevotes"` + ExchangeRateVotes []ExchangeRateVote `json:"exchange_rate_votes" yaml:"exchange_rate_votes"` + MissCounters map[string]int64 `json:"miss_counters" yaml:"miss_counters` } // NewGenesisState creates a new GenesisState object func NewGenesisState( - params Params, pricePrevotes []PricePrevote, - priceVotes []PriceVote, prices map[string]sdk.Dec, - feederDelegations map[string]sdk.AccAddress, + params Params, exchangeRatePrevotes []ExchangeRatePrevote, + exchangeRateVotes []ExchangeRateVote, rates map[string]sdk.Dec, + feederDelegations map[string]sdk.AccAddress, missCounters map[string]int64, ) GenesisState { return GenesisState{ - Params: params, - PricePrevotes: pricePrevotes, - PriceVotes: priceVotes, - Prices: prices, - FeederDelegations: feederDelegations, + Params: params, + ExchangeRatePrevotes: exchangeRatePrevotes, + ExchangeRateVotes: exchangeRateVotes, + ExchangeRates: rates, + FeederDelegations: feederDelegations, + MissCounters: missCounters, } } // DefaultGenesisState - default GenesisState used by columbus-2 func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), - PricePrevotes: []PricePrevote{}, - PriceVotes: []PriceVote{}, - Prices: make(map[string]sdk.Dec), - FeederDelegations: make(map[string]sdk.AccAddress), + Params: DefaultParams(), + ExchangeRatePrevotes: []ExchangeRatePrevote{}, + ExchangeRateVotes: []ExchangeRateVote{}, + ExchangeRates: make(map[string]sdk.Dec), + FeederDelegations: make(map[string]sdk.AccAddress), + MissCounters: make(map[string]int64), } } diff --git a/x/oracle/internal/types/keys.go b/x/oracle/internal/types/keys.go index 9040a94c6..e6d61e052 100644 --- a/x/oracle/internal/types/keys.go +++ b/x/oracle/internal/types/keys.go @@ -28,16 +28,19 @@ const ( // - 0x03: sdk.Dec // // - 0x04: accAddress +// +// - 0x05: int64 var ( // Keys for store prefixes PrevoteKey = []byte{0x01} // prefix for each key to a prevote VoteKey = []byte{0x02} // prefix for each key to a vote - PriceKey = []byte{0x03} // prefix for each key to a price + ExchangeRateKey = []byte{0x03} // prefix for each key to a rate FeederDelegationKey = []byte{0x04} // prefix for each key to a feeder delegation + MissCounterKey = []byte{0x05} // prefix for each key to a miss counter ) -// GetPrevoteKey - stored by *Validator* address and denom -func GetPrevoteKey(denom string, v sdk.ValAddress) []byte { +// GetExchangeRatePrevoteKey - stored by *Validator* address and denom +func GetExchangeRatePrevoteKey(denom string, v sdk.ValAddress) []byte { return append(append(PrevoteKey, []byte(denom)...), v.Bytes()...) } @@ -46,12 +49,17 @@ func GetVoteKey(denom string, v sdk.ValAddress) []byte { return append(append(VoteKey, []byte(denom)...), v.Bytes()...) } -// GetPriceKey - stored by *denom* -func GetPriceKey(denom string) []byte { - return append(PriceKey, []byte(denom)...) +// GetExchangeRateKey - stored by *denom* +func GetExchangeRateKey(denom string) []byte { + return append(ExchangeRateKey, []byte(denom)...) } // GetFeederDelegationKey - stored by *Validator* address func GetFeederDelegationKey(v sdk.ValAddress) []byte { return append(FeederDelegationKey, v.Bytes()...) } + +// GetMissCounterKey - stored by *Validator* address +func GetMissCounterKey(v sdk.ValAddress) []byte { + return append(MissCounterKey, v.Bytes()...) +} diff --git a/x/oracle/internal/types/msgs.go b/x/oracle/internal/types/msgs.go index 12b14c24b..c475b95ff 100644 --- a/x/oracle/internal/types/msgs.go +++ b/x/oracle/internal/types/msgs.go @@ -10,27 +10,27 @@ import ( // ensure Msg interface compliance at compile time var ( - _ sdk.Msg = &MsgDelegateFeederPermission{} - _ sdk.Msg = &MsgPricePrevote{} - _ sdk.Msg = &MsgPriceVote{} + _ sdk.Msg = &MsgDelegateFeedConsent{} + _ sdk.Msg = &MsgExchangeRatePrevote{} + _ sdk.Msg = &MsgExchangeRateVote{} ) //------------------------------------------------- //------------------------------------------------- -// MsgPricePrevote - struct for prevoting on the PriceVote. -// The purpose of prevote is to hide vote price with hash -// which is formatted as hex string in SHA256("salt:price:denom:voter") -type MsgPricePrevote struct { +// MsgExchangeRatePrevote - struct for prevoting on the ExchangeRateVote. +// The purpose of prevote is to hide vote exchange rate with hash +// which is formatted as hex string in SHA256("salt:exchange_rate:denom:voter") +type MsgExchangeRatePrevote struct { Hash string `json:"hash" yaml:"hash"` // hex string Denom string `json:"denom" yaml:"denom"` Feeder sdk.AccAddress `json:"feeder" yaml:"feeder"` Validator sdk.ValAddress `json:"validator" yaml:"validator"` } -// NewMsgPricePrevote creates a MsgPricePrevote instance -func NewMsgPricePrevote(VoteHash string, denom string, feederAddress sdk.AccAddress, valAddress sdk.ValAddress) MsgPricePrevote { - return MsgPricePrevote{ +// NewMsgExchangeRatePrevote creates a MsgExchangeRatePrevote instance +func NewMsgExchangeRatePrevote(VoteHash string, denom string, feederAddress sdk.AccAddress, valAddress sdk.ValAddress) MsgExchangeRatePrevote { + return MsgExchangeRatePrevote{ Hash: VoteHash, Denom: denom, Feeder: feederAddress, @@ -39,23 +39,23 @@ func NewMsgPricePrevote(VoteHash string, denom string, feederAddress sdk.AccAddr } // Route implements sdk.Msg -func (msg MsgPricePrevote) Route() string { return RouterKey } +func (msg MsgExchangeRatePrevote) Route() string { return RouterKey } // Type implements sdk.Msg -func (msg MsgPricePrevote) Type() string { return "priceprevote" } +func (msg MsgExchangeRatePrevote) Type() string { return "exchangerateprevote" } // GetSignBytes implements sdk.Msg -func (msg MsgPricePrevote) GetSignBytes() []byte { +func (msg MsgExchangeRatePrevote) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners implements sdk.Msg -func (msg MsgPricePrevote) GetSigners() []sdk.AccAddress { +func (msg MsgExchangeRatePrevote) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Feeder} } // ValidateBasic Implements sdk.Msg -func (msg MsgPricePrevote) ValidateBasic() sdk.Error { +func (msg MsgExchangeRatePrevote) ValidateBasic() sdk.Error { if bz, err := hex.DecodeString(msg.Hash); len(bz) != tmhash.TruncatedSize || err != nil { return ErrInvalidHashLength(DefaultCodespace, len(bz)) @@ -77,8 +77,8 @@ func (msg MsgPricePrevote) ValidateBasic() sdk.Error { } // String implements fmt.Stringer interface -func (msg MsgPricePrevote) String() string { - return fmt.Sprintf(`MsgPriceVote +func (msg MsgExchangeRatePrevote) String() string { + return fmt.Sprintf(`MsgExchangeRateVote hash: %s, feeder: %s, validator: %s, @@ -86,46 +86,46 @@ func (msg MsgPricePrevote) String() string { msg.Hash, msg.Feeder, msg.Validator, msg.Denom) } -// MsgPriceVote - struct for voting on the price of Luna denominated in various Terra assets. -// For example, if the validator believes that the effective price of Luna in USD is 10.39, that's -// what the price field would be, and if 1213.34 for KRW, same. -type MsgPriceVote struct { - Price sdk.Dec `json:"price" yaml:"price"` // the effective price of Luna in {Denom} - Salt string `json:"salt" yaml:"salt"` - Denom string `json:"denom" yaml:"denom"` - Feeder sdk.AccAddress `json:"feeder" yaml:"feeder"` - Validator sdk.ValAddress `json:"validator" yaml:"validator"` -} - -// NewMsgPriceVote creates a MsgPriceVote instance -func NewMsgPriceVote(price sdk.Dec, salt string, denom string, feederAddress sdk.AccAddress, valAddress sdk.ValAddress) MsgPriceVote { - return MsgPriceVote{ - Price: price, - Salt: salt, - Denom: denom, - Feeder: feederAddress, - Validator: valAddress, +// MsgExchangeRateVote - struct for voting on the exchange rate of Luna denominated in various Terra assets. +// For example, if the validator believes that the effective exchange rate of Luna in USD is 10.39, that's +// what the exchange rate field would be, and if 1213.34 for KRW, same. +type MsgExchangeRateVote struct { + ExchangeRate sdk.Dec `json:"exchange_rate" yaml:"exchange_rate"` // the effective rate of Luna in {Denom} + Salt string `json:"salt" yaml:"salt"` + Denom string `json:"denom" yaml:"denom"` + Feeder sdk.AccAddress `json:"feeder" yaml:"feeder"` + Validator sdk.ValAddress `json:"validator" yaml:"validator"` +} + +// NewMsgExchangeRateVote creates a MsgExchangeRateVote instance +func NewMsgExchangeRateVote(rate sdk.Dec, salt string, denom string, feederAddress sdk.AccAddress, valAddress sdk.ValAddress) MsgExchangeRateVote { + return MsgExchangeRateVote{ + ExchangeRate: rate, + Salt: salt, + Denom: denom, + Feeder: feederAddress, + Validator: valAddress, } } // Route implements sdk.Msg -func (msg MsgPriceVote) Route() string { return RouterKey } +func (msg MsgExchangeRateVote) Route() string { return RouterKey } // Type implements sdk.Msg -func (msg MsgPriceVote) Type() string { return "pricevote" } +func (msg MsgExchangeRateVote) Type() string { return "exchangeratevote" } // GetSignBytes implements sdk.Msg -func (msg MsgPriceVote) GetSignBytes() []byte { +func (msg MsgExchangeRateVote) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners implements sdk.Msg -func (msg MsgPriceVote) GetSigners() []sdk.AccAddress { +func (msg MsgExchangeRateVote) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Feeder} } // ValidateBasic implements sdk.Msg -func (msg MsgPriceVote) ValidateBasic() sdk.Error { +func (msg MsgExchangeRateVote) ValidateBasic() sdk.Error { if len(msg.Denom) == 0 { return ErrUnknownDenomination(DefaultCodespace, "") @@ -139,8 +139,8 @@ func (msg MsgPriceVote) ValidateBasic() sdk.Error { return sdk.ErrInvalidAddress("Invalid address: " + msg.Feeder.String()) } - if msg.Price.LTE(sdk.ZeroDec()) { - return ErrInvalidPrice(DefaultCodespace, msg.Price) + if msg.ExchangeRate.LTE(sdk.ZeroDec()) { + return ErrInvalidExchangeRate(DefaultCodespace, msg.ExchangeRate) } if len(msg.Salt) > 4 || len(msg.Salt) < 1 { @@ -151,48 +151,48 @@ func (msg MsgPriceVote) ValidateBasic() sdk.Error { } // String implements fmt.Stringer interface -func (msg MsgPriceVote) String() string { - return fmt.Sprintf(`MsgPriceVote - price: %s, +func (msg MsgExchangeRateVote) String() string { + return fmt.Sprintf(`MsgExchangeRateVote + exchangerate: %s, salt: %s, feeder: %s, validator: %s, denom: %s`, - msg.Price, msg.Salt, msg.Feeder, msg.Validator, msg.Denom) + msg.ExchangeRate, msg.Salt, msg.Feeder, msg.Validator, msg.Denom) } -// MsgDelegateFeederPermission - struct for delegating oracle voting rights to another address. -type MsgDelegateFeederPermission struct { +// MsgDelegateFeedConsent - struct for delegating oracle voting rights to another address. +type MsgDelegateFeedConsent struct { Operator sdk.ValAddress `json:"operator" yaml:"operator"` Delegatee sdk.AccAddress `json:"delegatee" yaml:"delegatee"` } -// NewMsgDelegateFeederPermission creates a MsgDelegateFeederPermission instance -func NewMsgDelegateFeederPermission(operatorAddress sdk.ValAddress, feederAddress sdk.AccAddress) MsgDelegateFeederPermission { - return MsgDelegateFeederPermission{ +// NewMsgDelegateFeedConsent creates a MsgDelegateFeedConsent instance +func NewMsgDelegateFeedConsent(operatorAddress sdk.ValAddress, feederAddress sdk.AccAddress) MsgDelegateFeedConsent { + return MsgDelegateFeedConsent{ Operator: operatorAddress, Delegatee: feederAddress, } } // Route implements sdk.Msg -func (msg MsgDelegateFeederPermission) Route() string { return RouterKey } +func (msg MsgDelegateFeedConsent) Route() string { return RouterKey } // Type implements sdk.Msg -func (msg MsgDelegateFeederPermission) Type() string { return "delegatefeeder" } +func (msg MsgDelegateFeedConsent) Type() string { return "delegatefeeder" } // GetSignBytes implements sdk.Msg -func (msg MsgDelegateFeederPermission) GetSignBytes() []byte { +func (msg MsgDelegateFeedConsent) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners implements sdk.Msg -func (msg MsgDelegateFeederPermission) GetSigners() []sdk.AccAddress { +func (msg MsgDelegateFeedConsent) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{sdk.AccAddress(msg.Operator)} } // ValidateBasic implements sdk.Msg -func (msg MsgDelegateFeederPermission) ValidateBasic() sdk.Error { +func (msg MsgDelegateFeedConsent) ValidateBasic() sdk.Error { if msg.Operator.Empty() { return sdk.ErrInvalidAddress("Invalid address: " + msg.Operator.String()) } @@ -205,8 +205,8 @@ func (msg MsgDelegateFeederPermission) ValidateBasic() sdk.Error { } // String implements fmt.Stringer interface -func (msg MsgDelegateFeederPermission) String() string { - return fmt.Sprintf(`MsgDelegateFeederPermission +func (msg MsgDelegateFeedConsent) String() string { + return fmt.Sprintf(`MsgDelegateFeedConsent operator: %s, delegatee: %s`, msg.Operator, msg.Delegatee) diff --git a/x/oracle/internal/types/msgs_test.go b/x/oracle/internal/types/msgs_test.go index cd5fed049..fb729b8bd 100644 --- a/x/oracle/internal/types/msgs_test.go +++ b/x/oracle/internal/types/msgs_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestMsgPricePrevote(t *testing.T) { +func TestMsgExchangeRatePrevote(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) bz, err := VoteHash("1", sdk.OneDec(), core.MicroSDRDenom, sdk.ValAddress(addrs[0])) @@ -31,7 +31,7 @@ func TestMsgPricePrevote(t *testing.T) { } for i, tc := range tests { - msg := NewMsgPricePrevote(tc.hash, tc.denom, tc.voter, sdk.ValAddress(tc.voter)) + msg := NewMsgExchangeRatePrevote(tc.hash, tc.denom, tc.voter, sdk.ValAddress(tc.voter)) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) } else { @@ -40,14 +40,14 @@ func TestMsgPricePrevote(t *testing.T) { } } -func TestMsgPriceVote(t *testing.T) { +func TestMsgExchangeRateVote(t *testing.T) { _, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{}) tests := []struct { denom string voter sdk.AccAddress salt string - price sdk.Dec + rate sdk.Dec expectPass bool }{ {"", addrs[0], "123", sdk.OneDec(), false}, @@ -58,7 +58,7 @@ func TestMsgPriceVote(t *testing.T) { } for i, tc := range tests { - msg := NewMsgPriceVote(tc.price, tc.salt, tc.denom, tc.voter, sdk.ValAddress(tc.voter)) + msg := NewMsgExchangeRateVote(tc.rate, tc.salt, tc.denom, tc.voter, sdk.ValAddress(tc.voter)) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) } else { @@ -82,7 +82,7 @@ func TestMsgFeederDelegation(t *testing.T) { } for i, tc := range tests { - msg := NewMsgDelegateFeederPermission(tc.delegator, tc.delegatee) + msg := NewMsgDelegateFeedConsent(tc.delegator, tc.delegatee) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", i) } else { diff --git a/x/oracle/internal/types/params.go b/x/oracle/internal/types/params.go index 77c7cbee9..a4f6c782c 100644 --- a/x/oracle/internal/types/params.go +++ b/x/oracle/internal/types/params.go @@ -17,23 +17,27 @@ var ( ParamStoreKeyVotePeriod = []byte("voteperiod") ParamStoreKeyVoteThreshold = []byte("votethreshold") ParamStoreKeyRewardBand = []byte("rewardband") - ParamStoreKeyRewardDistributionPeriod = []byte("rewarddistributionperiod") + ParamStoreKeyRewardDistributionWindow = []byte("rewarddistributionwindow") ParamStoreKeyWhitelist = []byte("whitelist") + ParamStoreKeySlashFraction = []byte("slashfraction") + ParamStoreKeySlashWindow = []byte("slashwindow") + ParamStoreKeyMinValidPerWindow = []byte("minvalidperwindow") ) // Default parameter values const ( - DefaultVotePeriod = core.BlocksPerMinute / 2 // 30 seconds - DefaultVotesWindow = int64(1000) // 1000 oracle period + DefaultVotePeriod = core.BlocksPerMinute / 2 // 30 seconds + DefaultSlashWindow = core.BlocksPerHour / DefaultVotePeriod // window for a hour + DefaultRewardDistributionWindow = core.BlocksPerMonth / DefaultVotePeriod // window for a month ) // Default parameter values var ( - DefaultVoteThreshold = sdk.NewDecWithPrec(50, 2) // 50% - DefaultRewardBand = sdk.NewDecWithPrec(1, 2) // 1% - DefaultRewardDistributionPeriod = core.BlocksPerMonth // 432,000 - DefaultMinValidVotesPerWindow = sdk.NewDecWithPrec(5, 2) // 5% - DefaultWhitelist = DenomList{core.MicroKRWDenom, core.MicroSDRDenom, core.MicroUSDDenom} // ukrw, usdr, uusd + DefaultVoteThreshold = sdk.NewDecWithPrec(50, 2) // 50% + DefaultRewardBand = sdk.NewDecWithPrec(1, 2) // 1% + DefaultWhitelist = DenomList{core.MicroKRWDenom, core.MicroSDRDenom, core.MicroUSDDenom} // ukrw, usdr, uusd + DefaultSlashFraction = sdk.NewDecWithPrec(1, 4) // 0.01% + DefaultMinValidPerWindow = sdk.NewDecWithPrec(5, 2) // 5% ) var _ subspace.ParamSet = &Params{} @@ -42,9 +46,12 @@ var _ subspace.ParamSet = &Params{} type Params struct { VotePeriod int64 `json:"vote_period" yaml:"vote_period"` // the number of blocks during which voting takes place. VoteThreshold sdk.Dec `json:"vote_threshold" yaml:"vote_threshold"` // the minimum percentage of votes that must be received for a ballot to pass. - RewardBand sdk.Dec `json:"reward_band" yaml:"reward_band"` // the ratio of allowable price error that can be rewared. - RewardDistributionPeriod int64 `json:"reward_distribution_period" yaml:"reward_distribution_period"` // the number of blocks of the the period during which seigiornage reward comes in and then is distributed. + RewardBand sdk.Dec `json:"reward_band" yaml:"reward_band"` // the ratio of allowable exchange rate error that can be rewared. + RewardDistributionWindow int64 `json:"reward_distribution_window" yaml:"reward_distribution_window"` // the number of vote periods during which seigiornage reward comes in and then is distributed. Whitelist DenomList `json:"whitelist" yaml:"whitelist"` // the denom list that can be acitivated, + SlashFraction sdk.Dec `json:"slash_fraction" yaml:"slash_fraction"` // the ratio of penalty on bonded tokens + SlashWindow int64 `json:"slash_window" yaml:"slash_window"` // the number of vote periods for slashing tallying + MinValidPerWindow sdk.Dec `json:"min_valid_per_window" yaml:"min_valid_per_window"` // the ratio of minimum valid oracle votes per slash window to avoid slashing } // DefaultParams creates default oracle module parameters @@ -53,8 +60,11 @@ func DefaultParams() Params { VotePeriod: DefaultVotePeriod, VoteThreshold: DefaultVoteThreshold, RewardBand: DefaultRewardBand, - RewardDistributionPeriod: DefaultRewardDistributionPeriod, + RewardDistributionWindow: DefaultRewardDistributionWindow, Whitelist: DefaultWhitelist, + SlashFraction: DefaultSlashFraction, + SlashWindow: DefaultSlashWindow, + MinValidPerWindow: DefaultMinValidPerWindow, } } @@ -66,11 +76,20 @@ func (params Params) Validate() error { if params.VoteThreshold.LTE(sdk.NewDecWithPrec(33, 2)) { return fmt.Errorf("oracle parameter VoteTheshold must be greater than 33 percent") } - if params.RewardBand.IsNegative() { - return fmt.Errorf("oracle parameter RewardBand must be positive") + if params.RewardBand.IsNegative() || params.RewardBand.GT(sdk.OneDec()) { + return fmt.Errorf("oracle parameter RewardBand must be between [0, 1]") } - if params.RewardDistributionPeriod < params.VotePeriod { - return fmt.Errorf("oracle parameter RewardDistributionPeriod must be bigger or equal than Voteperiod") + if params.RewardDistributionWindow < 100 { + return fmt.Errorf("oracle parameter RewardDistributionWindow must be bigger or equal than 100 votes period") + } + if params.SlashFraction.GT(sdk.OneDec()) || params.SlashFraction.IsNegative() { + return fmt.Errorf("oracle parameter SlashRraction must be between [0, 1]") + } + if params.SlashWindow < 50 { + return fmt.Errorf("oracle parameter SlashWindow must be greater than or equal 50 votes period") + } + if params.MinValidPerWindow.GT(sdk.NewDecWithPrec(5, 1)) || params.MinValidPerWindow.IsNegative() { + return fmt.Errorf("oracle parameter MinValidPerWindow must be between [0, 0.5]") } return nil } @@ -82,8 +101,11 @@ func (params *Params) ParamSetPairs() subspace.ParamSetPairs { {Key: ParamStoreKeyVotePeriod, Value: ¶ms.VotePeriod}, {Key: ParamStoreKeyVoteThreshold, Value: ¶ms.VoteThreshold}, {Key: ParamStoreKeyRewardBand, Value: ¶ms.RewardBand}, - {Key: ParamStoreKeyRewardDistributionPeriod, Value: ¶ms.RewardDistributionPeriod}, + {Key: ParamStoreKeyRewardDistributionWindow, Value: ¶ms.RewardDistributionWindow}, {Key: ParamStoreKeyWhitelist, Value: ¶ms.Whitelist}, + {Key: ParamStoreKeySlashFraction, Value: ¶ms.SlashFraction}, + {Key: ParamStoreKeySlashWindow, Value: ¶ms.SlashWindow}, + {Key: ParamStoreKeyMinValidPerWindow, Value: ¶ms.MinValidPerWindow}, } } @@ -93,8 +115,12 @@ func (params Params) String() string { VotePeriod: %d VoteThreshold: %s RewardBand: %s - RewardDistributionPeriod: %d + RewardDistributionWindow: %d Whitelist %s + SlashFraction %s + SlashWindow %d + MinValidPerWindow %s `, params.VotePeriod, params.VoteThreshold, params.RewardBand, - params.RewardDistributionPeriod, params.Whitelist) + params.RewardDistributionWindow, params.Whitelist, + params.SlashFraction, params.SlashWindow, params.MinValidPerWindow) } diff --git a/x/oracle/internal/types/params_test.go b/x/oracle/internal/types/params_test.go index addc4dbb5..66a919328 100644 --- a/x/oracle/internal/types/params_test.go +++ b/x/oracle/internal/types/params_test.go @@ -30,9 +30,27 @@ func TestParamsEqual(t *testing.T) { err = p3.Validate() require.Error(t, err) - // negative reward fraction + // negative slash fraction p4 := DefaultParams() - p4.RewardDistributionPeriod = p4.VotePeriod - 1 + p4.SlashFraction = sdk.NewDec(-1) err = p4.Validate() require.Error(t, err) + + // negative min valid per window + p5 := DefaultParams() + p5.MinValidPerWindow = sdk.NewDec(-1) + err = p5.Validate() + require.Error(t, err) + + // small slash window + p6 := DefaultParams() + p6.SlashWindow = int64(49) + err = p6.Validate() + require.Error(t, err) + + // small distribution window + p7 := DefaultParams() + p7.RewardDistributionWindow = int64(99) + err = p7.Validate() + require.Error(t, err) } diff --git a/x/oracle/internal/types/querier.go b/x/oracle/internal/types/querier.go index d98134c06..f56d234c9 100644 --- a/x/oracle/internal/types/querier.go +++ b/x/oracle/internal/types/querier.go @@ -7,23 +7,24 @@ import ( // Defines the prefix of each query path const ( QueryParameters = "parameters" - QueryPrice = "price" - QueryPrices = "prices" + QueryExchangeRate = "exchangeRate" + QueryExchangeRates = "exchangeRates" QueryActives = "actives" QueryPrevotes = "prevotes" QueryVotes = "votes" QueryFeederDelegation = "feederDelegation" + QueryMissCounter = "missCounter" ) -// QueryPriceParams defines the params for the following queries: -// - 'custom/oracle/price' -type QueryPriceParams struct { +// QueryExchangeRateParams defines the params for the following queries: +// - 'custom/oracle/exchange_rate' +type QueryExchangeRateParams struct { Denom string } -// NewQueryPriceParams returns params for price query -func NewQueryPriceParams(denom string) QueryPriceParams { - return QueryPriceParams{denom} +// NewQueryExchangeRateParams returns params for exchange_rate query +func NewQueryExchangeRateParams(denom string) QueryExchangeRateParams { + return QueryExchangeRateParams{denom} } // QueryPrevotesParams defines the params for the following queries: @@ -33,7 +34,7 @@ type QueryPrevotesParams struct { Denom string } -// NewQueryPrevotesParams returns params for price prevotes query +// NewQueryPrevotesParams returns params for exchange_rate prevotes query func NewQueryPrevotesParams(voter sdk.ValAddress, denom string) QueryPrevotesParams { return QueryPrevotesParams{voter, denom} } @@ -45,7 +46,7 @@ type QueryVotesParams struct { Denom string } -// NewQueryVotesParams returns params for price votes query +// NewQueryVotesParams returns params for exchange_rate votes query func NewQueryVotesParams(voter sdk.ValAddress, denom string) QueryVotesParams { return QueryVotesParams{voter, denom} } @@ -60,3 +61,14 @@ type QueryFeederDelegationParams struct { func NewQueryFeederDelegationParams(validator sdk.ValAddress) QueryFeederDelegationParams { return QueryFeederDelegationParams{validator} } + +// QueryMissCounterParams defeins the params for the following queries: +// - 'custom/oracle/missCounter' +type QueryMissCounterParams struct { + Validator sdk.ValAddress +} + +// NewQueryMissCounterParams returns params for feeder delegation query +func NewQueryMissCounterParams(validator sdk.ValAddress) QueryMissCounterParams { + return QueryMissCounterParams{validator} +} diff --git a/x/oracle/internal/types/test_utils.go b/x/oracle/internal/types/test_utils.go index 8bfe88d16..53dd54d62 100644 --- a/x/oracle/internal/types/test_utils.go +++ b/x/oracle/internal/types/test_utils.go @@ -15,7 +15,7 @@ import ( const oracleDecPrecision = 6 // GenerateRandomTestCase nolint -func GenerateRandomTestCase() (prices []float64, valValAddrs []sdk.ValAddress, stakingKeeper DummyStakingKeeper) { +func GenerateRandomTestCase() (rates []float64, valValAddrs []sdk.ValAddress, stakingKeeper DummyStakingKeeper) { valValAddrs = []sdk.ValAddress{} mockValidators := []MockValidator{} @@ -24,8 +24,8 @@ func GenerateRandomTestCase() (prices []float64, valValAddrs []sdk.ValAddress, s rand.Seed(int64(time.Now().Nanosecond())) numInputs := 10 + (rand.Int() % 100) for i := 0; i < numInputs; i++ { - price := float64(int64(rand.Float64()*base)) / base - prices = append(prices, price) + rate := float64(int64(rand.Float64()*base)) / base + rates = append(rates, rate) pubKey := secp256k1.GenPrivKey().PubKey() valValAddr := sdk.ValAddress(pubKey.Address()) @@ -76,6 +76,17 @@ func (DummyStakingKeeper) TotalBondedTokens(_ sdk.Context) sdk.Int { return sdk.ZeroInt() } +// Slash nolint +func (DummyStakingKeeper) Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) {} + +// IterateValidators nolint +func (DummyStakingKeeper) IterateValidators(sdk.Context, func(index int64, validator exported.ValidatorI) (stop bool)) { +} + +// Jail nolint +func (DummyStakingKeeper) Jail(sdk.Context, sdk.ConsAddress) { +} + type MockValidator struct { power int64 operator sdk.ValAddress diff --git a/x/oracle/internal/types/vote.go b/x/oracle/internal/types/vote.go index 55e090143..1ef32ea11 100644 --- a/x/oracle/internal/types/vote.go +++ b/x/oracle/internal/types/vote.go @@ -9,17 +9,17 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" ) -// PricePrevote - struct to store a validator's prevote on the price of Luna in the denom asset -type PricePrevote struct { +// ExchangeRatePrevote - struct to store a validator's prevote on the rate of Luna in the denom asset +type ExchangeRatePrevote struct { Hash string `json:"hash"` // Vote hex hash to protect centralize data source problem Denom string `json:"denom"` // Ticker name of target fiat currency Voter sdk.ValAddress `json:"voter"` // Voter val address SubmitBlock int64 `json:"submit_block"` } -// NewPricePrevote returns PricePrevote object -func NewPricePrevote(hash string, denom string, voter sdk.ValAddress, submitBlock int64) PricePrevote { - return PricePrevote{ +// NewExchangeRatePrevote returns ExchangeRatePrevote object +func NewExchangeRatePrevote(hash string, denom string, voter sdk.ValAddress, submitBlock int64) ExchangeRatePrevote { + return ExchangeRatePrevote{ Hash: hash, Denom: denom, Voter: voter, @@ -28,8 +28,8 @@ func NewPricePrevote(hash string, denom string, voter sdk.ValAddress, submitBloc } // String implements fmt.Stringer interface -func (pp PricePrevote) String() string { - return fmt.Sprintf(`PricePrevote +func (pp ExchangeRatePrevote) String() string { + return fmt.Sprintf(`ExchangeRatePrevote Hash: %s, Denom: %s, Voter: %s, @@ -37,64 +37,63 @@ func (pp PricePrevote) String() string { pp.Hash, pp.Denom, pp.Voter, pp.SubmitBlock) } -// PricePrevotes is a collection of PreicePrevote -type PricePrevotes []PricePrevote +// ExchangeRatePrevotes is a collection of PreicePrevote +type ExchangeRatePrevotes []ExchangeRatePrevote // String implements fmt.Stringer interface -func (v PricePrevotes) String() (out string) { +func (v ExchangeRatePrevotes) String() (out string) { for _, val := range v { out += val.String() + "\n" } return strings.TrimSpace(out) } -// VoteHash computes hash value of PriceVote -func VoteHash(salt string, price sdk.Dec, denom string, voter sdk.ValAddress) ([]byte, error) { +// VoteHash computes hash value of ExchangeRateVote +func VoteHash(salt string, rate sdk.Dec, denom string, voter sdk.ValAddress) ([]byte, error) { hash := tmhash.NewTruncated() - _, err := hash.Write([]byte(fmt.Sprintf("%s:%s:%s:%s", salt, price, denom, voter))) + _, err := hash.Write([]byte(fmt.Sprintf("%s:%s:%s:%s", salt, rate, denom, voter))) bz := hash.Sum(nil) return bz, err } -// PriceVote - struct to store a validator's vote on the price of Luna in the denom asset -type PriceVote struct { - Price sdk.Dec `json:"price"` // Price of Luna in target fiat currency - Denom string `json:"denom"` // Ticker name of target fiat currency - Voter sdk.ValAddress `json:"voter"` // voter val address of validator +// ExchangeRateVote - struct to store a validator's vote on the rate of Luna in the denom asset +type ExchangeRateVote struct { + ExchangeRate sdk.Dec `json:"exchange_rate"` // ExchangeRate of Luna in target fiat currency + Denom string `json:"denom"` // Ticker name of target fiat currency + Voter sdk.ValAddress `json:"voter"` // voter val address of validator } -// NewPriceVote creates a PriceVote instance -func NewPriceVote(price sdk.Dec, denom string, voter sdk.ValAddress) PriceVote { - return PriceVote{ - Price: price, - Denom: denom, - Voter: voter, +// NewExchangeRateVote creates a ExchangeRateVote instance +func NewExchangeRateVote(rate sdk.Dec, denom string, voter sdk.ValAddress) ExchangeRateVote { + return ExchangeRateVote{ + ExchangeRate: rate, + Denom: denom, + Voter: voter, } } -func (pv PriceVote) getPower(ctx sdk.Context, sk StakingKeeper) int64 { - validator := sk.Validator(ctx, pv.Voter) - if validator == nil { - return 0 +func (pv ExchangeRateVote) getPower(ctx sdk.Context, powerMap map[string]int64) int64 { + if power, ok := powerMap[pv.Voter.String()]; ok { + return power } - return validator.GetConsensusPower() + return 0 } // String implements fmt.Stringer interface -func (pv PriceVote) String() string { - return fmt.Sprintf(`PriceVote +func (pv ExchangeRateVote) String() string { + return fmt.Sprintf(`ExchangeRateVote Denom: %s, Voter: %s, - Price: %s`, - pv.Denom, pv.Voter, pv.Price) + ExchangeRate: %s`, + pv.Denom, pv.Voter, pv.ExchangeRate) } -// PriceVotes is a collection of PriceVote -type PriceVotes []PriceVote +// ExchangeRateVotes is a collection of ExchangeRateVote +type ExchangeRateVotes []ExchangeRateVote // String implements fmt.Stringer interface -func (v PriceVotes) String() (out string) { +func (v ExchangeRateVotes) String() (out string) { for _, val := range v { out += val.String() + "\n" } diff --git a/x/oracle/simulation/msgs.go b/x/oracle/simulation/msgs.go index eb3fd6ebb..12b6cd18b 100644 --- a/x/oracle/simulation/msgs.go +++ b/x/oracle/simulation/msgs.go @@ -23,7 +23,7 @@ func SimulateMsgPrevote(k oracle.Keeper) simulation.Operation { bz, _ := oracle.VoteHash("1234", sdk.NewDec(1700), core.MicroSDRDenom, valAddr) voteHash := hex.EncodeToString(bz) - msg := oracle.NewMsgPricePrevote(voteHash, core.MicroSDRDenom, acc.Address, valAddr) + msg := oracle.NewMsgExchangeRatePrevote(voteHash, core.MicroSDRDenom, acc.Address, valAddr) if msg.ValidateBasic() != nil { return simulation.NoOpMsg(oracle.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } @@ -45,7 +45,7 @@ func SimulateMsgVote(k oracle.Keeper) simulation.Operation { acc := simulation.RandomAcc(r, accs) valAddr := sdk.ValAddress(acc.Address) - msg := oracle.NewMsgPriceVote(sdk.NewDec(1700), "1234", core.MicroSDRDenom, acc.Address, valAddr) + msg := oracle.NewMsgExchangeRateVote(sdk.NewDec(1700), "1234", core.MicroSDRDenom, acc.Address, valAddr) if msg.ValidateBasic() != nil { return simulation.NoOpMsg(oracle.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } @@ -59,15 +59,15 @@ func SimulateMsgVote(k oracle.Keeper) simulation.Operation { } } -// SimulateMsgDelegateFeederPermission generates a MsgDelegateFeederPermission with random values -func SimulateMsgDelegateFeederPermission(k oracle.Keeper) simulation.Operation { +// SimulateMsgDelegateFeedConsent generates a MsgDelegateFeedConsent with random values +func SimulateMsgDelegateFeedConsent(k oracle.Keeper) simulation.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) { acc := simulation.RandomAcc(r, accs) acc2 := simulation.RandomAcc(r, accs) valAddr := sdk.ValAddress(acc.Address) - msg := oracle.NewMsgDelegateFeederPermission(valAddr, acc2.Address) + msg := oracle.NewMsgDelegateFeedConsent(valAddr, acc2.Address) if msg.ValidateBasic() != nil { return simulation.NoOpMsg(oracle.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } diff --git a/x/oracle/slash_test.go b/x/oracle/slash_test.go new file mode 100644 index 000000000..9fa5a75fa --- /dev/null +++ b/x/oracle/slash_test.go @@ -0,0 +1,33 @@ +package oracle + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/terra-project/core/x/oracle/internal/keeper" + + "github.com/cosmos/cosmos-sdk/x/staking" +) + +func TestSlashAndResetMissCounters(t *testing.T) { + input, _ := setup(t) + + slashWindow := input.OracleKeeper.SlashWindow(input.Ctx) + slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) + minValidVotes := input.OracleKeeper.MinValidPerWindow(input.Ctx).MulInt64(slashWindow).TruncateInt64() + // Case 1, no slash + input.OracleKeeper.SetMissCounter(input.Ctx, keeper.ValAddrs[0], slashWindow-minValidVotes) + SlashAndResetMissCounters(input.Ctx, input.OracleKeeper) + staking.EndBlocker(input.Ctx, input.StakingKeeper) + + validator := input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[0]) + require.Equal(t, stakingAmt, validator.GetBondedTokens()) + + // Case 2, slash + input.OracleKeeper.SetMissCounter(input.Ctx, keeper.ValAddrs[0], slashWindow-minValidVotes+1) + SlashAndResetMissCounters(input.Ctx, input.OracleKeeper) + validator = input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[0]) + require.Equal(t, stakingAmt.Sub(slashFraction.MulInt(stakingAmt).TruncateInt()), validator.GetBondedTokens()) + require.True(t, validator.IsJailed()) +} diff --git a/x/oracle/slashing.go b/x/oracle/slashing.go new file mode 100644 index 000000000..034102709 --- /dev/null +++ b/x/oracle/slashing.go @@ -0,0 +1,35 @@ +package oracle + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// SlashAndResetMissCounters do salsh any operator who over criteria & clear all operators miss counter to zero +func SlashAndResetMissCounters(ctx sdk.Context, k Keeper) { + height := ctx.BlockHeight() + distributionHeight := height - sdk.ValidatorUpdateDelay - 1 + + slashWindow := k.SlashWindow(ctx) + minValidPerWindow := k.MinValidPerWindow(ctx) + slashFraction := k.SlashFraction(ctx) + k.IterateMissCounters(ctx, func(operator sdk.ValAddress, missCounter int64) bool { + + // Calculate valid vote rate; (SlashWindow - MissCounter)/SlashWindow + validVoteRate := sdk.NewDecFromInt( + sdk.NewInt(slashWindow - missCounter)). + QuoInt64(k.SlashWindow(ctx)) + + // Penalize the validator whoes the valid vote rate is smaller than min threshold + if validVoteRate.LT(minValidPerWindow) { + validator := k.StakingKeeper.Validator(ctx, operator) + k.StakingKeeper.Slash( + ctx, validator.GetConsAddr(), + distributionHeight, validator.GetConsensusPower(), slashFraction, + ) + k.StakingKeeper.Jail(ctx, validator.GetConsAddr()) + } + + k.SetMissCounter(ctx, operator, 0) + return false + }) +} diff --git a/x/oracle/tally.go b/x/oracle/tally.go index 1e3c7c452..f9b78a42f 100644 --- a/x/oracle/tally.go +++ b/x/oracle/tally.go @@ -9,41 +9,41 @@ import ( // Calculates the median and returns it. Sets the set of voters to be rewarded, i.e. voted within // a reasonable spread from the weighted median to the store -func tally(ctx sdk.Context, pb types.PriceBallot, k Keeper) (weightedMedian sdk.Dec, ballotWinners types.ClaimPool) { +func tally(ctx sdk.Context, pb types.ExchangeRateBallot, rewardBand sdk.Dec) (weightedMedian sdk.Dec, ballotWinners []types.Claim) { if !sort.IsSorted(pb) { sort.Sort(pb) } - weightedMedian = pb.WeightedMedian(ctx, k.StakingKeeper) - standardDeviation := pb.StandardDeviation(ctx, k.StakingKeeper) - rewardSpread := k.RewardBand(ctx).QuoInt64(2) + weightedMedian = pb.WeightedMedian() + standardDeviation := pb.StandardDeviation() + rewardSpread := rewardBand.QuoInt64(2) if standardDeviation.GT(rewardSpread) { rewardSpread = standardDeviation } for _, vote := range pb { - // If a validator is not found, then just ignore the vote - if validator := k.StakingKeeper.Validator(ctx, vote.Voter); validator != nil { - if vote.Price.GTE(weightedMedian.Sub(rewardSpread)) && vote.Price.LTE(weightedMedian.Add(rewardSpread)) { - power := validator.GetConsensusPower() - - ballotWinners = append(ballotWinners, types.Claim{ - Recipient: vote.Voter, - Weight: power, - }) - } + // Filter ballot winners & abstain voters + if (vote.ExchangeRate.GTE(weightedMedian.Sub(rewardSpread)) && + vote.ExchangeRate.LTE(weightedMedian.Add(rewardSpread))) || + !vote.ExchangeRate.IsPositive() { + // Abstain votes will have zero vote power + ballotWinners = append(ballotWinners, types.Claim{ + Recipient: vote.Voter, + Weight: vote.Power, + }) } + } return } // ballot for the asset is passing the threshold amount of voting power -func ballotIsPassing(ctx sdk.Context, ballot types.PriceBallot, k Keeper) bool { +func ballotIsPassing(ctx sdk.Context, ballot types.ExchangeRateBallot, k Keeper) bool { totalBondedPower := sdk.TokensToConsensusPower(k.StakingKeeper.TotalBondedTokens(ctx)) voteThreshold := k.VoteThreshold(ctx) thresholdVotes := voteThreshold.MulInt64(totalBondedPower).RoundInt() - ballotPower := sdk.NewInt(ballot.Power(ctx, k.StakingKeeper)) + ballotPower := sdk.NewInt(ballot.Power()) return ballotPower.GTE(thresholdVotes) } diff --git a/x/oracle/test_utils.go b/x/oracle/test_utils.go index e1780a64c..92628724f 100644 --- a/x/oracle/test_utils.go +++ b/x/oracle/test_utils.go @@ -17,8 +17,8 @@ var ( uSDRAmt = sdk.NewInt(1005 * core.MicroUnit) stakingAmt = sdk.TokensFromConsensusPower(10) - randomPrice = sdk.NewDec(1700) - anotherRandomPrice = sdk.NewDecWithPrec(4882, 2) // swap rate + randomExchangeRate = sdk.NewDec(1700) + anotherRandomExchangeRate = sdk.NewDecWithPrec(4882, 2) // swap rate ) func setup(t *testing.T) (keeper.TestInput, sdk.Handler) { diff --git a/x/treasury/internal/keeper/indicator_test.go b/x/treasury/internal/keeper/indicator_test.go index 34361081b..b23843e96 100644 --- a/x/treasury/internal/keeper/indicator_test.go +++ b/x/treasury/internal/keeper/indicator_test.go @@ -16,10 +16,10 @@ func TestFeeRewardsForEpoch(t *testing.T) { taxAmount := sdk.NewInt(1000).MulRaw(core.MicroUnit) // Set random prices - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, sdk.NewDec(1)) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, sdk.NewDec(10)) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroGBPDenom, sdk.NewDec(100)) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroCNYDenom, sdk.NewDec(1000)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, sdk.NewDec(1)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, sdk.NewDec(10)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroGBPDenom, sdk.NewDec(100)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroCNYDenom, sdk.NewDec(1000)) // Record tax proceeds input.TreasuryKeeper.RecordEpochTaxProceeds(input.Ctx, sdk.Coins{ @@ -50,7 +50,7 @@ func TestSeigniorageRewardsForEpoch(t *testing.T) { input.TreasuryKeeper.RecordEpochInitialIssuance(input.Ctx) // Set random prices - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lnasdrRate) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, lnasdrRate) input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch) // Add seigniorage @@ -76,10 +76,10 @@ func TestMiningRewardsForEpoch(t *testing.T) { input.TreasuryKeeper.RecordEpochInitialIssuance(input.Ctx) // Set random prices - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, sdk.NewDec(1)) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, sdk.NewDec(10)) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroGBPDenom, sdk.NewDec(100)) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroCNYDenom, sdk.NewDec(1000)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, sdk.NewDec(1)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, sdk.NewDec(10)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroGBPDenom, sdk.NewDec(100)) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroCNYDenom, sdk.NewDec(1000)) input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch) @@ -109,6 +109,58 @@ func TestMiningRewardsForEpoch(t *testing.T) { func TestLoadIndicatorByEpoch(t *testing.T) { input := CreateTestInput(t) + + sh := staking.NewHandler(input.StakingKeeper) + + // Create Validators + amt := sdk.TokensFromConsensusPower(1) + addr, val := ValAddrs[0], PubKeys[0] + addr1, val1 := ValAddrs[1], PubKeys[1] + res := sh(input.Ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, res.IsOK()) + res = sh(input.Ctx, NewTestMsgCreateValidator(addr1, val1, amt)) + require.True(t, res.IsOK()) + staking.EndBlocker(input.Ctx, input.StakingKeeper) + + // Case 1: at epoch 0 and averaging over 0 epochs + rval := RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 0, linearFn) + require.Equal(t, sdk.ZeroDec(), rval) + + // Case 2: at epoch 0 and averaging over negative epochs + rval = RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, -1, linearFn) + require.Equal(t, sdk.ZeroDec(), rval) + + // Case 3: at epoch 3 and averaging over 3, 4, 5 epochs; all should have the same rval + input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch * 3) + rval = RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 4, linearFn) + rval2 := RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 5, linearFn) + rval3 := RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 6, linearFn) + require.Equal(t, sdk.NewDecWithPrec(15, 1), rval) + require.Equal(t, rval, rval2) + require.Equal(t, rval2, rval3) + + // Case 4: at epoch 3 and averaging over 0 epochs + rval = RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 0, linearFn) + require.Equal(t, sdk.ZeroDec(), rval) + + // Case 5: at epoch 3 and averaging over 1 epoch + rval = RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 1, linearFn) + require.Equal(t, sdk.NewDec(3), rval) + + // Case 6: at epoch 500 and averaging over 300 epochs + input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch * 500) + rval = RollingAverageIndicator(input.Ctx, input.TreasuryKeeper, 300, linearFn) + require.Equal(t, sdk.NewDecWithPrec(3505, 1), rval) + + // Test all of our reporting functions + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, sdk.OneDec()) + + // set initial supply + input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch * 200) + supply := input.SupplyKeeper.GetSupply(input.Ctx) + supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(100000000*core.MicroUnit)))) + input.SupplyKeeper.SetSupply(input.Ctx, supply) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) TRArr := []sdk.Dec{ sdk.NewDec(100), diff --git a/x/treasury/internal/keeper/policy_test.go b/x/treasury/internal/keeper/policy_test.go index dc152c4b8..93f393803 100644 --- a/x/treasury/internal/keeper/policy_test.go +++ b/x/treasury/internal/keeper/policy_test.go @@ -88,9 +88,9 @@ func TestUpdateTaxCap(t *testing.T) { // Create Validators sdrPrice := sdk.NewDecWithPrec(13, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, sdrPrice) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, sdrPrice) krwPrice := sdk.NewDecWithPrec(153412, 2) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, krwPrice) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroKRWDenom, krwPrice) input.TreasuryKeeper.UpdateTaxCap(input.Ctx) krwCap := input.TreasuryKeeper.GetTaxCap(input.Ctx, core.MicroKRWDenom) diff --git a/x/treasury/internal/keeper/seigniorage.go b/x/treasury/internal/keeper/seigniorage.go index cc564fe2a..4743ca4da 100644 --- a/x/treasury/internal/keeper/seigniorage.go +++ b/x/treasury/internal/keeper/seigniorage.go @@ -1,8 +1,6 @@ package keeper import ( - "fmt" - "github.com/terra-project/core/x/treasury/internal/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -22,17 +20,12 @@ func (k Keeper) SettleSeigniorage(ctx sdk.Context) { rewardWeight := k.GetRewardWeight(ctx) // Align seigniorage to usdr - seigniorageLunaDecCoin := sdk.NewDecCoin(core.MicroLunaDenom, seigniorageLunaAmt) - seigniorageDecCoin, err := k.marketKeeper.ComputeInternalSwap(ctx, seigniorageLunaDecCoin, core.MicroSDRDenom) - if err != nil { - k.Logger(ctx).Error(fmt.Sprintf("[Treasury] Failed to swap seigniorage to usdr, %s", err.Error())) - return - } + seigniorageDecCoin := sdk.NewDecCoin(core.MicroLunaDenom, seigniorageLunaAmt) // Mint seigniorage seigniorageCoin, _ := seigniorageDecCoin.TruncateDecimal() seigniorageCoins := sdk.NewCoins(seigniorageCoin) - err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, seigniorageCoins) + err := k.supplyKeeper.MintCoins(ctx, types.ModuleName, seigniorageCoins) if err != nil { panic(err) } @@ -40,7 +33,7 @@ func (k Keeper) SettleSeigniorage(ctx sdk.Context) { // Send reward to oracle module oracleRewardAmt := rewardWeight.MulInt(seigniorageAmt).TruncateInt() - oracleRewardCoins := sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, oracleRewardAmt)) + oracleRewardCoins := sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, oracleRewardAmt)) err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.oracleModuleName, oracleRewardCoins) if err != nil { panic(err) @@ -48,7 +41,7 @@ func (k Keeper) SettleSeigniorage(ctx sdk.Context) { // Send left to distribution module leftAmt := seigniorageAmt.Sub(oracleRewardAmt) - leftCoins := sdk.NewCoins(sdk.NewCoin(core.MicroSDRDenom, leftAmt)) + leftCoins := sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, leftAmt)) err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.distributionModuleName, leftCoins) if err != nil { panic(err) diff --git a/x/treasury/internal/keeper/seigniorage_test.go b/x/treasury/internal/keeper/seigniorage_test.go index 52298200a..359db124f 100644 --- a/x/treasury/internal/keeper/seigniorage_test.go +++ b/x/treasury/internal/keeper/seigniorage_test.go @@ -14,8 +14,6 @@ import ( func TestSettle(t *testing.T) { input := CreateTestInput(t) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, sdk.OneDec()) - issuance := sdk.NewInt(rand.Int63() + 1) supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, issuance))) @@ -37,6 +35,6 @@ func TestSettle(t *testing.T) { oracleRewardAmt := rewardWeight.MulInt(issuance).TruncateInt() leftAmt := issuance.Sub(oracleRewardAmt) - require.Equal(t, oracleRewardAmt, oracleAcc.GetCoins().AmountOf(core.MicroSDRDenom)) - require.Equal(t, leftAmt, feePool.CommunityPool.AmountOf(core.MicroSDRDenom).TruncateInt()) + require.Equal(t, oracleRewardAmt, oracleAcc.GetCoins().AmountOf(core.MicroLunaDenom)) + require.Equal(t, leftAmt, feePool.CommunityPool.AmountOf(core.MicroLunaDenom).TruncateInt()) }