Skip to content

Commit

Permalink
feat: support indexing denom metadata denom units (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez committed Jun 9, 2022
1 parent 367c8f6 commit e00a3eb
Show file tree
Hide file tree
Showing 12 changed files with 784 additions and 130 deletions.
35 changes: 34 additions & 1 deletion docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
- [QueryAllBalancesResponse](#cosmos.bank.v1beta1.QueryAllBalancesResponse)
- [QueryBalanceRequest](#cosmos.bank.v1beta1.QueryBalanceRequest)
- [QueryBalanceResponse](#cosmos.bank.v1beta1.QueryBalanceResponse)
- [QueryBaseDenomRequest](#cosmos.bank.v1beta1.QueryBaseDenomRequest)
- [QueryBaseDenomResponse](#cosmos.bank.v1beta1.QueryBaseDenomResponse)
- [QueryDenomMetadataRequest](#cosmos.bank.v1beta1.QueryDenomMetadataRequest)
- [QueryDenomMetadataResponse](#cosmos.bank.v1beta1.QueryDenomMetadataResponse)
- [QueryDenomsMetadataRequest](#cosmos.bank.v1beta1.QueryDenomsMetadataRequest)
Expand Down Expand Up @@ -911,7 +913,7 @@ Query defines the gRPC querier service.
Since: cosmos-sdk 0.43 | GET|/cosmos/auth/v1beta1/accounts|
| `Account` | [QueryAccountRequest](#cosmos.auth.v1beta1.QueryAccountRequest) | [QueryAccountResponse](#cosmos.auth.v1beta1.QueryAccountResponse) | Account returns account details based on address. | GET|/cosmos/auth/v1beta1/accounts/{address}|
| `Params` | [QueryParamsRequest](#cosmos.auth.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#cosmos.auth.v1beta1.QueryParamsResponse) | Params queries all parameters. | GET|/cosmos/auth/v1beta1/params|
| `ModuleAccounts` | [QueryModuleAccountsRequest](#cosmos.auth.v1beta1.QueryModuleAccountsRequest) | [QueryModuleAccountsResponse](#cosmos.auth.v1beta1.QueryModuleAccountsResponse) | ModuleAccounts returns all the existing Module Accounts. | GET|/cosmos/auth/v1beta1/module_accounts|
| `ModuleAccounts` | [QueryModuleAccountsRequest](#cosmos.auth.v1beta1.QueryModuleAccountsRequest) | [QueryModuleAccountsResponse](#cosmos.auth.v1beta1.QueryModuleAccountsResponse) | ModuleAccounts returns all the existing module accounts. | GET|/cosmos/auth/v1beta1/module_accounts|

<!-- end services -->

Expand Down Expand Up @@ -1841,6 +1843,36 @@ QueryBalanceResponse is the response type for the Query/Balance RPC method.



<a name="cosmos.bank.v1beta1.QueryBaseDenomRequest"></a>

### QueryBaseDenomRequest
QueryBaseDenomRequest defines the request type for the BaseDenom gRPC method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `denom` | [string](#string) | | |






<a name="cosmos.bank.v1beta1.QueryBaseDenomResponse"></a>

### QueryBaseDenomResponse
QueryBaseDenomResponse defines the response type for the BaseDenom gRPC method.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `base_denom` | [string](#string) | | |






<a name="cosmos.bank.v1beta1.QueryDenomMetadataRequest"></a>

### QueryDenomMetadataRequest
Expand Down Expand Up @@ -2085,6 +2117,7 @@ Query defines the gRPC querier service.
| `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|
| `BaseDenom` | [QueryBaseDenomRequest](#cosmos.bank.v1beta1.QueryBaseDenomRequest) | [QueryBaseDenomResponse](#cosmos.bank.v1beta1.QueryBaseDenomResponse) | BaseDenom queries for a base denomination given a denom that can either be the base denom itself or a metadata denom unit that maps to the base denom. | GET|/cosmos/bank/v1beta1/base_denom|

<!-- end services -->

Expand Down
16 changes: 16 additions & 0 deletions proto/cosmos/bank/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ service Query {
rpc DenomsMetadata(QueryDenomsMetadataRequest) returns (QueryDenomsMetadataResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata";
}

// BaseDenom queries for a base denomination given a denom that can either be
// the base denom itself or a metadata denom unit that maps to the base denom.
rpc BaseDenom(QueryBaseDenomRequest) returns (QueryBaseDenomResponse) {
option (google.api.http).get = "/cosmos/bank/v1beta1/base_denom";
}
}

// QueryBalanceRequest is the request type for the Query/Balance RPC method.
Expand Down Expand Up @@ -208,3 +214,13 @@ message QueryDenomMetadataResponse {
// metadata describes and provides all the client information for the requested token.
Metadata metadata = 1 [(gogoproto.nullable) = false];
}

// QueryBaseDenomRequest defines the request type for the BaseDenom gRPC method.
message QueryBaseDenomRequest {
string denom = 1;
}

// QueryBaseDenomResponse defines the response type for the BaseDenom gRPC method.
message QueryBaseDenomResponse {
string base_denom = 1;
}
7 changes: 4 additions & 3 deletions types/query/pagination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package query_test
import (
gocontext "context"
"fmt"
"github.com/tendermint/tendermint/libs/log"
"testing"

"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"

Expand All @@ -19,6 +19,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/query"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)

Expand Down Expand Up @@ -63,7 +64,7 @@ func (s *paginationTestSuite) TestParsePagination() {
func (s *paginationTestSuite) TestPagination() {
app, ctx, _ := setupTest()
queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, app.BankKeeper)
types.RegisterQueryServer(queryHelper, bankkeeper.Querier{BaseKeeper: app.BankKeeper.(bankkeeper.BaseKeeper)})
queryClient := types.NewQueryClient(queryHelper)

var balances sdk.Coins
Expand Down Expand Up @@ -172,7 +173,7 @@ func (s *paginationTestSuite) TestPagination() {
func (s *paginationTestSuite) TestReversePagination() {
app, ctx, _ := setupTest()
queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, app.BankKeeper)
types.RegisterQueryServer(queryHelper, bankkeeper.Querier{BaseKeeper: app.BankKeeper.(bankkeeper.BaseKeeper)})
queryClient := types.NewQueryClient(queryHelper)

var balances sdk.Coins
Expand Down
4 changes: 2 additions & 2 deletions x/auth/types/query.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 48 additions & 22 deletions x/bank/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank/types"
)

var _ types.QueryServer = BaseKeeper{}
var _ types.QueryServer = Querier{}

// Querier defines a wrapper around the x/bank keeper and implements the gRPC
// query interface.
type Querier struct {
BaseKeeper
}

// Balance implements the Query/Balance gRPC method
func (k BaseKeeper) Balance(ctx context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
func (q Querier) Balance(ctx context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
Expand All @@ -34,13 +40,13 @@ func (k BaseKeeper) Balance(ctx context.Context, req *types.QueryBalanceRequest)
return nil, status.Errorf(codes.InvalidArgument, "invalid address: %s", err.Error())
}

balance := k.GetBalance(sdkCtx, address, req.Denom)
balance := q.GetBalance(sdkCtx, address, req.Denom)

return &types.QueryBalanceResponse{Balance: &balance}, nil
}

// AllBalances implements the Query/AllBalances gRPC method
func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalancesRequest) (*types.QueryAllBalancesResponse, error) {
func (q Querier) AllBalances(ctx context.Context, req *types.QueryAllBalancesRequest) (*types.QueryAllBalancesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
Expand All @@ -57,14 +63,14 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances
sdkCtx := sdk.UnwrapSDKContext(ctx)

balances := sdk.NewCoins()
accountStore := k.getAccountStore(sdkCtx, addr)
accountStore := q.getAccountStore(sdkCtx, addr)

pageRes, err := query.Paginate(accountStore, req.Pagination, func(_, value []byte) error {
var result sdk.Coin
err := k.cdc.Unmarshal(value, &result)
if err != nil {
if err := q.cdc.Unmarshal(value, &result); err != nil {
return err
}

balances = append(balances, result)
return nil
})
Expand All @@ -77,9 +83,9 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances
}

// TotalSupply implements the Query/TotalSupply gRPC method
func (k BaseKeeper) TotalSupply(ctx context.Context, req *types.QueryTotalSupplyRequest) (*types.QueryTotalSupplyResponse, error) {
func (q Querier) TotalSupply(ctx context.Context, req *types.QueryTotalSupplyRequest) (*types.QueryTotalSupplyResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
totalSupply, pageRes, err := k.GetPaginatedTotalSupplyWithOffsets(sdkCtx, req.Pagination)
totalSupply, pageRes, err := q.GetPaginatedTotalSupplyWithOffsets(sdkCtx, req.Pagination)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
Expand All @@ -88,7 +94,7 @@ func (k BaseKeeper) TotalSupply(ctx context.Context, req *types.QueryTotalSupply
}

// SupplyOf implements the Query/SupplyOf gRPC method
func (k BaseKeeper) SupplyOf(c context.Context, req *types.QuerySupplyOfRequest) (*types.QuerySupplyOfResponse, error) {
func (q Querier) SupplyOf(c context.Context, req *types.QuerySupplyOfRequest) (*types.QuerySupplyOfResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
Expand All @@ -98,15 +104,15 @@ func (k BaseKeeper) SupplyOf(c context.Context, req *types.QuerySupplyOfRequest)
}

ctx := sdk.UnwrapSDKContext(c)
supply := k.GetSupplyWithOffset(ctx, req.Denom)
supply := q.GetSupplyWithOffset(ctx, req.Denom)

return &types.QuerySupplyOfResponse{Amount: sdk.NewCoin(req.Denom, supply.Amount)}, nil
}

// TotalSupply implements the Query/TotalSupplyWithoutOffset gRPC method
func (k BaseKeeper) TotalSupplyWithoutOffset(ctx context.Context, req *types.QueryTotalSupplyWithoutOffsetRequest) (*types.QueryTotalSupplyWithoutOffsetResponse, error) {
func (q Querier) TotalSupplyWithoutOffset(ctx context.Context, req *types.QueryTotalSupplyWithoutOffsetRequest) (*types.QueryTotalSupplyWithoutOffsetResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
totalSupply, pageRes, err := k.GetPaginatedTotalSupply(sdkCtx, req.Pagination)
totalSupply, pageRes, err := q.GetPaginatedTotalSupply(sdkCtx, req.Pagination)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
Expand All @@ -115,7 +121,7 @@ func (k BaseKeeper) TotalSupplyWithoutOffset(ctx context.Context, req *types.Que
}

// SupplyOf implements the Query/SupplyOf gRPC method
func (k BaseKeeper) SupplyOfWithoutOffset(c context.Context, req *types.QuerySupplyOfWithoutOffsetRequest) (*types.QuerySupplyOfWithoutOffsetResponse, error) {
func (q Querier) SupplyOfWithoutOffset(c context.Context, req *types.QuerySupplyOfWithoutOffsetRequest) (*types.QuerySupplyOfWithoutOffsetResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
Expand All @@ -125,36 +131,38 @@ func (k BaseKeeper) SupplyOfWithoutOffset(c context.Context, req *types.QuerySup
}

ctx := sdk.UnwrapSDKContext(c)
supply := k.GetSupply(ctx, req.Denom)
supply := q.GetSupply(ctx, req.Denom)

return &types.QuerySupplyOfWithoutOffsetResponse{Amount: sdk.NewCoin(req.Denom, supply.Amount)}, nil
}

// Params implements the gRPC service handler for querying x/bank parameters.
func (k BaseKeeper) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
func (q Querier) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
params := k.GetParams(sdkCtx)
params := q.GetParams(sdkCtx)

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) {
func (q Querier) 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)
store := prefix.NewStore(ctx.KVStore(q.storeKey), types.DenomMetadataPrefix)

metadatas := []types.Metadata{}
pageRes, err := query.Paginate(store, req.Pagination, func(_, value []byte) error {
var metadata types.Metadata
k.cdc.MustUnmarshal(value, &metadata)
if err := q.cdc.Unmarshal(value, &metadata); err != nil {
return err
}

metadatas = append(metadatas, metadata)
return nil
Expand All @@ -171,7 +179,7 @@ func (k BaseKeeper) DenomsMetadata(c context.Context, req *types.QueryDenomsMeta
}

// DenomMetadata implements Query/DenomMetadata gRPC method.
func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetadataRequest) (*types.QueryDenomMetadataResponse, error) {
func (q Querier) DenomMetadata(c context.Context, req *types.QueryDenomMetadataRequest) (*types.QueryDenomMetadataResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
Expand All @@ -182,7 +190,7 @@ func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetada

ctx := sdk.UnwrapSDKContext(c)

metadata, found := k.GetDenomMetaData(ctx, req.Denom)
metadata, found := q.GetDenomMetaData(ctx, req.Denom)
if !found {
return nil, status.Errorf(codes.NotFound, "client metadata for denom %s", req.Denom)
}
Expand All @@ -191,3 +199,21 @@ func (k BaseKeeper) DenomMetadata(c context.Context, req *types.QueryDenomMetada
Metadata: metadata,
}, nil
}

func (q Querier) BaseDenom(c context.Context, req *types.QueryBaseDenomRequest) (*types.QueryBaseDenomResponse, 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)

baseDenom, ok := q.GetBaseDenom(ctx, req.Denom)
if !ok {
return nil, status.Errorf(codes.NotFound, "base denom not found for: %s", req.Denom)
}

return &types.QueryBaseDenomResponse{BaseDenom: baseDenom}, nil
}
49 changes: 49 additions & 0 deletions x/bank/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,52 @@ func (suite *IntegrationTestSuite) QueryDenomMetadataRequest() {
})
}
}

func (s *IntegrationTestSuite) TestGRPC_BaseDenom() {
testCases := []struct {
name string
req *types.QueryBaseDenomRequest
expectErr bool
expectResult *types.QueryBaseDenomResponse
}{
{
name: "valid base denom",
req: &types.QueryBaseDenomRequest{Denom: "uatom"},
expectErr: false,
expectResult: &types.QueryBaseDenomResponse{BaseDenom: "uatom"},
},
{
name: "valid denom",
req: &types.QueryBaseDenomRequest{Denom: "atom"},
expectErr: false,
expectResult: &types.QueryBaseDenomResponse{BaseDenom: "uatom"},
},
{
name: "invalid denom",
req: &types.QueryBaseDenomRequest{Denom: "foo"},
expectErr: true,
},
}

expMetadata := s.getTestMetadata()
for _, md := range expMetadata {
s.app.BankKeeper.SetDenomMetaData(s.ctx, md)
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
ctx := sdk.WrapSDKContext(s.ctx)

res, err := s.queryClient.BaseDenom(ctx, tc.req)
if tc.expectErr {
s.Require().Error(err)
s.Require().Nil(res)
} else {
s.Require().NoError(err)
s.Require().Equal(tc.expectResult, res)
}
})
}
}
Loading

0 comments on commit e00a3eb

Please sign in to comment.