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
This commit is contained in:
Kevin Davis 2020-01-17 13:29:19 +01:00 committed by GitHub
parent dca59447aa
commit d04aad5cc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 559 additions and 368 deletions

View File

@ -21,7 +21,7 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
pfGenesis := pricefeed.GenesisState{ pfGenesis := pricefeed.GenesisState{
Params: pricefeed.Params{ Params: pricefeed.Params{
Markets: []pricefeed.Market{ 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{ PostedPrices: []pricefeed.PostedPrice{
@ -77,8 +77,8 @@ func NewPricefeedGenStateMulti() app.GenesisState {
pfGenesis := pricefeed.GenesisState{ pfGenesis := pricefeed.GenesisState{
Params: pricefeed.Params{ Params: pricefeed.Params{
Markets: []pricefeed.Market{ Markets: []pricefeed.Market{
pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", 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: pricefeed.Oracles{}, Active: true}, pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
}, },
}, },
PostedPrices: []pricefeed.PostedPrice{ PostedPrices: []pricefeed.PostedPrice{

View File

@ -21,7 +21,7 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
pfGenesis := pricefeed.GenesisState{ pfGenesis := pricefeed.GenesisState{
Params: pricefeed.Params{ Params: pricefeed.Params{
Markets: []pricefeed.Market{ 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{ PostedPrices: []pricefeed.PostedPrice{
@ -77,8 +77,8 @@ func NewPricefeedGenStateMulti() app.GenesisState {
pfGenesis := pricefeed.GenesisState{ pfGenesis := pricefeed.GenesisState{
Params: pricefeed.Params{ Params: pricefeed.Params{
Markets: []pricefeed.Market{ Markets: []pricefeed.Market{
pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", 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: pricefeed.Oracles{}, Active: true}, pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
}, },
}, },
PostedPrices: []pricefeed.PostedPrice{ PostedPrices: []pricefeed.PostedPrice{

View File

@ -9,7 +9,7 @@ import (
// EndBlocker updates the current pricefeed // EndBlocker updates the current pricefeed
func EndBlocker(ctx sdk.Context, k Keeper) { func EndBlocker(ctx sdk.Context, k Keeper) {
// Update the current price of each asset. // Update the current price of each asset.
for _, a := range k.GetMarketParams(ctx) { for _, a := range k.GetMarkets(ctx) {
if a.Active { if a.Active {
err := k.SetCurrentPrices(ctx, a.MarketID) err := k.SetCurrentPrices(ctx, a.MarketID)
if err != nil { if err != nil {

View File

@ -26,12 +26,12 @@ const (
DefaultParamspace = types.DefaultParamspace DefaultParamspace = types.DefaultParamspace
RawPriceFeedPrefix = types.RawPriceFeedPrefix RawPriceFeedPrefix = types.RawPriceFeedPrefix
CurrentPricePrefix = types.CurrentPricePrefix CurrentPricePrefix = types.CurrentPricePrefix
AssetPrefix = types.AssetPrefix MarketPrefix = types.MarketPrefix
OraclePrefix = types.OraclePrefix OraclePrefix = types.OraclePrefix
TypeMsgPostPrice = types.TypeMsgPostPrice TypeMsgPostPrice = types.TypeMsgPostPrice
QueryCurrentPrice = types.QueryCurrentPrice QueryCurrentPrice = types.QueryCurrentPrice
QueryRawPrices = types.QueryRawPrices QueryRawPrices = types.QueryRawPrices
QueryAssets = types.QueryAssets QueryMarkets = types.QueryMarkets
) )
var ( var (
@ -40,36 +40,32 @@ var (
ErrEmptyInput = types.ErrEmptyInput ErrEmptyInput = types.ErrEmptyInput
ErrExpired = types.ErrExpired ErrExpired = types.ErrExpired
ErrNoValidPrice = types.ErrNoValidPrice ErrNoValidPrice = types.ErrNoValidPrice
ErrInvalidAsset = types.ErrInvalidAsset ErrInvalidMarket = types.ErrInvalidMarket
ErrInvalidOracle = types.ErrInvalidOracle ErrInvalidOracle = types.ErrInvalidOracle
NewGenesisState = types.NewGenesisState NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState DefaultGenesisState = types.DefaultGenesisState
ValidateGenesis = types.ValidateGenesis
NewMsgPostPrice = types.NewMsgPostPrice NewMsgPostPrice = types.NewMsgPostPrice
ParamKeyTable = types.ParamKeyTable
NewParams = types.NewParams NewParams = types.NewParams
DefaultParams = types.DefaultParams DefaultParams = types.DefaultParams
ParamKeyTable = types.ParamKeyTable
NewKeeper = keeper.NewKeeper NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier NewQuerier = keeper.NewQuerier
// variable aliases // variable aliases
ModuleCdc = types.ModuleCdc ModuleCdc = types.ModuleCdc
KeyMarkets = types.KeyMarkets KeyMarkets = types.KeyMarkets
DefaultMarkets = types.DefaultMarkets
) )
type ( type (
Market = types.Market GenesisState = types.GenesisState
Markets = types.Markets Market = types.Market
Oracle = types.Oracle Markets = types.Markets
Oracles = types.Oracles CurrentPrice = types.CurrentPrice
CurrentPrice = types.CurrentPrice PostedPrice = types.PostedPrice
PostedPrice = types.PostedPrice SortDecs = types.SortDecs
SortDecs = types.SortDecs MsgPostPrice = types.MsgPostPrice
GenesisState = types.GenesisState Params = types.Params
MsgPostPrice = types.MsgPostPrice QueryPricesParams = types.QueryPricesParams
Params = types.Params Keeper = keeper.Keeper
ParamSubspace = types.ParamSubspace
QueryRawPricesResp = types.QueryRawPricesResp
QueryAssetsResp = types.QueryAssetsResp
Keeper = keeper.Keeper
) )

View File

@ -11,7 +11,7 @@ import (
) )
// GetQueryCmd returns the cli query commands for this module // 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 // Group nameservice queries under a subcommand
pricefeedQueryCmd := &cobra.Command{ pricefeedQueryCmd := &cobra.Command{
Use: types.ModuleName, Use: types.ModuleName,
@ -22,9 +22,9 @@ func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command {
} }
pricefeedQueryCmd.AddCommand(client.GetCommands( pricefeedQueryCmd.AddCommand(client.GetCommands(
GetCmdCurrentPrice(storeKey, cdc), GetCmdCurrentPrice(queryRoute, cdc),
GetCmdRawPrices(storeKey, cdc), GetCmdRawPrices(queryRoute, cdc),
GetCmdAssets(storeKey, cdc), GetCmdMarkets(queryRoute, cdc),
)...) )...)
return pricefeedQueryCmd return pricefeedQueryCmd
@ -33,21 +33,28 @@ func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command {
// GetCmdCurrentPrice queries the current price of an asset // GetCmdCurrentPrice queries the current price of an asset
func GetCmdCurrentPrice(queryRoute string, cdc *codec.Codec) *cobra.Command { func GetCmdCurrentPrice(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "price [assetCode]", Use: "price [marketID]",
Short: "get the current price of an asset", Short: "get the current price for the input market",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
assetCode := args[0] marketID := args[0]
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/price/%s", queryRoute, assetCode), nil)
bz, err := cdc.MarshalJSON(types.QueryPricesParams{
MarketID: marketID,
})
if err != nil { if err != nil {
fmt.Printf("error when querying current price - %s", err) return err
fmt.Printf("could not get current price for - %s \n", assetCode)
return nil
} }
var out types.CurrentPrice route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryCurrentPrice)
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out) 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 // GetCmdRawPrices queries the current price of an asset
func GetCmdRawPrices(queryRoute string, cdc *codec.Codec) *cobra.Command { func GetCmdRawPrices(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "rawprices [assetCode]", Use: "rawprices [marketID]",
Short: "get the raw oracle prices for an asset", Short: "get the raw oracle prices for the input market",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
assetCode := args[0] marketID := args[0]
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/rawprices/%s", queryRoute, assetCode), nil)
bz, err := cdc.MarshalJSON(types.QueryPricesParams{
MarketID: marketID,
})
if err != nil { if err != nil {
fmt.Printf("could not get raw prices for - %s \n", assetCode) return err
return nil
} }
var out types.QueryRawPricesResp route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryRawPrices)
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out) 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 // GetCmdMarkets queries list of markets in the pricefeed
func GetCmdAssets(queryRoute string, cdc *codec.Codec) *cobra.Command { func GetCmdMarkets(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "assets", Use: "markets",
Short: "get the assets in the pricefeed", Short: "get the markets in the pricefeed",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) 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 { if err != nil {
fmt.Printf("could not get assets") return err
return nil
} }
var out types.QueryAssetsResp var markets types.Markets
cdc.MustUnmarshalJSON(res, &out) cdc.MustUnmarshalJSON(res, &markets)
return cliCtx.PrintOutput(out) return cliCtx.PrintOutput(markets)
}, },
} }
} }

View File

@ -36,8 +36,8 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command {
// GetCmdPostPrice cli command for posting prices. // GetCmdPostPrice cli command for posting prices.
func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "postprice [assetCode] [price] [expiry]", Use: "postprice [marketID] [price] [expiry]",
Short: "post the latest price for a particular asset", Short: "post the latest price for a particular market",
Args: cobra.ExactArgs(3), Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
@ -51,8 +51,7 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command {
} }
expiryInt, ok := sdk.NewIntFromString(args[2]) expiryInt, ok := sdk.NewIntFromString(args[2])
if !ok { if !ok {
fmt.Printf("invalid expiry - %s \n", args[2]) return fmt.Errorf("invalid expiry - %s", args[2])
return nil
} }
expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0)) expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0))

View File

@ -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)
}
}

View File

@ -1,116 +1,24 @@
package rest package rest
import ( import (
"fmt"
"net/http"
"time"
"github.com/cosmos/cosmos-sdk/client/context" "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/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/kava-labs/kava/x/pricefeed/types"
tmtime "github.com/tendermint/tendermint/types/time"
) )
const ( const (
restName = "assetCode" restName = "marketID"
) )
type postPriceReq struct { type postPriceReq struct {
BaseReq rest.BaseReq `json:"base_req"` BaseReq rest.BaseReq `json:"base_req"`
AssetCode string `json:"asset_code"` MarketID string `json:"market_id"`
Price string `json:"price"` Price string `json:"price"`
Expiry string `json:"expiry"` Expiry string `json:"expiry"`
} }
// RegisterRoutes - Central function to define routes that get registered by the main application // RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, storeName string) { func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/rawprices", storeName), postPriceHandler(cliCtx)).Methods("PUT") registerQueryRoutes(cliCtx, r)
r.HandleFunc(fmt.Sprintf("/%s/rawprices/{%s}", storeName, restName), getRawPricesHandler(cliCtx, storeName)).Methods("GET") registerTxRoutes(cliCtx, r)
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)
}
} }

View File

@ -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})
}
}

View File

@ -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 package pricefeed

View File

@ -5,23 +5,27 @@ import (
) )
// InitGenesis sets distribution information for genesis. // InitGenesis sets distribution information for genesis.
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { func InitGenesis(ctx sdk.Context, keeper Keeper, gs GenesisState) {
err := gs.Validate()
// Set the assets and oracles from params if err != nil {
keeper.SetParams(ctx, data.Params) 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 // 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) _, err := keeper.SetPrice(ctx, pp.OracleAddress, pp.MarketID, pp.Price, pp.Expiry)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
params := keeper.GetParams(ctx)
// Set the current price (if any) based on what's now in the store // Set the current price (if any) based on what's now in the store
for _, a := range data.Params.Markets { for _, market := range params.Markets {
if a.Active { if market.Active {
err := keeper.SetCurrentPrices(ctx, a.MarketID) err := keeper.SetCurrentPrices(ctx, market.MarketID)
if err != nil { if err != nil {
panic(err) 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. // ExportGenesis returns a GenesisState for a given context and keeper.
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { 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) params := keeper.GetParams(ctx)
var postedPrices []PostedPrice var postedPrices []PostedPrice
for _, asset := range keeper.GetMarketParams(ctx) { for _, market := range keeper.GetMarkets(ctx) {
pp := keeper.GetRawPrices(ctx, asset.MarketID) pp := keeper.GetRawPrices(ctx, market.MarketID)
postedPrices = append(postedPrices, pp...) postedPrices = append(postedPrices, pp...)
} }

View File

@ -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))
}

View File

@ -28,11 +28,13 @@ func HandleMsgPostPrice(
k Keeper, k Keeper,
msg MsgPostPrice) sdk.Result { msg MsgPostPrice) sdk.Result {
// TODO cleanup message validation and errors _, err := k.GetOracle(ctx, msg.MarketID, msg.From)
_, err := k.GetOracle(ctx, msg.AssetCode, msg.From)
if err != nil { 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{} return sdk.Result{}
} }

View File

@ -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)}
}

View File

@ -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)}
}

View File

@ -6,34 +6,32 @@ import (
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" 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" "github.com/kava-labs/kava/x/pricefeed/types"
) )
// Keeper struct for pricefeed module // Keeper struct for pricefeed module
type Keeper struct { type Keeper struct {
// The keys used to access the stores from Context // key used to access the stores from Context
storeKey sdk.StoreKey key sdk.StoreKey
// Codec for binary encoding/decoding // Codec for binary encoding/decoding
cdc *codec.Codec cdc *codec.Codec
// The reference to the Paramstore to get and set pricefeed specific params // The reference to the Paramstore to get and set pricefeed specific params
paramstore params.Subspace paramSubspace subspace.Subspace
// Reserved codespace // Reserved codespace
codespace sdk.CodespaceType codespace sdk.CodespaceType
} }
// NewKeeper returns a new keeper for the pricefeed module. It handles: // NewKeeper returns a new keeper for the pricefeed module.
// - adding oracles
// - adding/removing assets from the pricefeed
func NewKeeper( 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 { ) Keeper {
return Keeper{ return Keeper{
paramstore: paramstore.WithKeyTable(types.ParamKeyTable()), paramSubspace: paramSubspace.WithKeyTable(types.ParamKeyTable()),
storeKey: storeKey, key: key,
cdc: cdc, cdc: cdc,
codespace: codespace, codespace: codespace,
} }
} }
@ -46,7 +44,7 @@ func (k Keeper) SetPrice(
expiry time.Time) (types.PostedPrice, sdk.Error) { 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 the expiry is less than or equal to the current blockheight, we consider the price valid
if expiry.After(ctx.BlockTime()) { if expiry.After(ctx.BlockTime()) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.key)
prices := k.GetRawPrices(ctx, marketID) prices := k.GetRawPrices(ctx, marketID)
var index int var index int
found := false found := false
@ -82,7 +80,7 @@ func (k Keeper) SetPrice(
func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error {
_, ok := k.GetMarket(ctx, marketID) _, ok := k.GetMarket(ctx, marketID)
if !ok { if !ok {
return types.ErrInvalidAsset(k.codespace) return types.ErrInvalidMarket(k.codespace, marketID)
} }
prices := k.GetRawPrices(ctx, marketID) prices := k.GetRawPrices(ctx, marketID)
var notExpiredPrices []types.CurrentPrice var notExpiredPrices []types.CurrentPrice
@ -96,7 +94,7 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error {
} }
} }
if len(notExpiredPrices) == 0 { if len(notExpiredPrices) == 0 {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.key)
store.Set( store.Set(
[]byte(types.CurrentPricePrefix+marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), []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) medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices)
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.key)
currentPrice := types.CurrentPrice{ currentPrice := types.CurrentPrice{
MarketID: marketID, MarketID: marketID,
Price: medianPrice, Price: medianPrice,
@ -144,9 +142,9 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice)
return mean 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) { 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)) bz := store.Get([]byte(types.CurrentPricePrefix + marketID))
if bz == nil { 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 // GetRawPrices fetches the set of all prices posted by oracles for an asset
func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) []types.PostedPrice { 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)) bz := store.Get([]byte(types.RawPriceFeedPrefix + marketID))
var prices []types.PostedPrice var prices []types.PostedPrice
k.cdc.MustUnmarshalBinaryBare(bz, &prices) k.cdc.MustUnmarshalBinaryBare(bz, &prices)

View File

@ -20,11 +20,11 @@ func TestKeeper_SetGetMarket(t *testing.T) {
mp := types.Params{ mp := types.Params{
Markets: types.Markets{ 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) keeper.SetParams(ctx, mp)
markets := keeper.GetMarketParams(ctx) markets := keeper.GetMarkets(ctx)
require.Equal(t, len(markets), 1) require.Equal(t, len(markets), 1)
require.Equal(t, markets[0].MarketID, "tstusd") require.Equal(t, markets[0].MarketID, "tstusd")
@ -33,12 +33,12 @@ func TestKeeper_SetGetMarket(t *testing.T) {
mp = types.Params{ mp = types.Params{
Markets: types.Markets{ 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},
types.Market{MarketID: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, types.Market{MarketID: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
}, },
} }
keeper.SetParams(ctx, mp) keeper.SetParams(ctx, mp)
markets = keeper.GetMarketParams(ctx) markets = keeper.GetMarkets(ctx)
require.Equal(t, len(markets), 2) require.Equal(t, len(markets), 2)
require.Equal(t, markets[0].MarketID, "tstusd") require.Equal(t, markets[0].MarketID, "tstusd")
require.Equal(t, markets[1].MarketID, "tst2usd") require.Equal(t, markets[1].MarketID, "tst2usd")
@ -56,7 +56,7 @@ func TestKeeper_GetSetPrice(t *testing.T) {
mp := types.Params{ mp := types.Params{
Markets: types.Markets{ 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) keeper.SetParams(ctx, mp)
@ -100,7 +100,7 @@ func TestKeeper_GetSetCurrentPrice(t *testing.T) {
mp := types.Params{ mp := types.Params{
Markets: types.Markets{ 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) keeper.SetParams(ctx, mp)

View File

@ -1,58 +1,56 @@
package keeper package keeper
import ( import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/pricefeed/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 { 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) { func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramstore.SetParamSet(ctx, &params) k.paramSubspace.SetParamSet(ctx, &params)
} }
// GetMarketParams get asset params from store // GetMarkets returns the markets from params
func (k Keeper) GetMarketParams(ctx sdk.Context) types.Markets { func (k Keeper) GetMarkets(ctx sdk.Context) types.Markets {
var markets types.Markets return k.GetParams(ctx).Markets
k.paramstore.Get(ctx, types.KeyMarkets, &markets)
return markets
} }
// GetOracles returns the oracles in the pricefeed store // 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 { if marketID == m.MarketID {
return m.Oracles, nil 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 // 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) oracles, err := k.GetOracles(ctx, marketID)
if err != nil { 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 { for _, addr := range oracles {
if address.Equals(o.Address) { if address.Equals(addr) {
return o, nil 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 // GetMarket returns the market if it is in the pricefeed system
func (k Keeper) GetMarket(ctx sdk.Context, marketID string) (types.Market, bool) { func (k Keeper) GetMarket(ctx sdk.Context, marketID string) (types.Market, bool) {
markets := k.GetMarketParams(ctx) markets := k.GetMarkets(ctx)
for i := range markets { for i := range markets {
if markets[i].MarketID == marketID { if markets[i].MarketID == marketID {

View File

@ -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))
}

View File

@ -1,6 +1,8 @@
package keeper package keeper
import ( import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" 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) { return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] { switch path[0] {
case types.QueryCurrentPrice: case types.QueryCurrentPrice:
return queryCurrentPrice(ctx, path[1:], req, keeper) return queryCurrentPrice(ctx, req, keeper)
case types.QueryRawPrices: case types.QueryRawPrices:
return queryRawPrices(ctx, path[1:], req, keeper) return queryRawPrices(ctx, req, keeper)
case types.QueryAssets: case types.QueryMarkets:
return queryAssets(ctx, req, keeper) return queryMarkets(ctx, req, keeper)
default: default:
return nil, sdk.ErrUnknownRequest("unknown pricefeed query endpoint") 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) { func queryCurrentPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) {
marketID := path[0] var requestParams types.QueryPricesParams
_, found := keeper.GetMarket(ctx, marketID) err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams)
if !found {
return []byte{}, sdk.ErrUnknownRequest("asset not found")
}
currentPrice, err := keeper.GetCurrentPrice(ctx, marketID)
if err != nil { 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) _, found := keeper.GetMarket(ctx, requestParams.MarketID)
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)
if !found { if !found {
return []byte{}, sdk.ErrUnknownRequest("asset not found") return []byte{}, sdk.ErrUnknownRequest("asset not found")
} }
rawPrices := keeper.GetRawPrices(ctx, marketID) currentPrice, sdkErr := keeper.GetCurrentPrice(ctx, requestParams.MarketID)
for _, price := range rawPrices { if sdkErr != nil {
priceList = append(priceList, price.String()) return nil, sdkErr
} }
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, priceList) bz, err := codec.MarshalJSONIndent(keeper.cdc, currentPrice)
if err2 != nil { if err != nil {
panic("could not marshal result to JSON") panic("could not marshal result to JSON")
} }
return bz, nil return bz, nil
} }
func queryAssets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { func queryRawPrices(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) {
var assetList types.QueryAssetsResp var requestParams types.QueryPricesParams
assets := keeper.GetMarketParams(ctx) err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams)
for _, asset := range assets { if err != nil {
assetList = append(assetList, asset.String()) return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err))
} }
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, assetList) _, found := keeper.GetMarket(ctx, requestParams.MarketID)
if err2 != nil { 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") panic("could not marshal result to JSON")
} }

View File

@ -20,6 +20,7 @@ var (
_ module.AppModule = AppModule{} _ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{} _ module.AppModuleBasic = AppModuleBasic{}
) )
// AppModuleBasic app module basics object // AppModuleBasic app module basics object
type AppModuleBasic struct{} type AppModuleBasic struct{}
@ -40,17 +41,17 @@ func (AppModuleBasic) DefaultGenesis() json.RawMessage {
// ValidateGenesis module validate genesis // ValidateGenesis module validate genesis
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
var data GenesisState var gs GenesisState
err := ModuleCdc.UnmarshalJSON(bz, &data) err := ModuleCdc.UnmarshalJSON(bz, &gs)
if err != nil { if err != nil {
return err return err
} }
return ValidateGenesis(data) return gs.Validate()
} }
// RegisterRESTRoutes register rest routes // RegisterRESTRoutes register rest routes
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { 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 // GetTxCmd get the root tx command of this module

View File

@ -37,12 +37,12 @@ func ErrNoValidPrice(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidPrice, fmt.Sprintf("All input prices are expired.")) return sdk.NewError(codespace, CodeInvalidPrice, fmt.Sprintf("All input prices are expired."))
} }
// ErrInvalidAsset Error constructor for posted price messages for invalid assets // ErrInvalidAsset Error constructor for posted price messages for invalid markets
func ErrInvalidAsset(codespace sdk.CodespaceType) sdk.Error { func ErrInvalidMarket(codespace sdk.CodespaceType, marketId string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidAsset, fmt.Sprintf("Asset code does not exist.")) return sdk.NewError(codespace, CodeInvalidAsset, fmt.Sprintf("market %s does not exist", marketId))
} }
// ErrInvalidOracle Error constructor for posted price messages for invalid oracles // ErrInvalidOracle Error constructor for posted price messages for invalid oracles
func ErrInvalidOracle(codespace sdk.CodespaceType) sdk.Error { func ErrInvalidOracle(codespace sdk.CodespaceType, addr sdk.AccAddress) sdk.Error {
return sdk.NewError(codespace, CodeInvalidOracle, fmt.Sprintf("Oracle does not exist or not authorized.")) return sdk.NewError(codespace, CodeInvalidOracle, fmt.Sprintf("oracle %s does not exist or not authorized", addr))
} }

View File

@ -27,22 +27,22 @@ func DefaultGenesisState() GenesisState {
} }
// Equal checks whether two gov GenesisState structs are equivalent // Equal checks whether two gov GenesisState structs are equivalent
func (data GenesisState) Equal(data2 GenesisState) bool { func (gs GenesisState) Equal(gs2 GenesisState) bool {
b1 := ModuleCdc.MustMarshalBinaryBare(data) b1 := ModuleCdc.MustMarshalBinaryBare(gs)
b2 := ModuleCdc.MustMarshalBinaryBare(data2) b2 := ModuleCdc.MustMarshalBinaryBare(gs2)
return bytes.Equal(b1, b2) return bytes.Equal(b1, b2)
} }
// IsEmpty returns true if a GenesisState is empty // IsEmpty returns true if a GenesisState is empty
func (data GenesisState) IsEmpty() bool { func (gs GenesisState) IsEmpty() bool {
return data.Equal(GenesisState{}) return gs.Equal(GenesisState{})
} }
// ValidateGenesis performs basic validation of genesis data returning an // ValidateGenesis performs basic validation of genesis data returning an
// error for any failed validation criteria. // 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 err
} }
return nil return nil

View File

@ -22,8 +22,8 @@ const (
// CurrentPricePrefix prefix for the current price of an asset // CurrentPricePrefix prefix for the current price of an asset
CurrentPricePrefix = StoreKey + ":currentprice:" CurrentPricePrefix = StoreKey + ":currentprice:"
// AssetPrefix Prefix for the assets in the pricefeed system // MarketPrefix Prefix for the assets in the pricefeed system
AssetPrefix = StoreKey + ":assets" MarketPrefix = StoreKey + ":markets"
// OraclePrefix store prefix for the oracle accounts // OraclePrefix store prefix for the oracle accounts
OraclePrefix = StoreKey + ":oracles" OraclePrefix = StoreKey + ":oracles"

View File

@ -8,13 +8,13 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" 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 { type Market struct {
MarketID string `json:"market_id" yaml:"market_id"` MarketID string `json:"market_id" yaml:"market_id"`
BaseAsset string `json:"base_asset" yaml:"base_asset"` BaseAsset string `json:"base_asset" yaml:"base_asset"`
QuoteAsset string `json:"quote_asset" yaml:"quote_asset"` QuoteAsset string `json:"quote_asset" yaml:"quote_asset"`
Oracles Oracles `json:"oracles" yaml:"oracles"` Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"`
Active bool `json:"active" yaml:"active"` Active bool `json:"active" yaml:"active"`
} }
// String implement fmt.Stringer // String implement fmt.Stringer
@ -40,35 +40,13 @@ func (ms Markets) String() string {
return strings.TrimSpace(out) return strings.TrimSpace(out)
} }
// Oracle struct that documents which address an oracle is using // CurrentPrice struct that contains the metadata of a current price for a particular market in the pricefeed module.
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.
type CurrentPrice struct { type CurrentPrice struct {
MarketID string `json:"market_id" yaml:"market_id"` MarketID string `json:"market_id" yaml:"market_id"`
Price sdk.Dec `json:"price" yaml:"price"` 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 { type PostedPrice struct {
MarketID string `json:"market_id" yaml:"market_id"` MarketID string `json:"market_id" yaml:"market_id"`
OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"` OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"`

View File

@ -14,10 +14,10 @@ const (
// MsgPostPrice struct representing a posted price message. // MsgPostPrice struct representing a posted price message.
// Used by oracles to input prices to the pricefeed // Used by oracles to input prices to the pricefeed
type MsgPostPrice struct { type MsgPostPrice struct {
From sdk.AccAddress // client that sent in this address From sdk.AccAddress // client that sent in this address
AssetCode string // asset code used by exchanges/api MarketID string // asset code used by exchanges/api
Price sdk.Dec // price in decimal (max precision 18) Price sdk.Dec // price in decimal (max precision 18)
Expiry time.Time // expiry time Expiry time.Time // expiry time
} }
// NewMsgPostPrice creates a new post price msg // NewMsgPostPrice creates a new post price msg
@ -27,10 +27,10 @@ func NewMsgPostPrice(
price sdk.Dec, price sdk.Dec,
expiry time.Time) MsgPostPrice { expiry time.Time) MsgPostPrice {
return MsgPostPrice{ return MsgPostPrice{
From: from, From: from,
AssetCode: assetCode, MarketID: assetCode,
Price: price, Price: price,
Expiry: expiry, 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. // ValidateBasic does a simple validation check that doesn't require access to any other information.
func (msg MsgPostPrice) ValidateBasic() sdk.Error { func (msg MsgPostPrice) ValidateBasic() sdk.Error {
if msg.From.Empty() { if msg.From.Empty() {
return sdk.ErrInternal("invalid (empty) bidder address") return sdk.ErrInternal("invalid (empty) from address")
} }
if len(msg.AssetCode) == 0 { if len(msg.MarketID) == 0 {
return sdk.ErrInternal("invalid (empty) asset code") return sdk.ErrInternal("invalid (empty) market id")
} }
if msg.Price.LT(sdk.ZeroDec()) { if msg.Price.LT(sdk.ZeroDec()) {
return sdk.ErrInternal("invalid (negative) price") return sdk.ErrInternal("invalid (negative) price")

View File

@ -4,33 +4,20 @@ import (
"fmt" "fmt"
"strings" "strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/params"
) )
// Parameter keys
var ( 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 // Params params for pricefeed. Can be altered via governance
type Params struct { type Params struct {
Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed 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 // NewParams creates a new AssetParams object
func NewParams(markets Markets) Params { func NewParams(markets Markets) Params {
return Params{ return Params{
@ -40,24 +27,31 @@ func NewParams(markets Markets) Params {
// DefaultParams default params for pricefeed // DefaultParams default params for pricefeed
func DefaultParams() Params { 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 // String implements fmt.stringer
func (p Params) String() string { func (p Params) String() string {
out := "Params:\n" out := "Params:\n"
for _, a := range p.Markets { for _, a := range p.Markets {
out += a.String() out += fmt.Sprintf("%s\n", a.String())
} }
return strings.TrimSpace(out) 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 // Validate ensure that params have valid values
func (p Params) Validate() error { func (p Params) Validate() error {
// iterate over assets and verify them // iterate over assets and verify them

View File

@ -1,9 +1,5 @@
package types package types
import (
"strings"
)
// price Takes an [assetcode] and returns CurrentPrice for that asset // price Takes an [assetcode] and returns CurrentPrice for that asset
// pricefeed Takes an [assetcode] and returns the raw []PostedPrice for that asset // pricefeed Takes an [assetcode] and returns the raw []PostedPrice for that asset
// assets Returns []Assets in the pricefeed system // assets Returns []Assets in the pricefeed system
@ -13,23 +9,11 @@ const (
QueryCurrentPrice = "price" QueryCurrentPrice = "price"
// QueryRawPrices command for raw price queries // QueryRawPrices command for raw price queries
QueryRawPrices = "rawprices" QueryRawPrices = "rawprices"
// QueryAssets command for assets query // QueryMarkets command for assets query
QueryAssets = "assets" QueryMarkets = "markets"
) )
// QueryRawPricesResp response to a rawprice query // QueryPricesParams fields for querying prices
type QueryRawPricesResp []string type QueryPricesParams struct {
MarketID string
// implement fmt.Stringer
func (n QueryRawPricesResp) String() string {
return strings.Join(n[:], "\n")
} }
// QueryAssetsResp response to a assets query
type QueryAssetsResp []string
// implement fmt.Stringer
func (n QueryAssetsResp) String() string {
return strings.Join(n[:], "\n")
}