parent
a485b9a263
commit
48e8161f25
1
.pending/improvements/sdk/4595-supply-queriers
Normal file
1
.pending/improvements/sdk/4595-supply-queriers
Normal file
@ -0,0 +1 @@
|
||||
#4082 supply module queriers for CLI and REST endpoints
|
||||
File diff suppressed because one or more lines are too long
47
client/lcd/swagger-ui/swagger.yaml
vendored
47
client/lcd/swagger-ui/swagger.yaml
vendored
@ -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"
|
||||
104
x/supply/client/cli/query.go
Normal file
104
x/supply/client/cli/query.go
Normal 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)
|
||||
}
|
||||
90
x/supply/client/rest/query.go
Normal file
90
x/supply/client/rest/query.go
Normal 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)
|
||||
}
|
||||
}
|
||||
78
x/supply/keeper/querier.go
Normal file
78
x/supply/keeper/querier.go
Normal 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, ¶ms)
|
||||
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, ¶ms)
|
||||
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
|
||||
}
|
||||
105
x/supply/keeper/querier_test.go
Normal file
105
x/supply/keeper/querier_test.go
Normal 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))
|
||||
|
||||
}
|
||||
@ -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
32
x/supply/types/querier.go
Normal 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}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user