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{
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{

View File

@ -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{

View File

@ -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 {

View File

@ -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
DefaultMarkets = types.DefaultMarkets
)
type (
GenesisState = types.GenesisState
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
QueryPricesParams = types.QueryPricesParams
Keeper = keeper.Keeper
)

View File

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

View File

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

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
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"`
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)
}

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

View File

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

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,
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{}
}

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,32 +6,30 @@ 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,
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)

View File

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

View File

@ -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, &params)
k.paramSubspace.SetParamSet(ctx, &params)
}
// 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 {

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

View File

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

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."))
}
// 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))
}

View File

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

View File

@ -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"

View File

@ -8,12 +8,12 @@ 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"`
Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"`
Active bool `json:"active" yaml:"active"`
}
@ -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"`

View File

@ -15,7 +15,7 @@ const (
// 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
MarketID string // asset code used by exchanges/api
Price sdk.Dec // price in decimal (max precision 18)
Expiry time.Time // expiry time
}
@ -28,7 +28,7 @@ func NewMsgPostPrice(
expiry time.Time) MsgPostPrice {
return MsgPostPrice{
From: from,
AssetCode: assetCode,
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")

View File

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

View File

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