From d04aad5cc9a8f889cef7ff71dc5d72ec14fd5710 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 17 Jan 2020 13:29:19 +0100 Subject: [PATCH] Pricefeed fixes (#308) * fix: remove redundant debt limit param * wip: test pricefeed genesis * fix: pricefeed querier * fix: comments, naming * fix: query path * fix: store methods * fix: query methods * fix: standardize genesis validation --- x/cdp/integration_test.go | 6 +- x/cdp/keeper/integration_test.go | 6 +- x/pricefeed/abci.go | 2 +- x/pricefeed/alias.go | 38 ++++---- x/pricefeed/client/cli/query.go | 82 +++++++++------- x/pricefeed/client/cli/tx.go | 7 +- x/pricefeed/client/rest/query.go | 57 ++++++++++++ x/pricefeed/client/rest/rest.go | 108 ++-------------------- x/pricefeed/client/rest/tx.go | 66 +++++++++++++ x/pricefeed/doc.go | 2 +- x/pricefeed/genesis.go | 26 +++--- x/pricefeed/genesis_test.go | 39 ++++++++ x/pricefeed/handler.go | 10 +- x/pricefeed/integration_test.go | 62 +++++++++++++ x/pricefeed/keeper/integration_test.go | 36 ++++++++ x/pricefeed/keeper/keeper.go | 36 ++++---- x/pricefeed/keeper/keeper_test.go | 14 +-- x/pricefeed/keeper/params.go | 40 ++++---- x/pricefeed/keeper/params_test.go | 49 ++++++++++ x/pricefeed/keeper/querier.go | 76 +++++++-------- x/pricefeed/module.go | 9 +- x/pricefeed/types/errors.go | 10 +- x/pricefeed/types/genesis.go | 14 +-- x/pricefeed/types/key.go | 4 +- x/pricefeed/types/{asset.go => market.go} | 38 ++------ x/pricefeed/types/msgs.go | 22 ++--- x/pricefeed/types/params.go | 42 ++++----- x/pricefeed/types/querier.go | 26 +----- 28 files changed, 559 insertions(+), 368 deletions(-) create mode 100644 x/pricefeed/client/rest/query.go create mode 100644 x/pricefeed/client/rest/tx.go create mode 100644 x/pricefeed/genesis_test.go create mode 100644 x/pricefeed/integration_test.go create mode 100644 x/pricefeed/keeper/integration_test.go create mode 100644 x/pricefeed/keeper/params_test.go rename x/pricefeed/types/{asset.go => market.go} (63%) diff --git a/x/cdp/integration_test.go b/x/cdp/integration_test.go index ef648956..bb429006 100644 --- a/x/cdp/integration_test.go +++ b/x/cdp/integration_test.go @@ -21,7 +21,7 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState { pfGenesis := pricefeed.GenesisState{ Params: pricefeed.Params{ Markets: []pricefeed.Market{ - pricefeed.Market{MarketID: asset + ":usd", BaseAsset: asset, QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + pricefeed.Market{MarketID: asset + ":usd", BaseAsset: asset, QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, }, PostedPrices: []pricefeed.PostedPrice{ @@ -77,8 +77,8 @@ func NewPricefeedGenStateMulti() app.GenesisState { pfGenesis := pricefeed.GenesisState{ Params: pricefeed.Params{ Markets: []pricefeed.Market{ - pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, - pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, }, PostedPrices: []pricefeed.PostedPrice{ diff --git a/x/cdp/keeper/integration_test.go b/x/cdp/keeper/integration_test.go index 7b149f35..6905882a 100644 --- a/x/cdp/keeper/integration_test.go +++ b/x/cdp/keeper/integration_test.go @@ -21,7 +21,7 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState { pfGenesis := pricefeed.GenesisState{ Params: pricefeed.Params{ Markets: []pricefeed.Market{ - pricefeed.Market{MarketID: asset + ":usd", BaseAsset: asset, QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + pricefeed.Market{MarketID: asset + ":usd", BaseAsset: asset, QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, }, PostedPrices: []pricefeed.PostedPrice{ @@ -77,8 +77,8 @@ func NewPricefeedGenStateMulti() app.GenesisState { pfGenesis := pricefeed.GenesisState{ Params: pricefeed.Params{ Markets: []pricefeed.Market{ - pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, - pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, }, PostedPrices: []pricefeed.PostedPrice{ diff --git a/x/pricefeed/abci.go b/x/pricefeed/abci.go index ac0ca068..1e56f918 100644 --- a/x/pricefeed/abci.go +++ b/x/pricefeed/abci.go @@ -9,7 +9,7 @@ import ( // EndBlocker updates the current pricefeed func EndBlocker(ctx sdk.Context, k Keeper) { // Update the current price of each asset. - for _, a := range k.GetMarketParams(ctx) { + for _, a := range k.GetMarkets(ctx) { if a.Active { err := k.SetCurrentPrices(ctx, a.MarketID) if err != nil { diff --git a/x/pricefeed/alias.go b/x/pricefeed/alias.go index 01a51ea7..e3fa62ac 100644 --- a/x/pricefeed/alias.go +++ b/x/pricefeed/alias.go @@ -26,12 +26,12 @@ const ( DefaultParamspace = types.DefaultParamspace RawPriceFeedPrefix = types.RawPriceFeedPrefix CurrentPricePrefix = types.CurrentPricePrefix - AssetPrefix = types.AssetPrefix + MarketPrefix = types.MarketPrefix OraclePrefix = types.OraclePrefix TypeMsgPostPrice = types.TypeMsgPostPrice QueryCurrentPrice = types.QueryCurrentPrice QueryRawPrices = types.QueryRawPrices - QueryAssets = types.QueryAssets + QueryMarkets = types.QueryMarkets ) var ( @@ -40,36 +40,32 @@ var ( ErrEmptyInput = types.ErrEmptyInput ErrExpired = types.ErrExpired ErrNoValidPrice = types.ErrNoValidPrice - ErrInvalidAsset = types.ErrInvalidAsset + ErrInvalidMarket = types.ErrInvalidMarket ErrInvalidOracle = types.ErrInvalidOracle NewGenesisState = types.NewGenesisState DefaultGenesisState = types.DefaultGenesisState - ValidateGenesis = types.ValidateGenesis NewMsgPostPrice = types.NewMsgPostPrice - ParamKeyTable = types.ParamKeyTable NewParams = types.NewParams DefaultParams = types.DefaultParams + ParamKeyTable = types.ParamKeyTable NewKeeper = keeper.NewKeeper NewQuerier = keeper.NewQuerier // variable aliases - ModuleCdc = types.ModuleCdc - KeyMarkets = types.KeyMarkets + ModuleCdc = types.ModuleCdc + KeyMarkets = types.KeyMarkets + DefaultMarkets = types.DefaultMarkets ) type ( - Market = types.Market - Markets = types.Markets - Oracle = types.Oracle - Oracles = types.Oracles - CurrentPrice = types.CurrentPrice - PostedPrice = types.PostedPrice - SortDecs = types.SortDecs - GenesisState = types.GenesisState - MsgPostPrice = types.MsgPostPrice - Params = types.Params - ParamSubspace = types.ParamSubspace - QueryRawPricesResp = types.QueryRawPricesResp - QueryAssetsResp = types.QueryAssetsResp - Keeper = keeper.Keeper + GenesisState = types.GenesisState + Market = types.Market + Markets = types.Markets + CurrentPrice = types.CurrentPrice + PostedPrice = types.PostedPrice + SortDecs = types.SortDecs + MsgPostPrice = types.MsgPostPrice + Params = types.Params + QueryPricesParams = types.QueryPricesParams + Keeper = keeper.Keeper ) diff --git a/x/pricefeed/client/cli/query.go b/x/pricefeed/client/cli/query.go index 3de15af3..0100a373 100644 --- a/x/pricefeed/client/cli/query.go +++ b/x/pricefeed/client/cli/query.go @@ -11,7 +11,7 @@ import ( ) // GetQueryCmd returns the cli query commands for this module -func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command { +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { // Group nameservice queries under a subcommand pricefeedQueryCmd := &cobra.Command{ Use: types.ModuleName, @@ -22,9 +22,9 @@ func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command { } pricefeedQueryCmd.AddCommand(client.GetCommands( - GetCmdCurrentPrice(storeKey, cdc), - GetCmdRawPrices(storeKey, cdc), - GetCmdAssets(storeKey, cdc), + GetCmdCurrentPrice(queryRoute, cdc), + GetCmdRawPrices(queryRoute, cdc), + GetCmdMarkets(queryRoute, cdc), )...) return pricefeedQueryCmd @@ -33,21 +33,28 @@ func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command { // GetCmdCurrentPrice queries the current price of an asset func GetCmdCurrentPrice(queryRoute string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "price [assetCode]", - Short: "get the current price of an asset", + Use: "price [marketID]", + Short: "get the current price for the input market", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - assetCode := args[0] - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/price/%s", queryRoute, assetCode), nil) + marketID := args[0] + + bz, err := cdc.MarshalJSON(types.QueryPricesParams{ + MarketID: marketID, + }) if err != nil { - fmt.Printf("error when querying current price - %s", err) - fmt.Printf("could not get current price for - %s \n", assetCode) - return nil + return err } - var out types.CurrentPrice - cdc.MustUnmarshalJSON(res, &out) - return cliCtx.PrintOutput(out) + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryCurrentPrice) + + res, _, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + var price types.CurrentPrice + cdc.MustUnmarshalJSON(res, &price) + return cliCtx.PrintOutput(price) }, } } @@ -55,39 +62,48 @@ func GetCmdCurrentPrice(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdRawPrices queries the current price of an asset func GetCmdRawPrices(queryRoute string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "rawprices [assetCode]", - Short: "get the raw oracle prices for an asset", + Use: "rawprices [marketID]", + Short: "get the raw oracle prices for the input market", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - assetCode := args[0] - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/rawprices/%s", queryRoute, assetCode), nil) + marketID := args[0] + + bz, err := cdc.MarshalJSON(types.QueryPricesParams{ + MarketID: marketID, + }) if err != nil { - fmt.Printf("could not get raw prices for - %s \n", assetCode) - return nil + return err } - var out types.QueryRawPricesResp - cdc.MustUnmarshalJSON(res, &out) - return cliCtx.PrintOutput(out) + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryRawPrices) + + res, _, err := cliCtx.QueryWithData(route, bz) + + if err != nil { + return err + } + var prices []types.PostedPrice + cdc.MustUnmarshalJSON(res, &prices) + return cliCtx.PrintOutput(prices) }, } } -// GetCmdAssets queries list of assets in the pricefeed -func GetCmdAssets(queryRoute string, cdc *codec.Codec) *cobra.Command { +// GetCmdMarkets queries list of markets in the pricefeed +func GetCmdMarkets(queryRoute string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "assets", - Short: "get the assets in the pricefeed", + Use: "markets", + Short: "get the markets in the pricefeed", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/assets", queryRoute), nil) + route := fmt.Sprintf("custom/%s/markets", queryRoute) + res, _, err := cliCtx.QueryWithData(route, nil) if err != nil { - fmt.Printf("could not get assets") - return nil + return err } - var out types.QueryAssetsResp - cdc.MustUnmarshalJSON(res, &out) - return cliCtx.PrintOutput(out) + var markets types.Markets + cdc.MustUnmarshalJSON(res, &markets) + return cliCtx.PrintOutput(markets) }, } } diff --git a/x/pricefeed/client/cli/tx.go b/x/pricefeed/client/cli/tx.go index 199ad2a0..1793e59c 100644 --- a/x/pricefeed/client/cli/tx.go +++ b/x/pricefeed/client/cli/tx.go @@ -36,8 +36,8 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { // GetCmdPostPrice cli command for posting prices. func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "postprice [assetCode] [price] [expiry]", - Short: "post the latest price for a particular asset", + Use: "postprice [marketID] [price] [expiry]", + Short: "post the latest price for a particular market", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) @@ -51,8 +51,7 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { } expiryInt, ok := sdk.NewIntFromString(args[2]) if !ok { - fmt.Printf("invalid expiry - %s \n", args[2]) - return nil + return fmt.Errorf("invalid expiry - %s", args[2]) } expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0)) diff --git a/x/pricefeed/client/rest/query.go b/x/pricefeed/client/rest/query.go new file mode 100644 index 00000000..30b2af46 --- /dev/null +++ b/x/pricefeed/client/rest/query.go @@ -0,0 +1,57 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/types/rest" + + "github.com/kava-labs/kava/x/pricefeed/types" +) + +// define routes that get registered by the main application +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc(fmt.Sprintf("/%s/rawprices/{%s}", types.ModuleName, restName), queryRawPricesHandler(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/currentprice/{%s}", types.ModuleName, restName), queryCurrentPriceHandler(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/markets", types.ModuleName), queryMarketsHandler(cliCtx)).Methods("GET") +} + +func queryRawPricesHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + paramType := vars[restName] + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/rawprices/%s", paramType), nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryCurrentPriceHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + paramType := vars[restName] + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/price/%s", paramType), nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + rest.PostProcessResponse(w, cliCtx, res) + } +} + +func queryMarketsHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/markets/"), nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) + return + } + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/x/pricefeed/client/rest/rest.go b/x/pricefeed/client/rest/rest.go index 988d8ea5..4e24a5fd 100644 --- a/x/pricefeed/client/rest/rest.go +++ b/x/pricefeed/client/rest/rest.go @@ -1,116 +1,24 @@ package rest import ( - "fmt" - "net/http" - "time" - "github.com/cosmos/cosmos-sdk/client/context" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" - "github.com/cosmos/cosmos-sdk/x/auth/client/utils" "github.com/gorilla/mux" - "github.com/kava-labs/kava/x/pricefeed/types" - tmtime "github.com/tendermint/tendermint/types/time" ) const ( - restName = "assetCode" + restName = "marketID" ) type postPriceReq struct { - BaseReq rest.BaseReq `json:"base_req"` - AssetCode string `json:"asset_code"` - Price string `json:"price"` - Expiry string `json:"expiry"` + BaseReq rest.BaseReq `json:"base_req"` + MarketID string `json:"market_id"` + Price string `json:"price"` + Expiry string `json:"expiry"` } // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, storeName string) { - r.HandleFunc(fmt.Sprintf("/%s/rawprices", storeName), postPriceHandler(cliCtx)).Methods("PUT") - r.HandleFunc(fmt.Sprintf("/%s/rawprices/{%s}", storeName, restName), getRawPricesHandler(cliCtx, storeName)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/%s/currentprice/{%s}", storeName, restName), getCurrentPriceHandler(cliCtx, storeName)).Methods("GET") - r.HandleFunc(fmt.Sprintf("/%s/assets", storeName), getAssetsHandler(cliCtx, storeName)).Methods("GET") -} - -func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req postPriceReq - - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { - rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") - return - } - - baseReq := req.BaseReq.Sanitize() - if !baseReq.ValidateBasic(w) { - return - } - - addr, err := sdk.AccAddressFromBech32(baseReq.From) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - price, err := sdk.NewDecFromStr(req.Price) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - expiryInt, ok := sdk.NewIntFromString(req.Expiry) - if !ok { - rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid expiry") - return - } - expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0)) - - // create the message - msg := types.NewMsgPostPrice(addr, req.AssetCode, price, expiry) - err = msg.ValidateBasic() - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) - } -} - -func getRawPricesHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - paramType := vars[restName] - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/rawprices/%s", storeName, paramType), nil) - if err != nil { - rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) - return - } - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func getCurrentPriceHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - paramType := vars[restName] - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/price/%s", storeName, paramType), nil) - if err != nil { - rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) - return - } - rest.PostProcessResponse(w, cliCtx, res) - } -} - -func getAssetsHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/assets/", storeName), nil) - if err != nil { - rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) - return - } - rest.PostProcessResponse(w, cliCtx, res) - } +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + registerQueryRoutes(cliCtx, r) + registerTxRoutes(cliCtx, r) } diff --git a/x/pricefeed/client/rest/tx.go b/x/pricefeed/client/rest/tx.go new file mode 100644 index 00000000..6d72a972 --- /dev/null +++ b/x/pricefeed/client/rest/tx.go @@ -0,0 +1,66 @@ +package rest + +import ( + "fmt" + "net/http" + "time" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/kava-labs/kava/x/pricefeed/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc(fmt.Sprintf("/%s/rawprices", types.ModuleName), postPriceHandler(cliCtx)).Methods("PUT") +} + +func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req postPriceReq + + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request") + return + } + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + addr, err := sdk.AccAddressFromBech32(baseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + price, err := sdk.NewDecFromStr(req.Price) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + expiryInt, ok := sdk.NewIntFromString(req.Expiry) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid expiry") + return + } + expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0)) + + // create the message + msg := types.NewMsgPostPrice(addr, req.MarketID, price, expiry) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg}) + } + +} \ No newline at end of file diff --git a/x/pricefeed/doc.go b/x/pricefeed/doc.go index 0d403145..e25ab6b0 100644 --- a/x/pricefeed/doc.go +++ b/x/pricefeed/doc.go @@ -1,6 +1,6 @@ /* -Package pricefeed allows a group of white-listed oracles to post price information of specific assets that are tracked by the system. For each asset, the module computes the median of all posted prices by white-listed oracles and takes that as the current price value. +Package pricefeed allows a group of white-listed oracles to post price information of specific markets that are tracked by the system. For each market, the module computes the median of all posted prices by white-listed oracles and takes that as the current price value. */ package pricefeed diff --git a/x/pricefeed/genesis.go b/x/pricefeed/genesis.go index b80fe9a5..158d84e6 100644 --- a/x/pricefeed/genesis.go +++ b/x/pricefeed/genesis.go @@ -5,23 +5,27 @@ import ( ) // InitGenesis sets distribution information for genesis. -func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { - - // Set the assets and oracles from params - keeper.SetParams(ctx, data.Params) +func InitGenesis(ctx sdk.Context, keeper Keeper, gs GenesisState) { + err := gs.Validate() + if err != nil { + panic(err) + } + // Set the markets and oracles from params + keeper.SetParams(ctx, gs.Params) // Iterate through the posted prices and set them in the store - for _, pp := range data.PostedPrices { + for _, pp := range gs.PostedPrices { _, err := keeper.SetPrice(ctx, pp.OracleAddress, pp.MarketID, pp.Price, pp.Expiry) if err != nil { panic(err) } } + params := keeper.GetParams(ctx) // Set the current price (if any) based on what's now in the store - for _, a := range data.Params.Markets { - if a.Active { - err := keeper.SetCurrentPrices(ctx, a.MarketID) + for _, market := range params.Markets { + if market.Active { + err := keeper.SetCurrentPrices(ctx, market.MarketID) if err != nil { panic(err) } @@ -32,12 +36,12 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { // ExportGenesis returns a GenesisState for a given context and keeper. func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { - // Get the params for assets and oracles + // Get the params for markets and oracles params := keeper.GetParams(ctx) var postedPrices []PostedPrice - for _, asset := range keeper.GetMarketParams(ctx) { - pp := keeper.GetRawPrices(ctx, asset.MarketID) + for _, market := range keeper.GetMarkets(ctx) { + pp := keeper.GetRawPrices(ctx, market.MarketID) postedPrices = append(postedPrices, pp...) } diff --git a/x/pricefeed/genesis_test.go b/x/pricefeed/genesis_test.go new file mode 100644 index 00000000..968729bc --- /dev/null +++ b/x/pricefeed/genesis_test.go @@ -0,0 +1,39 @@ +package pricefeed_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/pricefeed" + + "github.com/stretchr/testify/suite" +) + +type GenesisTestSuite struct { + suite.Suite + + ctx sdk.Context + keeper pricefeed.Keeper +} + +func (suite *GenesisTestSuite) TestValidGenState() { + tApp := app.NewTestApp() + + suite.NotPanics(func() { + tApp.InitializeFromGenesisStates( + NewPricefeedGenStateMulti(), + ) + }) + _, addrs := app.GeneratePrivKeyAddressPairs(10) + + suite.NotPanics(func() { + tApp.InitializeFromGenesisStates( + NewPricefeedGenStateWithOracles(addrs), + ) + }) +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(GenesisTestSuite)) +} diff --git a/x/pricefeed/handler.go b/x/pricefeed/handler.go index 704839f9..0c738a65 100644 --- a/x/pricefeed/handler.go +++ b/x/pricefeed/handler.go @@ -28,11 +28,13 @@ func HandleMsgPostPrice( k Keeper, msg MsgPostPrice) sdk.Result { - // TODO cleanup message validation and errors - _, err := k.GetOracle(ctx, msg.AssetCode, msg.From) + _, err := k.GetOracle(ctx, msg.MarketID, msg.From) if err != nil { - return ErrInvalidOracle(k.Codespace()).Result() + return err.Result() + } + _, err = k.SetPrice(ctx, msg.From, msg.MarketID, msg.Price, msg.Expiry) + if err != nil { + err.Result() } - k.SetPrice(ctx, msg.From, msg.AssetCode, msg.Price, msg.Expiry) return sdk.Result{} } diff --git a/x/pricefeed/integration_test.go b/x/pricefeed/integration_test.go new file mode 100644 index 00000000..4d5f2c76 --- /dev/null +++ b/x/pricefeed/integration_test.go @@ -0,0 +1,62 @@ +package pricefeed_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/pricefeed" +) + +func NewPricefeedGenStateMulti() app.GenesisState { + pfGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: "btc:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("8000.00"), + Expiry: time.Now().Add(1 * time.Hour), + }, + pricefeed.PostedPrice{ + MarketID: "xrp:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("0.25"), + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} + +func NewPricefeedGenStateWithOracles(addrs []sdk.AccAddress) app.GenesisState { + pfGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: addrs, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: addrs, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: "btc:usd", + OracleAddress: addrs[0], + Price: sdk.MustNewDecFromStr("8000.00"), + Expiry: time.Now().Add(1 * time.Hour), + }, + pricefeed.PostedPrice{ + MarketID: "xrp:usd", + OracleAddress: addrs[0], + Price: sdk.MustNewDecFromStr("0.25"), + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} diff --git a/x/pricefeed/keeper/integration_test.go b/x/pricefeed/keeper/integration_test.go new file mode 100644 index 00000000..5594cda8 --- /dev/null +++ b/x/pricefeed/keeper/integration_test.go @@ -0,0 +1,36 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/pricefeed" +) + +func NewPricefeedGenStateMulti() app.GenesisState { + pfGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: "btc:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("8000.00"), + Expiry: time.Now().Add(1 * time.Hour), + }, + pricefeed.PostedPrice{ + MarketID: "xrp:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("0.25"), + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index ca76e5ae..10c8b410 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -6,34 +6,32 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/params/subspace" "github.com/kava-labs/kava/x/pricefeed/types" ) // Keeper struct for pricefeed module type Keeper struct { - // The keys used to access the stores from Context - storeKey sdk.StoreKey + // key used to access the stores from Context + key sdk.StoreKey // Codec for binary encoding/decoding cdc *codec.Codec // The reference to the Paramstore to get and set pricefeed specific params - paramstore params.Subspace + paramSubspace subspace.Subspace // Reserved codespace codespace sdk.CodespaceType } -// NewKeeper returns a new keeper for the pricefeed module. It handles: -// - adding oracles -// - adding/removing assets from the pricefeed +// NewKeeper returns a new keeper for the pricefeed module. func NewKeeper( - cdc *codec.Codec, storeKey sdk.StoreKey, paramstore params.Subspace, codespace sdk.CodespaceType, + cdc *codec.Codec, key sdk.StoreKey, paramSubspace subspace.Subspace, codespace sdk.CodespaceType, ) Keeper { return Keeper{ - paramstore: paramstore.WithKeyTable(types.ParamKeyTable()), - storeKey: storeKey, - cdc: cdc, - codespace: codespace, + paramSubspace: paramSubspace.WithKeyTable(types.ParamKeyTable()), + key: key, + cdc: cdc, + codespace: codespace, } } @@ -46,7 +44,7 @@ func (k Keeper) SetPrice( expiry time.Time) (types.PostedPrice, sdk.Error) { // If the expiry is less than or equal to the current blockheight, we consider the price valid if expiry.After(ctx.BlockTime()) { - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.key) prices := k.GetRawPrices(ctx, marketID) var index int found := false @@ -82,7 +80,7 @@ func (k Keeper) SetPrice( func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { _, ok := k.GetMarket(ctx, marketID) if !ok { - return types.ErrInvalidAsset(k.codespace) + return types.ErrInvalidMarket(k.codespace, marketID) } prices := k.GetRawPrices(ctx, marketID) var notExpiredPrices []types.CurrentPrice @@ -96,7 +94,7 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { } } if len(notExpiredPrices) == 0 { - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.key) store.Set( []byte(types.CurrentPricePrefix+marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), ) @@ -104,7 +102,7 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { } medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices) - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.key) currentPrice := types.CurrentPrice{ MarketID: marketID, Price: medianPrice, @@ -144,9 +142,9 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice) return mean } -// GetCurrentPrice fetches the current median price of all oracles for a specific asset +// GetCurrentPrice fetches the current median price of all oracles for a specific market func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, sdk.Error) { - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.key) bz := store.Get([]byte(types.CurrentPricePrefix + marketID)) if bz == nil { @@ -162,7 +160,7 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current // GetRawPrices fetches the set of all prices posted by oracles for an asset func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) []types.PostedPrice { - store := ctx.KVStore(k.storeKey) + store := ctx.KVStore(k.key) bz := store.Get([]byte(types.RawPriceFeedPrefix + marketID)) var prices []types.PostedPrice k.cdc.MustUnmarshalBinaryBare(bz, &prices) diff --git a/x/pricefeed/keeper/keeper_test.go b/x/pricefeed/keeper/keeper_test.go index d29298a3..0f79b026 100644 --- a/x/pricefeed/keeper/keeper_test.go +++ b/x/pricefeed/keeper/keeper_test.go @@ -20,11 +20,11 @@ func TestKeeper_SetGetMarket(t *testing.T) { mp := types.Params{ Markets: types.Markets{ - types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, + types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, } keeper.SetParams(ctx, mp) - markets := keeper.GetMarketParams(ctx) + markets := keeper.GetMarkets(ctx) require.Equal(t, len(markets), 1) require.Equal(t, markets[0].MarketID, "tstusd") @@ -33,12 +33,12 @@ func TestKeeper_SetGetMarket(t *testing.T) { mp = types.Params{ Markets: types.Markets{ - types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, - types.Market{MarketID: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, + types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + types.Market{MarketID: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, } keeper.SetParams(ctx, mp) - markets = keeper.GetMarketParams(ctx) + markets = keeper.GetMarkets(ctx) require.Equal(t, len(markets), 2) require.Equal(t, markets[0].MarketID, "tstusd") require.Equal(t, markets[1].MarketID, "tst2usd") @@ -56,7 +56,7 @@ func TestKeeper_GetSetPrice(t *testing.T) { mp := types.Params{ Markets: types.Markets{ - types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, + types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, } keeper.SetParams(ctx, mp) @@ -100,7 +100,7 @@ func TestKeeper_GetSetCurrentPrice(t *testing.T) { mp := types.Params{ Markets: types.Markets{ - types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, + types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, }, } keeper.SetParams(ctx, mp) diff --git a/x/pricefeed/keeper/params.go b/x/pricefeed/keeper/params.go index aa64cca1..8b297f57 100644 --- a/x/pricefeed/keeper/params.go +++ b/x/pricefeed/keeper/params.go @@ -1,58 +1,56 @@ package keeper import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/pricefeed/types" ) -// GetParams gets params from the store +// GetParams returns the params from the store func (k Keeper) GetParams(ctx sdk.Context) types.Params { - return types.NewParams(k.GetMarketParams(ctx)) + var p types.Params + k.paramSubspace.GetParamSet(ctx, &p) + return p } -// SetParams updates params in the store +// SetParams sets params on the store func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramstore.SetParamSet(ctx, ¶ms) + k.paramSubspace.SetParamSet(ctx, ¶ms) } -// GetMarketParams get asset params from store -func (k Keeper) GetMarketParams(ctx sdk.Context) types.Markets { - var markets types.Markets - k.paramstore.Get(ctx, types.KeyMarkets, &markets) - return markets +// GetMarkets returns the markets from params +func (k Keeper) GetMarkets(ctx sdk.Context) types.Markets { + return k.GetParams(ctx).Markets } // GetOracles returns the oracles in the pricefeed store -func (k Keeper) GetOracles(ctx sdk.Context, marketID string) (types.Oracles, error) { +func (k Keeper) GetOracles(ctx sdk.Context, marketID string) ([]sdk.AccAddress, sdk.Error) { - for _, m := range k.GetMarketParams(ctx) { + for _, m := range k.GetMarkets(ctx) { if marketID == m.MarketID { return m.Oracles, nil } } - return types.Oracles{}, fmt.Errorf("asset %s not found", marketID) + return []sdk.AccAddress{}, types.ErrInvalidMarket(k.Codespace(), marketID) } // GetOracle returns the oracle from the store or an error if not found -func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (types.Oracle, error) { +func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (sdk.AccAddress, sdk.Error) { oracles, err := k.GetOracles(ctx, marketID) if err != nil { - return types.Oracle{}, fmt.Errorf("asset %s not found", marketID) + return sdk.AccAddress{}, types.ErrInvalidMarket(k.Codespace(), marketID) } - for _, o := range oracles { - if address.Equals(o.Address) { - return o, nil + for _, addr := range oracles { + if address.Equals(addr) { + return addr, nil } } - return types.Oracle{}, fmt.Errorf("oracle %s not found for asset %s", address, marketID) + return sdk.AccAddress{}, types.ErrInvalidOracle(k.codespace, address) } // GetMarket returns the market if it is in the pricefeed system func (k Keeper) GetMarket(ctx sdk.Context, marketID string) (types.Market, bool) { - markets := k.GetMarketParams(ctx) + markets := k.GetMarkets(ctx) for i := range markets { if markets[i].MarketID == marketID { diff --git a/x/pricefeed/keeper/params_test.go b/x/pricefeed/keeper/params_test.go new file mode 100644 index 00000000..d4ccc483 --- /dev/null +++ b/x/pricefeed/keeper/params_test.go @@ -0,0 +1,49 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/pricefeed/keeper" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +type KeeperTestSuite struct { + suite.Suite + + keeper keeper.Keeper + addrs []sdk.AccAddress + app app.TestApp + ctx sdk.Context +} + +func (suite *KeeperTestSuite) SetupTest() { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + _, addrs := app.GeneratePrivKeyAddressPairs(10) + tApp.InitializeFromGenesisStates( + NewPricefeedGenStateMulti(), + ) + suite.keeper = tApp.GetPriceFeedKeeper() + suite.ctx = ctx + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) TestGetSetOracles() { + params := suite.keeper.GetParams(suite.ctx) + suite.Equal([]sdk.AccAddress(nil), params.Markets[0].Oracles) + params.Markets[0].Oracles = suite.addrs + suite.NotPanics(func() { suite.keeper.SetParams(suite.ctx, params) }) + params = suite.keeper.GetParams(suite.ctx) + suite.Equal(suite.addrs, params.Markets[0].Oracles) + addr, err := suite.keeper.GetOracle(suite.ctx, params.Markets[0].MarketID, suite.addrs[0]) + suite.NoError(err) + suite.Equal(suite.addrs[0], addr) +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/pricefeed/keeper/querier.go b/x/pricefeed/keeper/querier.go index 022011af..7952e688 100644 --- a/x/pricefeed/keeper/querier.go +++ b/x/pricefeed/keeper/querier.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,11 +16,11 @@ 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.QueryCurrentPrice: - return queryCurrentPrice(ctx, path[1:], req, keeper) + return queryCurrentPrice(ctx, req, keeper) case types.QueryRawPrices: - return queryRawPrices(ctx, path[1:], req, keeper) - case types.QueryAssets: - return queryAssets(ctx, req, keeper) + return queryRawPrices(ctx, req, keeper) + case types.QueryMarkets: + return queryMarkets(ctx, req, keeper) default: return nil, sdk.ErrUnknownRequest("unknown pricefeed query endpoint") } @@ -26,51 +28,53 @@ func NewQuerier(keeper Keeper) sdk.Querier { } -func queryCurrentPrice(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - marketID := path[0] - _, found := keeper.GetMarket(ctx, marketID) - if !found { - return []byte{}, sdk.ErrUnknownRequest("asset not found") - } - currentPrice, err := keeper.GetCurrentPrice(ctx, marketID) +func queryCurrentPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { + var requestParams types.QueryPricesParams + err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, err + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, currentPrice) - if err2 != nil { - panic("could not marshal result to JSON") - } - - return bz, nil -} - -func queryRawPrices(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var priceList types.QueryRawPricesResp - marketID := path[0] - _, found := keeper.GetMarket(ctx, marketID) + _, found := keeper.GetMarket(ctx, requestParams.MarketID) if !found { return []byte{}, sdk.ErrUnknownRequest("asset not found") } - rawPrices := keeper.GetRawPrices(ctx, marketID) - for _, price := range rawPrices { - priceList = append(priceList, price.String()) + currentPrice, sdkErr := keeper.GetCurrentPrice(ctx, requestParams.MarketID) + if sdkErr != nil { + return nil, sdkErr } - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, priceList) - if err2 != nil { + bz, err := codec.MarshalJSONIndent(keeper.cdc, currentPrice) + if err != nil { panic("could not marshal result to JSON") } return bz, nil } -func queryAssets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { - var assetList types.QueryAssetsResp - assets := keeper.GetMarketParams(ctx) - for _, asset := range assets { - assetList = append(assetList, asset.String()) +func queryRawPrices(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { + var requestParams types.QueryPricesParams + err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + if err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, assetList) - if err2 != nil { + _, found := keeper.GetMarket(ctx, requestParams.MarketID) + if !found { + return []byte{}, sdk.ErrUnknownRequest("asset not found") + } + rawPrices := keeper.GetRawPrices(ctx, requestParams.MarketID) + + bz, err := codec.MarshalJSONIndent(keeper.cdc, rawPrices) + if err != nil { + panic("could not marshal result to JSON") + } + + return bz, nil +} + +func queryMarkets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { + markets := keeper.GetMarkets(ctx) + + bz, err := codec.MarshalJSONIndent(keeper.cdc, markets) + if err != nil { panic("could not marshal result to JSON") } diff --git a/x/pricefeed/module.go b/x/pricefeed/module.go index 7b52be5a..e4136b14 100644 --- a/x/pricefeed/module.go +++ b/x/pricefeed/module.go @@ -20,6 +20,7 @@ var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} ) + // AppModuleBasic app module basics object type AppModuleBasic struct{} @@ -40,17 +41,17 @@ func (AppModuleBasic) DefaultGenesis() json.RawMessage { // ValidateGenesis module validate genesis func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { - var data GenesisState - err := ModuleCdc.UnmarshalJSON(bz, &data) + var gs GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &gs) if err != nil { return err } - return ValidateGenesis(data) + return gs.Validate() } // RegisterRESTRoutes register rest routes func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { - rest.RegisterRoutes(ctx, rtr, StoreKey) + rest.RegisterRoutes(ctx, rtr) } // GetTxCmd get the root tx command of this module diff --git a/x/pricefeed/types/errors.go b/x/pricefeed/types/errors.go index 7a7c4548..325b0c1b 100644 --- a/x/pricefeed/types/errors.go +++ b/x/pricefeed/types/errors.go @@ -37,12 +37,12 @@ func ErrNoValidPrice(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidPrice, fmt.Sprintf("All input prices are expired.")) } -// ErrInvalidAsset Error constructor for posted price messages for invalid assets -func ErrInvalidAsset(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeInvalidAsset, fmt.Sprintf("Asset code does not exist.")) +// ErrInvalidAsset Error constructor for posted price messages for invalid markets +func ErrInvalidMarket(codespace sdk.CodespaceType, marketId string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidAsset, fmt.Sprintf("market %s does not exist", marketId)) } // ErrInvalidOracle Error constructor for posted price messages for invalid oracles -func ErrInvalidOracle(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeInvalidOracle, fmt.Sprintf("Oracle does not exist or not authorized.")) +func ErrInvalidOracle(codespace sdk.CodespaceType, addr sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeInvalidOracle, fmt.Sprintf("oracle %s does not exist or not authorized", addr)) } diff --git a/x/pricefeed/types/genesis.go b/x/pricefeed/types/genesis.go index 4bcba82c..eb8ba380 100644 --- a/x/pricefeed/types/genesis.go +++ b/x/pricefeed/types/genesis.go @@ -27,22 +27,22 @@ func DefaultGenesisState() GenesisState { } // Equal checks whether two gov GenesisState structs are equivalent -func (data GenesisState) Equal(data2 GenesisState) bool { - b1 := ModuleCdc.MustMarshalBinaryBare(data) - b2 := ModuleCdc.MustMarshalBinaryBare(data2) +func (gs GenesisState) Equal(gs2 GenesisState) bool { + b1 := ModuleCdc.MustMarshalBinaryBare(gs) + b2 := ModuleCdc.MustMarshalBinaryBare(gs2) return bytes.Equal(b1, b2) } // IsEmpty returns true if a GenesisState is empty -func (data GenesisState) IsEmpty() bool { - return data.Equal(GenesisState{}) +func (gs GenesisState) IsEmpty() bool { + return gs.Equal(GenesisState{}) } // ValidateGenesis performs basic validation of genesis data returning an // error for any failed validation criteria. -func ValidateGenesis(data GenesisState) error { +func (gs GenesisState) Validate() error { - if err := data.Params.Validate(); err != nil { + if err := gs.Params.Validate(); err != nil { return err } return nil diff --git a/x/pricefeed/types/key.go b/x/pricefeed/types/key.go index 4a20c25a..a1107954 100644 --- a/x/pricefeed/types/key.go +++ b/x/pricefeed/types/key.go @@ -22,8 +22,8 @@ const ( // CurrentPricePrefix prefix for the current price of an asset CurrentPricePrefix = StoreKey + ":currentprice:" - // AssetPrefix Prefix for the assets in the pricefeed system - AssetPrefix = StoreKey + ":assets" + // MarketPrefix Prefix for the assets in the pricefeed system + MarketPrefix = StoreKey + ":markets" // OraclePrefix store prefix for the oracle accounts OraclePrefix = StoreKey + ":oracles" diff --git a/x/pricefeed/types/asset.go b/x/pricefeed/types/market.go similarity index 63% rename from x/pricefeed/types/asset.go rename to x/pricefeed/types/market.go index bc19b532..a357db73 100644 --- a/x/pricefeed/types/asset.go +++ b/x/pricefeed/types/market.go @@ -8,13 +8,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Market struct that represents an asset in the pricefeed +// Market an asset in the pricefeed type Market struct { - 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 Oracles `json:"oracles" yaml:"oracles"` - Active bool `json:"active" yaml:"active"` + 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 @@ -40,35 +40,13 @@ func (ms Markets) String() string { return strings.TrimSpace(out) } -// Oracle struct that documents which address an oracle is using -type Oracle struct { - Address sdk.AccAddress `json:"address" yaml:"address"` -} - -// String implements fmt.Stringer -func (o Oracle) String() string { - return fmt.Sprintf(`Address: %s`, o.Address) -} - -// Oracles array type for oracle -type Oracles []Oracle - -// String implements fmt.Stringer -func (os Oracles) String() string { - out := "Oracles:\n" - for _, o := range os { - out += fmt.Sprintf("%s\n", o.String()) - } - return strings.TrimSpace(out) -} - -// CurrentPrice struct that contains the metadata of a current price for a particular asset in the pricefeed module. +// 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"` } -// PostedPrice struct represented a price for an asset posted by a specific oracle +// 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"` diff --git a/x/pricefeed/types/msgs.go b/x/pricefeed/types/msgs.go index eed227bc..e73e32dd 100644 --- a/x/pricefeed/types/msgs.go +++ b/x/pricefeed/types/msgs.go @@ -14,10 +14,10 @@ const ( // MsgPostPrice struct representing a posted price message. // Used by oracles to input prices to the pricefeed type MsgPostPrice struct { - From sdk.AccAddress // client that sent in this address - AssetCode string // asset code used by exchanges/api - Price sdk.Dec // price in decimal (max precision 18) - Expiry time.Time // expiry time + From sdk.AccAddress // client that sent in this address + MarketID string // asset code used by exchanges/api + Price sdk.Dec // price in decimal (max precision 18) + Expiry time.Time // expiry time } // NewMsgPostPrice creates a new post price msg @@ -27,10 +27,10 @@ func NewMsgPostPrice( price sdk.Dec, expiry time.Time) MsgPostPrice { return MsgPostPrice{ - From: from, - AssetCode: assetCode, - Price: price, - Expiry: expiry, + From: from, + MarketID: assetCode, + Price: price, + Expiry: expiry, } } @@ -54,10 +54,10 @@ func (msg MsgPostPrice) GetSigners() []sdk.AccAddress { // ValidateBasic does a simple validation check that doesn't require access to any other information. func (msg MsgPostPrice) ValidateBasic() sdk.Error { if msg.From.Empty() { - return sdk.ErrInternal("invalid (empty) bidder address") + return sdk.ErrInternal("invalid (empty) from address") } - if len(msg.AssetCode) == 0 { - return sdk.ErrInternal("invalid (empty) asset code") + if len(msg.MarketID) == 0 { + return sdk.ErrInternal("invalid (empty) market id") } if msg.Price.LT(sdk.ZeroDec()) { return sdk.ErrInternal("invalid (negative) price") diff --git a/x/pricefeed/types/params.go b/x/pricefeed/types/params.go index fdef14d3..6236a8eb 100644 --- a/x/pricefeed/types/params.go +++ b/x/pricefeed/types/params.go @@ -4,33 +4,20 @@ import ( "fmt" "strings" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) +// Parameter keys var ( - // KeyMarkets store key for markets - KeyMarkets = []byte("Markets") + KeyMarkets = []byte("Markets") + DefaultMarkets = Markets{} ) -// ParamKeyTable Key declaration for parameters -func ParamKeyTable() params.KeyTable { - return params.NewKeyTable().RegisterParamSet(&Params{}) -} - // Params params for pricefeed. Can be altered via governance type Params struct { Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed } -// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs -// pairs of pricefeed module's parameters. -func (p Params) ParamSetPairs() params.ParamSetPairs { - return params.ParamSetPairs{ - {Key: KeyMarkets, Value: &p.Markets}, - } -} - // NewParams creates a new AssetParams object func NewParams(markets Markets) Params { return Params{ @@ -40,24 +27,31 @@ func NewParams(markets Markets) Params { // DefaultParams default params for pricefeed func DefaultParams() Params { - return NewParams(Markets{}) + return NewParams(DefaultMarkets) +} + +// ParamKeyTable Key declaration for parameters +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs +// pairs of pricefeed module's parameters. +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{ + {Key: KeyMarkets, Value: &p.Markets}, + } } // String implements fmt.stringer func (p Params) String() string { out := "Params:\n" for _, a := range p.Markets { - out += a.String() + out += fmt.Sprintf("%s\n", a.String()) } return strings.TrimSpace(out) } -// ParamSubspace defines the expected Subspace interface for parameters -type ParamSubspace interface { - Get(ctx sdk.Context, key []byte, ptr interface{}) - Set(ctx sdk.Context, key []byte, param interface{}) -} - // Validate ensure that params have valid values func (p Params) Validate() error { // iterate over assets and verify them diff --git a/x/pricefeed/types/querier.go b/x/pricefeed/types/querier.go index 7439c362..074a1ee4 100644 --- a/x/pricefeed/types/querier.go +++ b/x/pricefeed/types/querier.go @@ -1,9 +1,5 @@ package types -import ( - "strings" -) - // price Takes an [assetcode] and returns CurrentPrice for that asset // pricefeed Takes an [assetcode] and returns the raw []PostedPrice for that asset // assets Returns []Assets in the pricefeed system @@ -13,23 +9,11 @@ const ( QueryCurrentPrice = "price" // QueryRawPrices command for raw price queries QueryRawPrices = "rawprices" - // QueryAssets command for assets query - QueryAssets = "assets" + // QueryMarkets command for assets query + QueryMarkets = "markets" ) -// QueryRawPricesResp response to a rawprice query -type QueryRawPricesResp []string - -// implement fmt.Stringer -func (n QueryRawPricesResp) String() string { - return strings.Join(n[:], "\n") +// QueryPricesParams fields for querying prices +type QueryPricesParams struct { + MarketID string } - -// QueryAssetsResp response to a assets query -type QueryAssetsResp []string - -// implement fmt.Stringer -func (n QueryAssetsResp) String() string { - return strings.Join(n[:], "\n") -} -