Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion proto/tokenfactory/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";
package seiprotocol.seichain.tokenfactory;

import "cosmos/bank/v1beta1/bank.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "tokenfactory/authority_metadata.proto";
Expand Down Expand Up @@ -69,12 +70,14 @@ message QueryDenomAuthorityMetadataResponse {
// DenomsFromCreator gRPC query.
message QueryDenomsFromCreatorRequest {
string creator = 1 [(gogoproto.moretags) = "yaml:\"creator\""];
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// QueryDenomsFromCreatorRequest defines the response structure for the
// QueryDenomsFromCreatorResponse defines the response structure for the
// DenomsFromCreator gRPC query.
message QueryDenomsFromCreatorResponse {
repeated string denoms = 1 [(gogoproto.moretags) = "yaml:\"denoms\""];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryDenomMetadataRequest is the request type for the DenomMetadata gRPC method.
Expand Down
6 changes: 3 additions & 3 deletions wasmbinding/test/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func TestWasmGetDenomsFromCreator(t *testing.T) {
var parsedRes tokenfactorytypes.QueryDenomsFromCreatorResponse
err = json.Unmarshal(res, &parsedRes)
require.NoError(t, err)
require.Equal(t, tokenfactorytypes.QueryDenomsFromCreatorResponse{Denoms: nil}, parsedRes)
require.Empty(t, parsedRes.Denoms)

// Add first denom
testWrapper.App.TokenFactoryKeeper.CreateDenom(testWrapper.Ctx, app.TestUser, "test1")
Expand All @@ -260,7 +260,7 @@ func TestWasmGetDenomsFromCreator(t *testing.T) {
var parsedRes2 tokenfactorytypes.QueryDenomsFromCreatorResponse
err = json.Unmarshal(res, &parsedRes2)
require.NoError(t, err)
require.Equal(t, tokenfactorytypes.QueryDenomsFromCreatorResponse{Denoms: []string{denom1}}, parsedRes2)
require.Equal(t, []string{denom1}, parsedRes2.Denoms)

// Add second denom
testWrapper.App.TokenFactoryKeeper.CreateDenom(testWrapper.Ctx, app.TestUser, "test2")
Expand All @@ -271,7 +271,7 @@ func TestWasmGetDenomsFromCreator(t *testing.T) {
var parsedRes3 tokenfactorytypes.QueryDenomsFromCreatorResponse
err = json.Unmarshal(res, &parsedRes3)
require.NoError(t, err)
require.Equal(t, tokenfactorytypes.QueryDenomsFromCreatorResponse{Denoms: []string{denom1, denom2}}, parsedRes3)
require.Equal(t, []string{denom1, denom2}, parsedRes3.Denoms)

}

Expand Down
9 changes: 8 additions & 1 deletion x/tokenfactory/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,14 @@ func GetCmdDenomsFromCreator() *cobra.Command {
return err
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

res, err := queryClient.DenomsFromCreator(cmd.Context(), &types.QueryDenomsFromCreatorRequest{
Creator: args[0],
Creator: args[0],
Pagination: pageReq,
})
if err != nil {
return err
Expand All @@ -117,6 +123,7 @@ func GetCmdDenomsFromCreator() *cobra.Command {
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "denoms-from-creator")

return cmd
}
9 changes: 9 additions & 0 deletions x/tokenfactory/client/wasm/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ package wasm

import (
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/sei-protocol/sei-chain/sei-cosmos/types/query"
tokenfactorykeeper "github.com/sei-protocol/sei-chain/x/tokenfactory/keeper"
"github.com/sei-protocol/sei-chain/x/tokenfactory/types"
)

// defaultDenomsFromCreatorLimit is the wasm query default, higher than the gRPC DefaultLimit of 100
// for backwards compatibility with contracts that expect all denoms in one response, but still
// bounded to limit DoS risk since denom creation has no fee.
const defaultDenomsFromCreatorLimit = 2000

type TokenFactoryWasmQueryHandler struct {
tokenfactoryKeeper tokenfactorykeeper.Keeper
}
Expand All @@ -23,5 +29,8 @@ func (handler TokenFactoryWasmQueryHandler) GetDenomAuthorityMetadata(ctx sdk.Co

func (handler TokenFactoryWasmQueryHandler) GetDenomsFromCreator(ctx sdk.Context, req *types.QueryDenomsFromCreatorRequest) (*types.QueryDenomsFromCreatorResponse, error) {
c := sdk.WrapSDKContext(ctx)
if req.Pagination == nil {
req.Pagination = &query.PageRequest{Limit: defaultDenomsFromCreatorLimit}
}
return handler.tokenfactoryKeeper.DenomsFromCreator(c, req)
}
19 changes: 10 additions & 9 deletions x/tokenfactory/keeper/creators.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@ package keeper

import (
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/sei-protocol/sei-chain/sei-cosmos/types/query"
)

func (k Keeper) addDenomFromCreator(ctx sdk.Context, creator, denom string) {
store := k.GetCreatorPrefixStore(ctx, creator)
store.Set([]byte(denom), []byte(denom))
}

func (k Keeper) getDenomsFromCreator(ctx sdk.Context, creator string) []string {
func (k Keeper) getDenomsFromCreator(ctx sdk.Context, creator string, pagination *query.PageRequest) ([]string, *query.PageResponse, error) {
store := k.GetCreatorPrefixStore(ctx, creator)

iterator := store.Iterator(nil, nil)
defer func() { _ = iterator.Close() }()

denoms := []string{}
for ; iterator.Valid(); iterator.Next() {
denoms = append(denoms, string(iterator.Key()))
var denoms []string
pageRes, err := query.Paginate(store, pagination, func(key []byte, _ []byte) error {
denoms = append(denoms, string(key))
return nil
})
if err != nil {
return nil, nil, err
}
return denoms
return denoms, pageRes, nil
Comment thread
cursor[bot] marked this conversation as resolved.
}

func (k Keeper) GetAllDenomsIterator(ctx sdk.Context) sdk.Iterator {
Expand Down
7 changes: 5 additions & 2 deletions x/tokenfactory/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ func (k Keeper) DenomAuthorityMetadata(ctx context.Context, req *types.QueryDeno

func (k Keeper) DenomsFromCreator(ctx context.Context, req *types.QueryDenomsFromCreatorRequest) (*types.QueryDenomsFromCreatorResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
denoms := k.getDenomsFromCreator(sdkCtx, req.GetCreator())
return &types.QueryDenomsFromCreatorResponse{Denoms: denoms}, nil
denoms, pageRes, err := k.getDenomsFromCreator(sdkCtx, req.GetCreator(), req.GetPagination())
if err != nil {
return nil, err
}
return &types.QueryDenomsFromCreatorResponse{Denoms: denoms, Pagination: pageRes}, nil
}

// DenomMetadata implements Query/DenomMetadata gRPC method.
Expand Down
68 changes: 66 additions & 2 deletions x/tokenfactory/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package keeper_test
import (
"context"
"fmt"
"reflect"
"testing"

sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/sei-protocol/sei-chain/sei-cosmos/types/query"
banktypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/bank/types"
"github.com/sei-protocol/sei-chain/x/tokenfactory/keeper"
"github.com/sei-protocol/sei-chain/x/tokenfactory/types"
"reflect"
"testing"
)

func (suite *KeeperTestSuite) TestDenomMetadataRequest() {
Expand Down Expand Up @@ -158,6 +160,68 @@ func (suite *KeeperTestSuite) TestDenomAllowListRequest() {
}
}

func (suite *KeeperTestSuite) TestDenomsFromCreatorPagination() {
creator := suite.TestAccs[0].String()
ctx := sdk.WrapSDKContext(suite.Ctx)

denomSubdirs := []string{"aaa", "bbb", "ccc", "ddd", "eee"}
for _, sub := range denomSubdirs {
_, err := suite.msgServer.CreateDenom(ctx, types.NewMsgCreateDenom(creator, sub))
suite.Require().NoError(err)
}

suite.Run("no pagination returns all denoms", func() {
res, err := suite.queryClient.DenomsFromCreator(ctx, &types.QueryDenomsFromCreatorRequest{Creator: creator})
suite.Require().NoError(err)
suite.Require().Len(res.Denoms, len(denomSubdirs))
suite.Require().NotNil(res.Pagination)
})

suite.Run("limit 2 returns first page with next key", func() {
res, err := suite.queryClient.DenomsFromCreator(ctx, &types.QueryDenomsFromCreatorRequest{
Creator: creator,
Pagination: &query.PageRequest{Limit: 2},
})
suite.Require().NoError(err)
suite.Require().Len(res.Denoms, 2)
suite.Require().NotNil(res.Pagination)
suite.Require().NotNil(res.Pagination.NextKey)
})

suite.Run("key-based second page returns remaining denoms", func() {
first, err := suite.queryClient.DenomsFromCreator(ctx, &types.QueryDenomsFromCreatorRequest{
Creator: creator,
Pagination: &query.PageRequest{Limit: 2},
})
suite.Require().NoError(err)

second, err := suite.queryClient.DenomsFromCreator(ctx, &types.QueryDenomsFromCreatorRequest{
Creator: creator,
Pagination: &query.PageRequest{Key: first.Pagination.NextKey, Limit: 2},
})
suite.Require().NoError(err)
suite.Require().NotEmpty(second.Denoms)
// pages must not overlap
for _, d := range second.Denoms {
suite.Require().NotContains(first.Denoms, d)
}
})

suite.Run("offset-based pagination", func() {
all, err := suite.queryClient.DenomsFromCreator(ctx, &types.QueryDenomsFromCreatorRequest{Creator: creator})
suite.Require().NoError(err)

const offset = 2
res, err := suite.queryClient.DenomsFromCreator(ctx, &types.QueryDenomsFromCreatorRequest{
Creator: creator,
Pagination: &query.PageRequest{Offset: offset, Limit: 2},
})
suite.Require().NoError(err)
suite.Require().NotEmpty(res.Denoms)
suite.Require().Equal(all.Denoms[offset:offset+len(res.Denoms)], res.Denoms)
})
}

func TestKeeper_DenomAllowList(t *testing.T) {
type args struct {
req *types.QueryDenomAllowListRequest
Expand Down
Loading
Loading