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
}
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default pagination silently truncates results to 100

Medium Severity

Previously getDenomsFromCreator iterated all entries and returned every denom. Now it delegates to query.Paginate, which applies DefaultLimit = 100 when no PageRequest is supplied. Existing callers that pass nil pagination — notably the wasm binding via GetDenomsFromCreator — will silently receive at most 100 denoms instead of all. Any creator with more than 100 denoms gets truncated results without the caller knowing, which is a breaking behavior change for the wasm query path where smart contracts may rely on a complete list.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7a6849e. Configure here.

}

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