diff --git a/migrate/v0_11/migrate.go b/migrate/v0_11/migrate.go index 1c206a62..262f03e5 100644 --- a/migrate/v0_11/migrate.go +++ b/migrate/v0_11/migrate.go @@ -7,6 +7,8 @@ import ( v0_11bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_11" v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9" + v0_11pricefeed "github.com/kava-labs/kava/x/pricefeed" + v0_9pricefeed "github.com/kava-labs/kava/x/pricefeed/legacy/v0_9" ) // MigrateBep3 migrates from a v0.9 (or v0.10) bep3 genesis state to a v0.11 bep3 genesis state @@ -65,3 +67,32 @@ func MigrateBep3(oldGenState v0_9bep3.GenesisState) v0_11bep3.GenesisState { PreviousBlockTime: v0_11bep3.DefaultPreviousBlockTime, } } + +// MigratePricefeed migrates from a v0.9 (or v0.10) pricefeed genesis state to a v0.11 pricefeed genesis state +func MigratePricefeed(oldGenState v0_9pricefeed.GenesisState) v0_11pricefeed.GenesisState { + var newMarkets v0_11pricefeed.Markets + var newPostedPrices v0_11pricefeed.PostedPrices + var oracles []sdk.AccAddress + + for _, market := range oldGenState.Params.Markets { + newMarket := v0_11pricefeed.NewMarket(market.MarketID, market.BaseAsset, market.QuoteAsset, market.Oracles, market.Active) + newMarkets = append(newMarkets, newMarket) + oracles = market.Oracles + } + // ------- add btc, xrp, busd markets -------- + btcSpotMarket := v0_11pricefeed.NewMarket("btc:usd", "btc", "usd", oracles, true) + btcLiquidationMarket := v0_11pricefeed.NewMarket("btc:usd:30", "btc", "usd", oracles, true) + xrpSpotMarket := v0_11pricefeed.NewMarket("xrp:usd", "xrp", "usd", oracles, true) + xrpLiquidationMarket := v0_11pricefeed.NewMarket("xrp:usd:30", "xrp", "usd", oracles, true) + busdSpotMarket := v0_11pricefeed.NewMarket("busd:usd", "busd", "usd", oracles, true) + busdLiquidationMarket := v0_11pricefeed.NewMarket("busd:usd:30", "busd", "usd", oracles, true) + newMarkets = append(newMarkets, btcSpotMarket, btcLiquidationMarket, xrpSpotMarket, xrpLiquidationMarket, busdSpotMarket, busdLiquidationMarket) + + for _, price := range oldGenState.PostedPrices { + newPrice := v0_11pricefeed.NewPostedPrice(price.MarketID, price.OracleAddress, price.Price, price.Expiry) + newPostedPrices = append(newPostedPrices, newPrice) + } + newParams := v0_11pricefeed.NewParams(newMarkets) + + return v0_11pricefeed.NewGenesisState(newParams, newPostedPrices) +} diff --git a/migrate/v0_11/migrate_test.go b/migrate/v0_11/migrate_test.go index e4aab33e..9a2532b3 100644 --- a/migrate/v0_11/migrate_test.go +++ b/migrate/v0_11/migrate_test.go @@ -12,6 +12,7 @@ import ( "github.com/kava-labs/kava/app" v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9" + v0_9pricefeed "github.com/kava-labs/kava/x/pricefeed/legacy/v0_9" ) func TestMain(m *testing.M) { @@ -35,3 +36,16 @@ func TestMigrateBep3(t *testing.T) { err = newGenState.Validate() require.NoError(t, err) } + +func TestMigratePricefeed(t *testing.T) { + bz, err := ioutil.ReadFile(filepath.Join("testdata", "pricefeed-v09.json")) + require.NoError(t, err) + var oldGenState v0_9pricefeed.GenesisState + cdc := app.MakeCodec() + require.NotPanics(t, func() { + cdc.MustUnmarshalJSON(bz, &oldGenState) + }) + newGenState := MigratePricefeed(oldGenState) + err = newGenState.Validate() + require.NoError(t, err) +} diff --git a/migrate/v0_11/testdata/pricefeed-v09.json b/migrate/v0_11/testdata/pricefeed-v09.json new file mode 100644 index 00000000..1b841fe4 --- /dev/null +++ b/migrate/v0_11/testdata/pricefeed-v09.json @@ -0,0 +1,152 @@ +{ + "params": { + "markets": [ + { + "active": true, + "base_asset": "bnb", + "market_id": "bnb:usd", + "oracles": [ + "kava12dyshua9nkvx9w8ywp72wdnzrc4t4mnnycz0dl", + "kava1tuxyepdrkwraa22k99w04c0wa64tgh70mv87fs", + "kava1ueak7nzesm3pnev6lngp6lgk0ry02djz8pjpcg", + "kava1sl62nqm89c780yxm3m9lp3tacmpnfljq6tytvl", + "kava1ujfrlcd0ted58mzplnyxzklsw0sqevlgxndanp", + "kava17fatl3wzxvk4rwfu3tqsctdp5x9vute67j9ufj", + "kava19rjk5qmmwywnzfccwzyn02jywgpwjqf60afj92", + "kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn", + "kava1pt6q4kdmwawr3thm9cd82pq7hml8u84rd0f3jy", + "kava13tpwqygswyzupqfggfgh9dmtgthgucn5wpfksh" + ], + "quote_asset": "usd" + }, + { + "active": true, + "base_asset": "bnb", + "market_id": "bnb:usd:30", + "oracles": [ + "kava12dyshua9nkvx9w8ywp72wdnzrc4t4mnnycz0dl", + "kava1tuxyepdrkwraa22k99w04c0wa64tgh70mv87fs", + "kava1ueak7nzesm3pnev6lngp6lgk0ry02djz8pjpcg", + "kava1sl62nqm89c780yxm3m9lp3tacmpnfljq6tytvl", + "kava1ujfrlcd0ted58mzplnyxzklsw0sqevlgxndanp", + "kava17fatl3wzxvk4rwfu3tqsctdp5x9vute67j9ufj", + "kava19rjk5qmmwywnzfccwzyn02jywgpwjqf60afj92", + "kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn", + "kava1pt6q4kdmwawr3thm9cd82pq7hml8u84rd0f3jy", + "kava13tpwqygswyzupqfggfgh9dmtgthgucn5wpfksh" + ], + "quote_asset": "usd" + } + ] + }, + "posted_prices": [ + { + "expiry": "2020-08-19T06:14:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava19rjk5qmmwywnzfccwzyn02jywgpwjqf60afj92", + "price": "22.925799999999998846" + }, + { + "expiry": "2020-08-19T06:14:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava1pt6q4kdmwawr3thm9cd82pq7hml8u84rd0f3jy", + "price": "22.932800000000000296" + }, + { + "expiry": "2020-07-28T07:30:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava1ujfrlcd0ted58mzplnyxzklsw0sqevlgxndanp", + "price": "20.084499999999998465" + }, + { + "expiry": "2020-08-19T06:14:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava1ueak7nzesm3pnev6lngp6lgk0ry02djz8pjpcg", + "price": "22.932800000000000296" + }, + { + "expiry": "2020-08-19T06:22:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava17fatl3wzxvk4rwfu3tqsctdp5x9vute67j9ufj", + "price": "22.866299999999998960" + }, + { + "expiry": "2020-08-19T06:14:01Z", + "market_id": "bnb:usd", + "oracle_address": "kava12dyshua9nkvx9w8ywp72wdnzrc4t4mnnycz0dl", + "price": "22.932800000000000296" + }, + { + "expiry": "2020-08-19T06:14:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn", + "price": "22.932800000000000296" + }, + { + "expiry": "2020-08-19T06:28:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava13tpwqygswyzupqfggfgh9dmtgthgucn5wpfksh", + "price": "22.836200000000001609" + }, + { + "expiry": "2020-08-19T06:29:31Z", + "market_id": "bnb:usd", + "oracle_address": "kava1sl62nqm89c780yxm3m9lp3tacmpnfljq6tytvl", + "price": "22.834499999999998465" + }, + { + "expiry": "2020-08-19T06:25:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava19rjk5qmmwywnzfccwzyn02jywgpwjqf60afj92", + "price": "22.967586666666665707" + }, + { + "expiry": "2020-08-19T06:24:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava1pt6q4kdmwawr3thm9cd82pq7hml8u84rd0f3jy", + "price": "22.972826666666666284" + }, + { + "expiry": "2020-08-19T06:22:30Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava1ueak7nzesm3pnev6lngp6lgk0ry02djz8pjpcg", + "price": "22.980756666666668053" + }, + { + "expiry": "2020-07-28T08:30:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava1ujfrlcd0ted58mzplnyxzklsw0sqevlgxndanp", + "price": "20.056519999999995463" + }, + { + "expiry": "2020-08-19T06:25:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava17fatl3wzxvk4rwfu3tqsctdp5x9vute67j9ufj", + "price": "22.967586666666665707" + }, + { + "expiry": "2020-08-19T06:25:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava12dyshua9nkvx9w8ywp72wdnzrc4t4mnnycz0dl", + "price": "22.967586666666665707" + }, + { + "expiry": "2020-08-19T06:25:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava1xd39avn2f008jmvua0eupg39zsp2xn3wf802vn", + "price": "22.967586666666665707" + }, + { + "expiry": "2020-08-19T06:22:00Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava13tpwqygswyzupqfggfgh9dmtgthgucn5wpfksh", + "price": "22.972629999999998773" + }, + { + "expiry": "2020-08-19T06:24:31Z", + "market_id": "bnb:usd:30", + "oracle_address": "kava1sl62nqm89c780yxm3m9lp3tacmpnfljq6tytvl", + "price": "22.967533333333335577" + } + ] +} \ No newline at end of file diff --git a/x/pricefeed/alias.go b/x/pricefeed/alias.go index c3d44a41..b4ecec4d 100644 --- a/x/pricefeed/alias.go +++ b/x/pricefeed/alias.go @@ -1,81 +1,78 @@ package pricefeed +// DO NOT EDIT - generated by aliasgen tool (github.com/rhuairahrighairidh/aliasgen) + import ( "github.com/kava-labs/kava/x/pricefeed/keeper" "github.com/kava-labs/kava/x/pricefeed/types" ) -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types/ -// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper/ - -// nolint const ( - EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated - EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice - EventTypeNoValidPrices = types.EventTypeNoValidPrices - AttributeValueCategory = types.AttributeValueCategory + AttributeExpiry = types.AttributeExpiry AttributeMarketID = types.AttributeMarketID AttributeMarketPrice = types.AttributeMarketPrice AttributeOracle = types.AttributeOracle - AttributeExpiry = types.AttributeExpiry - ModuleName = types.ModuleName - StoreKey = types.StoreKey - RouterKey = types.RouterKey - QuerierRoute = types.QuerierRoute + AttributeValueCategory = types.AttributeValueCategory DefaultParamspace = types.DefaultParamspace - TypeMsgPostPrice = types.TypeMsgPostPrice + EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated + EventTypeNoValidPrices = types.EventTypeNoValidPrices + EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice + MaxExpiry = types.MaxExpiry + ModuleName = types.ModuleName + QuerierRoute = types.QuerierRoute QueryGetParams = types.QueryGetParams QueryMarkets = types.QueryMarkets QueryOracles = types.QueryOracles - QueryRawPrices = types.QueryRawPrices QueryPrice = types.QueryPrice - MaxExpiry = types.MaxExpiry + QueryRawPrices = types.QueryRawPrices + RouterKey = types.RouterKey + StoreKey = types.StoreKey + TypeMsgPostPrice = types.TypeMsgPostPrice ) -// nolint var ( - // functions aliases + // function aliases NewKeeper = keeper.NewKeeper NewQuerier = keeper.NewQuerier - RegisterCodec = types.RegisterCodec - ErrEmptyInput = types.ErrEmptyInput - ErrExpired = types.ErrExpired - ErrNoValidPrice = types.ErrNoValidPrice - ErrInvalidMarket = types.ErrInvalidMarket - ErrInvalidOracle = types.ErrInvalidOracle - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState CurrentPriceKey = types.CurrentPriceKey - RawPriceKey = types.RawPriceKey + DefaultGenesisState = types.DefaultGenesisState + DefaultParams = types.DefaultParams NewCurrentPrice = types.NewCurrentPrice - NewPostedPrice = types.NewPostedPrice + NewGenesisState = types.NewGenesisState + NewMarket = types.NewMarket NewMsgPostPrice = types.NewMsgPostPrice NewParams = types.NewParams - DefaultParams = types.DefaultParams - ParamKeyTable = types.ParamKeyTable + NewPostedPrice = types.NewPostedPrice NewQueryWithMarketIDParams = types.NewQueryWithMarketIDParams + ParamKeyTable = types.ParamKeyTable + RawPriceKey = types.RawPriceKey + RegisterCodec = types.RegisterCodec // variable aliases - ModuleCdc = types.ModuleCdc CurrentPricePrefix = types.CurrentPricePrefix - RawPriceFeedPrefix = types.RawPriceFeedPrefix - KeyMarkets = types.KeyMarkets DefaultMarkets = types.DefaultMarkets + ErrAssetNotFound = types.ErrAssetNotFound + ErrEmptyInput = types.ErrEmptyInput + ErrExpired = types.ErrExpired + ErrInvalidMarket = types.ErrInvalidMarket + ErrInvalidOracle = types.ErrInvalidOracle + ErrNoValidPrice = types.ErrNoValidPrice + KeyMarkets = types.KeyMarkets + ModuleCdc = types.ModuleCdc + RawPriceFeedPrefix = types.RawPriceFeedPrefix ) -// nolint type ( Keeper = keeper.Keeper + CurrentPrice = types.CurrentPrice + CurrentPrices = types.CurrentPrices GenesisState = types.GenesisState Market = types.Market Markets = types.Markets - CurrentPrice = types.CurrentPrice - PostedPrice = types.PostedPrice - PostedPrices = types.PostedPrices - SortDecs = types.SortDecs MsgPostPrice = types.MsgPostPrice Params = types.Params + PostedPrice = types.PostedPrice + PostedPrices = types.PostedPrices QueryWithMarketIDParams = types.QueryWithMarketIDParams + SortDecs = types.SortDecs ) diff --git a/x/pricefeed/genesis.go b/x/pricefeed/genesis.go index 0bfe7f48..065d34fa 100644 --- a/x/pricefeed/genesis.go +++ b/x/pricefeed/genesis.go @@ -9,11 +9,13 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, gs GenesisState) { // Set the markets and oracles from params keeper.SetParams(ctx, gs.Params) - // Iterate through the posted prices and set them in the store + // Iterate through the posted prices and set them in the store if they are not expired for _, pp := range gs.PostedPrices { - _, err := keeper.SetPrice(ctx, pp.OracleAddress, pp.MarketID, pp.Price, pp.Expiry) - if err != nil { - panic(err) + if pp.Expiry.After(ctx.BlockTime()) { + _, err := keeper.SetPrice(ctx, pp.OracleAddress, pp.MarketID, pp.Price, pp.Expiry) + if err != nil { + panic(err) + } } } params := keeper.GetParams(ctx) diff --git a/x/pricefeed/legacy/v0_9/types.go b/x/pricefeed/legacy/v0_9/types.go new file mode 100644 index 00000000..1fcbca40 --- /dev/null +++ b/x/pricefeed/legacy/v0_9/types.go @@ -0,0 +1,193 @@ +package v0_9 + +import ( + "errors" + "fmt" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - pricefeed state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + PostedPrices PostedPrices `json:"posted_prices" yaml:"posted_prices"` +} + +// NewGenesisState creates a new genesis state for the pricefeed module +func NewGenesisState(p Params, pp []PostedPrice) GenesisState { + return GenesisState{ + Params: p, + PostedPrices: pp, + } +} + +type Params struct { + Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed +} + +type Market struct { + // TODO: rename to ID + MarketID string `json:"market_id" yaml:"market_id"` + BaseAsset string `json:"base_asset" yaml:"base_asset"` + QuoteAsset string `json:"quote_asset" yaml:"quote_asset"` + Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"` + Active bool `json:"active" yaml:"active"` +} + +// String implement fmt.Stringer +func (m Market) String() string { + return fmt.Sprintf(`Asset: + Market ID: %s + Base Asset: %s + Quote Asset: %s + Oracles: %s + Active: %t`, + m.MarketID, m.BaseAsset, m.QuoteAsset, m.Oracles, m.Active) +} + +// Validate performs a basic validation of the market params +func (m Market) Validate() error { + if strings.TrimSpace(m.MarketID) == "" { + return errors.New("market id cannot be blank") + } + if err := sdk.ValidateDenom(m.BaseAsset); err != nil { + return fmt.Errorf("invalid base asset: %w", err) + } + if err := sdk.ValidateDenom(m.QuoteAsset); err != nil { + return fmt.Errorf("invalid quote asset: %w", err) + } + seenOracles := make(map[string]bool) + for i, oracle := range m.Oracles { + if oracle.Empty() { + return fmt.Errorf("oracle %d is empty", i) + } + if seenOracles[oracle.String()] { + return fmt.Errorf("duplicated oracle %s", oracle) + } + seenOracles[oracle.String()] = true + } + return nil +} + +// Markets array type for oracle +type Markets []Market + +// Validate checks if all the markets are valid and there are no duplicated +// entries. +func (ms Markets) Validate() error { + seenMarkets := make(map[string]bool) + for _, m := range ms { + if seenMarkets[m.MarketID] { + return fmt.Errorf("duplicated market %s", m.MarketID) + } + if err := m.Validate(); err != nil { + return err + } + seenMarkets[m.MarketID] = true + } + return nil +} + +// String implements fmt.Stringer +func (ms Markets) String() string { + out := "Markets:\n" + for _, m := range ms { + out += fmt.Sprintf("%s\n", m.String()) + } + return strings.TrimSpace(out) +} + +// CurrentPrice struct that contains the metadata of a current price for a particular market in the pricefeed module. +type CurrentPrice struct { + MarketID string `json:"market_id" yaml:"market_id"` + Price sdk.Dec `json:"price" yaml:"price"` +} + +// NewCurrentPrice returns an instance of CurrentPrice +func NewCurrentPrice(marketID string, price sdk.Dec) CurrentPrice { + return CurrentPrice{MarketID: marketID, Price: price} +} + +// CurrentPrices type for an array of CurrentPrice +type CurrentPrices []CurrentPrice + +// PostedPrice price for market posted by a specific oracle +type PostedPrice struct { + MarketID string `json:"market_id" yaml:"market_id"` + OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"` + Price sdk.Dec `json:"price" yaml:"price"` + Expiry time.Time `json:"expiry" yaml:"expiry"` +} + +// NewPostedPrice returns a new PostedPrice +func NewPostedPrice(marketID string, oracle sdk.AccAddress, price sdk.Dec, expiry time.Time) PostedPrice { + return PostedPrice{ + MarketID: marketID, + OracleAddress: oracle, + Price: price, + Expiry: expiry, + } +} + +// Validate performs a basic check of a PostedPrice params. +func (pp PostedPrice) Validate() error { + if strings.TrimSpace(pp.MarketID) == "" { + return errors.New("market id cannot be blank") + } + if pp.OracleAddress.Empty() { + return errors.New("oracle address cannot be empty") + } + if pp.Price.IsNegative() { + return fmt.Errorf("posted price cannot be negative %s", pp.Price) + } + if pp.Expiry.IsZero() { + return errors.New("expiry time cannot be zero") + } + return nil +} + +// PostedPrices type for an array of PostedPrice +type PostedPrices []PostedPrice + +// Validate checks if all the posted prices are valid and there are no duplicated +// entries. +func (pps PostedPrices) Validate() error { + seenPrices := make(map[string]bool) + for _, pp := range pps { + if pp.OracleAddress != nil && seenPrices[pp.MarketID+pp.OracleAddress.String()] { + return fmt.Errorf("duplicated posted price for marked id %s and oracle address %s", pp.MarketID, pp.OracleAddress) + } + + if err := pp.Validate(); err != nil { + return err + } + seenPrices[pp.MarketID+pp.OracleAddress.String()] = true + } + + return nil +} + +// implement fmt.Stringer +func (cp CurrentPrice) String() string { + return strings.TrimSpace(fmt.Sprintf(`Market ID: %s +Price: %s`, cp.MarketID, cp.Price)) +} + +// implement fmt.Stringer +func (pp PostedPrice) String() string { + return strings.TrimSpace(fmt.Sprintf(`Market ID: %s +Oracle Address: %s +Price: %s +Expiry: %s`, pp.MarketID, pp.OracleAddress, pp.Price, pp.Expiry)) +} + +// String implements fmt.Stringer +func (ps PostedPrices) String() string { + out := "Posted Prices:\n" + for _, p := range ps { + out += fmt.Sprintf("%s\n", p.String()) + } + return strings.TrimSpace(out) +} diff --git a/x/pricefeed/types/market.go b/x/pricefeed/types/market.go index 66c0d9c0..4a22da43 100644 --- a/x/pricefeed/types/market.go +++ b/x/pricefeed/types/market.go @@ -19,6 +19,17 @@ type Market struct { Active bool `json:"active" yaml:"active"` } +// NewMarket returns a new Market +func NewMarket(id, base, quote string, oracles []sdk.AccAddress, active bool) Market { + return Market{ + MarketID: id, + BaseAsset: base, + QuoteAsset: quote, + Oracles: oracles, + Active: active, + } +} + // String implement fmt.Stringer func (m Market) String() string { return fmt.Sprintf(`Asset: