supply querier (#4592)

Add querier to the supply module.

Closes: #4082
This commit is contained in:
Federico Kunze 2019-07-01 13:32:04 +02:00 committed by Alessio Treglia
parent a485b9a263
commit 48e8161f25
9 changed files with 464 additions and 4 deletions

View File

@ -0,0 +1 @@
#4082 supply module queriers for CLI and REST endpoints

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,9 @@ tags:
description: Slashing module APIs
- name: Distribution
description: Fee distribution module APIs
- name: Supply
description: Supply module APIs
- name: version
- name: Mint
description: Minting module APIs
- name: Misc
@ -1827,6 +1830,43 @@ paths:
type: string
500:
description: Internal Server Error
/supply/total:
get:
summary: Total supply of coins in the chain
tags:
- Supply
produces:
- application/json
responses:
200:
description: OK
schema:
$ref: "#/definitions/Supply"
500:
description: Internal Server Error
/supply/total/{denomination}:
parameters:
- in: path
name: denomination
description: Coin denomination
required: true
type: string
x-example: uatom
get:
summary: Total supply of a single coin denomination
tags:
- Supply
produces:
- application/json
responses:
200:
description: OK
schema:
type: integer
400:
description: Invalid coin denomination
500:
description: Internal Server Error
definitions:
CheckTxResult:
type: object
@ -2435,3 +2475,10 @@ definitions:
example: ""
value:
type: object
Supply:
type: object
properties:
total:
type: array
items:
$ref: "#/definitions/Coin"

View File

@ -0,0 +1,104 @@
package cli
import (
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/supply/types"
"github.com/spf13/cobra"
)
// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(cdc *codec.Codec) *cobra.Command {
// Group supply queries under a subcommand
supplyQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the supply module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
supplyQueryCmd.AddCommand(client.GetCommands(
GetCmdQueryTotalSupply(cdc),
)...)
return supplyQueryCmd
}
// GetCmdQueryTotalSupply implements the query total supply command.
func GetCmdQueryTotalSupply(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "total [denom]",
Args: cobra.MaximumNArgs(1),
Short: "Query the total supply of coins of the chain",
Long: strings.TrimSpace(
fmt.Sprintf(`Query total supply of coins that are held by accounts in the
chain.
Example:
$ %s query %s total
To query for the total supply of a specific coin denomination use:
$ %s query %s total stake
`,
version.ClientName, types.ModuleName, version.ClientName, types.ModuleName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
if len(args) == 0 {
return queryTotalSupply(cliCtx, cdc)
}
return querySupplyOf(cliCtx, cdc, args[0])
},
}
}
func queryTotalSupply(cliCtx context.CLIContext, cdc *codec.Codec) error {
params := types.NewQueryTotalSupplyParams(1, 0) // no pagination
bz, err := cdc.MarshalJSON(params)
if err != nil {
return err
}
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTotalSupply), bz)
if err != nil {
return err
}
var totalSupply sdk.Coins
err = cdc.UnmarshalJSON(res, &totalSupply)
if err != nil {
return err
}
return cliCtx.PrintOutput(totalSupply)
}
func querySupplyOf(cliCtx context.CLIContext, cdc *codec.Codec, denom string) error {
params := types.NewQuerySupplyOfParams(denom)
bz, err := cdc.MarshalJSON(params)
if err != nil {
return err
}
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySupplyOf), bz)
if err != nil {
return err
}
var supply sdk.Int
err = cdc.UnmarshalJSON(res, &supply)
if err != nil {
return err
}
return cliCtx.PrintOutput(supply)
}

View File

@ -0,0 +1,90 @@
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/cosmos/cosmos-sdk/x/supply/types"
)
// RegisterRoutes registers staking-related REST handlers to a router
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
registerQueryRoutes(cliCtx, r)
}
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
// Query the total supply of coins
r.HandleFunc(
"/supply/total/",
totalSupplyHandlerFn(cliCtx),
).Methods("GET")
// Query the supply of a single denom
r.HandleFunc(
"/supply/total/{denom}",
supplyOfHandlerFn(cliCtx),
).Methods("GET")
}
// HTTP request handler to query the total supply of coins
func totalSupplyHandlerFn(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
}
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
params := types.NewQueryTotalSupplyParams(page, limit)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTotalSupply), bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}
// HTTP request handler to query the supply of a single denom
func supplyOfHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
denom := mux.Vars(r)["denom"]
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
params := types.NewQuerySupplyOfParams(denom)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySupplyOf), bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}

View File

@ -0,0 +1,78 @@
package keeper
import (
"fmt"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/supply/types"
)
// NewQuerier creates a querier for supply REST endpoints
func NewQuerier(k Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] {
case types.QueryTotalSupply:
return queryTotalSupply(ctx, req, k)
case types.QuerySupplyOf:
return querySupplyOf(ctx, req, k)
default:
return nil, sdk.ErrUnknownRequest("unknown supply query endpoint")
}
}
}
func queryTotalSupply(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) {
var params types.QueryTotalSupplyParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err))
}
totalSupply := k.GetSupply(ctx).Total
totalSupplyLen := len(totalSupply)
if params.Limit == 0 {
params.Limit = totalSupplyLen
}
start := (params.Page - 1) * params.Limit
end := params.Limit + start
if end >= totalSupplyLen {
end = totalSupplyLen
}
if start >= totalSupplyLen {
// page is out of bounds
totalSupply = sdk.Coins{}
} else {
totalSupply = totalSupply[start:end]
}
res, err := totalSupply.MarshalJSON()
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error()))
}
return res, nil
}
func querySupplyOf(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) {
var params types.QuerySupplyOfParams
err := types.ModuleCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err))
}
supply := k.GetSupply(ctx).Total.AmountOf(params.Denom)
res, err := supply.MarshalJSON()
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error()))
}
return res, nil
}

View File

@ -0,0 +1,105 @@
package keeper
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/supply/types"
)
func TestNewQuerier(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 1000, 2)
supplyCoins := sdk.NewCoins(
sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
sdk.NewCoin("photon", sdk.NewInt(50)),
sdk.NewCoin("atom", sdk.NewInt(2000)),
sdk.NewCoin("btc", sdk.NewInt(21000000)),
)
keeper.SetSupply(ctx, types.NewSupply(supplyCoins))
query := abci.RequestQuery{
Path: "",
Data: []byte{},
}
querier := NewQuerier(keeper)
bz, err := querier(ctx, []string{"other"}, query)
require.Error(t, err)
require.Nil(t, bz)
queryTotalSupplyParams := types.NewQueryTotalSupplyParams(1, 20)
bz, errRes := keeper.cdc.MarshalJSON(queryTotalSupplyParams)
require.Nil(t, errRes)
query.Path = fmt.Sprintf("/custom/supply/%s", types.QueryTotalSupply)
query.Data = bz
_, err = querier(ctx, []string{types.QueryTotalSupply}, query)
require.Nil(t, err)
querySupplyParams := types.NewQuerySupplyOfParams(sdk.DefaultBondDenom)
bz, errRes = keeper.cdc.MarshalJSON(querySupplyParams)
require.Nil(t, errRes)
query.Path = fmt.Sprintf("/custom/supply/%s", types.QuerySupplyOf)
query.Data = bz
_, err = querier(ctx, []string{types.QuerySupplyOf}, query)
require.Nil(t, err)
}
func TestQuerySupply(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, 1000, 2)
supplyCoins := sdk.NewCoins(
sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)),
sdk.NewCoin("photon", sdk.NewInt(50)),
sdk.NewCoin("atom", sdk.NewInt(2000)),
sdk.NewCoin("btc", sdk.NewInt(21000000)),
)
keeper.SetSupply(ctx, types.NewSupply(supplyCoins))
queryTotalSupplyParams := types.NewQueryTotalSupplyParams(1, 10)
bz, errRes := keeper.cdc.MarshalJSON(queryTotalSupplyParams)
require.Nil(t, errRes)
query := abci.RequestQuery{
Path: "",
Data: []byte{},
}
query.Path = fmt.Sprintf("/custom/supply/%s", types.QueryTotalSupply)
query.Data = bz
res, err := queryTotalSupply(ctx, query, keeper)
require.Nil(t, err)
var totalCoins sdk.Coins
errRes = keeper.cdc.UnmarshalJSON(res, &totalCoins)
require.Nil(t, errRes)
require.Equal(t, supplyCoins, totalCoins)
querySupplyParams := types.NewQuerySupplyOfParams(sdk.DefaultBondDenom)
bz, errRes = keeper.cdc.MarshalJSON(querySupplyParams)
require.Nil(t, errRes)
query.Path = fmt.Sprintf("/custom/supply/%s", types.QuerySupplyOf)
query.Data = bz
res, err = querySupplyOf(ctx, query, keeper)
require.Nil(t, err)
var supply sdk.Int
errRes = supply.UnmarshalJSON(res)
require.Nil(t, errRes)
require.True(sdk.IntEq(t, sdk.NewInt(100), supply))
}

View File

@ -12,6 +12,8 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/supply/client/cli"
"github.com/cosmos/cosmos-sdk/x/supply/client/rest"
"github.com/cosmos/cosmos-sdk/x/supply/types"
)
@ -49,15 +51,16 @@ func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
}
// register rest routes
func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
rest.RegisterRoutes(ctx, rtr)
}
// get the root tx command of this module
func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil }
// get the root query command of this module
func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command {
return nil
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
return cli.GetQueryCmd(cdc)
}
// app module

32
x/supply/types/querier.go Normal file
View File

@ -0,0 +1,32 @@
package types
// query endpoints supported by the supply Querier
const (
QueryTotalSupply = "total_supply"
QuerySupplyOf = "supply_of"
)
// QueryTotalSupply defines the params for the following queries:
//
// - 'custom/supply/totalSupply'
type QueryTotalSupplyParams struct {
Page, Limit int
}
// NewQueryTotalSupplyParams creates a new instance to query the total supply
func NewQueryTotalSupplyParams(page, limit int) QueryTotalSupplyParams {
return QueryTotalSupplyParams{page, limit}
}
// QuerySupplyOfParams defines the params for the following queries:
//
// - 'custom/supply/totalSupplyOf'
type QuerySupplyOfParams struct {
Denom string
}
// NewQuerySupplyOfParams creates a new instance to query the total supply
// of a given denomination
func NewQuerySupplyOfParams(denom string) QuerySupplyOfParams {
return QuerySupplyOfParams{denom}
}