From 914af6044a75abe8f5ac93bcadef45fd70d2938e Mon Sep 17 00:00:00 2001 From: Marie Gauthier Date: Wed, 15 Dec 2021 16:17:28 +0100 Subject: [PATCH] feat: Add CLI to group module (#10663) ## Description Closes: #9899 --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- x/group/client/cli/query.go | 440 +++++++ x/group/client/cli/tx.go | 650 +++++++++++ x/group/client/cli/util.go | 38 + x/group/client/testutil/cli_test.go | 18 + x/group/client/testutil/query.go | 671 +++++++++++ x/group/client/testutil/tx.go | 1642 +++++++++++++++++++++++++++ x/group/go.mod | 17 +- x/group/go.sum | 49 +- x/group/module/module.go | 11 +- x/group/types.go | 10 + 10 files changed, 3515 insertions(+), 31 deletions(-) create mode 100644 x/group/client/cli/query.go create mode 100644 x/group/client/cli/tx.go create mode 100644 x/group/client/cli/util.go create mode 100644 x/group/client/testutil/cli_test.go create mode 100644 x/group/client/testutil/query.go create mode 100644 x/group/client/testutil/tx.go diff --git a/x/group/client/cli/query.go b/x/group/client/cli/query.go new file mode 100644 index 0000000000..c80a93afd3 --- /dev/null +++ b/x/group/client/cli/query.go @@ -0,0 +1,440 @@ +package cli + +import ( + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/x/group" + "github.com/spf13/cobra" +) + +// QueryCmd returns the cli query commands for the group module. +func QueryCmd(name string) *cobra.Command { + queryCmd := &cobra.Command{ + Use: name, + Short: "Querying commands for the group module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + QueryGroupInfoCmd(), + QueryGroupAccountInfoCmd(), + QueryGroupMembersCmd(), + QueryGroupsByAdminCmd(), + QueryGroupAccountsByGroupCmd(), + QueryGroupAccountsByAdminCmd(), + QueryProposalCmd(), + QueryProposalsByGroupAccountCmd(), + QueryVoteByProposalVoterCmd(), + QueryVotesByProposalCmd(), + QueryVotesByVoterCmd(), + ) + + return queryCmd +} + +// QueryGroupInfoCmd creates a CLI command for Query/GroupInfo. +func QueryGroupInfoCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "group-info [id]", + Short: "Query for group info by group id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + groupID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.GroupInfo(cmd.Context(), &group.QueryGroupInfoRequest{ + GroupId: groupID, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res.Info) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryGroupAccountInfoCmd creates a CLI command for Query/GroupAccountInfo. +func QueryGroupAccountInfoCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "group-account-info [group-account]", + Short: "Query for group account info by group account address", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.GroupAccountInfo(cmd.Context(), &group.QueryGroupAccountInfoRequest{ + Address: args[0], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res.Info) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryGroupMembersCmd creates a CLI command for Query/GroupMembers. +func QueryGroupMembersCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "group-members [id]", + Short: "Query for group members by group id with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + groupID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.GroupMembers(cmd.Context(), &group.QueryGroupMembersRequest{ + GroupId: groupID, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryGroupsByAdminCmd creates a CLI command for Query/GroupsByAdmin. +func QueryGroupsByAdminCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "groups-by-admin [admin]", + Short: "Query for groups by admin account address with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.GroupsByAdmin(cmd.Context(), &group.QueryGroupsByAdminRequest{ + Admin: args[0], + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryGroupAccountsByGroupCmd creates a CLI command for Query/GroupAccountsByGroup. +func QueryGroupAccountsByGroupCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "group-accounts-by-group [group-id]", + Short: "Query for group accounts by group id with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + groupID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.GroupAccountsByGroup(cmd.Context(), &group.QueryGroupAccountsByGroupRequest{ + GroupId: groupID, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryGroupAccountsByAdminCmd creates a CLI command for Query/GroupAccountsByAdmin. +func QueryGroupAccountsByAdminCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "group-accounts-by-admin [admin]", + Short: "Query for group accounts by admin account address with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.GroupAccountsByAdmin(cmd.Context(), &group.QueryGroupAccountsByAdminRequest{ + Admin: args[0], + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryProposalCmd creates a CLI command for Query/Proposal. +func QueryProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "proposal [id]", + Short: "Query for proposal by id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + proposalID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.Proposal(cmd.Context(), &group.QueryProposalRequest{ + ProposalId: proposalID, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryProposalsByGroupAccountCmd creates a CLI command for Query/ProposalsByGroupAccount. +func QueryProposalsByGroupAccountCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "proposals-by-group-account [group-account]", + Short: "Query for proposals by group account address with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.ProposalsByGroupAccount(cmd.Context(), &group.QueryProposalsByGroupAccountRequest{ + Address: args[0], + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryVoteByProposalVoterCmd creates a CLI command for Query/VoteByProposalVoter. +func QueryVoteByProposalVoterCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "vote [proposal-id] [voter]", + Short: "Query for vote by proposal id and voter account address", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + proposalID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.VoteByProposalVoter(cmd.Context(), &group.QueryVoteByProposalVoterRequest{ + ProposalId: proposalID, + Voter: args[1], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryVotesByProposalCmd creates a CLI command for Query/VotesByProposal. +func QueryVotesByProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "votes-by-proposal [proposal-id]", + Short: "Query for votes by proposal id with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + proposalID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.VotesByProposal(cmd.Context(), &group.QueryVotesByProposalRequest{ + ProposalId: proposalID, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// QueryVotesByVoterCmd creates a CLI command for Query/VotesByVoter. +func QueryVotesByVoterCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "votes-by-voter [voter]", + Short: "Query for votes by voter account address with pagination flags", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + queryClient := group.NewQueryClient(clientCtx) + + res, err := queryClient.VotesByVoter(cmd.Context(), &group.QueryVotesByVoterRequest{ + Voter: args[0], + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/group/client/cli/tx.go b/x/group/client/cli/tx.go new file mode 100644 index 0000000000..8ef9633ce6 --- /dev/null +++ b/x/group/client/cli/tx.go @@ -0,0 +1,650 @@ +package cli + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/version" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + "github.com/cosmos/cosmos-sdk/x/group" +) + +const ( + FlagExec = "exec" + ExecTry = "try" +) + +// TxCmd returns a root CLI command handler for all x/group transaction commands. +func TxCmd(name string) *cobra.Command { + txCmd := &cobra.Command{ + Use: name, + Short: "Group transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + MsgCreateGroupCmd(), + MsgUpdateGroupAdminCmd(), + MsgUpdateGroupMetadataCmd(), + MsgUpdateGroupMembersCmd(), + MsgCreateGroupAccountCmd(), + MsgUpdateGroupAccountAdminCmd(), + MsgUpdateGroupAccountDecisionPolicyCmd(), + MsgUpdateGroupAccountMetadataCmd(), + MsgCreateProposalCmd(), + MsgVoteCmd(), + MsgExecCmd(), + ) + + return txCmd +} + +// MsgCreateGroupCmd creates a CLI command for Msg/CreateGroup. +func MsgCreateGroupCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-group [admin] [metadata] [members-json-file]", + Short: "Create a group which is an aggregation " + + "of member accounts with associated weights and " + + "an administrator account. Note, the '--from' flag is " + + "ignored as it is implied from [admin].", + Long: strings.TrimSpace( + fmt.Sprintf(`Create a group which is an aggregation of member accounts with associated weights and +an administrator account. Note, the '--from' flag is ignored as it is implied from [admin]. +Members accounts can be given through a members JSON file that contains an array of members. + +Example: +$ %s tx group create-group [admin] [metadata] [members-json-file] + +Where members.json contains: + +{ + "members": [ + { + "address": "addr1", + "weight": "1", + "metadata": "some metadata" + }, + { + "address": "addr2", + "weight": "1", + "metadata": "some metadata" + } + ] +} +`, + version.AppName, + ), + ), + 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 + } + + members, err := parseMembers(clientCtx, args[2]) + if err != nil { + return err + } + + metadata, err := base64.StdEncoding.DecodeString(args[1]) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required") + } + + msg := &group.MsgCreateGroup{ + Admin: clientCtx.GetFromAddress().String(), + Members: members, + Metadata: metadata, + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// MsgUpdateGroupMembersCmd creates a CLI command for Msg/UpdateGroupMembers. +func MsgUpdateGroupMembersCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-group-members [admin] [group-id] [members-json-file]", + Short: "Update a group's members. Set a member's weight to \"0\" to delete it.", + Long: strings.TrimSpace( + fmt.Sprintf(`Update a group's members + +Example: +$ %s tx group update-group-members [admin] [group-id] [members-json-file] + +Where members.json contains: + +{ + "members": [ + { + "address": "addr1", + "weight": "1", + "metadata": "some new metadata" + }, + { + "address": "addr2", + "weight": "0", + "metadata": "some metadata" + } + ] +} + +Set a member's weight to "0" to delete it. +`, + version.AppName, + ), + ), + 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 + } + + members, err := parseMembers(clientCtx, args[2]) + if err != nil { + return err + } + + groupID, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + msg := &group.MsgUpdateGroupMembers{ + Admin: clientCtx.GetFromAddress().String(), + MemberUpdates: members, + GroupId: groupID, + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + 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 { + 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 + } + + msg := &group.MsgUpdateGroupAdmin{ + Admin: clientCtx.GetFromAddress().String(), + NewAdmin: args[2], + GroupId: groupID, + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + 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 + } + + b, err := base64.StdEncoding.DecodeString(args[2]) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required") + } + + msg := &group.MsgUpdateGroupMetadata{ + Admin: clientCtx.GetFromAddress().String(), + Metadata: b, + GroupId: groupID, + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// MsgCreateGroupAccountCmd creates a CLI command for Msg/CreateGroupAccount. +func MsgCreateGroupAccountCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-group-account [admin] [group-id] [metadata] [decision-policy]", + Short: "Create a group account which is an account " + + "associated with a group and a decision policy. " + + "Note, the '--from' flag is " + + "ignored as it is implied from [admin].", + Long: strings.TrimSpace( + fmt.Sprintf(`Create a group account which is an account associated with a group and a decision policy. +Note, the '--from' flag is ignored as it is implied from [admin]. + +Example: +$ %s tx group create-group-account [admin] [group-id] [metadata] \ +'{"@type":"/cosmos.group.v1beta1.ThresholdDecisionPolicy", "threshold":"1", "timeout":"1s"}' +`, + version.AppName, + ), + ), + Args: cobra.ExactArgs(4), + 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 + } + + var policy group.DecisionPolicy + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[3]), &policy); err != nil { + return err + } + + b, err := base64.StdEncoding.DecodeString(args[2]) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required") + } + + msg, err := group.NewMsgCreateGroupAccount( + clientCtx.GetFromAddress(), + groupID, + b, + policy, + ) + if err != nil { + return err + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// MsgUpdateGroupAccountAdminCmd creates a CLI command for Msg/UpdateGroupAccountAdmin. +func MsgUpdateGroupAccountAdminCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-group-account-admin [admin] [group-account] [new-admin]", + Short: "Update a group account admin", + 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 + } + + msg := &group.MsgUpdateGroupAccountAdmin{ + Admin: clientCtx.GetFromAddress().String(), + Address: args[1], + NewAdmin: args[2], + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// MsgUpdateGroupAccountDecisionPolicyCmd creates a CLI command for Msg/UpdateGroupAccountDecisionPolicy. +func MsgUpdateGroupAccountDecisionPolicyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-group-account-policy [admin] [group-account] [decision-policy]", + Short: "Update a group account decision policy", + 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 + } + + var policy group.DecisionPolicy + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[2]), &policy); err != nil { + return err + } + + accountAddress, err := sdk.AccAddressFromBech32(args[1]) + if err != nil { + return err + } + + msg, err := group.NewMsgUpdateGroupAccountDecisionPolicyRequest( + clientCtx.GetFromAddress(), + accountAddress, + policy, + ) + if err != nil { + return err + } + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// MsgUpdateGroupAccountMetadataCmd creates a CLI command for Msg/MsgUpdateGroupAccountMetadata. +func MsgUpdateGroupAccountMetadataCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-group-account-metadata [admin] [group-account] [new-metadata]", + Short: "Update a group account 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 + } + + b, err := base64.StdEncoding.DecodeString(args[2]) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required") + } + + msg := &group.MsgUpdateGroupAccountMetadata{ + Admin: clientCtx.GetFromAddress().String(), + Address: args[1], + Metadata: b, + } + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// MsgCreateProposalCmd creates a CLI command for Msg/CreateProposal. +func MsgCreateProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-proposal [group-account] [proposer[,proposer]*] [msg_tx_json_file] [metadata]", + Short: "Submit a new proposal", + Long: `Submit a new proposal. + +Parameters: + group-account: address of the group account + proposer: comma separated (no spaces) list of proposer account addresses. Example: "addr1,addr2" + Metadata: metadata for the proposal + msg_tx_json_file: path to json file with messages that will be executed if the proposal is accepted. +`, + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + proposers := strings.Split(args[1], ",") + for i := range proposers { + proposers[i] = strings.TrimSpace(proposers[i]) + } + + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + theTx, err := authclient.ReadTxFromFile(clientCtx, args[2]) + if err != nil { + return err + } + msgs := theTx.GetMsgs() + + b, err := base64.StdEncoding.DecodeString(args[3]) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required") + } + + execStr, _ := cmd.Flags().GetString(FlagExec) + + msg, err := group.NewMsgCreateProposalRequest( + args[0], + proposers, + msgs, + b, + execFromString(execStr), + ) + if err != nil { + return err + } + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().String(FlagExec, "", "Set to 1 to try to execute proposal immediately after creation (proposers signatures are considered as Yes votes)") + 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] [choice] [metadata]", + Short: "Vote on a proposal", + Long: `Vote on a proposal. + +Parameters: + proposal-id: unique ID of the proposal + voter: voter account addresses. + choice: choice of the voter(s) + CHOICE_UNSPECIFIED: no-op + CHOICE_NO: no + CHOICE_YES: yes + CHOICE_ABSTAIN: abstain + CHOICE_VETO: 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 + } + + choice, err := group.ChoiceFromString(args[2]) + if err != nil { + return err + } + + b, err := base64.StdEncoding.DecodeString(args[3]) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "metadata is malformed, proper base64 string is required") + } + + execStr, _ := cmd.Flags().GetString(FlagExec) + + msg := &group.MsgVote{ + ProposalId: proposalID, + Voter: args[1], + Choice: choice, + Metadata: b, + Exec: execFromString(execStr), + } + if err != nil { + return err + } + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + 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/MsgExec. +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, + Signer: clientCtx.GetFromAddress().String(), + } + if err != nil { + return err + } + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/group/client/cli/util.go b/x/group/client/cli/util.go new file mode 100644 index 0000000000..99702383ea --- /dev/null +++ b/x/group/client/cli/util.go @@ -0,0 +1,38 @@ +package cli + +import ( + "io/ioutil" + + "github.com/cosmos/cosmos-sdk/client" + + "github.com/cosmos/cosmos-sdk/x/group" +) + +func parseMembers(clientCtx client.Context, membersFile string) ([]group.Member, error) { + members := group.Members{} + + if membersFile == "" { + return members.Members, nil + } + + contents, err := ioutil.ReadFile(membersFile) + if err != nil { + return nil, err + } + + err = clientCtx.Codec.UnmarshalJSON(contents, &members) + if err != nil { + return nil, err + } + + return members.Members, nil +} + +func execFromString(execStr string) group.Exec { + exec := group.Exec_EXEC_UNSPECIFIED + switch execStr { + case ExecTry: + exec = group.Exec_EXEC_TRY + } + return exec +} diff --git a/x/group/client/testutil/cli_test.go b/x/group/client/testutil/cli_test.go new file mode 100644 index 0000000000..f23e88847c --- /dev/null +++ b/x/group/client/testutil/cli_test.go @@ -0,0 +1,18 @@ +//go:build norace +// +build norace + +package testutil + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/testutil/network" + + "github.com/stretchr/testify/suite" +) + +func TestIntegrationTestSuite(t *testing.T) { + cfg := network.DefaultConfig() + cfg.NumValidators = 2 + suite.Run(t, NewIntegrationTestSuite(cfg)) +} diff --git a/x/group/client/testutil/query.go b/x/group/client/testutil/query.go new file mode 100644 index 0000000000..8c1012cdb9 --- /dev/null +++ b/x/group/client/testutil/query.go @@ -0,0 +1,671 @@ +package testutil + +import ( + "fmt" + "strconv" + + "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/x/group" + client "github.com/cosmos/cosmos-sdk/x/group/client/cli" + + tmcli "github.com/tendermint/tendermint/libs/cli" +) + +func (s *IntegrationTestSuite) TestQueryGroupInfo() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + }{ + { + "group not found", + []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "not found: invalid request", + 0, + }, + { + "group id invalid", + []string{"", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "strconv.ParseUint: parsing \"\": invalid syntax", + 0, + }, + { + "group found", + []string{strconv.FormatUint(s.group.GroupId, 10), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryGroupInfoCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var g group.GroupInfo + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &g)) + s.Require().Equal(s.group.GroupId, g.GroupId) + s.Require().Equal(s.group.Admin, g.Admin) + s.Require().Equal(s.group.TotalWeight, g.TotalWeight) + s.Require().Equal(s.group.Metadata, g.Metadata) + s.Require().Equal(s.group.Version, g.Version) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryGroupMembers() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectMembers []*group.GroupMember + }{ + { + "no group", + []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupMember{}, + }, + { + "members found", + []string{strconv.FormatUint(s.group.GroupId, 10), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupMember{ + { + GroupId: s.group.GroupId, + Member: &group.Member{ + Address: val.Address.String(), + Weight: "3", + Metadata: []byte{1}, + }, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryGroupMembersCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryGroupMembersResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.Members), len(tc.expectMembers)) + for i := range res.Members { + s.Require().Equal(res.Members[i].GroupId, tc.expectMembers[i].GroupId) + s.Require().Equal(res.Members[i].Member.Address, tc.expectMembers[i].Member.Address) + s.Require().Equal(res.Members[i].Member.Metadata, tc.expectMembers[i].Member.Metadata) + s.Require().Equal(res.Members[i].Member.Weight, tc.expectMembers[i].Member.Weight) + } + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryGroupsByAdmin() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectGroups []*group.GroupInfo + }{ + { + "invalid admin address", + []string{"invalid"}, + true, + "decoding bech32 failed: invalid bech32 string", + 0, + []*group.GroupInfo{}, + }, + { + "no group", + []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupInfo{}, + }, + { + "found groups", + []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupInfo{ + s.group, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryGroupsByAdminCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryGroupsByAdminResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.Groups), len(tc.expectGroups)) + for i := range res.Groups { + s.Require().Equal(res.Groups[i].GroupId, tc.expectGroups[i].GroupId) + s.Require().Equal(res.Groups[i].Metadata, tc.expectGroups[i].Metadata) + s.Require().Equal(res.Groups[i].Version, tc.expectGroups[i].Version) + s.Require().Equal(res.Groups[i].TotalWeight, tc.expectGroups[i].TotalWeight) + s.Require().Equal(res.Groups[i].Admin, tc.expectGroups[i].Admin) + } + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryGroupAccountInfo() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + }{ + { + "group account not found", + []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "not found: invalid request", + 0, + }, + { + "group account found", + []string{s.groupAccounts[0].Address, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryGroupAccountInfoCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var g group.GroupAccountInfo + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &g)) + s.Require().Equal(s.groupAccounts[0].GroupId, g.GroupId) + s.Require().Equal(s.groupAccounts[0].Address, g.Address) + s.Require().Equal(s.groupAccounts[0].Admin, g.Admin) + s.Require().Equal(s.groupAccounts[0].Metadata, g.Metadata) + s.Require().Equal(s.groupAccounts[0].Version, g.Version) + s.Require().Equal(s.groupAccounts[0].GetDecisionPolicy(), g.GetDecisionPolicy()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryGroupAccountsByGroup() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectGroupAccounts []*group.GroupAccountInfo + }{ + { + "invalid group id", + []string{""}, + true, + "strconv.ParseUint: parsing \"\": invalid syntax", + 0, + []*group.GroupAccountInfo{}, + }, + { + "no group account", + []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupAccountInfo{}, + }, + { + "found group accounts", + []string{strconv.FormatUint(s.group.GroupId, 10), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupAccountInfo{ + s.groupAccounts[0], + s.groupAccounts[1], + s.groupAccounts[2], + s.groupAccounts[3], + s.groupAccounts[4], + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryGroupAccountsByGroupCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryGroupAccountsByGroupResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.GroupAccounts), len(tc.expectGroupAccounts)) + for i := range res.GroupAccounts { + s.Require().Equal(res.GroupAccounts[i].GroupId, tc.expectGroupAccounts[i].GroupId) + s.Require().Equal(res.GroupAccounts[i].Metadata, tc.expectGroupAccounts[i].Metadata) + s.Require().Equal(res.GroupAccounts[i].Version, tc.expectGroupAccounts[i].Version) + s.Require().Equal(res.GroupAccounts[i].Admin, tc.expectGroupAccounts[i].Admin) + s.Require().Equal(res.GroupAccounts[i].GetDecisionPolicy(), tc.expectGroupAccounts[i].GetDecisionPolicy()) + } + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryGroupAccountsByAdmin() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectGroupAccounts []*group.GroupAccountInfo + }{ + { + "invalid admin address", + []string{"invalid"}, + true, + "decoding bech32 failed: invalid bech32 string", + 0, + []*group.GroupAccountInfo{}, + }, + { + "no group account", + []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupAccountInfo{}, + }, + { + "found group accounts", + []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.GroupAccountInfo{ + s.groupAccounts[0], + s.groupAccounts[1], + s.groupAccounts[2], + s.groupAccounts[3], + s.groupAccounts[4], + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryGroupAccountsByAdminCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryGroupAccountsByAdminResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.GroupAccounts), len(tc.expectGroupAccounts)) + for i := range res.GroupAccounts { + s.Require().Equal(res.GroupAccounts[i].GroupId, tc.expectGroupAccounts[i].GroupId) + s.Require().Equal(res.GroupAccounts[i].Metadata, tc.expectGroupAccounts[i].Metadata) + s.Require().Equal(res.GroupAccounts[i].Version, tc.expectGroupAccounts[i].Version) + s.Require().Equal(res.GroupAccounts[i].Admin, tc.expectGroupAccounts[i].Admin) + s.Require().Equal(res.GroupAccounts[i].GetDecisionPolicy(), tc.expectGroupAccounts[i].GetDecisionPolicy()) + } + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryProposal() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + }{ + { + "not found", + []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "not found", + 0, + }, + { + "invalid proposal id", + []string{"", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "strconv.ParseUint: parsing \"\": invalid syntax", + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryProposalCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryProposalsByGroupAccount() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectProposals []*group.Proposal + }{ + { + "invalid group account address", + []string{"invalid"}, + true, + "decoding bech32 failed: invalid bech32 string", + 0, + []*group.Proposal{}, + }, + { + "no group account", + []string{s.network.Validators[1].Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.Proposal{}, + }, + { + "found proposals", + []string{s.groupAccounts[0].Address, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.Proposal{ + s.proposal, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryProposalsByGroupAccountCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryProposalsByGroupAccountResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.Proposals), len(tc.expectProposals)) + for i := range res.Proposals { + s.Require().Equal(res.Proposals[i], tc.expectProposals[i]) + } + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryVoteByProposalVoter() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + }{ + { + "invalid voter address", + []string{"1", "invalid", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "decoding bech32 failed: invalid bech32", + 0, + }, + { + "invalid proposal id", + []string{"", val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "strconv.ParseUint: parsing \"\": invalid syntax", + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryVoteByProposalVoterCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryVotesByProposal() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectVotes []*group.Vote + }{ + { + "invalid proposal id", + []string{"", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "strconv.ParseUint: parsing \"\": invalid syntax", + 0, + []*group.Vote{}, + }, + { + "no votes", + []string{"12345", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.Vote{}, + }, + { + "found votes", + []string{"1", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.Vote{ + s.vote, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryVotesByProposalCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryVotesByProposalResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.Votes), len(tc.expectVotes)) + for i := range res.Votes { + s.Require().Equal(res.Votes[i], tc.expectVotes[i]) + } + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryVotesByVoter() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + expectedCode uint32 + expectVotes []*group.Vote + }{ + { + "invalid voter address", + []string{"abcd", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "decoding bech32 failed: invalid bech32", + 0, + []*group.Vote{}, + }, + { + "no votes", + []string{s.groupAccounts[0].Address, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + true, + "", + 0, + []*group.Vote{}, + }, + { + "found votes", + []string{val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, + false, + "", + 0, + []*group.Vote{ + s.vote, + }, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.QueryVotesByVoterCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + + var res group.QueryVotesByVoterResponse + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.Votes), len(tc.expectVotes)) + for i := range res.Votes { + s.Require().Equal(res.Votes[i], tc.expectVotes[i]) + } + } + }) + } +} diff --git a/x/group/client/testutil/tx.go b/x/group/client/testutil/tx.go new file mode 100644 index 0000000000..efe75e1e2c --- /dev/null +++ b/x/group/client/testutil/tx.go @@ -0,0 +1,1642 @@ +package testutil + +import ( + "fmt" + "strconv" + "strings" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" + "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/suite" + tmcli "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/cosmos-sdk/x/group" + client "github.com/cosmos/cosmos-sdk/x/group/client/cli" +) + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + + group *group.GroupInfo + groupAccounts []*group.GroupAccountInfo + proposal *group.Proposal + vote *group.Vote +} + +const validMetadata = "AQ==" + +func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite { + return &IntegrationTestSuite{cfg: cfg} +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + var err error + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + val := s.network.Validators[0] + + // create a new account + info, _, err := val.ClientCtx.Keyring.NewMnemonic("NewValidator", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + + pk, err := info.GetPubKey() + s.Require().NoError(err) + + account := sdk.AccAddress(pk.Address()) + _, err = banktestutil.MsgSendExec( + val.ClientCtx, + val.Address, + account, + sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(2000))), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + ) + s.Require().NoError(err) + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + // create a group + validMembers := fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "3", + "metadata": "%s" + }]}`, val.Address.String(), validMetadata) + validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers) + out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupCmd(), + append( + []string{ + val.Address.String(), + validMetadata, + validMembersFile.Name(), + }, + commonFlags..., + ), + ) + + s.Require().NoError(err, out.String()) + var txResp = sdk.TxResponse{} + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) + s.Require().Equal(uint32(0), txResp.Code, out.String()) + + s.group = &group.GroupInfo{GroupId: 1, Admin: val.Address.String(), Metadata: []byte{1}, TotalWeight: "3", Version: 1} + + // create 5 group accounts + for i := 0; i < 5; i++ { + threshold := i + 1 + if threshold > 3 { + threshold = 3 + } + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupAccountCmd(), + append( + []string{ + val.Address.String(), + "1", + validMetadata, + fmt.Sprintf("{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"%d\", \"timeout\":\"30000s\"}", threshold), + }, + commonFlags..., + ), + ) + s.Require().NoError(err, out.String()) + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) + s.Require().Equal(uint32(0), txResp.Code, out.String()) + + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.QueryGroupAccountsByGroupCmd(), []string{"1", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}) + s.Require().NoError(err, out.String()) + } + + var res group.QueryGroupAccountsByGroupResponse + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) + s.Require().Equal(len(res.GroupAccounts), 5) + s.groupAccounts = res.GroupAccounts + + // create a proposal + validTxFileName := getTxSendFileName(s, s.groupAccounts[0].Address, val.Address.String()) + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateProposalCmd(), + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + ) + s.Require().NoError(err, out.String()) + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) + s.Require().Equal(uint32(0), txResp.Code, out.String()) + + // vote + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgVoteCmd(), + append( + []string{ + "1", + val.Address.String(), + "CHOICE_YES", + "", + }, + commonFlags..., + ), + ) + s.Require().NoError(err, out.String()) + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) + s.Require().Equal(uint32(0), txResp.Code, out.String()) + + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.QueryProposalCmd(), []string{"1", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}) + s.Require().NoError(err, out.String()) + + var proposalRes group.QueryProposalResponse + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &proposalRes)) + s.proposal = proposalRes.Proposal + + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.QueryVoteByProposalVoterCmd(), []string{"1", val.Address.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag)}) + s.Require().NoError(err, out.String()) + + var voteRes group.QueryVoteByProposalVoterResponse + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &voteRes)) + s.vote = voteRes.Vote +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestTxCreateGroup() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + validMembers := fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "1", + "metadata": "%s" + }]}`, val.Address.String(), validMetadata) + validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers) + + invalidMembersAddress := `{"members": [{ + "address": "", + "weight": "1" +}]}` + invalidMembersAddressFile := testutil.WriteToNewTempFile(s.T(), invalidMembersAddress) + + invalidMembersWeight := fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "0" + }]}`, val.Address.String()) + invalidMembersWeightFile := testutil.WriteToNewTempFile(s.T(), invalidMembersWeight) + + invalidMembersMetadata := fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "1", + "metadata": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==" + }]}`, val.Address.String()) + invalidMembersMetadataFile := testutil.WriteToNewTempFile(s.T(), invalidMembersMetadata) + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + val.Address.String(), + "", + validMembersFile.Name(), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + val.Address.String(), + "", + validMembersFile.Name(), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "group metadata too long", + append( + []string{ + val.Address.String(), + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==", + "", + }, + commonFlags..., + ), + true, + "group metadata: limit exceeded", + nil, + 0, + }, + { + "invalid members address", + append( + []string{ + val.Address.String(), + "null", + invalidMembersAddressFile.Name(), + }, + commonFlags..., + ), + true, + "message validation failed: members: address: empty address string is not allowed", + nil, + 0, + }, + { + "invalid members weight", + append( + []string{ + val.Address.String(), + "null", + invalidMembersWeightFile.Name(), + }, + commonFlags..., + ), + true, + "expected a positive decimal, got 0: invalid decimal string", + nil, + 0, + }, + { + "members metadata too long", + append( + []string{ + val.Address.String(), + "null", + invalidMembersMetadataFile.Name(), + }, + commonFlags..., + ), + true, + "member metadata: limit exceeded", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgCreateGroupCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxUpdateGroupAdmin() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + validMembers := fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "1", + "metadata": "%s" + }]}`, val.Address.String(), validMetadata) + validMembersFile := testutil.WriteToNewTempFile(s.T(), validMembers) + out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateGroupCmd(), + append( + []string{ + val.Address.String(), + validMetadata, + validMembersFile.Name(), + }, + commonFlags..., + ), + ) + + s.Require().NoError(err, out.String()) + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + val.Address.String(), + "3", + s.network.Validators[1].Address.String(), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + val.Address.String(), + "4", + s.network.Validators[1].Address.String(), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "group id invalid", + append( + []string{ + val.Address.String(), + "", + s.network.Validators[1].Address.String(), + }, + commonFlags..., + ), + true, + "strconv.ParseUint: parsing \"\": invalid syntax", + nil, + 0, + }, + { + "group doesn't exist", + append( + []string{ + val.Address.String(), + "12345", + s.network.Validators[1].Address.String(), + }, + commonFlags..., + ), + true, + "not found", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgUpdateGroupAdminCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxUpdateGroupMetadata() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + val.Address.String(), + "2", + validMetadata, + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + val.Address.String(), + "2", + validMetadata, + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "group metadata too long", + append( + []string{ + val.Address.String(), + strconv.FormatUint(s.group.GroupId, 10), + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==", + }, + commonFlags..., + ), + true, + "group metadata: limit exceeded", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgUpdateGroupMetadataCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxUpdateGroupMembers() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + validUpdatedMembersFileName := testutil.WriteToNewTempFile(s.T(), fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "0", + "metadata": "%s" + }, { + "address": "%s", + "weight": "1", + "metadata": "%s" + }]}`, val.Address.String(), validMetadata, s.groupAccounts[0].Address, validMetadata)).Name() + + invalidMembersMetadata := fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "1", + "metadata": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==" + }]}`, val.Address.String()) + invalidMembersMetadataFileName := testutil.WriteToNewTempFile(s.T(), invalidMembersMetadata).Name() + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + val.Address.String(), + "2", + validUpdatedMembersFileName, + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + val.Address.String(), + "2", + testutil.WriteToNewTempFile(s.T(), fmt.Sprintf(`{"members": [{ + "address": "%s", + "weight": "2", + "metadata": "%s" + }]}`, s.groupAccounts[0].Address, validMetadata)).Name(), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "group member metadata too long", + append( + []string{ + val.Address.String(), + strconv.FormatUint(s.group.GroupId, 10), + invalidMembersMetadataFileName, + }, + commonFlags..., + ), + true, + "group member metadata: limit exceeded", + nil, + 0, + }, + { + "group doesn't exist", + append( + []string{ + val.Address.String(), + "12345", + validUpdatedMembersFileName, + }, + commonFlags..., + ), + true, + "not found", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgUpdateGroupMembersCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxCreateGroupAccount() { + val := s.network.Validators[0] + wrongAdmin := s.network.Validators[1].Address + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + groupID := s.group.GroupId + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + val.Address.String(), + fmt.Sprintf("%v", groupID), + validMetadata, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + val.Address.String(), + fmt.Sprintf("%v", groupID), + validMetadata, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "wrong admin", + append( + []string{ + wrongAdmin.String(), + fmt.Sprintf("%v", groupID), + validMetadata, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + }, + commonFlags..., + ), + true, + "key not found", + &sdk.TxResponse{}, + 0, + }, + { + "metadata too long", + append( + []string{ + val.Address.String(), + fmt.Sprintf("%v", groupID), + strings.Repeat("a", 500), + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + }, + commonFlags..., + ), + true, + "group account metadata: limit exceeded", + &sdk.TxResponse{}, + 0, + }, + { + "wrong group id", + append( + []string{ + val.Address.String(), + "10", + validMetadata, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + }, + commonFlags..., + ), + true, + "not found", + &sdk.TxResponse{}, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgCreateGroupAccountCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxUpdateGroupAccountAdmin() { + val := s.network.Validators[0] + newAdmin := s.network.Validators[1].Address + clientCtx := val.ClientCtx + groupAccount := s.groupAccounts[3] + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + groupAccount.Admin, + groupAccount.Address, + newAdmin.String(), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + groupAccount.Admin, + s.groupAccounts[4].Address, + newAdmin.String(), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "wrong admin", + append( + []string{ + newAdmin.String(), + groupAccount.Address, + newAdmin.String(), + }, + commonFlags..., + ), + true, + "key not found", + &sdk.TxResponse{}, + 0, + }, + { + "wrong group account", + append( + []string{ + groupAccount.Admin, + newAdmin.String(), + newAdmin.String(), + }, + commonFlags..., + ), + true, + "load group account: not found", + &sdk.TxResponse{}, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgUpdateGroupAccountAdminCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxUpdateGroupAccountDecisionPolicy() { + val := s.network.Validators[0] + newAdmin := s.network.Validators[1].Address + clientCtx := val.ClientCtx + groupAccount := s.groupAccounts[2] + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + groupAccount.Admin, + groupAccount.Address, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"40000s\"}", + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + groupAccount.Admin, + groupAccount.Address, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"50000s\"}", + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "wrong admin", + append( + []string{ + newAdmin.String(), + groupAccount.Address, + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + }, + commonFlags..., + ), + true, + "key not found", + &sdk.TxResponse{}, + 0, + }, + { + "wrong group account", + append( + []string{ + groupAccount.Admin, + newAdmin.String(), + "{\"@type\":\"/cosmos.group.v1beta1.ThresholdDecisionPolicy\", \"threshold\":\"1\", \"timeout\":\"1s\"}", + }, + commonFlags..., + ), + true, + "load group account: not found", + &sdk.TxResponse{}, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgUpdateGroupAccountDecisionPolicyCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxUpdateGroupAccountMetadata() { + val := s.network.Validators[0] + newAdmin := s.network.Validators[1].Address + clientCtx := val.ClientCtx + groupAccount := s.groupAccounts[2] + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + groupAccount.Admin, + groupAccount.Address, + validMetadata, + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + groupAccount.Admin, + groupAccount.Address, + validMetadata, + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "long metadata", + append( + []string{ + groupAccount.Admin, + groupAccount.Address, + strings.Repeat("a", 500), + }, + commonFlags..., + ), + true, + "group account metadata: limit exceeded", + &sdk.TxResponse{}, + 0, + }, + { + "wrong admin", + append( + []string{ + newAdmin.String(), + groupAccount.Address, + validMetadata, + }, + commonFlags..., + ), + true, + "key not found", + &sdk.TxResponse{}, + 0, + }, + { + "wrong group account", + append( + []string{ + groupAccount.Admin, + newAdmin.String(), + validMetadata, + }, + commonFlags..., + ), + true, + "load group account: not found", + &sdk.TxResponse{}, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgUpdateGroupAccountMetadataCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxCreateProposal() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + validTxFileName := getTxSendFileName(s, s.groupAccounts[0].Address, val.Address.String()) + unauthzTxFileName := getTxSendFileName(s, val.Address.String(), s.groupAccounts[0].Address) + validTxFileName2 := getTxSendFileName(s, s.groupAccounts[3].Address, val.Address.String()) + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with try exec", + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=try", client.FlagExec), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with try exec, not enough yes votes for proposal to pass", + append( + []string{ + s.groupAccounts[3].Address, + val.Address.String(), + validTxFileName2, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=try", client.FlagExec), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "metadata too long", + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + validTxFileName, + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "metadata: limit exceeded", + nil, + 0, + }, + { + "unauthorized msg", + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + unauthzTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "msg does not have group account authorization: unauthorized", + nil, + 0, + }, + { + "invalid proposers", + append( + []string{ + s.groupAccounts[0].Address, + "invalid", + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "proposers: decoding bech32 failed", + nil, + 0, + }, + { + "invalid group account", + append( + []string{ + "invalid", + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "group account: decoding bech32 failed", + nil, + 0, + }, + { + "no group account", + append( + []string{ + val.Address.String(), + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "group account: not found", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgCreateProposalCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxVote() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + validTxFileName := getTxSendFileName(s, s.groupAccounts[1].Address, val.Address.String()) + for i := 0; i < 2; i++ { + out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateProposalCmd(), + append( + []string{ + s.groupAccounts[1].Address, + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + ) + s.Require().NoError(err, out.String()) + } + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + "2", + val.Address.String(), + "CHOICE_YES", + "", + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with try exec", + append( + []string{ + "7", + val.Address.String(), + "CHOICE_YES", + "", + fmt.Sprintf("--%s=try", client.FlagExec), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with try exec, not enough yes votes for proposal to pass", + append( + []string{ + "8", + val.Address.String(), + "CHOICE_NO", + "", + fmt.Sprintf("--%s=try", client.FlagExec), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + "5", + val.Address.String(), + "CHOICE_YES", + "", + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "invalid proposal id", + append( + []string{ + "abcd", + val.Address.String(), + "CHOICE_YES", + "", + }, + commonFlags..., + ), + true, + "invalid syntax", + nil, + 0, + }, + { + "proposal not found", + append( + []string{ + "1234", + val.Address.String(), + "CHOICE_YES", + "", + }, + commonFlags..., + ), + true, + "proposal: not found", + nil, + 0, + }, + { + "metadata too long", + append( + []string{ + "2", + val.Address.String(), + "CHOICE_YES", + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==", + }, + commonFlags..., + ), + true, + "metadata: limit exceeded", + nil, + 0, + }, + { + "invalid choice", + append( + []string{ + "2", + val.Address.String(), + "INVALID_CHOICE", + "", + }, + commonFlags..., + ), + true, + "not a valid vote choice", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgVoteCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxExec() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + + var commonFlags = []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + // create proposals and vote + for i := 3; i <= 4; i++ { + validTxFileName := getTxSendFileName(s, s.groupAccounts[0].Address, val.Address.String()) + out, err := cli.ExecTestCLICmd(val.ClientCtx, client.MsgCreateProposalCmd(), + append( + []string{ + s.groupAccounts[0].Address, + val.Address.String(), + validTxFileName, + "", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + ) + s.Require().NoError(err, out.String()) + + out, err = cli.ExecTestCLICmd(val.ClientCtx, client.MsgVoteCmd(), + append( + []string{ + fmt.Sprintf("%d", i), + val.Address.String(), + "CHOICE_YES", + "", + }, + commonFlags..., + ), + ) + s.Require().NoError(err, out.String()) + } + + testCases := []struct { + name string + args []string + expectErr bool + expectErrMsg string + respType proto.Message + expectedCode uint32 + }{ + { + "correct data", + append( + []string{ + "3", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "with amino-json", + append( + []string{ + "4", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, + "", + &sdk.TxResponse{}, + 0, + }, + { + "invalid proposal id", + append( + []string{ + "abcd", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "invalid syntax", + nil, + 0, + }, + { + "proposal not found", + append( + []string{ + "1234", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + }, + commonFlags..., + ), + true, + "proposal: not found", + nil, + 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := client.MsgExecCmd() + + out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Contains(out.String(), tc.expectErrMsg) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func getTxSendFileName(s *IntegrationTestSuite, from string, to string) string { + tx := fmt.Sprintf( + `{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","from_address":"%s","to_address":"%s","amount":[{"denom":"%s","amount":"10"}]}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, + from, to, s.cfg.BondDenom, + ) + return testutil.WriteToNewTempFile(s.T(), tx).Name() +} diff --git a/x/group/go.mod b/x/group/go.mod index 0386d0a2a6..ec949fbaf6 100644 --- a/x/group/go.mod +++ b/x/group/go.mod @@ -22,7 +22,7 @@ require ( ) require ( - cloud.google.com/go v0.93.3 // indirect + cloud.google.com/go v0.99.0 // indirect cloud.google.com/go/storage v1.10.0 // indirect filippo.io/edwards25519 v1.0.0-beta.2 // indirect github.com/99designs/keyring v1.1.6 // indirect @@ -63,7 +63,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gax-go/v2 v2.1.0 // indirect + github.com/googleapis/gax-go/v2 v2.1.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -113,7 +113,7 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.9.0 // indirect + github.com/spf13/viper v1.10.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect @@ -126,15 +126,16 @@ require ( go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect - golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 // indirect + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/api v0.56.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/api v0.62.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect - gopkg.in/ini.v1 v1.63.2 // indirect + google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect + gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect nhooyr.io/websocket v1.8.6 // indirect diff --git a/x/group/go.sum b/x/group/go.sum index a30d0a6bba..f25c00f508 100644 --- a/x/group/go.sum +++ b/x/group/go.sum @@ -27,8 +27,12 @@ cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAV cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -300,7 +304,6 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= -github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= @@ -511,8 +514,6 @@ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9 github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -564,8 +565,9 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= @@ -707,7 +709,6 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jhump/protoreflect v1.10.1 h1:iH+UZfsbRE6vpyZH7asAjTPWJf7RJbpZ9j/N3lDlKs0= -github.com/jhump/protoreflect v1.10.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -929,7 +930,6 @@ github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8BfaIeMzgBNKvc= github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= -github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -1170,8 +1170,9 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1476,8 +1477,9 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1588,13 +1590,15 @@ golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 h1:7NCfEGl0sfUojmX78nK9pBJuUlSZWEJA/TwASvfiPLo= -golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1680,7 +1684,6 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1689,7 +1692,6 @@ golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1765,8 +1767,12 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.56.0 h1:08F9XVYTLOGeSQb3xI9C0gXMuQanhdGed0cWFhDozbI= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0 h1:PhGymJMXfGBzc4lBRmrx9+1w4w2wEzURHNGF/sD/xGc= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1841,8 +1847,17 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -1856,7 +1871,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -1880,8 +1894,9 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/x/group/module/module.go b/x/group/module/module.go index 570e5f9091..0f578c672e 100644 --- a/x/group/module/module.go +++ b/x/group/module/module.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/group" + "github.com/cosmos/cosmos-sdk/x/group/client/cli" groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" ) @@ -67,15 +68,13 @@ func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config sdkclient.TxEn } // GetQueryCmd returns the cli query commands for the group module -func (AppModuleBasic) GetQueryCmd() *cobra.Command { - // TODO: return CLI query commands - return nil +func (a AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.QueryCmd(a.Name()) } // GetTxCmd returns the transaction commands for the group module -func (AppModuleBasic) GetTxCmd() *cobra.Command { - // TODO: return CLI tx commands - return nil +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.TxCmd(a.Name()) } // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the group module. diff --git a/x/group/types.go b/x/group/types.go index 9ecc96f63e..942ef2a3e9 100644 --- a/x/group/types.go +++ b/x/group/types.go @@ -385,3 +385,13 @@ func (t Tally) TotalCounts() (math.Dec, error) { } return totalCounts, nil } + +// ChoiceFromString returns a Choice from a string. It returns an error +// if the string is invalid. +func ChoiceFromString(str string) (Choice, error) { + choice, ok := Choice_value[str] + if !ok { + return Choice_CHOICE_UNSPECIFIED, fmt.Errorf("'%s' is not a valid vote choice", str) + } + return Choice(choice), nil +}