feat(x/group): autocli tx support (#18102)
This commit is contained in:
parent
fe99b0c2f8
commit
b8b32cf494
@ -171,18 +171,14 @@ func (s *E2ETestSuite) SetupSuite() {
|
||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String())
|
||||
s.Require().NoError(clitestutil.CheckTxCode(s.network, val.ClientCtx, txResp.TxHash, 0))
|
||||
|
||||
msg := &group.MsgVote{
|
||||
ProposalId: uint64(1),
|
||||
Voter: val.Address.String(),
|
||||
Option: group.VOTE_OPTION_YES,
|
||||
}
|
||||
|
||||
// vote
|
||||
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, client.MsgVoteCmd(),
|
||||
append(
|
||||
[]string{
|
||||
"1",
|
||||
val.Address.String(),
|
||||
"VOTE_OPTION_YES",
|
||||
"",
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
)
|
||||
out, err = clitestutil.SubmitTestTx(val.ClientCtx, msg, val.Address, clitestutil.TestTxConfig{})
|
||||
s.Require().NoError(err, out.String())
|
||||
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String())
|
||||
s.Require().NoError(clitestutil.CheckTxCode(s.network, val.ClientCtx, txResp.TxHash, 0))
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@ -38,19 +37,11 @@ func TxCmd(name string, ac address.Codec) *cobra.Command {
|
||||
|
||||
txCmd.AddCommand(
|
||||
MsgCreateGroupCmd(),
|
||||
MsgUpdateGroupAdminCmd(),
|
||||
MsgUpdateGroupMetadataCmd(),
|
||||
MsgUpdateGroupMembersCmd(),
|
||||
MsgCreateGroupWithPolicyCmd(),
|
||||
MsgCreateGroupPolicyCmd(),
|
||||
MsgUpdateGroupPolicyAdminCmd(),
|
||||
MsgUpdateGroupPolicyDecisionPolicyCmd(ac),
|
||||
MsgUpdateGroupPolicyMetadataCmd(),
|
||||
MsgWithdrawProposalCmd(),
|
||||
MsgSubmitProposalCmd(),
|
||||
MsgVoteCmd(),
|
||||
MsgExecCmd(),
|
||||
MsgLeaveGroupCmd(),
|
||||
NewCmdDraftProposal(),
|
||||
)
|
||||
|
||||
@ -58,6 +49,8 @@ func TxCmd(name string, ac address.Codec) *cobra.Command {
|
||||
}
|
||||
|
||||
// MsgCreateGroupCmd creates a CLI command for Msg/CreateGroup.
|
||||
//
|
||||
// This command is being handled better here, not converting to autocli
|
||||
func MsgCreateGroupCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create-group [admin] [metadata] [members-json-file]",
|
||||
@ -122,6 +115,8 @@ Where members.json contains:
|
||||
}
|
||||
|
||||
// MsgUpdateGroupMembersCmd creates a CLI command for Msg/UpdateGroupMembers.
|
||||
//
|
||||
// This command is being handled better here, not converting to autocli
|
||||
func MsgUpdateGroupMembersCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-group-members [admin] [group-id] [members-json-file]",
|
||||
@ -195,92 +190,9 @@ Set a member's weight to "0" to delete it.
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgUpdateGroupAdminCmd creates a CLI command for Msg/UpdateGroupAdmin.
|
||||
func MsgUpdateGroupAdminCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-group-admin [admin] [group-id] [new-admin]",
|
||||
Short: "Update a group's admin",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupID, err := strconv.ParseUint(args[1], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if groupID == 0 {
|
||||
return errZeroGroupID
|
||||
}
|
||||
|
||||
if strings.EqualFold(args[0], args[2]) {
|
||||
return errors.New("new admin cannot be the same as the current admin")
|
||||
}
|
||||
|
||||
msg := &group.MsgUpdateGroupAdmin{
|
||||
Admin: clientCtx.GetFromAddress().String(),
|
||||
NewAdmin: args[2],
|
||||
GroupId: groupID,
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgUpdateGroupMetadataCmd creates a CLI command for Msg/UpdateGroupMetadata.
|
||||
func MsgUpdateGroupMetadataCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-group-metadata [admin] [group-id] [metadata]",
|
||||
Short: "Update a group's metadata",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := cmd.Flags().Set(flags.FlagFrom, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupID, err := strconv.ParseUint(args[1], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if groupID == 0 {
|
||||
return errZeroGroupID
|
||||
}
|
||||
|
||||
msg := &group.MsgUpdateGroupMetadata{
|
||||
Admin: clientCtx.GetFromAddress().String(),
|
||||
Metadata: args[2],
|
||||
GroupId: groupID,
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgCreateGroupWithPolicyCmd creates a CLI command for Msg/CreateGroupWithPolicy.
|
||||
//
|
||||
// This command is being handled better here, not converting to autocli
|
||||
func MsgCreateGroupWithPolicyCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create-group-with-policy [admin] [group-metadata] [group-policy-metadata] [members-json-file] [decision-policy-json-file]",
|
||||
@ -379,6 +291,8 @@ and policy.json contains:
|
||||
}
|
||||
|
||||
// MsgCreateGroupPolicyCmd creates a CLI command for Msg/CreateGroupPolicy.
|
||||
//
|
||||
// This command is being handled better here, not converting to autocli
|
||||
func MsgCreateGroupPolicyCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create-group-policy [admin] [group-id] [metadata] [decision-policy-json-file]",
|
||||
@ -456,42 +370,9 @@ Here, we can use percentage decision policy when needed, where 0 < percentage <=
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgUpdateGroupPolicyAdminCmd creates a CLI command for Msg/UpdateGroupPolicyAdmin.
|
||||
func MsgUpdateGroupPolicyAdminCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-group-policy-admin [admin] [group-policy-account] [new-admin]",
|
||||
Short: "Update a group policy admin",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.EqualFold(args[0], args[2]) {
|
||||
return errors.New("new admin cannot be the same as the current admin")
|
||||
}
|
||||
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &group.MsgUpdateGroupPolicyAdmin{
|
||||
Admin: clientCtx.GetFromAddress().String(),
|
||||
GroupPolicyAddress: args[1],
|
||||
NewAdmin: args[2],
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgUpdateGroupPolicyDecisionPolicyCmd creates a CLI command for Msg/UpdateGroupPolicyDecisionPolicy.
|
||||
//
|
||||
// This command is being handled better here, not converting to autocli
|
||||
func MsgUpdateGroupPolicyDecisionPolicyCmd(ac address.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-group-policy-decision-policy [admin] [group-policy-account] [decision-policy-json-file]",
|
||||
@ -536,38 +417,9 @@ func MsgUpdateGroupPolicyDecisionPolicyCmd(ac address.Codec) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgUpdateGroupPolicyMetadataCmd creates a CLI command for Msg/UpdateGroupPolicyMetadata.
|
||||
func MsgUpdateGroupPolicyMetadataCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update-group-policy-metadata [admin] [group-policy-account] [new-metadata]",
|
||||
Short: "Update a group policy metadata",
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &group.MsgUpdateGroupPolicyMetadata{
|
||||
Admin: clientCtx.GetFromAddress().String(),
|
||||
GroupPolicyAddress: args[1],
|
||||
Metadata: args[2],
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgSubmitProposalCmd creates a CLI command for Msg/SubmitProposal.
|
||||
//
|
||||
// This command is being handled better here, not converting to autocli
|
||||
func MsgSubmitProposalCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "submit-proposal [proposal_json_file]",
|
||||
@ -659,186 +511,3 @@ metadata example:
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgWithdrawProposalCmd creates a CLI command for Msg/WithdrawProposal.
|
||||
func MsgWithdrawProposalCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "withdraw-proposal [proposal-id] [group-policy-admin-or-proposer]",
|
||||
Short: "Withdraw a submitted proposal",
|
||||
Long: `Withdraw a submitted proposal.
|
||||
|
||||
Parameters:
|
||||
proposal-id: unique ID of the proposal.
|
||||
group-policy-admin-or-proposer: either admin of the group policy or one the proposer of the proposal.
|
||||
Note: --from flag will be ignored here.
|
||||
`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := cmd.Flags().Set(flags.FlagFrom, args[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if proposalID == 0 {
|
||||
return fmt.Errorf("invalid proposal id: %d", proposalID)
|
||||
}
|
||||
|
||||
msg := &group.MsgWithdrawProposal{
|
||||
ProposalId: proposalID,
|
||||
Address: clientCtx.GetFromAddress().String(),
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgVoteCmd creates a CLI command for Msg/Vote.
|
||||
func MsgVoteCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "vote [proposal-id] [voter] [vote-option] [metadata]",
|
||||
Short: "Vote on a proposal",
|
||||
Long: `Vote on a proposal.
|
||||
|
||||
Parameters:
|
||||
proposal-id: unique ID of the proposal
|
||||
voter: voter account addresses.
|
||||
vote-option: choice of the voter(s)
|
||||
VOTE_OPTION_UNSPECIFIED: no-op
|
||||
VOTE_OPTION_NO: no
|
||||
VOTE_OPTION_YES: yes
|
||||
VOTE_OPTION_ABSTAIN: abstain
|
||||
VOTE_OPTION_NO_WITH_VETO: no-with-veto
|
||||
Metadata: metadata for the vote
|
||||
`,
|
||||
Args: cobra.ExactArgs(4),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := cmd.Flags().Set(flags.FlagFrom, args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
voteOption, err := group.VoteOptionFromString(args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
execStr, _ := cmd.Flags().GetString(FlagExec)
|
||||
|
||||
msg := &group.MsgVote{
|
||||
ProposalId: proposalID,
|
||||
Voter: args[1],
|
||||
Option: voteOption,
|
||||
Metadata: args[3],
|
||||
Exec: execFromString(execStr),
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(FlagExec, "", "Set to 1 to try to execute proposal immediately after voting")
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgExecCmd creates a CLI command for Msg/Exec.
|
||||
func MsgExecCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "exec [proposal-id]",
|
||||
Short: "Execute a proposal",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposalID, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &group.MsgExec{
|
||||
ProposalId: proposalID,
|
||||
Executor: clientCtx.GetFromAddress().String(),
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MsgLeaveGroupCmd creates a CLI command for Msg/LeaveGroup.
|
||||
func MsgLeaveGroupCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "leave-group [member-address] [group-id]",
|
||||
Short: "Remove member from the group",
|
||||
Long: `Remove member from the group
|
||||
|
||||
Parameters:
|
||||
group-id: unique id of the group
|
||||
member-address: account address of the group member
|
||||
Note, the '--from' flag is ignored as it is implied from [member-address]
|
||||
`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := cmd.Flags().Set(flags.FlagFrom, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupID, err := strconv.ParseUint(args[1], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if groupID == 0 {
|
||||
return errZeroGroupID
|
||||
}
|
||||
|
||||
msg := &group.MsgLeaveGroup{
|
||||
Address: clientCtx.GetFromAddress().String(),
|
||||
GroupId: groupID,
|
||||
}
|
||||
|
||||
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -239,231 +239,6 @@ func (s *CLITestSuite) TestTxCreateGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxUpdateGroupAdmin() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 2)
|
||||
|
||||
cmd := groupcli.MsgUpdateGroupAdminCmd()
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
cmd.SetContext(ctx)
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
groupIDs := make([]string, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
validMembers := fmt.Sprintf(`{"members": [{
|
||||
"address": "%s",
|
||||
"weight": "1",
|
||||
"metadata": "%s"
|
||||
}]}`, accounts[0].Address.String(), validMetadata)
|
||||
validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers)
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, groupcli.MsgCreateGroupCmd(),
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
validMetadata,
|
||||
validMembersFile.Name(),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
)
|
||||
s.Require().NoError(err, out.String())
|
||||
groupIDs[i] = fmt.Sprintf("%d", i+1)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
ctxGen func() client.Context
|
||||
args []string
|
||||
expCmdOutput string
|
||||
expectErrMsg string
|
||||
}{
|
||||
{
|
||||
"correct data",
|
||||
func() client.Context {
|
||||
bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{})
|
||||
c := clitestutil.NewMockCometRPC(abci.ResponseQuery{
|
||||
Value: bz,
|
||||
})
|
||||
return s.baseCtx.WithClient(c)
|
||||
},
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
groupIDs[0],
|
||||
accounts[1].Address.String(),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", accounts[0].Address.String(), groupIDs[0], accounts[1].Address.String()),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"with amino-json",
|
||||
func() client.Context {
|
||||
bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{})
|
||||
c := clitestutil.NewMockCometRPC(abci.ResponseQuery{
|
||||
Value: bz,
|
||||
})
|
||||
return s.baseCtx.WithClient(c)
|
||||
},
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
groupIDs[1],
|
||||
accounts[1].Address.String(),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s --%s=%s", accounts[0].Address.String(), groupIDs[1], accounts[1].Address.String(), flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"group id invalid",
|
||||
func() client.Context {
|
||||
bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{})
|
||||
c := clitestutil.NewMockCometRPC(abci.ResponseQuery{
|
||||
Value: bz,
|
||||
})
|
||||
return s.baseCtx.WithClient(c)
|
||||
},
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
"",
|
||||
accounts[1].Address.String(),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", accounts[0].Address.String(), "", accounts[1].Address.String()),
|
||||
"strconv.ParseUint: parsing \"\": invalid syntax",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
s.Run(tc.name, func() {
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
if len(tc.args) != 0 {
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
}
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, cmd, tc.args)
|
||||
if tc.expectErrMsg != "" {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(out.String(), tc.expectErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
msg := &sdk.TxResponse{}
|
||||
s.Require().NoError(s.baseCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxUpdateGroupMetadata() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
|
||||
|
||||
cmd := groupcli.MsgUpdateGroupMetadataCmd()
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
expectErrMsg string
|
||||
}{
|
||||
{
|
||||
"correct data",
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
"1",
|
||||
validMetadata,
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", accounts[0].Address.String(), "1", validMetadata),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"with amino-json",
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
"1",
|
||||
validMetadata,
|
||||
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s --%s=%s", accounts[0].Address.String(), "1", validMetadata, flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"invalid group id",
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
"abc",
|
||||
validMetadata,
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", accounts[0].Address.String(), "abc", validMetadata),
|
||||
"Error: strconv.ParseUint: parsing \"abc\"",
|
||||
},
|
||||
{
|
||||
"empty group id",
|
||||
append(
|
||||
[]string{
|
||||
accounts[0].Address.String(),
|
||||
"0",
|
||||
validMetadata,
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", accounts[0].Address.String(), "0", validMetadata),
|
||||
"group id cannot be 0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
s.Run(tc.name, func() {
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
if len(tc.args) != 0 {
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
}
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, cmd, tc.args)
|
||||
if tc.expectErrMsg != "" {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(out.String(), tc.expectErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
msg := &sdk.TxResponse{}
|
||||
s.Require().NoError(s.baseCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxUpdateGroupMembers() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 3)
|
||||
groupPolicyAddress := accounts[2]
|
||||
@ -836,107 +611,6 @@ func (s *CLITestSuite) TestTxCreateGroupPolicy() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxUpdateGroupPolicyAdmin() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 4)
|
||||
newAdmin := accounts[0]
|
||||
groupPolicyAdmin := accounts[1]
|
||||
groupPolicyAddress := accounts[2]
|
||||
|
||||
commonFlags := s.commonFlags
|
||||
commonFlags = append(commonFlags, fmt.Sprintf("--%s=%d", flags.FlagGas, 300000))
|
||||
|
||||
cmd := groupcli.MsgUpdateGroupPolicyAdminCmd()
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
expectErrMsg string
|
||||
}{
|
||||
{
|
||||
"correct data",
|
||||
append(
|
||||
[]string{
|
||||
groupPolicyAdmin.Address.String(),
|
||||
groupPolicyAddress.Address.String(),
|
||||
newAdmin.Address.String(),
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", groupPolicyAdmin.Address.String(), groupPolicyAddress.Address.String(), newAdmin.Address.String()),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"with amino-json",
|
||||
append(
|
||||
[]string{
|
||||
groupPolicyAdmin.Address.String(),
|
||||
groupPolicyAddress.Address.String(),
|
||||
newAdmin.Address.String(),
|
||||
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s --%s=%s", groupPolicyAdmin.Address.String(), groupPolicyAddress.Address.String(), newAdmin.Address.String(), flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"wrong admin",
|
||||
append(
|
||||
[]string{
|
||||
"wrong admin",
|
||||
groupPolicyAddress.Address.String(),
|
||||
newAdmin.Address.String(),
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", "wrong admin", groupPolicyAddress.Address.String(), newAdmin.Address.String()),
|
||||
"key not found",
|
||||
},
|
||||
{
|
||||
"identical admin and new admin",
|
||||
append(
|
||||
[]string{
|
||||
groupPolicyAdmin.Address.String(),
|
||||
groupPolicyAddress.Address.String(),
|
||||
groupPolicyAdmin.Address.String(),
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", groupPolicyAdmin.Address.String(), groupPolicyAddress.Address.String(), groupPolicyAdmin.Address.String()),
|
||||
"new admin cannot be the same as the current admin",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
s.Run(tc.name, func() {
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
if len(tc.args) != 0 {
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
}
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, cmd, tc.args)
|
||||
if tc.expectErrMsg != "" {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(out.String(), tc.expectErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
msg := &sdk.TxResponse{}
|
||||
s.Require().NoError(s.baseCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 3)
|
||||
newAdmin := accounts[0]
|
||||
@ -1041,93 +715,6 @@ func (s *CLITestSuite) TestTxUpdateGroupPolicyDecisionPolicy() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxUpdateGroupPolicyMetadata() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 2)
|
||||
groupPolicyAdmin := accounts[0].Address
|
||||
groupPolicyAddress := accounts[1].Address
|
||||
|
||||
commonFlags := s.commonFlags
|
||||
commonFlags = append(commonFlags, fmt.Sprintf("--%s=%d", flags.FlagGas, 300000))
|
||||
|
||||
cmd := groupcli.MsgUpdateGroupPolicyMetadataCmd()
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
expectErrMsg string
|
||||
}{
|
||||
{
|
||||
"correct data",
|
||||
append(
|
||||
[]string{
|
||||
groupPolicyAdmin.String(),
|
||||
groupPolicyAddress.String(),
|
||||
validMetadata,
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", groupPolicyAdmin.String(), groupPolicyAddress.String(), validMetadata),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"with amino-json",
|
||||
append(
|
||||
[]string{
|
||||
groupPolicyAdmin.String(),
|
||||
groupPolicyAddress.String(),
|
||||
validMetadata,
|
||||
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s --%s=%s", groupPolicyAdmin.String(), groupPolicyAddress.String(), validMetadata, flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"wrong admin",
|
||||
append(
|
||||
[]string{
|
||||
"wrong admin",
|
||||
groupPolicyAddress.String(),
|
||||
validMetadata,
|
||||
},
|
||||
commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", "wrong admin", groupPolicyAddress.String(), validMetadata),
|
||||
"key not found",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
s.Run(tc.name, func() {
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
if len(tc.args) != 0 {
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
}
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, cmd, tc.args)
|
||||
if tc.expectErrMsg != "" {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(out.String(), tc.expectErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
msg := &sdk.TxResponse{}
|
||||
s.Require().NoError(s.baseCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxSubmitProposal() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 2)
|
||||
groupPolicyAddress := accounts[1].Address
|
||||
@ -1227,190 +814,3 @@ func (s *CLITestSuite) TestTxSubmitProposal() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxVote() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 4)
|
||||
|
||||
cmd := groupcli.MsgVoteCmd()
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
ids := make([]string, 4)
|
||||
for i := 0; i < len(ids); i++ {
|
||||
ids[i] = fmt.Sprint(i + 1)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
expectErrMsg string
|
||||
}{
|
||||
{
|
||||
"invalid vote",
|
||||
append(
|
||||
[]string{
|
||||
ids[0],
|
||||
accounts[0].Address.String(),
|
||||
"AYE",
|
||||
"",
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", ids[0], accounts[0].Address.String(), "AYE"),
|
||||
"Error: 'AYE' is not a valid vote option",
|
||||
},
|
||||
{
|
||||
"correct data",
|
||||
append(
|
||||
[]string{
|
||||
ids[0],
|
||||
accounts[0].Address.String(),
|
||||
"VOTE_OPTION_YES",
|
||||
"",
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s", ids[0], accounts[0].Address.String(), "VOTE_OPTION_YES"),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"with try exec",
|
||||
append(
|
||||
[]string{
|
||||
ids[1],
|
||||
accounts[0].Address.String(),
|
||||
"VOTE_OPTION_YES",
|
||||
"",
|
||||
fmt.Sprintf("--%s=try", groupcli.FlagExec),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s %s --%s=try", ids[1], accounts[0].Address.String(), "VOTE_OPTION_YES", "", groupcli.FlagExec),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"with amino-json",
|
||||
append(
|
||||
[]string{
|
||||
ids[3],
|
||||
accounts[0].Address.String(),
|
||||
"VOTE_OPTION_YES",
|
||||
"",
|
||||
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s %s %s --%s=%s", ids[3], accounts[0].Address.String(), "VOTE_OPTION_YES", "", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
s.Run(tc.name, func() {
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
if len(tc.args) != 0 {
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
}
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, cmd, tc.args)
|
||||
if tc.expectErrMsg != "" {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(out.String(), tc.expectErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
msg := &sdk.TxResponse{}
|
||||
s.Require().NoError(s.baseCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CLITestSuite) TestTxWithdrawProposal() {
|
||||
accounts := testutil.CreateKeyringAccounts(s.T(), s.kr, 1)
|
||||
|
||||
cmd := groupcli.MsgWithdrawProposalCmd()
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
ids := make([]string, 2)
|
||||
ids[0] = "1"
|
||||
ids[1] = "2"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expCmdOutput string
|
||||
expectErrMsg string
|
||||
}{
|
||||
{
|
||||
"correct data",
|
||||
append(
|
||||
[]string{
|
||||
ids[0],
|
||||
accounts[0].Address.String(),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s", ids[0], accounts[0].Address.String()),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"wrong admin",
|
||||
append(
|
||||
[]string{
|
||||
ids[1],
|
||||
"wrongAdmin",
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s", ids[1], "wrongAdmin"),
|
||||
"key not found",
|
||||
},
|
||||
{
|
||||
"wrong proposal id",
|
||||
append(
|
||||
[]string{
|
||||
"abc",
|
||||
accounts[0].Address.String(),
|
||||
},
|
||||
s.commonFlags...,
|
||||
),
|
||||
fmt.Sprintf("%s %s", "abc", accounts[0].Address.String()),
|
||||
"Error: strconv.ParseUint: parsing \"abc\"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
s.Run(tc.name, func() {
|
||||
ctx := svrcmd.CreateExecuteContext(context.Background())
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
s.Require().NoError(client.SetCmdClientContextHandler(s.baseCtx, cmd))
|
||||
|
||||
if len(tc.args) != 0 {
|
||||
s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput)
|
||||
}
|
||||
|
||||
out, err := clitestutil.ExecTestCLICmd(s.baseCtx, cmd, tc.args)
|
||||
if tc.expectErrMsg != "" {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(out.String(), tc.expectErrMsg)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
msg := &sdk.TxResponse{}
|
||||
s.Require().NoError(s.baseCtx.Codec.UnmarshalJSON(out.Bytes(), msg), out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +124,88 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
|
||||
},
|
||||
},
|
||||
Tx: &autocliv1.ServiceCommandDescriptor{
|
||||
Service: groupv1.Query_ServiceDesc.ServiceName,
|
||||
Service: groupv1.Msg_ServiceDesc.ServiceName,
|
||||
EnhanceCustomCommand: true,
|
||||
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
||||
{
|
||||
RpcMethod: "UpdateGroupAdmin",
|
||||
Use: "update-group-admin [admin] [group-id] [new-admin]",
|
||||
Short: "Update a group's admin",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "admin"}, {ProtoField: "group_id"}, {ProtoField: "new_admin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "UpdateGroupMetadata",
|
||||
Use: "update-group-metadata [admin] [group-id] [metadata]",
|
||||
Short: "Update a group's metadata",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "admin"}, {ProtoField: "group_id"}, {ProtoField: "metadata"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "UpdateGroupPolicyAdmin",
|
||||
Use: "update-group-policy-admin [admin] [group-policy-account] [new-admin]",
|
||||
Short: "Update a group policy admin",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "admin"}, {ProtoField: "group_policy_address"}, {ProtoField: "new_admin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "UpdateGroupPolicyMetadata",
|
||||
Use: "update-group-policy-admin [admin] [group-policy-account] [new-admin]",
|
||||
Short: "Update a group policy admin",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "admin"}, {ProtoField: "group_policy_address"}, {ProtoField: "metadata"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "WithdrawProposal",
|
||||
Use: "withdraw-proposal [proposal-id] [group-policy-admin-or-proposer]",
|
||||
Short: "Withdraw a submitted proposal",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "proposal_id"}, {ProtoField: "address"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "Vote",
|
||||
Use: "vote [proposal-id] [voter] [vote-option] [metadata]",
|
||||
Long: `Vote on a proposal.
|
||||
Parameters:
|
||||
proposal-id: unique ID of the proposal
|
||||
voter: voter account addresses.
|
||||
vote-option: choice of the voter(s)
|
||||
VOTE_OPTION_UNSPECIFIED: no-op
|
||||
VOTE_OPTION_NO: no
|
||||
VOTE_OPTION_YES: yes
|
||||
VOTE_OPTION_ABSTAIN: abstain
|
||||
VOTE_OPTION_NO_WITH_VETO: no-with-veto
|
||||
Metadata: metadata for the vote
|
||||
`,
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "proposal_id"}, {ProtoField: "voter"}, {ProtoField: "option"}, {ProtoField: "metadata"},
|
||||
},
|
||||
FlagOptions: map[string]*autocliv1.FlagOptions{
|
||||
"exec": {Name: "exec", DefaultValue: "", Usage: "Set to 'try' for trying to execute proposal immediately after voting"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "Exec",
|
||||
Use: "exec [proposal-id]",
|
||||
Short: "Execute a proposal",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "proposal_id"},
|
||||
},
|
||||
},
|
||||
{
|
||||
RpcMethod: "LeaveGroup",
|
||||
Use: "leave-group [member-address] [group-id]",
|
||||
Short: "Remove member from the group",
|
||||
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
|
||||
{ProtoField: "address"}, {ProtoField: "group_id"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user