mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
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:
parent
dca59447aa
commit
d04aad5cc9
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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 {
|
||||
|
@ -26,12 +26,12 @@ const (
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
RawPriceFeedPrefix = types.RawPriceFeedPrefix
|
||||
CurrentPricePrefix = types.CurrentPricePrefix
|
||||
AssetPrefix = types.AssetPrefix
|
||||
MarketPrefix = types.MarketPrefix
|
||||
OraclePrefix = types.OraclePrefix
|
||||
TypeMsgPostPrice = types.TypeMsgPostPrice
|
||||
QueryCurrentPrice = types.QueryCurrentPrice
|
||||
QueryRawPrices = types.QueryRawPrices
|
||||
QueryAssets = types.QueryAssets
|
||||
QueryMarkets = types.QueryMarkets
|
||||
)
|
||||
|
||||
var (
|
||||
@ -40,36 +40,32 @@ var (
|
||||
ErrEmptyInput = types.ErrEmptyInput
|
||||
ErrExpired = types.ErrExpired
|
||||
ErrNoValidPrice = types.ErrNoValidPrice
|
||||
ErrInvalidAsset = types.ErrInvalidAsset
|
||||
ErrInvalidMarket = types.ErrInvalidMarket
|
||||
ErrInvalidOracle = types.ErrInvalidOracle
|
||||
NewGenesisState = types.NewGenesisState
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
ValidateGenesis = types.ValidateGenesis
|
||||
NewMsgPostPrice = types.NewMsgPostPrice
|
||||
ParamKeyTable = types.ParamKeyTable
|
||||
NewParams = types.NewParams
|
||||
DefaultParams = types.DefaultParams
|
||||
ParamKeyTable = types.ParamKeyTable
|
||||
NewKeeper = keeper.NewKeeper
|
||||
NewQuerier = keeper.NewQuerier
|
||||
|
||||
// variable aliases
|
||||
ModuleCdc = types.ModuleCdc
|
||||
KeyMarkets = types.KeyMarkets
|
||||
ModuleCdc = types.ModuleCdc
|
||||
KeyMarkets = types.KeyMarkets
|
||||
DefaultMarkets = types.DefaultMarkets
|
||||
)
|
||||
|
||||
type (
|
||||
Market = types.Market
|
||||
Markets = types.Markets
|
||||
Oracle = types.Oracle
|
||||
Oracles = types.Oracles
|
||||
CurrentPrice = types.CurrentPrice
|
||||
PostedPrice = types.PostedPrice
|
||||
SortDecs = types.SortDecs
|
||||
GenesisState = types.GenesisState
|
||||
MsgPostPrice = types.MsgPostPrice
|
||||
Params = types.Params
|
||||
ParamSubspace = types.ParamSubspace
|
||||
QueryRawPricesResp = types.QueryRawPricesResp
|
||||
QueryAssetsResp = types.QueryAssetsResp
|
||||
Keeper = keeper.Keeper
|
||||
GenesisState = types.GenesisState
|
||||
Market = types.Market
|
||||
Markets = types.Markets
|
||||
CurrentPrice = types.CurrentPrice
|
||||
PostedPrice = types.PostedPrice
|
||||
SortDecs = types.SortDecs
|
||||
MsgPostPrice = types.MsgPostPrice
|
||||
Params = types.Params
|
||||
QueryPricesParams = types.QueryPricesParams
|
||||
Keeper = keeper.Keeper
|
||||
)
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
||||
|
57
x/pricefeed/client/rest/query.go
Normal file
57
x/pricefeed/client/rest/query.go
Normal 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)
|
||||
}
|
||||
}
|
@ -1,116 +1,24 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
const (
|
||||
restName = "assetCode"
|
||||
restName = "marketID"
|
||||
)
|
||||
|
||||
type postPriceReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
AssetCode string `json:"asset_code"`
|
||||
Price string `json:"price"`
|
||||
Expiry string `json:"expiry"`
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
MarketID string `json:"market_id"`
|
||||
Price string `json:"price"`
|
||||
Expiry string `json:"expiry"`
|
||||
}
|
||||
|
||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, storeName string) {
|
||||
r.HandleFunc(fmt.Sprintf("/%s/rawprices", storeName), postPriceHandler(cliCtx)).Methods("PUT")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/rawprices/{%s}", storeName, restName), getRawPricesHandler(cliCtx, storeName)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/currentprice/{%s}", storeName, restName), getCurrentPriceHandler(cliCtx, storeName)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/assets", storeName), getAssetsHandler(cliCtx, storeName)).Methods("GET")
|
||||
}
|
||||
|
||||
func postPriceHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req postPriceReq
|
||||
|
||||
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
|
||||
return
|
||||
}
|
||||
|
||||
baseReq := req.BaseReq.Sanitize()
|
||||
if !baseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
|
||||
addr, err := sdk.AccAddressFromBech32(baseReq.From)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
price, err := sdk.NewDecFromStr(req.Price)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
expiryInt, ok := sdk.NewIntFromString(req.Expiry)
|
||||
if !ok {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid expiry")
|
||||
return
|
||||
}
|
||||
expiry := tmtime.Canonical(time.Unix(expiryInt.Int64(), 0))
|
||||
|
||||
// create the message
|
||||
msg := types.NewMsgPostPrice(addr, req.AssetCode, price, expiry)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
|
||||
}
|
||||
}
|
||||
|
||||
func getRawPricesHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
paramType := vars[restName]
|
||||
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/rawprices/%s", storeName, paramType), nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func getCurrentPriceHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
paramType := vars[restName]
|
||||
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/price/%s", storeName, paramType), nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
func getAssetsHandler(cliCtx context.CLIContext, storeName string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/assets/", storeName), nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
registerQueryRoutes(cliCtx, r)
|
||||
registerTxRoutes(cliCtx, r)
|
||||
}
|
||||
|
66
x/pricefeed/client/rest/tx.go
Normal file
66
x/pricefeed/client/rest/tx.go
Normal 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})
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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...)
|
||||
}
|
||||
|
||||
|
39
x/pricefeed/genesis_test.go
Normal file
39
x/pricefeed/genesis_test.go
Normal 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))
|
||||
}
|
@ -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{}
|
||||
}
|
||||
|
62
x/pricefeed/integration_test.go
Normal file
62
x/pricefeed/integration_test.go
Normal 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)}
|
||||
}
|
36
x/pricefeed/keeper/integration_test.go
Normal file
36
x/pricefeed/keeper/integration_test.go
Normal 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)}
|
||||
}
|
@ -6,34 +6,32 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
||||
|
||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
||||
// Keeper struct for pricefeed module
|
||||
type Keeper struct {
|
||||
// The keys used to access the stores from Context
|
||||
storeKey sdk.StoreKey
|
||||
// key used to access the stores from Context
|
||||
key sdk.StoreKey
|
||||
// Codec for binary encoding/decoding
|
||||
cdc *codec.Codec
|
||||
// The reference to the Paramstore to get and set pricefeed specific params
|
||||
paramstore params.Subspace
|
||||
paramSubspace subspace.Subspace
|
||||
// Reserved codespace
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
// NewKeeper returns a new keeper for the pricefeed module. It handles:
|
||||
// - adding oracles
|
||||
// - adding/removing assets from the pricefeed
|
||||
// NewKeeper returns a new keeper for the pricefeed module.
|
||||
func NewKeeper(
|
||||
cdc *codec.Codec, storeKey sdk.StoreKey, paramstore params.Subspace, codespace sdk.CodespaceType,
|
||||
cdc *codec.Codec, key sdk.StoreKey, paramSubspace subspace.Subspace, codespace sdk.CodespaceType,
|
||||
) Keeper {
|
||||
return Keeper{
|
||||
paramstore: paramstore.WithKeyTable(types.ParamKeyTable()),
|
||||
storeKey: storeKey,
|
||||
cdc: cdc,
|
||||
codespace: codespace,
|
||||
paramSubspace: paramSubspace.WithKeyTable(types.ParamKeyTable()),
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
codespace: codespace,
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +44,7 @@ func (k Keeper) SetPrice(
|
||||
expiry time.Time) (types.PostedPrice, sdk.Error) {
|
||||
// If the expiry is less than or equal to the current blockheight, we consider the price valid
|
||||
if expiry.After(ctx.BlockTime()) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store := ctx.KVStore(k.key)
|
||||
prices := k.GetRawPrices(ctx, marketID)
|
||||
var index int
|
||||
found := false
|
||||
@ -82,7 +80,7 @@ func (k Keeper) SetPrice(
|
||||
func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error {
|
||||
_, ok := k.GetMarket(ctx, marketID)
|
||||
if !ok {
|
||||
return types.ErrInvalidAsset(k.codespace)
|
||||
return types.ErrInvalidMarket(k.codespace, marketID)
|
||||
}
|
||||
prices := k.GetRawPrices(ctx, marketID)
|
||||
var notExpiredPrices []types.CurrentPrice
|
||||
@ -96,7 +94,7 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error {
|
||||
}
|
||||
}
|
||||
if len(notExpiredPrices) == 0 {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store := ctx.KVStore(k.key)
|
||||
store.Set(
|
||||
[]byte(types.CurrentPricePrefix+marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}),
|
||||
)
|
||||
@ -104,7 +102,7 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error {
|
||||
}
|
||||
medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices)
|
||||
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store := ctx.KVStore(k.key)
|
||||
currentPrice := types.CurrentPrice{
|
||||
MarketID: marketID,
|
||||
Price: medianPrice,
|
||||
@ -144,9 +142,9 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice)
|
||||
return mean
|
||||
}
|
||||
|
||||
// GetCurrentPrice fetches the current median price of all oracles for a specific asset
|
||||
// GetCurrentPrice fetches the current median price of all oracles for a specific market
|
||||
func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, sdk.Error) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store := ctx.KVStore(k.key)
|
||||
bz := store.Get([]byte(types.CurrentPricePrefix + marketID))
|
||||
|
||||
if bz == nil {
|
||||
@ -162,7 +160,7 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current
|
||||
|
||||
// GetRawPrices fetches the set of all prices posted by oracles for an asset
|
||||
func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) []types.PostedPrice {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store := ctx.KVStore(k.key)
|
||||
bz := store.Get([]byte(types.RawPriceFeedPrefix + marketID))
|
||||
var prices []types.PostedPrice
|
||||
k.cdc.MustUnmarshalBinaryBare(bz, &prices)
|
||||
|
@ -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)
|
||||
|
@ -1,58 +1,56 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
||||
// GetParams gets params from the store
|
||||
// GetParams returns the params from the store
|
||||
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
||||
return types.NewParams(k.GetMarketParams(ctx))
|
||||
var p types.Params
|
||||
k.paramSubspace.GetParamSet(ctx, &p)
|
||||
return p
|
||||
}
|
||||
|
||||
// SetParams updates params in the store
|
||||
// SetParams sets params on the store
|
||||
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||
k.paramstore.SetParamSet(ctx, ¶ms)
|
||||
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
||||
}
|
||||
|
||||
// GetMarketParams get asset params from store
|
||||
func (k Keeper) GetMarketParams(ctx sdk.Context) types.Markets {
|
||||
var markets types.Markets
|
||||
k.paramstore.Get(ctx, types.KeyMarkets, &markets)
|
||||
return markets
|
||||
// GetMarkets returns the markets from params
|
||||
func (k Keeper) GetMarkets(ctx sdk.Context) types.Markets {
|
||||
return k.GetParams(ctx).Markets
|
||||
}
|
||||
|
||||
// GetOracles returns the oracles in the pricefeed store
|
||||
func (k Keeper) GetOracles(ctx sdk.Context, marketID string) (types.Oracles, error) {
|
||||
func (k Keeper) GetOracles(ctx sdk.Context, marketID string) ([]sdk.AccAddress, sdk.Error) {
|
||||
|
||||
for _, m := range k.GetMarketParams(ctx) {
|
||||
for _, m := range k.GetMarkets(ctx) {
|
||||
if marketID == m.MarketID {
|
||||
return m.Oracles, nil
|
||||
}
|
||||
}
|
||||
return types.Oracles{}, fmt.Errorf("asset %s not found", marketID)
|
||||
return []sdk.AccAddress{}, types.ErrInvalidMarket(k.Codespace(), marketID)
|
||||
}
|
||||
|
||||
// GetOracle returns the oracle from the store or an error if not found
|
||||
func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (types.Oracle, error) {
|
||||
func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (sdk.AccAddress, sdk.Error) {
|
||||
oracles, err := k.GetOracles(ctx, marketID)
|
||||
if err != nil {
|
||||
return types.Oracle{}, fmt.Errorf("asset %s not found", marketID)
|
||||
return sdk.AccAddress{}, types.ErrInvalidMarket(k.Codespace(), marketID)
|
||||
}
|
||||
for _, o := range oracles {
|
||||
if address.Equals(o.Address) {
|
||||
return o, nil
|
||||
for _, addr := range oracles {
|
||||
if address.Equals(addr) {
|
||||
return addr, nil
|
||||
}
|
||||
}
|
||||
return types.Oracle{}, fmt.Errorf("oracle %s not found for asset %s", address, marketID)
|
||||
return sdk.AccAddress{}, types.ErrInvalidOracle(k.codespace, address)
|
||||
}
|
||||
|
||||
// GetMarket returns the market if it is in the pricefeed system
|
||||
func (k Keeper) GetMarket(ctx sdk.Context, marketID string) (types.Market, bool) {
|
||||
markets := k.GetMarketParams(ctx)
|
||||
markets := k.GetMarkets(ctx)
|
||||
|
||||
for i := range markets {
|
||||
if markets[i].MarketID == marketID {
|
||||
|
49
x/pricefeed/keeper/params_test.go
Normal file
49
x/pricefeed/keeper/params_test.go
Normal 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))
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -8,13 +8,13 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Market struct that represents an asset in the pricefeed
|
||||
// Market an asset in the pricefeed
|
||||
type Market struct {
|
||||
MarketID string `json:"market_id" yaml:"market_id"`
|
||||
BaseAsset string `json:"base_asset" yaml:"base_asset"`
|
||||
QuoteAsset string `json:"quote_asset" yaml:"quote_asset"`
|
||||
Oracles Oracles `json:"oracles" yaml:"oracles"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
MarketID string `json:"market_id" yaml:"market_id"`
|
||||
BaseAsset string `json:"base_asset" yaml:"base_asset"`
|
||||
QuoteAsset string `json:"quote_asset" yaml:"quote_asset"`
|
||||
Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
}
|
||||
|
||||
// String implement fmt.Stringer
|
||||
@ -40,35 +40,13 @@ func (ms Markets) String() string {
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// Oracle struct that documents which address an oracle is using
|
||||
type Oracle struct {
|
||||
Address sdk.AccAddress `json:"address" yaml:"address"`
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (o Oracle) String() string {
|
||||
return fmt.Sprintf(`Address: %s`, o.Address)
|
||||
}
|
||||
|
||||
// Oracles array type for oracle
|
||||
type Oracles []Oracle
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (os Oracles) String() string {
|
||||
out := "Oracles:\n"
|
||||
for _, o := range os {
|
||||
out += fmt.Sprintf("%s\n", o.String())
|
||||
}
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// CurrentPrice struct that contains the metadata of a current price for a particular asset in the pricefeed module.
|
||||
// CurrentPrice struct that contains the metadata of a current price for a particular market in the pricefeed module.
|
||||
type CurrentPrice struct {
|
||||
MarketID string `json:"market_id" yaml:"market_id"`
|
||||
Price sdk.Dec `json:"price" yaml:"price"`
|
||||
}
|
||||
|
||||
// PostedPrice struct represented a price for an asset posted by a specific oracle
|
||||
// PostedPrice price for market posted by a specific oracle
|
||||
type PostedPrice struct {
|
||||
MarketID string `json:"market_id" yaml:"market_id"`
|
||||
OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"`
|
@ -14,10 +14,10 @@ const (
|
||||
// MsgPostPrice struct representing a posted price message.
|
||||
// Used by oracles to input prices to the pricefeed
|
||||
type MsgPostPrice struct {
|
||||
From sdk.AccAddress // client that sent in this address
|
||||
AssetCode string // asset code used by exchanges/api
|
||||
Price sdk.Dec // price in decimal (max precision 18)
|
||||
Expiry time.Time // expiry time
|
||||
From sdk.AccAddress // client that sent in this address
|
||||
MarketID string // asset code used by exchanges/api
|
||||
Price sdk.Dec // price in decimal (max precision 18)
|
||||
Expiry time.Time // expiry time
|
||||
}
|
||||
|
||||
// NewMsgPostPrice creates a new post price msg
|
||||
@ -27,10 +27,10 @@ func NewMsgPostPrice(
|
||||
price sdk.Dec,
|
||||
expiry time.Time) MsgPostPrice {
|
||||
return MsgPostPrice{
|
||||
From: from,
|
||||
AssetCode: assetCode,
|
||||
Price: price,
|
||||
Expiry: expiry,
|
||||
From: from,
|
||||
MarketID: assetCode,
|
||||
Price: price,
|
||||
Expiry: expiry,
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,10 +54,10 @@ func (msg MsgPostPrice) GetSigners() []sdk.AccAddress {
|
||||
// ValidateBasic does a simple validation check that doesn't require access to any other information.
|
||||
func (msg MsgPostPrice) ValidateBasic() sdk.Error {
|
||||
if msg.From.Empty() {
|
||||
return sdk.ErrInternal("invalid (empty) bidder address")
|
||||
return sdk.ErrInternal("invalid (empty) from address")
|
||||
}
|
||||
if len(msg.AssetCode) == 0 {
|
||||
return sdk.ErrInternal("invalid (empty) asset code")
|
||||
if len(msg.MarketID) == 0 {
|
||||
return sdk.ErrInternal("invalid (empty) market id")
|
||||
}
|
||||
if msg.Price.LT(sdk.ZeroDec()) {
|
||||
return sdk.ErrInternal("invalid (negative) price")
|
||||
|
@ -4,33 +4,20 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
var (
|
||||
// KeyMarkets store key for markets
|
||||
KeyMarkets = []byte("Markets")
|
||||
KeyMarkets = []byte("Markets")
|
||||
DefaultMarkets = Markets{}
|
||||
)
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
func ParamKeyTable() params.KeyTable {
|
||||
return params.NewKeyTable().RegisterParamSet(&Params{})
|
||||
}
|
||||
|
||||
// Params params for pricefeed. Can be altered via governance
|
||||
type Params struct {
|
||||
Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed
|
||||
}
|
||||
|
||||
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||
// pairs of pricefeed module's parameters.
|
||||
func (p Params) ParamSetPairs() params.ParamSetPairs {
|
||||
return params.ParamSetPairs{
|
||||
{Key: KeyMarkets, Value: &p.Markets},
|
||||
}
|
||||
}
|
||||
|
||||
// NewParams creates a new AssetParams object
|
||||
func NewParams(markets Markets) Params {
|
||||
return Params{
|
||||
@ -40,24 +27,31 @@ func NewParams(markets Markets) Params {
|
||||
|
||||
// DefaultParams default params for pricefeed
|
||||
func DefaultParams() Params {
|
||||
return NewParams(Markets{})
|
||||
return NewParams(DefaultMarkets)
|
||||
}
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
func ParamKeyTable() params.KeyTable {
|
||||
return params.NewKeyTable().RegisterParamSet(&Params{})
|
||||
}
|
||||
|
||||
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
|
||||
// pairs of pricefeed module's parameters.
|
||||
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||
return params.ParamSetPairs{
|
||||
{Key: KeyMarkets, Value: &p.Markets},
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.stringer
|
||||
func (p Params) String() string {
|
||||
out := "Params:\n"
|
||||
for _, a := range p.Markets {
|
||||
out += a.String()
|
||||
out += fmt.Sprintf("%s\n", a.String())
|
||||
}
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// ParamSubspace defines the expected Subspace interface for parameters
|
||||
type ParamSubspace interface {
|
||||
Get(ctx sdk.Context, key []byte, ptr interface{})
|
||||
Set(ctx sdk.Context, key []byte, param interface{})
|
||||
}
|
||||
|
||||
// Validate ensure that params have valid values
|
||||
func (p Params) Validate() error {
|
||||
// iterate over assets and verify them
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user