Skip to content

Commit

Permalink
[R4R] Add flags to auction queries (#522)
Browse files Browse the repository at this point in the history
* cli auction query flags

* update bep3 filter method name

* rest auction query flags

* add constants for auction type, phase

* fix test

* revisions
  • Loading branch information
denalimarsh authored May 25, 2020
1 parent e7ceada commit b2edeb8
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 41 deletions.
102 changes: 92 additions & 10 deletions x/auction/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ package cli
import (
"fmt"
"strconv"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/kava-labs/kava/x/auction/types"
)

// Query auction flags
const (
flagType = "type"
flagDenom = "denom"
flagPhase = "phase"
)

// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
// Group nameservice queries under a subcommand
Expand Down Expand Up @@ -52,7 +62,7 @@ func QueryGetAuctionCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
}

// Query
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuction), bz)
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuction), bz)
if err != nil {
return err
}
Expand All @@ -61,37 +71,108 @@ func QueryGetAuctionCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
var auction types.Auction
cdc.MustUnmarshalJSON(res, &auction)
auctionWithPhase := types.NewAuctionWithPhase(auction)

cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(auctionWithPhase)
},
}
}

// QueryGetAuctionsCmd queries the auctions in the store
func QueryGetAuctionsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "auctions",
Short: "get a list of active auctions",
Args: cobra.NoArgs,
Short: "query auctions with optional filters",
Long: strings.TrimSpace(`Query for all paginated auctions that match optional filters:
Example:
$ kvcli q auction auctions --type=(collateral|surplus|debt)
$ kvcli q auction auctions --denom=bnb
$ kvcli q auction auctions --phase=(forward|reverse)
$ kvcli q auction auctions --page=2 --limit=100
`,
),
RunE: func(cmd *cobra.Command, args []string) error {
strType := viper.GetString(flagType)
strDenom := viper.GetString(flagDenom)
strPhase := viper.GetString(flagPhase)
page := viper.GetInt(flags.FlagPage)
limit := viper.GetInt(flags.FlagLimit)

var (
auctionType string
auctionDenom string
auctionPhase string
)

params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase)

if len(strType) != 0 {
auctionType = strings.ToLower(strings.TrimSpace(strType))
if auctionType != types.CollateralAuctionType &&
auctionType != types.SurplusAuctionType &&
auctionType != types.DebtAuctionType {
return fmt.Errorf("invalid auction type %s", strType)
}
params.Type = auctionType
}

if len(strDenom) != 0 {
auctionDenom := strings.TrimSpace(strDenom)
err := sdk.ValidateDenom(auctionDenom)
if err != nil {
return err
}
params.Denom = auctionDenom
}

if len(strPhase) != 0 {
auctionPhase := strings.ToLower(strings.TrimSpace(strPhase))
if auctionType != types.CollateralAuctionType && len(auctionType) > 0 {
return fmt.Errorf("cannot apply phase flag to non-collateral auction type")
}
if auctionPhase != types.ForwardAuctionPhase && auctionPhase != types.ReverseAuctionPhase {
return fmt.Errorf("invalid auction phase %s", strPhase)
}
params.Phase = auctionPhase
}

bz, err := cdc.MarshalJSON(params)
if err != nil {
return err
}

cliCtx := context.NewCLIContext().WithCodec(cdc)

// Query
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuctions), nil)
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuctions), bz)
if err != nil {
return err
}

// Decode and print results
var auctions types.Auctions
cdc.MustUnmarshalJSON(res, &auctions)
var matchingAuctions types.Auctions
cdc.MustUnmarshalJSON(res, &matchingAuctions)

if len(matchingAuctions) == 0 {
return fmt.Errorf("No matching auctions found")
}

auctionsWithPhase := []types.AuctionWithPhase{} // using empty slice so json returns [] instead of null when there's no auctions
for _, a := range auctions {
for _, a := range matchingAuctions {
auctionsWithPhase = append(auctionsWithPhase, types.NewAuctionWithPhase(a))
}
return cliCtx.PrintOutput(auctionsWithPhase)
cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(auctionsWithPhase) // nolint:errcheck
},
}

cmd.Flags().Int(flags.FlagPage, 1, "pagination page of auctions to to query for")
cmd.Flags().Int(flags.FlagLimit, 100, "pagination limit of auctions to query for")
cmd.Flags().String(flagType, "", "(optional) filter by auction type, type: collateral, debt, surplus")
cmd.Flags().String(flagDenom, "", "(optional) filter by auction denom")
cmd.Flags().String(flagPhase, "", "(optional) filter by collateral auction phase, phase: forward/reverse")

return cmd
}

// QueryParamsCmd queries the auction module parameters
Expand All @@ -106,14 +187,15 @@ func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {

// Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetParams)
res, _, err := cliCtx.QueryWithData(route, nil)
res, height, err := cliCtx.QueryWithData(route, nil)
if err != nil {
return err
}

// Decode and print results
var out types.Params
cdc.MustUnmarshalJSON(res, &out)
cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(out)
},
}
Expand Down
58 changes: 54 additions & 4 deletions x/auction/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package rest
import (
"fmt"
"net/http"
"strings"

"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/kava-labs/kava/x/auction/types"
Expand Down Expand Up @@ -68,22 +70,70 @@ func queryAuctionHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {

func queryAuctionsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

// Parse the query height
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}

// Get all auctions
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetAuctions), nil)
var auctionType string
var auctionDenom string
var auctionPhase string

if x := r.URL.Query().Get(RestType); len(x) != 0 {
auctionType = strings.ToLower(strings.TrimSpace(x))
if auctionType != types.CollateralAuctionType &&
auctionType != types.SurplusAuctionType &&
auctionType != types.DebtAuctionType {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("invalid auction type %s", x))
return
}
}

if x := r.URL.Query().Get(RestDenom); len(x) != 0 {
auctionDenom = strings.TrimSpace(x)
err := sdk.ValidateDenom(auctionDenom)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
}

if x := r.URL.Query().Get(RestPhase); len(x) != 0 {
auctionPhase = strings.ToLower(strings.TrimSpace(x))
if auctionType != types.CollateralAuctionType && len(auctionType) > 0 {
rest.WriteErrorResponse(w, http.StatusBadRequest, "cannot apply phase flag to non-collateral auction type")
return
}
if auctionPhase != types.ForwardAuctionPhase && auctionPhase != types.ReverseAuctionPhase {
rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("invalid auction phase %s", x))
return
}
}

params := types.NewQueryAllAuctionParams(page, limit, auctionType, auctionDenom, auctionPhase)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetAuctions)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

// Decode and return results
cliCtx = cliCtx.WithHeight(height)

// Unmarshal to Auction and remarshal as AuctionWithPhase
var auctions types.Auctions
err = cliCtx.Codec.UnmarshalJSON(res, &auctions)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions x/auction/client/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,26 @@ import (
"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"
)

// REST Variable names
// nolint
const (
RestType = "type"
RestDenom = "denom"
RestPhase = "phase"
)

// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
registerQueryRoutes(cliCtx, r)
registerTxRoutes(cliCtx, r)
}

// placeBidReq defines the properties of a bid request's body
type placeBidReq struct {
BaseReq rest.BaseReq `json:"base_req"`
Amount sdk.Coin `json:"amount"`
}
6 changes: 0 additions & 6 deletions x/auction/client/rest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,8 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/auctions/{%s}/bids", types.ModuleName, restAuctionID), bidHandlerFn(cliCtx)).Methods("POST")
}

type placeBidReq struct {
BaseReq rest.BaseReq `json:"base_req"`
Amount sdk.Coin `json:"amount"`
}

func bidHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

// Get auction ID from url
auctionID, ok := rest.ParseUint64OrReturnBadRequest(w, mux.Vars(r)[restAuctionID])
if !ok {
Expand Down
9 changes: 9 additions & 0 deletions x/auction/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,12 @@ func (k Keeper) IterateAuctions(ctx sdk.Context, cb func(auction types.Auction)
}
}
}

// GetAllAuctions returns all auctions from the store
func (k Keeper) GetAllAuctions(ctx sdk.Context) (auctions types.Auctions) {
k.IterateAuctions(ctx, func(auction types.Auction) bool {
auctions = append(auctions, auction)
return false
})
return
}
61 changes: 52 additions & 9 deletions x/auction/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
abci "github.com/tendermint/tendermint/abci/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -50,15 +51,19 @@ func queryAuction(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte
}

func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Get all auctions
auctionsList := types.Auctions{}
keeper.IterateAuctions(ctx, func(a types.Auction) bool {
auctionsList = append(auctionsList, a)
return false
})

// Encode Results
bz, err := codec.MarshalJSONIndent(keeper.cdc, auctionsList)
var params types.QueryAllAuctionParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}

unfilteredAuctions := keeper.GetAllAuctions(ctx)
auctions := filterAuctions(ctx, unfilteredAuctions, params)
if auctions == nil {
auctions = types.Auctions{}
}

bz, err := codec.MarshalJSONIndent(keeper.cdc, auctions)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
Expand All @@ -79,3 +84,41 @@ func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]by

return bz, nil
}

// filterAuctions retrieves auctions filtered by a given set of params.
// If no filters are provided, all auctions will be returned in paginated form.
func filterAuctions(ctx sdk.Context, auctions types.Auctions, params types.QueryAllAuctionParams) types.Auctions {
filteredAuctions := make(types.Auctions, 0, len(auctions))

for _, auc := range auctions {
matchType, matchDenom, matchPhase := true, true, true

// match auction type (if supplied)
if len(params.Type) > 0 {
matchType = auc.GetType() == params.Type
}

// match auction denom (if supplied)
if len(params.Denom) > 0 {
matchDenom = auc.GetBid().Denom == params.Denom || auc.GetLot().Denom == params.Denom
}

// match auction phase (if supplied)
if len(params.Phase) > 0 {
matchPhase = auc.GetPhase() == params.Phase
}

if matchType && matchDenom && matchPhase {
filteredAuctions = append(filteredAuctions, auc)
}
}

start, end := client.Paginate(len(filteredAuctions), params.Page, params.Limit, 100)
if start < 0 || end < 0 {
filteredAuctions = types.Auctions{}
} else {
filteredAuctions = filteredAuctions[start:end]
}

return filteredAuctions
}
Loading

0 comments on commit b2edeb8

Please sign in to comment.