Skip to content

Commit

Permalink
feat: query denoms by admin (#25)
Browse files Browse the repository at this point in the history
* feat: query denoms by admin

* docs: denoms-from-admin

* fix: local docker
  • Loading branch information
fmorency authored Dec 10, 2024
1 parent 9f137b7 commit 59eb848
Showing 9 changed files with 657 additions and 39 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ and the following queries:
- `params`: Get the tokenfactory module parameters.
- `denom-authority-metadata`: Get the authority metadata of a denom.
- `denoms-from-creator`: Returns a list of all denoms created by a given creator.
- `denoms-from-admin`: Returns a list of all denoms for which a given address is the admin.

## Testing

20 changes: 20 additions & 0 deletions proto/osmosis/tokenfactory/v1beta1/query.proto
Original file line number Diff line number Diff line change
@@ -32,6 +32,14 @@ service Query {
option (google.api.http).get =
"/osmosis/tokenfactory/v1beta1/denoms_from_creator/{creator}";
}

// DenomsFromAdmin defines a gRPC query method for fetching all
// denominations owned by a specific admin.
rpc DenomsFromAdmin(QueryDenomsFromAdminRequest)
returns (QueryDenomsFromAdminResponse) {
option (google.api.http).get =
"/osmosis/tokenfactory/v1beta1/denoms_from_admin/{admin}";
}
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
@@ -69,3 +77,15 @@ message QueryDenomsFromCreatorRequest {
message QueryDenomsFromCreatorResponse {
repeated string denoms = 1 [ (gogoproto.moretags) = "yaml:\"denoms\"" ];
}

// QueryDenomsFromAdminRequest defines the request structure for the
// DenomsFromAdmin gRPC query.
message QueryDenomsFromAdminRequest {
string admin = 1 [ (gogoproto.moretags) = "yaml:\"admin\"" ];
}

// QueryDenomsFromAdminRequest defines the response structure for the
// DenomsFromAdmin gRPC query.
message QueryDenomsFromAdminResponse {
repeated string denoms = 1 [ (gogoproto.moretags) = "yaml:\"denoms\"" ];
}
29 changes: 29 additions & 0 deletions x/tokenfactory/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ func GetQueryCmd() *cobra.Command {
GetParams(),
GetCmdDenomAuthorityMetadata(),
GetCmdDenomsFromCreator(),
GetCmdDenomsFromAdmin(),
)

return cmd
@@ -114,3 +115,31 @@ func GetCmdDenomsFromCreator() *cobra.Command {

return cmd
}

func GetCmdDenomsFromAdmin() *cobra.Command {
cmd := &cobra.Command{
Use: "denoms-from-admin [admin address] [flags]",
Short: "Returns a list of all tokens owned by a specific admin address",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.DenomsFromAdmin(cmd.Context(), &types.QueryDenomsFromAdminRequest{
Admin: args[0],
})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
19 changes: 19 additions & 0 deletions x/tokenfactory/keeper/admins.go
Original file line number Diff line number Diff line change
@@ -50,3 +50,22 @@ func (k Keeper) setAdmin(ctx context.Context, denom string, admin string) error

return k.setAuthorityMetadata(ctx, denom, metadata)
}

// GetDenomsFromAdmin returns all denoms for which the provided address is the admin
func (k Keeper) GetDenomsFromAdmin(ctx context.Context, admin string) ([]string, error) {
iterator := k.GetAllDenomsIterator(ctx)
defer iterator.Close()

denoms := []string{}
for ; iterator.Valid(); iterator.Next() {
denom := string(iterator.Value())
metadata, err := k.GetAuthorityMetadata(sdk.UnwrapSDKContext(ctx), denom)
if err != nil {
return nil, err
}
if metadata.Admin == admin {
denoms = append(denoms, denom)
}
}
return denoms, nil
}
48 changes: 48 additions & 0 deletions x/tokenfactory/keeper/admins_test.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,20 @@ func (suite *KeeperTestSuite) TestAdminMsgs() {
suite.Require().NoError(err)
suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin)

// Test getting denoms from admin
adminRes, err := suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: suite.TestAccs[0].String(),
})
suite.Require().NoError(err)
suite.Require().Equal([]string{suite.defaultDenom}, adminRes.Denoms)

// Veriry that the other account has no denoms
adminRes, err = suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: suite.TestAccs[1].String(),
})
suite.Require().NoError(err)
suite.Require().Nil(adminRes.Denoms)

// Test minting to admins own account
_, err = suite.msgServer.Mint(suite.Ctx, types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10)))
addr0bal += 10
@@ -57,6 +71,20 @@ func (suite *KeeperTestSuite) TestAdminMsgs() {
suite.Require().NoError(err)
suite.Require().Equal(suite.TestAccs[1].String(), queryRes.AuthorityMetadata.Admin)

// Test query from admin returns the correct denoms. The old admin should have no denoms
adminRes, err = suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: suite.TestAccs[0].String(),
})
suite.Require().NoError(err)
suite.Require().Nil(adminRes.Denoms)

// The new admin should have the default denom
adminRes, err = suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: suite.TestAccs[1].String(),
})
suite.Require().NoError(err)
suite.Require().Equal([]string{suite.defaultDenom}, adminRes.Denoms)

// Make sure old admin can no longer do actions
_, err = suite.msgServer.Burn(suite.Ctx, types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5)))
suite.Require().Error(err)
@@ -75,6 +103,26 @@ func (suite *KeeperTestSuite) TestAdminMsgs() {
})
suite.Require().NoError(err)
suite.Require().Equal("", queryRes.AuthorityMetadata.Admin)

// Make sure retrieving denoms by admin works with empty admin
adminRes, err = suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: "",
})
suite.Require().NoError(err)
suite.Require().Equal([]string{suite.defaultDenom}, adminRes.Denoms)

// Make sure the other accounts are not admins
adminRes, err = suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: suite.TestAccs[0].String(),
})
suite.Require().NoError(err)
suite.Require().Nil(adminRes.Denoms)

adminRes, err = suite.queryClient.DenomsFromAdmin(suite.Ctx.Context(), &types.QueryDenomsFromAdminRequest{
Admin: suite.TestAccs[1].String(),
})
suite.Require().NoError(err)
suite.Require().Nil(adminRes.Denoms)
}

// TestMintDenom ensures the following properties of the MintMessage:
1 change: 0 additions & 1 deletion x/tokenfactory/keeper/creators.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import (
"context"

"cosmossdk.io/store"

sdk "github.com/cosmos/cosmos-sdk/types"
)

9 changes: 9 additions & 0 deletions x/tokenfactory/keeper/grpc_query.go
Original file line number Diff line number Diff line change
@@ -33,3 +33,12 @@ func (k Keeper) DenomsFromCreator(ctx context.Context, req *types.QueryDenomsFro
denoms := k.GetDenomsFromCreator(sdkCtx, req.GetCreator())
return &types.QueryDenomsFromCreatorResponse{Denoms: denoms}, nil
}

func (k Keeper) DenomsFromAdmin(ctx context.Context, req *types.QueryDenomsFromAdminRequest) (*types.QueryDenomsFromAdminResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
denoms, err := k.GetDenomsFromAdmin(sdkCtx, req.GetAdmin())
if err != nil {
return nil, err
}
return &types.QueryDenomsFromAdminResponse{Denoms: denoms}, nil
}
Loading

0 comments on commit 59eb848

Please sign in to comment.