Skip to content

Commit 0b8eee7

Browse files
mergify[bot]gsk967julienrbrt
authored andcommitted
feat(cli): add module-account cli cmd and grpc get api (backport cosmos#13612) (cosmos#13616)
* feat(cli): add module-account cli cmd and grpc get api (cosmos#13612) (cherry picked from commit ddf5cf0) # Conflicts: # CHANGELOG.md # api/cosmos/auth/v1beta1/query.pulsar.go # api/cosmos/auth/v1beta1/query_grpc.pb.go # x/auth/client/testutil/suite.go # x/auth/types/query.pb.go * fix conflicts * updates * updates Co-authored-by: Sai Kumar <17549398+gsk967@users.noreply.github.com> Co-authored-by: Julien Robert <julien@rbrt.fr>
1 parent 88f19be commit 0b8eee7

File tree

8 files changed

+862
-203
lines changed

8 files changed

+862
-203
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
3838

3939
## [Unreleased]
4040

41+
### Features
42+
43+
* (x/auth) [#13612](https://github.com/cosmos/cosmos-sdk/pull/13612) Add `Query/ModuleAccountByName` endpoint for accessing the module account info by module name.
44+
4145
## [v0.46.3](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.3) - 2022-10-20
4246

4347
ATTENTION:

proto/cosmos/auth/v1beta1/query.proto

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ service Query {
6161
option (google.api.http).get = "/cosmos/auth/v1beta1/module_accounts/{name}";
6262
}
6363

64+
// ModuleAccountByName returns the module account info by module name
65+
rpc ModuleAccountByName(QueryModuleAccountByNameRequest) returns (QueryModuleAccountByNameResponse) {
66+
option (google.api.http).get = "/cosmos/auth/v1beta1/module_accounts/{name}";
67+
}
68+
6469
// Bech32Prefix queries bech32Prefix
6570
rpc Bech32Prefix(Bech32PrefixRequest) returns (Bech32PrefixResponse) {
6671
option (cosmos_proto.method_added_in) = "cosmos-sdk 0.46";
@@ -137,6 +142,54 @@ message QueryParamsResponse {
137142
Params params = 1 [(gogoproto.nullable) = false];
138143
}
139144

145+
// QueryModuleAccountsRequest is the request type for the Query/ModuleAccounts RPC method.
146+
//
147+
// Since: cosmos-sdk 0.46
148+
message QueryModuleAccountsRequest {}
149+
150+
// QueryModuleAccountsResponse is the response type for the Query/ModuleAccounts RPC method.
151+
//
152+
// Since: cosmos-sdk 0.46
153+
message QueryModuleAccountsResponse {
154+
repeated google.protobuf.Any accounts = 1 [(cosmos_proto.accepts_interface) = "ModuleAccountI"];
155+
}
156+
157+
// QueryModuleAccountByNameRequest is the request type for the Query/ModuleAccountByName RPC method.
158+
message QueryModuleAccountByNameRequest {
159+
string name = 1;
160+
}
161+
162+
// QueryModuleAccountByNameResponse is the response type for the Query/ModuleAccountByName RPC method.
163+
message QueryModuleAccountByNameResponse {
164+
google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "ModuleAccountI"];
165+
}
166+
167+
// Bech32PrefixRequest is the request type for Bech32Prefix rpc method.
168+
//
169+
// Since: cosmos-sdk 0.46
170+
message Bech32PrefixRequest {}
171+
172+
// Bech32PrefixResponse is the response type for Bech32Prefix rpc method.
173+
//
174+
// Since: cosmos-sdk 0.46
175+
message Bech32PrefixResponse {
176+
string bech32_prefix = 1;
177+
}
178+
179+
// AddressBytesToStringRequest is the request type for AddressString rpc method.
180+
//
181+
// Since: cosmos-sdk 0.46
182+
message AddressBytesToStringRequest {
183+
bytes address_bytes = 1;
184+
}
185+
186+
// AddressBytesToStringResponse is the response type for AddressString rpc method.
187+
//
188+
// Since: cosmos-sdk 0.46
189+
message AddressBytesToStringResponse {
190+
string address_string = 1;
191+
}
192+
140193
// QueryModuleAccountByNameRequest is the request type for the Query/ModuleAccountByName RPC method.
141194
message QueryModuleAccountByNameRequest {
142195
string name = 1;

tests/e2e/auth/suite.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,82 @@ func (s *E2ETestSuite) TestMultisignBatch() {
14331433
}
14341434
}
14351435

1436+
func (s *IntegrationTestSuite) TestGetAccountsCmd() {
1437+
val := s.network.Validators[0]
1438+
clientCtx := val.ClientCtx
1439+
1440+
out, err := clitestutil.ExecTestCLICmd(clientCtx, authcli.GetAccountsCmd(), []string{
1441+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
1442+
})
1443+
s.Require().NoError(err)
1444+
1445+
var res authtypes.QueryAccountsResponse
1446+
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res))
1447+
s.Require().NotEmpty(res.Accounts)
1448+
}
1449+
1450+
func (s *IntegrationTestSuite) TestQueryModuleAccountByNameCmd() {
1451+
val := s.network.Validators[0]
1452+
1453+
testCases := []struct {
1454+
name string
1455+
moduleName string
1456+
expectErr bool
1457+
}{
1458+
{
1459+
"invalid module name",
1460+
"gover",
1461+
true,
1462+
},
1463+
{
1464+
"valid module name",
1465+
"mint",
1466+
false,
1467+
},
1468+
}
1469+
1470+
for _, tc := range testCases {
1471+
tc := tc
1472+
s.Run(tc.name, func() {
1473+
clientCtx := val.ClientCtx
1474+
1475+
out, err := clitestutil.ExecTestCLICmd(clientCtx, authcli.QueryModuleAccountByNameCmd(), []string{
1476+
tc.moduleName,
1477+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
1478+
})
1479+
if tc.expectErr {
1480+
s.Require().Error(err)
1481+
s.Require().NotEqual("internal", err.Error())
1482+
} else {
1483+
var res authtypes.QueryModuleAccountByNameResponse
1484+
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res))
1485+
1486+
var account authtypes.AccountI
1487+
err := val.ClientCtx.InterfaceRegistry.UnpackAny(res.Account, &account)
1488+
s.Require().NoError(err)
1489+
1490+
moduleAccount, ok := account.(authtypes.ModuleAccountI)
1491+
s.Require().True(ok)
1492+
s.Require().Equal(tc.moduleName, moduleAccount.GetName())
1493+
}
1494+
})
1495+
}
1496+
}
1497+
1498+
func (s *IntegrationTestSuite) TestQueryModuleAccountsCmd() {
1499+
val := s.network.Validators[0]
1500+
clientCtx := val.ClientCtx
1501+
1502+
out, err := clitestutil.ExecTestCLICmd(clientCtx, authcli.QueryModuleAccountsCmd(), []string{
1503+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
1504+
})
1505+
s.Require().NoError(err)
1506+
1507+
var res authtypes.QueryModuleAccountsResponse
1508+
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res))
1509+
s.Require().NotEmpty(res.Accounts)
1510+
}
1511+
14361512
func TestGetBroadcastCommandOfflineFlag(t *testing.T) {
14371513
clientCtx := client.Context{}.WithOffline(true)
14381514
clientCtx = clientCtx.WithTxConfig(simapp.MakeTestEncodingConfig().TxConfig) //nolint:staticcheck

x/auth/client/cli/query.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func GetQueryCmd() *cobra.Command {
4444
GetAccountAddressByIDCmd(),
4545
GetAccountsCmd(),
4646
QueryParamsCmd(),
47+
QueryModuleAccountsCmd(),
4748
QueryModuleAccountByNameCmd(),
4849
)
4950

@@ -241,6 +242,40 @@ func QueryModuleAccountByNameCmd() *cobra.Command {
241242
return cmd
242243
}
243244

245+
// QueryModuleAccountByNameCmd returns a command to
246+
func QueryModuleAccountByNameCmd() *cobra.Command {
247+
cmd := &cobra.Command{
248+
Use: "module-account [module-name]",
249+
Short: "Query module account info by module name",
250+
Args: cobra.ExactArgs(1),
251+
Example: fmt.Sprintf("%s q auth module-account auth", version.AppName),
252+
RunE: func(cmd *cobra.Command, args []string) error {
253+
clientCtx, err := client.GetClientQueryContext(cmd)
254+
if err != nil {
255+
return err
256+
}
257+
258+
moduleName := args[0]
259+
if len(moduleName) == 0 {
260+
return fmt.Errorf("module name should not be empty")
261+
}
262+
263+
queryClient := types.NewQueryClient(clientCtx)
264+
265+
res, err := queryClient.ModuleAccountByName(context.Background(), &types.QueryModuleAccountByNameRequest{Name: moduleName})
266+
if err != nil {
267+
return err
268+
}
269+
270+
return clientCtx.PrintProto(res)
271+
},
272+
}
273+
274+
flags.AddQueryFlagsToCmd(cmd)
275+
276+
return cmd
277+
}
278+
244279
// QueryTxsByEventsCmd returns a command to search through transactions by events.
245280
func QueryTxsByEventsCmd() *cobra.Command {
246281
cmd := &cobra.Command{

x/auth/keeper/grpc_query.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,38 @@ func (ak accountKeeper) Params(c context.Context, req *types.QueryParamsRequest)
115115
return &types.QueryParamsResponse{Params: params}, nil
116116
}
117117

118+
// ModuleAccounts returns all the existing Module Accounts
119+
func (ak AccountKeeper) ModuleAccounts(c context.Context, req *types.QueryModuleAccountsRequest) (*types.QueryModuleAccountsResponse, error) {
120+
if req == nil {
121+
return nil, status.Error(codes.InvalidArgument, "empty request")
122+
}
123+
124+
ctx := sdk.UnwrapSDKContext(c)
125+
126+
// For deterministic output, sort the permAddrs by module name.
127+
sortedPermAddrs := make([]string, 0, len(ak.permAddrs))
128+
for moduleName := range ak.permAddrs {
129+
sortedPermAddrs = append(sortedPermAddrs, moduleName)
130+
}
131+
sort.Strings(sortedPermAddrs)
132+
133+
modAccounts := make([]*codectypes.Any, 0, len(ak.permAddrs))
134+
135+
for _, moduleName := range sortedPermAddrs {
136+
account := ak.GetModuleAccount(ctx, moduleName)
137+
if account == nil {
138+
return nil, status.Errorf(codes.NotFound, "account %s not found", moduleName)
139+
}
140+
any, err := codectypes.NewAnyWithValue(account)
141+
if err != nil {
142+
return nil, status.Errorf(codes.Internal, err.Error())
143+
}
144+
modAccounts = append(modAccounts, any)
145+
}
146+
147+
return &types.QueryModuleAccountsResponse{Accounts: modAccounts}, nil
148+
}
149+
118150
// ModuleAccountByName returns module account by module name
119151
func (ak AccountKeeper) ModuleAccountByName(c context.Context, req *types.QueryModuleAccountByNameRequest) (*types.QueryModuleAccountByNameResponse, error) {
120152
if req == nil {
@@ -139,3 +171,39 @@ func (ak AccountKeeper) ModuleAccountByName(c context.Context, req *types.QueryM
139171

140172
return &types.QueryModuleAccountByNameResponse{Account: any}, nil
141173
}
174+
175+
// Bech32Prefix returns the keeper internally stored bech32 prefix.
176+
func (ak AccountKeeper) Bech32Prefix(ctx context.Context, req *types.Bech32PrefixRequest) (*types.Bech32PrefixResponse, error) {
177+
bech32Prefix, err := ak.getBech32Prefix()
178+
if err != nil {
179+
return nil, err
180+
}
181+
182+
return &types.Bech32PrefixResponse{Bech32Prefix: bech32Prefix}, nil
183+
}
184+
185+
// AddressBytesToString converts an address from bytes to string, using the
186+
// keeper's bech32 prefix.
187+
func (ak AccountKeeper) AddressBytesToString(ctx context.Context, req *types.AddressBytesToStringRequest) (*types.AddressBytesToStringResponse, error) {
188+
if req == nil {
189+
return nil, status.Errorf(codes.InvalidArgument, "empty request")
190+
}
191+
192+
if len(req.Name) == 0 {
193+
return nil, status.Error(codes.InvalidArgument, "module name is empty")
194+
}
195+
196+
ctx := sdk.UnwrapSDKContext(c)
197+
moduleName := req.Name
198+
199+
account := ak.GetModuleAccount(ctx, moduleName)
200+
if account == nil {
201+
return nil, status.Errorf(codes.NotFound, "account %s not found", moduleName)
202+
}
203+
any, err := codectypes.NewAnyWithValue(account)
204+
if err != nil {
205+
return nil, status.Errorf(codes.Internal, err.Error())
206+
}
207+
208+
return &types.QueryModuleAccountByNameResponse{Account: any}, nil
209+
}

x/auth/keeper/grpc_query_test.go

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,69 @@ func (suite *KeeperTestSuite) TestGRPCQueryModuleAccounts() {
327327
}
328328
}
329329

330-
func (suite *KeeperTestSuite) TestGRPCQueryParams() {
331-
var (
332-
req *types.QueryParamsRequest
333-
expParams types.Params
334-
)
330+
func (suite *KeeperTestSuite) TestGRPCQueryModuleAccountByName() {
331+
var req *types.QueryModuleAccountByNameRequest
332+
333+
testCases := []struct {
334+
msg string
335+
malleate func()
336+
expPass bool
337+
posttests func(res *types.QueryModuleAccountByNameResponse)
338+
}{
339+
{
340+
"success",
341+
func() {
342+
req = &types.QueryModuleAccountByNameRequest{Name: "mint"}
343+
},
344+
true,
345+
func(res *types.QueryModuleAccountByNameResponse) {
346+
var account types.AccountI
347+
err := suite.app.InterfaceRegistry().UnpackAny(res.Account, &account)
348+
suite.Require().NoError(err)
349+
350+
moduleAccount, ok := account.(types.ModuleAccountI)
351+
suite.Require().True(ok)
352+
suite.Require().Equal(moduleAccount.GetName(), "mint")
353+
},
354+
},
355+
{
356+
"invalid module name",
357+
func() {
358+
req = &types.QueryModuleAccountByNameRequest{Name: "gover"}
359+
},
360+
false,
361+
func(res *types.QueryModuleAccountByNameResponse) {
362+
},
363+
},
364+
}
365+
366+
for _, tc := range testCases {
367+
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
368+
suite.SetupTest() // reset
369+
tc.malleate()
370+
ctx := sdk.WrapSDKContext(suite.ctx)
371+
res, err := suite.queryClient.ModuleAccountByName(ctx, req)
372+
if tc.expPass {
373+
suite.Require().NoError(err)
374+
suite.Require().NotNil(res)
375+
} else {
376+
suite.Require().Error(err)
377+
suite.Require().Nil(res)
378+
}
379+
380+
tc.posttests(res)
381+
})
382+
}
383+
}
384+
385+
func (suite *KeeperTestSuite) TestBech32Prefix() {
386+
suite.SetupTest() // reset
387+
req := &types.Bech32PrefixRequest{}
388+
res, err := suite.queryClient.Bech32Prefix(context.Background(), req)
389+
suite.Require().NoError(err)
390+
suite.Require().NotNil(res)
391+
suite.Require().Equal(sdk.Bech32MainPrefix, res.Bech32Prefix)
392+
}
335393

336394
testCases := []struct {
337395
msg string

0 commit comments

Comments
 (0)