x/bank: client denom metadata gRPC (#8317)

* deps: bump tendermint to v0.34.2

* x/bank: denom metadata gRPC

* cli: add cmd and tests

* gRPC test'

* changelog

* fix test

* Apply suggestions from code review

Co-authored-by: Robert Zaremba <robert@zaremba.ch>
Co-authored-by: Aditya <adityasripal@gmail.com>

* fix panic

* use cmd.Context()

* update tests

* Update x/bank/types/errors.go

Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>

* test fix

Co-authored-by: Robert Zaremba <robert@zaremba.ch>
Co-authored-by: Aditya <adityasripal@gmail.com>
Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Federico Kunze 2021-01-18 15:15:16 -03:00 committed by Sunny Aggarwal
parent 21575b01aa
commit 48006b435d
17 changed files with 1800 additions and 70 deletions

View File

@ -38,15 +38,16 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements
* (x/bank) [\#8302](https://github.com/cosmos/cosmos-sdk/issues/8302) Add gRPC and CLI queries for client denomination metadata.
* (tendermint) [\#8316](https://github.com/cosmos/cosmos-sdk/pull/8316) Bump Tendermint version to [v0.34.2](https://github.com/tendermint/tendermint/releases/tag/v0.34.2)
### Bug Fixes
* (x/bank) [\#8317](https://github.com/cosmos/cosmos-sdk/pull/8317) Fix panic when querying for a not found client denomination metadata.
* (x/auth) [\#8287](https://github.com/cosmos/cosmos-sdk/pull/8287) Fix `tx sign --signature-only` to return correct sequence value in signature.
* (x/ibc) [\#8341](https://github.com/cosmos/cosmos-sdk/pull/8341) Fix query latest consensus state.
* (types/errors) [\#8355][https://github.com/cosmos/cosmos-sdk/pull/8355] Fix errorWrap `Is` method.
## [v0.40.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0) - 2021-01-08
v0.40.0, known as the Stargate release of the Cosmos SDK, is one of the largest releases

View File

@ -34,7 +34,7 @@ UX and remove the requirement for making any assumptions on the unit of denomina
The `x/bank` module will be updated to store and index metadata by `denom`, specifically the "base" or
smallest unit -- the unit the Cosmos SDK state-machine works with.
Metadata may also include a non-zero length list of denominations. Each entry containts the name of
Metadata may also include a non-zero length list of denominations. Each entry contains the name of
the denomination `denom`, the exponent to the base and a list of aliases. An entry is to be
interpreted as `1 denom = 10^exponent base_denom` (e.g. `1 ETH = 10^18 wei` and `1 uatom = 10^0 uatom`).
@ -92,6 +92,7 @@ As an example, the ATOM's metadata can be defined as follows:
```
Given the above metadata, a client may infer the following things:
- 4.3atom = 4.3 * (10^6) = 4,300,000uatom
- The string "atom" can be used as a display name in a list of tokens.
- The balance 4300000 can be displayed as 4,300,000uatom or 4,300matom or 4.3atom.

View File

@ -48,6 +48,10 @@
- [QueryAllBalancesResponse](#cosmos.bank.v1beta1.QueryAllBalancesResponse)
- [QueryBalanceRequest](#cosmos.bank.v1beta1.QueryBalanceRequest)
- [QueryBalanceResponse](#cosmos.bank.v1beta1.QueryBalanceResponse)
- [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest)
- [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse)
- [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest)
- [QueryDenomsMetadataResponse](#cosmos.bank.v1beta1.QueryDenomsMetadataResponse)
- [QueryParamsRequest](#cosmos.bank.v1beta1.QueryParamsRequest)
- [QueryParamsResponse](#cosmos.bank.v1beta1.QueryParamsResponse)
- [QuerySupplyOfRequest](#cosmos.bank.v1beta1.QuerySupplyOfRequest)
@ -1274,6 +1278,69 @@ QueryBalanceResponse is the response type for the Query/Balance RPC method.
<a name="cosmos.bank.v1beta1.QueryDenomMetadataRequest"></a>
### QueryDenomMetadataRequest
QueryDenomMetadataRequest is the request type for the Query/DenomMetadata RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `denom` | [string](#string) | | denom is the coin denom to query the metadata for. |
<a name="cosmos.bank.v1beta1.QueryDenomMetadataResponse"></a>
### QueryDenomMetadataResponse
QueryDenomMetadataResponse is the response type for the Query/DenomMetadata RPC
method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `metadata` | [Metadata](#cosmos.bank.v1beta1.Metadata) | | metadata describes and provides all the client information for the requested token. |
<a name="cosmos.bank.v1beta1.QueryDenomsMetadataRequest"></a>
### QueryDenomsMetadataRequest
QueryDenomsMetadataRequest is the request type for the Query/DenomsMetadata RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination defines an optional pagination for the request. |
<a name="cosmos.bank.v1beta1.QueryDenomsMetadataResponse"></a>
### QueryDenomsMetadataResponse
QueryDenomsMetadataResponse is the response type for the Query/DenomsMetadata RPC
method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `metadatas` | [Metadata](#cosmos.bank.v1beta1.Metadata) | repeated | metadata provides the client information for all the registered tokens. |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination defines the pagination in the response. |
<a name="cosmos.bank.v1beta1.QueryParamsRequest"></a>
### QueryParamsRequest
@ -1374,6 +1441,8 @@ Query defines the gRPC querier service.
| `TotalSupply` | [QueryTotalSupplyRequest](#cosmos.bank.v1beta1.QueryTotalSupplyRequest) | [QueryTotalSupplyResponse](#cosmos.bank.v1beta1.QueryTotalSupplyResponse) | TotalSupply queries the total supply of all coins. | GET|/cosmos/bank/v1beta1/supply|
| `SupplyOf` | [QuerySupplyOfRequest](#cosmos.bank.v1beta1.QuerySupplyOfRequest) | [QuerySupplyOfResponse](#cosmos.bank.v1beta1.QuerySupplyOfResponse) | SupplyOf queries the supply of a single coin. | GET|/cosmos/bank/v1beta1/supply/{denom}|
| `Params` | [QueryParamsRequest](#cosmos.bank.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#cosmos.bank.v1beta1.QueryParamsResponse) | Params queries the parameters of x/bank module. | GET|/cosmos/bank/v1beta1/params|
| `DenomMetadata` | [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest) | [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse) | DenomsMetadata queries the client metadata of a given coin denomination. | GET|/cosmos/bank/v1beta1/denoms_metadata/{denom}|
| `DenomsMetadata` | [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest) | [QueryDenomsMetadataResponse](#cosmos.bank.v1beta1.QueryDenomsMetadataResponse) | DenomsMetadata queries the client metadata for all registered coin denominations. | GET|/cosmos/bank/v1beta1/denoms_metadata|
<!-- end services -->

4
go.sum
View File

@ -544,7 +544,6 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -562,7 +561,6 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4=
github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX6mjJTgbLHTwi17VDVg=
github.com/tendermint/tendermint v0.34.0 h1:eXCfMgoqVSzrjzOj6clI9GAejcHH0LvOlRjpCmMJksU=
github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ=
github.com/tendermint/tendermint v0.34.2 h1:bB4xReGw4jalTDeNg0npYoONuZrD55F90LrWPF4m/PQ=
github.com/tendermint/tendermint v0.34.2/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ=
@ -618,7 +616,6 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
@ -797,7 +794,6 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4 h1:Rt0FRalMgdSlXAVJvX4pr65KfqaxHXSLkSJRD9pw6g0=
google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f h1:izedQ6yVIc5mZsRuXzmSreCOlzI0lCU1HpG8yEdMiKw=
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=

View File

@ -35,6 +35,16 @@ service Query {
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/params";
}
// DenomsMetadata queries the client metadata of a given coin denomination.
rpc DenomMetadata(QueryDenomMetadataRequest) returns (QueryDenomMetadataResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata/{denom}";
}
// DenomsMetadata queries the client metadata for all registered coin denominations.
rpc DenomsMetadata(QueryDenomsMetadataRequest) returns (QueryDenomsMetadataResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata";
}
}
// QueryBalanceRequest is the request type for the Query/Balance RPC method.
@ -109,3 +119,32 @@ message QueryParamsRequest {}
message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}
// QueryDenomsMetadataRequest is the request type for the Query/DenomsMetadata RPC method.
message QueryDenomsMetadataRequest {
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}
// QueryDenomsMetadataResponse is the response type for the Query/DenomsMetadata RPC
// method.
message QueryDenomsMetadataResponse {
// metadata provides the client information for all the registered tokens.
repeated Metadata metadatas = 1 [(gogoproto.nullable) = false];
// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
// QueryDenomMetadataRequest is the request type for the Query/DenomMetadata RPC method.
message QueryDenomMetadataRequest {
// denom is the coin denom to query the metadata for.
string denom = 1;
}
// QueryDenomMetadataResponse is the response type for the Query/DenomMetadata RPC
// method.
message QueryDenomMetadataResponse {
// metadata describes and provides all the client information for the requested token.
Metadata metadata = 1 [(gogoproto.nullable) = false];
}

View File

@ -32,12 +32,57 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
cfg := network.DefaultConfig()
genesisState := cfg.GenesisState
cfg.NumValidators = 1
var bankGenesis types.GenesisState
s.Require().NoError(cfg.Codec.UnmarshalJSON(genesisState[types.ModuleName], &bankGenesis))
bankGenesis.DenomMetadata = []types.Metadata{
{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
},
{
Description: "Ethereum mainnet token",
DenomUnits: []*types.DenomUnit{
{
Denom: "wei",
Exponent: 0,
},
{
Denom: "eth",
Exponent: 6,
Aliases: []string{"ETH"},
},
},
Base: "wei",
Display: "eth",
},
}
bankGenesisBz, err := cfg.Codec.MarshalJSON(&bankGenesis)
s.Require().NoError(err)
genesisState[types.ModuleName] = bankGenesisBz
cfg.GenesisState = genesisState
s.cfg = cfg
s.network = network.New(s.T(), cfg)
_, err := s.network.WaitForHeight(1)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
}
@ -181,6 +226,127 @@ func (s *IntegrationTestSuite) TestGetCmdQueryTotalSupply() {
}
}
func (s *IntegrationTestSuite) TestGetCmdQueryDenomsMetadata() {
val := s.network.Validators[0]
testCases := []struct {
name string
args []string
expectErr bool
respType proto.Message
expected proto.Message
}{
{
name: "all denoms client metadata",
args: []string{
fmt.Sprintf("--%s=1", flags.FlagHeight),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
respType: &types.QueryDenomsMetadataResponse{},
expected: &types.QueryDenomsMetadataResponse{
Metadatas: []types.Metadata{
{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
},
{
Description: "Ethereum mainnet token",
DenomUnits: []*types.DenomUnit{
{
Denom: "wei",
Exponent: 0,
Aliases: []string{},
},
{
Denom: "eth",
Exponent: 6,
Aliases: []string{"ETH"},
},
},
Base: "wei",
Display: "eth",
},
},
Pagination: &query.PageResponse{Total: 2},
},
},
{
name: "client metadata of a specific denomination",
args: []string{
fmt.Sprintf("--%s=1", flags.FlagHeight),
fmt.Sprintf("--%s=%s", cli.FlagDenom, "uatom"),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
respType: &types.QueryDenomMetadataResponse{},
expected: &types.QueryDenomMetadataResponse{
Metadata: types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
},
},
},
{
name: "client metadata of a bogus denom",
args: []string{
fmt.Sprintf("--%s=1", flags.FlagHeight),
fmt.Sprintf("--%s=foobar", cli.FlagDenom),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
expectErr: true,
respType: &types.QueryDenomMetadataResponse{},
expected: &types.QueryDenomMetadataResponse{
Metadata: types.Metadata{
DenomUnits: []*types.DenomUnit{},
},
},
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := cli.GetCmdDenomsMetadata()
clientCtx := val.ClientCtx
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType))
s.Require().Equal(tc.expected, tc.respType)
}
})
}
}
func (s *IntegrationTestSuite) TestNewSendTxCmdGenOnly() {
val := s.network.Validators[0]

View File

@ -1,7 +1,6 @@
package cli
import (
"context"
"fmt"
"strings"
@ -33,6 +32,7 @@ func GetQueryCmd() *cobra.Command {
cmd.AddCommand(
GetBalancesCmd(),
GetCmdQueryTotalSupply(),
GetCmdDenomsMetadata(),
)
return cmd
@ -78,7 +78,7 @@ Example:
if denom == "" {
params := types.NewQueryAllBalancesRequest(addr, pageReq)
res, err := queryClient.AllBalances(context.Background(), params)
res, err := queryClient.AllBalances(cmd.Context(), params)
if err != nil {
return err
}
@ -86,7 +86,7 @@ Example:
}
params := types.NewQueryBalanceRequest(addr, denom)
res, err := queryClient.Balance(context.Background(), params)
res, err := queryClient.Balance(cmd.Context(), params)
if err != nil {
return err
}
@ -102,6 +102,60 @@ Example:
return cmd
}
// GetCmdDenomsMetadata defines the cobra command to query client denomination metadata.
func GetCmdDenomsMetadata() *cobra.Command {
cmd := &cobra.Command{
Use: "denom-metadata",
Short: "Query the client metadata for coin denominations",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the client metadata for all the registered coin denominations
Example:
To query for the client metadata of all coin denominations use:
$ %s query %s denom-metadata
To query for the client metadata of a specific coin denomination use:
$ %s query %s denom-metadata --denom=[denom]
`,
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
denom, err := cmd.Flags().GetString(FlagDenom)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)
if denom == "" {
res, err := queryClient.DenomsMetadata(cmd.Context(), &types.QueryDenomsMetadataRequest{})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
}
res, err := queryClient.DenomMetadata(cmd.Context(), &types.QueryDenomMetadataRequest{Denom: denom})
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
cmd.Flags().String(FlagDenom, "", "The specific denomination to query client metadata for")
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
func GetCmdQueryTotalSupply() *cobra.Command {
cmd := &cobra.Command{
Use: "total",
@ -131,7 +185,7 @@ To query for the total supply of a specific coin denomination use:
queryClient := types.NewQueryClient(clientCtx)
if denom == "" {
res, err := queryClient.TotalSupply(context.Background(), &types.QueryTotalSupplyRequest{})
res, err := queryClient.TotalSupply(cmd.Context(), &types.QueryTotalSupplyRequest{})
if err != nil {
return err
}
@ -139,7 +193,7 @@ To query for the total supply of a specific coin denomination use:
return clientCtx.PrintProto(res)
}
res, err := queryClient.SupplyOf(context.Background(), &types.QuerySupplyOfRequest{Denom: denom})
res, err := queryClient.SupplyOf(cmd.Context(), &types.QuerySupplyOfRequest{Denom: denom})
if err != nil {
return err
}

View File

@ -98,6 +98,109 @@ func (s *IntegrationTestSuite) TestTotalSupplyGRPCHandler() {
}
}
func (s *IntegrationTestSuite) TestDenomMetadataGRPCHandler() {
val := s.network.Validators[0]
baseURL := val.APIAddress
testCases := []struct {
name string
url string
headers map[string]string
expErr bool
respType proto.Message
expected proto.Message
}{
{
"test GRPC client metadata",
fmt.Sprintf("%s/cosmos/bank/v1beta1/denoms_metadata", baseURL),
map[string]string{
grpctypes.GRPCBlockHeightHeader: "1",
},
false,
&types.QueryDenomsMetadataResponse{},
&types.QueryDenomsMetadataResponse{
Metadatas: []types.Metadata{
{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
},
},
Pagination: &query.PageResponse{Total: 1},
},
},
{
"GRPC client metadata of a specific denom",
fmt.Sprintf("%s/cosmos/bank/v1beta1/denoms_metadata/uatom", baseURL),
map[string]string{
grpctypes.GRPCBlockHeightHeader: "1",
},
false,
&types.QueryDenomMetadataResponse{},
&types.QueryDenomMetadataResponse{
Metadata: types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
},
},
},
{
"GRPC client metadata of a bogus denom",
fmt.Sprintf("%s/cosmos/bank/v1beta1/denoms_metadata/foobar", baseURL),
map[string]string{
grpctypes.GRPCBlockHeightHeader: "1",
},
true,
&types.QueryDenomMetadataResponse{},
&types.QueryDenomMetadataResponse{
Metadata: types.Metadata{
DenomUnits: []*types.DenomUnit{},
},
},
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers)
s.Require().NoError(err)
if tc.expErr {
s.Require().Error(val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, tc.respType))
} else {
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, tc.respType))
s.Require().Equal(tc.expected.String(), tc.respType.String())
}
})
}
}
func (s *IntegrationTestSuite) TestBalancesGRPCHandler() {
val := s.network.Validators[0]
baseURL := val.APIAddress

View File

@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
type IntegrationTestSuite struct {
@ -24,12 +25,41 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
cfg := network.DefaultConfig()
genesisState := cfg.GenesisState
cfg.NumValidators = 1
var bankGenesis types.GenesisState
s.Require().NoError(cfg.Codec.UnmarshalJSON(genesisState[types.ModuleName], &bankGenesis))
bankGenesis.DenomMetadata = []types.Metadata{
{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
},
}
bankGenesisBz, err := cfg.Codec.MarshalJSON(&bankGenesis)
s.Require().NoError(err)
genesisState[types.ModuleName] = bankGenesisBz
cfg.GenesisState = genesisState
s.cfg = cfg
s.network = network.New(s.T(), cfg)
_, err := s.network.WaitForHeight(2)
_, err = s.network.WaitForHeight(2)
s.Require().NoError(err)
}

View File

@ -44,13 +44,13 @@ func (suite *IntegrationTestSuite) getTestBalances() []types.Balance {
}
func (suite *IntegrationTestSuite) TestInitGenesis() {
require := suite.Require()
m := types.Metadata{Description: sdk.DefaultBondDenom, Base: sdk.DefaultBondDenom, Display: sdk.DefaultBondDenom}
g := types.DefaultGenesisState()
g.DenomMetadata = []types.Metadata{m}
bk := suite.app.BankKeeper
bk.InitGenesis(suite.ctx, g)
m2 := bk.GetDenomMetaData(suite.ctx, m.Base)
require.Equal(m, m2)
m2, found := bk.GetDenomMetaData(suite.ctx, m.Base)
suite.Require().True(found)
suite.Require().Equal(m, m2)
}

View File

@ -61,7 +61,7 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
pageRes, err := query.Paginate(accountStore, req.Pagination, func(key []byte, value []byte) error {
pageRes, err := query.Paginate(accountStore, req.Pagination, func(_, value []byte) error {
var result sdk.Coin
err := k.cdc.UnmarshalBinaryBare(value, &result)
if err != nil {
@ -113,3 +113,53 @@ func (k BaseKeeper) Params(ctx context.Context, req *types.QueryParamsRequest) (
return &types.QueryParamsResponse{Params: params}, nil
}
// DenomsMetadata implements Query/DenomsMetadata gRPC method.
func (k BaseKeeper) DenomsMetadata(c context.Context, req *types.QueryDenomsMetadataRequest) (*types.QueryDenomsMetadataResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(c)
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomMetadataPrefix)
metadatas := []types.Metadata{}
pageRes, err := query.Paginate(store, req.Pagination, func(_, value []byte) error {
var metadata types.Metadata
k.cdc.MustUnmarshalBinaryBare(value, &metadata)
metadatas = append(metadatas, metadata)
return nil
})
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &types.QueryDenomsMetadataResponse{
Metadatas: metadatas,
Pagination: pageRes,
}, nil
}
// DenomMetadata implements Query/DenomMetadata gRPC method.
func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetadataRequest) (*types.QueryDenomMetadataResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
if req.Denom == "" {
return nil, status.Error(codes.InvalidArgument, "invalid denom")
}
ctx := sdk.UnwrapSDKContext(c)
metadata, found := k.GetDenomMetaData(ctx, req.Denom)
if !found {
return nil, status.Errorf(codes.NotFound, "client metadata for denom %s", req.Denom)
}
return &types.QueryDenomMetadataResponse{
Metadata: metadata,
}, nil
}

View File

@ -4,6 +4,7 @@ package keeper_test
import (
gocontext "context"
"fmt"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -119,3 +120,182 @@ func (suite *IntegrationTestSuite) TestQueryParams() {
suite.Require().NotNil(res)
suite.Require().Equal(suite.app.BankKeeper.GetParams(suite.ctx), res.GetParams())
}
func (suite *IntegrationTestSuite) QueryDenomsMetadataRequest() {
var (
req *types.QueryDenomsMetadataRequest
expMetadata = []types.Metadata{}
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"empty pagination",
func() {
req = &types.QueryDenomsMetadataRequest{}
},
true,
},
{
"success, no results",
func() {
req = &types.QueryDenomsMetadataRequest{
Pagination: &query.PageRequest{
Limit: 3,
CountTotal: true,
},
}
},
true,
},
{
"success",
func() {
metadataAtom := types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
}
metadataEth := types.Metadata{
Description: "Ethereum native token",
DenomUnits: []*types.DenomUnit{
{
Denom: "wei",
Exponent: 0,
},
{
Denom: "eth",
Exponent: 18,
Aliases: []string{"ETH", "ether"},
},
},
Base: "wei",
Display: "eth",
}
suite.app.BankKeeper.SetDenomMetaData(suite.ctx, metadataAtom)
suite.app.BankKeeper.SetDenomMetaData(suite.ctx, metadataEth)
expMetadata = []types.Metadata{metadataAtom, metadataEth}
req = &types.QueryDenomsMetadataRequest{
Pagination: &query.PageRequest{
Limit: 7,
CountTotal: true,
},
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.DenomsMetadata(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expMetadata, res.Metadatas)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *IntegrationTestSuite) QueryDenomMetadataRequest() {
var (
req *types.QueryDenomMetadataRequest
expMetadata = types.Metadata{}
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"empty denom",
func() {
req = &types.QueryDenomMetadataRequest{}
},
false,
},
{
"not found denom",
func() {
req = &types.QueryDenomMetadataRequest{
Denom: "foo",
}
},
false,
},
{
"success",
func() {
expMetadata := types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{
Denom: "uatom",
Exponent: 0,
Aliases: []string{"microatom"},
},
{
Denom: "atom",
Exponent: 6,
Aliases: []string{"ATOM"},
},
},
Base: "uatom",
Display: "atom",
}
suite.app.BankKeeper.SetDenomMetaData(suite.ctx, expMetadata)
req = &types.QueryDenomMetadataRequest{
Denom: expMetadata.Base,
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.DenomMetadata(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expMetadata, res.Metadata)
} else {
suite.Require().Error(err)
}
})
}
}

View File

@ -27,7 +27,7 @@ type Keeper interface {
GetSupply(ctx sdk.Context) exported.SupplyI
SetSupply(ctx sdk.Context, supply exported.SupplyI)
GetDenomMetaData(ctx sdk.Context, denom string) types.Metadata
GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool)
SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata)
IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool)
@ -180,15 +180,19 @@ func (k BaseKeeper) SetSupply(ctx sdk.Context, supply exported.SupplyI) {
}
// GetDenomMetaData retrieves the denomination metadata
func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) types.Metadata {
func (k BaseKeeper) GetDenomMetaData(ctx sdk.Context, denom string) (types.Metadata, bool) {
store := ctx.KVStore(k.storeKey)
store = prefix.NewStore(store, types.DenomMetadataKey(denom))
bz := store.Get([]byte(denom))
if bz == nil {
return types.Metadata{}, false
}
var metadata types.Metadata
k.cdc.MustUnmarshalBinaryBare(bz, &metadata)
return metadata
return metadata, true
}
// GetAllDenomMetaData retrieves all denominations metadata

View File

@ -981,8 +981,8 @@ func (suite *IntegrationTestSuite) TestSetDenomMetaData() {
app.BankKeeper.SetDenomMetaData(ctx, metadata[i])
}
actualMetadata := app.BankKeeper.GetDenomMetaData(ctx, metadata[1].Base)
actualMetadata, found := app.BankKeeper.GetDenomMetaData(ctx, metadata[1].Base)
suite.Require().True(found)
suite.Require().Equal(metadata[1].GetBase(), actualMetadata.GetBase())
suite.Require().Equal(metadata[1].GetDisplay(), actualMetadata.GetDisplay())
suite.Require().Equal(metadata[1].GetDescription(), actualMetadata.GetDescription())

View File

@ -6,8 +6,9 @@ import (
// x/bank module sentinel errors
var (
ErrNoInputs = sdkerrors.Register(ModuleName, 2, "no inputs to send transaction")
ErrNoOutputs = sdkerrors.Register(ModuleName, 3, "no outputs to send transaction")
ErrInputOutputMismatch = sdkerrors.Register(ModuleName, 4, "sum inputs != sum outputs")
ErrSendDisabled = sdkerrors.Register(ModuleName, 5, "send transactions are disabled")
ErrNoInputs = sdkerrors.Register(ModuleName, 2, "no inputs to send transaction")
ErrNoOutputs = sdkerrors.Register(ModuleName, 3, "no outputs to send transaction")
ErrInputOutputMismatch = sdkerrors.Register(ModuleName, 4, "sum inputs != sum outputs")
ErrSendDisabled = sdkerrors.Register(ModuleName, 5, "send transactions are disabled")
ErrDenomMetadataNotFound = sdkerrors.Register(ModuleName, 6, "client denom metadata not found")
)

File diff suppressed because it is too large Load Diff

View File

@ -269,6 +269,96 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal
}
func request_Query_DenomMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryDenomMetadataRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["denom"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom")
}
protoReq.Denom, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
}
msg, err := client.DenomMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Query_DenomMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryDenomMetadataRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["denom"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom")
}
protoReq.Denom, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
}
msg, err := server.DenomMetadata(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Query_DenomsMetadata_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Query_DenomsMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryDenomsMetadataRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_DenomsMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DenomsMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Query_DenomsMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryDenomsMetadataRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_DenomsMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DenomsMetadata(ctx, &protoReq)
return msg, metadata, err
}
// RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
// UnaryRPC :call QueryServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -375,6 +465,46 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
})
mux.Handle("GET", pattern_Query_DenomMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Query_DenomMetadata_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_DenomMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Query_DenomsMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Query_DenomsMetadata_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_DenomsMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -516,6 +646,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
})
mux.Handle("GET", pattern_Query_DenomMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Query_DenomMetadata_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_DenomMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Query_DenomsMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Query_DenomsMetadata_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_DenomsMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -529,6 +699,10 @@ var (
pattern_Query_SupplyOf_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"cosmos", "bank", "v1beta1", "supply", "denom"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"cosmos", "bank", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Query_DenomMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"cosmos", "bank", "v1beta1", "denoms_metadata", "denom"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Query_DenomsMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"cosmos", "bank", "v1beta1", "denoms_metadata"}, "", runtime.AssumeColonVerbOpt(true)))
)
var (
@ -541,4 +715,8 @@ var (
forward_Query_SupplyOf_0 = runtime.ForwardResponseMessage
forward_Query_Params_0 = runtime.ForwardResponseMessage
forward_Query_DenomMetadata_0 = runtime.ForwardResponseMessage
forward_Query_DenomsMetadata_0 = runtime.ForwardResponseMessage
)