github.com/cosmos/cosmos-sdk@v0.50.10/x/group/client/cli/tx.go (about)

     1  package cli
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/spf13/cobra"
    10  
    11  	"cosmossdk.io/core/address"
    12  
    13  	"github.com/cosmos/cosmos-sdk/client"
    14  	"github.com/cosmos/cosmos-sdk/client/flags"
    15  	"github.com/cosmos/cosmos-sdk/client/tx"
    16  	"github.com/cosmos/cosmos-sdk/version"
    17  	"github.com/cosmos/cosmos-sdk/x/group"
    18  	"github.com/cosmos/cosmos-sdk/x/group/internal/math"
    19  )
    20  
    21  const (
    22  	FlagExec               = "exec"
    23  	ExecTry                = "try"
    24  	FlagGroupPolicyAsAdmin = "group-policy-as-admin"
    25  )
    26  
    27  var errZeroGroupID = errors.New("group id cannot be 0")
    28  
    29  // TxCmd returns a root CLI command handler for all x/group transaction commands.
    30  func TxCmd(name string, ac address.Codec) *cobra.Command {
    31  	txCmd := &cobra.Command{
    32  		Use:                        name,
    33  		Short:                      "Group transaction subcommands",
    34  		DisableFlagParsing:         true,
    35  		SuggestionsMinimumDistance: 2,
    36  		RunE:                       client.ValidateCmd,
    37  	}
    38  
    39  	txCmd.AddCommand(
    40  		MsgCreateGroupCmd(),
    41  		MsgUpdateGroupAdminCmd(),
    42  		MsgUpdateGroupMetadataCmd(),
    43  		MsgUpdateGroupMembersCmd(),
    44  		MsgCreateGroupWithPolicyCmd(),
    45  		MsgCreateGroupPolicyCmd(),
    46  		MsgUpdateGroupPolicyAdminCmd(),
    47  		MsgUpdateGroupPolicyDecisionPolicyCmd(ac),
    48  		MsgUpdateGroupPolicyMetadataCmd(),
    49  		MsgWithdrawProposalCmd(),
    50  		MsgSubmitProposalCmd(),
    51  		MsgVoteCmd(),
    52  		MsgExecCmd(),
    53  		MsgLeaveGroupCmd(),
    54  		NewCmdDraftProposal(),
    55  	)
    56  
    57  	return txCmd
    58  }
    59  
    60  // MsgCreateGroupCmd creates a CLI command for Msg/CreateGroup.
    61  func MsgCreateGroupCmd() *cobra.Command {
    62  	cmd := &cobra.Command{
    63  		Use:   "create-group [admin] [metadata] [members-json-file]",
    64  		Short: "Create a group which is an aggregation of member accounts with associated weights and an administrator account.",
    65  		Long: `Create a group which is an aggregation of member accounts with associated weights and an administrator account.
    66  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.`,
    67  		Example: fmt.Sprintf(`
    68  %s tx group create-group [admin] [metadata] [members-json-file]
    69  
    70  Where members.json contains:
    71  
    72  {
    73  	"members": [
    74  		{
    75  			"address": "addr1",
    76  			"weight": "1",
    77  			"metadata": "some metadata"
    78  		},
    79  		{
    80  			"address": "addr2",
    81  			"weight": "1",
    82  			"metadata": "some metadata"
    83  		}
    84  	]
    85  }`, version.AppName),
    86  		Args: cobra.ExactArgs(3),
    87  		RunE: func(cmd *cobra.Command, args []string) error {
    88  			err := cmd.Flags().Set(flags.FlagFrom, args[0])
    89  			if err != nil {
    90  				return err
    91  			}
    92  
    93  			clientCtx, err := client.GetClientTxContext(cmd)
    94  			if err != nil {
    95  				return err
    96  			}
    97  
    98  			members, err := parseMembers(args[2])
    99  			if err != nil {
   100  				return err
   101  			}
   102  
   103  			for _, member := range members {
   104  				if _, err := math.NewPositiveDecFromString(member.Weight); err != nil {
   105  					return fmt.Errorf("invalid weight %s for %s: weight must be positive", member.Weight, member.Address)
   106  				}
   107  			}
   108  
   109  			msg := &group.MsgCreateGroup{
   110  				Admin:    clientCtx.GetFromAddress().String(),
   111  				Members:  members,
   112  				Metadata: args[1],
   113  			}
   114  
   115  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   116  		},
   117  	}
   118  
   119  	flags.AddTxFlagsToCmd(cmd)
   120  
   121  	return cmd
   122  }
   123  
   124  // MsgUpdateGroupMembersCmd creates a CLI command for Msg/UpdateGroupMembers.
   125  func MsgUpdateGroupMembersCmd() *cobra.Command {
   126  	cmd := &cobra.Command{
   127  		Use:   "update-group-members [admin] [group-id] [members-json-file]",
   128  		Short: "Update a group's members. Set a member's weight to \"0\" to delete it.",
   129  		Example: fmt.Sprintf(`
   130  %s tx group update-group-members [admin] [group-id] [members-json-file]
   131  
   132  Where members.json contains:
   133  
   134  {
   135  	"members": [
   136  		{
   137  			"address": "addr1",
   138  			"weight": "1",
   139  			"metadata": "some new metadata"
   140  		},
   141  		{
   142  			"address": "addr2",
   143  			"weight": "0",
   144  			"metadata": "some metadata"
   145  		}
   146  	]
   147  }
   148  
   149  Set a member's weight to "0" to delete it.
   150  `, version.AppName),
   151  		Args: cobra.ExactArgs(3),
   152  		RunE: func(cmd *cobra.Command, args []string) error {
   153  			err := cmd.Flags().Set(flags.FlagFrom, args[0])
   154  			if err != nil {
   155  				return err
   156  			}
   157  
   158  			clientCtx, err := client.GetClientTxContext(cmd)
   159  			if err != nil {
   160  				return err
   161  			}
   162  
   163  			members, err := parseMembers(args[2])
   164  			if err != nil {
   165  				return err
   166  			}
   167  
   168  			for _, member := range members {
   169  				if _, err := math.NewNonNegativeDecFromString(member.Weight); err != nil {
   170  					return fmt.Errorf("invalid weight %s for %s: weight must not be negative", member.Weight, member.Address)
   171  				}
   172  			}
   173  
   174  			groupID, err := strconv.ParseUint(args[1], 10, 64)
   175  			if err != nil {
   176  				return err
   177  			}
   178  
   179  			if groupID == 0 {
   180  				return errZeroGroupID
   181  			}
   182  
   183  			msg := &group.MsgUpdateGroupMembers{
   184  				Admin:         clientCtx.GetFromAddress().String(),
   185  				MemberUpdates: members,
   186  				GroupId:       groupID,
   187  			}
   188  
   189  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   190  		},
   191  	}
   192  
   193  	flags.AddTxFlagsToCmd(cmd)
   194  
   195  	return cmd
   196  }
   197  
   198  // MsgUpdateGroupAdminCmd creates a CLI command for Msg/UpdateGroupAdmin.
   199  func MsgUpdateGroupAdminCmd() *cobra.Command {
   200  	cmd := &cobra.Command{
   201  		Use:   "update-group-admin [admin] [group-id] [new-admin]",
   202  		Short: "Update a group's admin",
   203  		Args:  cobra.ExactArgs(3),
   204  		RunE: func(cmd *cobra.Command, args []string) error {
   205  			if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil {
   206  				return err
   207  			}
   208  
   209  			clientCtx, err := client.GetClientTxContext(cmd)
   210  			if err != nil {
   211  				return err
   212  			}
   213  
   214  			groupID, err := strconv.ParseUint(args[1], 10, 64)
   215  			if err != nil {
   216  				return err
   217  			}
   218  
   219  			if groupID == 0 {
   220  				return errZeroGroupID
   221  			}
   222  
   223  			if strings.EqualFold(args[0], args[2]) {
   224  				return errors.New("new admin cannot be the same as the current admin")
   225  			}
   226  
   227  			msg := &group.MsgUpdateGroupAdmin{
   228  				Admin:    clientCtx.GetFromAddress().String(),
   229  				NewAdmin: args[2],
   230  				GroupId:  groupID,
   231  			}
   232  
   233  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   234  		},
   235  	}
   236  
   237  	flags.AddTxFlagsToCmd(cmd)
   238  
   239  	return cmd
   240  }
   241  
   242  // MsgUpdateGroupMetadataCmd creates a CLI command for Msg/UpdateGroupMetadata.
   243  func MsgUpdateGroupMetadataCmd() *cobra.Command {
   244  	cmd := &cobra.Command{
   245  		Use:   "update-group-metadata [admin] [group-id] [metadata]",
   246  		Short: "Update a group's metadata",
   247  		Args:  cobra.ExactArgs(3),
   248  		RunE: func(cmd *cobra.Command, args []string) error {
   249  			err := cmd.Flags().Set(flags.FlagFrom, args[0])
   250  			if err != nil {
   251  				return err
   252  			}
   253  
   254  			clientCtx, err := client.GetClientTxContext(cmd)
   255  			if err != nil {
   256  				return err
   257  			}
   258  
   259  			groupID, err := strconv.ParseUint(args[1], 10, 64)
   260  			if err != nil {
   261  				return err
   262  			}
   263  
   264  			if groupID == 0 {
   265  				return errZeroGroupID
   266  			}
   267  
   268  			msg := &group.MsgUpdateGroupMetadata{
   269  				Admin:    clientCtx.GetFromAddress().String(),
   270  				Metadata: args[2],
   271  				GroupId:  groupID,
   272  			}
   273  
   274  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   275  		},
   276  	}
   277  
   278  	flags.AddTxFlagsToCmd(cmd)
   279  
   280  	return cmd
   281  }
   282  
   283  // MsgCreateGroupWithPolicyCmd creates a CLI command for Msg/CreateGroupWithPolicy.
   284  func MsgCreateGroupWithPolicyCmd() *cobra.Command {
   285  	cmd := &cobra.Command{
   286  		Use:   "create-group-with-policy [admin] [group-metadata] [group-policy-metadata] [members-json-file] [decision-policy-json-file]",
   287  		Short: "Create a group with policy which is an aggregation of member accounts with associated weights, an administrator account and decision policy.",
   288  		Long: `Create a group with policy which is an aggregation of member accounts with associated weights,
   289  an administrator account and decision policy. Note, the '--from' flag is ignored as it is implied from [admin].
   290  Members accounts can be given through a members JSON file that contains an array of members.
   291  If group-policy-as-admin flag is set to true, the admin of the newly created group and group policy is set with the group policy address itself.`,
   292  		Example: fmt.Sprintf(`
   293  %s tx group create-group-with-policy [admin] [group-metadata] [group-policy-metadata] members.json policy.json
   294  
   295  where members.json contains:
   296  
   297  {
   298  	"members": [
   299  		{
   300  			"address": "addr1",
   301  			"weight": "1",
   302  			"metadata": "some metadata"
   303  		},
   304  		{
   305  			"address": "addr2",
   306  			"weight": "1",
   307  			"metadata": "some metadata"
   308  		}
   309  	]
   310  }
   311  
   312  and policy.json contains:
   313  
   314  {
   315      "@type": "/cosmos.group.v1.ThresholdDecisionPolicy",
   316      "threshold": "1",
   317      "windows": {
   318          "voting_period": "120h",
   319          "min_execution_period": "0s"
   320      }
   321  }
   322  `, version.AppName),
   323  		Args: cobra.MinimumNArgs(5),
   324  		RunE: func(cmd *cobra.Command, args []string) error {
   325  			err := cmd.Flags().Set(flags.FlagFrom, args[0])
   326  			if err != nil {
   327  				return err
   328  			}
   329  
   330  			clientCtx, err := client.GetClientTxContext(cmd)
   331  			if err != nil {
   332  				return err
   333  			}
   334  
   335  			groupPolicyAsAdmin, err := cmd.Flags().GetBool(FlagGroupPolicyAsAdmin)
   336  			if err != nil {
   337  				return err
   338  			}
   339  
   340  			members, err := parseMembers(args[3])
   341  			if err != nil {
   342  				return err
   343  			}
   344  
   345  			for _, member := range members {
   346  				if _, err := math.NewPositiveDecFromString(member.Weight); err != nil {
   347  					return fmt.Errorf("invalid weight %s for %s: weight must be positive", member.Weight, member.Address)
   348  				}
   349  			}
   350  
   351  			policy, err := parseDecisionPolicy(clientCtx.Codec, args[4])
   352  			if err != nil {
   353  				return err
   354  			}
   355  
   356  			if err := policy.ValidateBasic(); err != nil {
   357  				return err
   358  			}
   359  
   360  			msg, err := group.NewMsgCreateGroupWithPolicy(
   361  				clientCtx.GetFromAddress().String(),
   362  				members,
   363  				args[1],
   364  				args[2],
   365  				groupPolicyAsAdmin,
   366  				policy,
   367  			)
   368  			if err != nil {
   369  				return err
   370  			}
   371  
   372  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   373  		},
   374  	}
   375  	cmd.Flags().Bool(FlagGroupPolicyAsAdmin, false, "Sets admin of the newly created group and group policy with group policy address itself when true")
   376  	flags.AddTxFlagsToCmd(cmd)
   377  
   378  	return cmd
   379  }
   380  
   381  // MsgCreateGroupPolicyCmd creates a CLI command for Msg/CreateGroupPolicy.
   382  func MsgCreateGroupPolicyCmd() *cobra.Command {
   383  	cmd := &cobra.Command{
   384  		Use:   "create-group-policy [admin] [group-id] [metadata] [decision-policy-json-file]",
   385  		Short: `Create a group policy which is an account associated with a group and a decision policy. Note, the '--from' flag is ignored as it is implied from [admin].`,
   386  		Example: fmt.Sprintf(`
   387  %s tx group create-group-policy [admin] [group-id] [metadata] policy.json
   388  
   389  where policy.json contains:
   390  
   391  {
   392      "@type": "/cosmos.group.v1.ThresholdDecisionPolicy",
   393      "threshold": "1",
   394      "windows": {
   395          "voting_period": "120h",
   396          "min_execution_period": "0s"
   397      }
   398  }
   399  
   400  Here, we can use percentage decision policy when needed, where 0 < percentage <= 1:
   401  
   402  {
   403      "@type": "/cosmos.group.v1.PercentageDecisionPolicy",
   404      "percentage": "0.5",
   405      "windows": {
   406          "voting_period": "120h",
   407          "min_execution_period": "0s"
   408      }
   409  }`, version.AppName),
   410  		Args: cobra.ExactArgs(4),
   411  		RunE: func(cmd *cobra.Command, args []string) error {
   412  			err := cmd.Flags().Set(flags.FlagFrom, args[0])
   413  			if err != nil {
   414  				return err
   415  			}
   416  
   417  			clientCtx, err := client.GetClientTxContext(cmd)
   418  			if err != nil {
   419  				return err
   420  			}
   421  
   422  			groupID, err := strconv.ParseUint(args[1], 10, 64)
   423  			if err != nil {
   424  				return err
   425  			}
   426  
   427  			if groupID == 0 {
   428  				return errZeroGroupID
   429  			}
   430  
   431  			policy, err := parseDecisionPolicy(clientCtx.Codec, args[3])
   432  			if err != nil {
   433  				return err
   434  			}
   435  
   436  			if err := policy.ValidateBasic(); err != nil {
   437  				return err
   438  			}
   439  
   440  			msg, err := group.NewMsgCreateGroupPolicy(
   441  				clientCtx.GetFromAddress(),
   442  				groupID,
   443  				args[2],
   444  				policy,
   445  			)
   446  			if err != nil {
   447  				return err
   448  			}
   449  
   450  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   451  		},
   452  	}
   453  
   454  	flags.AddTxFlagsToCmd(cmd)
   455  
   456  	return cmd
   457  }
   458  
   459  // MsgUpdateGroupPolicyAdminCmd creates a CLI command for Msg/UpdateGroupPolicyAdmin.
   460  func MsgUpdateGroupPolicyAdminCmd() *cobra.Command {
   461  	cmd := &cobra.Command{
   462  		Use:   "update-group-policy-admin [admin] [group-policy-account] [new-admin]",
   463  		Short: "Update a group policy admin",
   464  		Args:  cobra.ExactArgs(3),
   465  		RunE: func(cmd *cobra.Command, args []string) error {
   466  			if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil {
   467  				return err
   468  			}
   469  
   470  			if strings.EqualFold(args[0], args[2]) {
   471  				return errors.New("new admin cannot be the same as the current admin")
   472  			}
   473  
   474  			clientCtx, err := client.GetClientTxContext(cmd)
   475  			if err != nil {
   476  				return err
   477  			}
   478  
   479  			msg := &group.MsgUpdateGroupPolicyAdmin{
   480  				Admin:              clientCtx.GetFromAddress().String(),
   481  				GroupPolicyAddress: args[1],
   482  				NewAdmin:           args[2],
   483  			}
   484  
   485  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   486  		},
   487  	}
   488  
   489  	flags.AddTxFlagsToCmd(cmd)
   490  
   491  	return cmd
   492  }
   493  
   494  // MsgUpdateGroupPolicyDecisionPolicyCmd creates a CLI command for Msg/UpdateGroupPolicyDecisionPolicy.
   495  func MsgUpdateGroupPolicyDecisionPolicyCmd(ac address.Codec) *cobra.Command {
   496  	cmd := &cobra.Command{
   497  		Use:   "update-group-policy-decision-policy [admin] [group-policy-account] [decision-policy-json-file]",
   498  		Short: "Update a group policy's decision policy",
   499  		Args:  cobra.ExactArgs(3),
   500  		RunE: func(cmd *cobra.Command, args []string) error {
   501  			err := cmd.Flags().Set(flags.FlagFrom, args[0])
   502  			if err != nil {
   503  				return err
   504  			}
   505  
   506  			clientCtx, err := client.GetClientTxContext(cmd)
   507  			if err != nil {
   508  				return err
   509  			}
   510  
   511  			policy, err := parseDecisionPolicy(clientCtx.Codec, args[2])
   512  			if err != nil {
   513  				return err
   514  			}
   515  
   516  			accountAddress, err := ac.StringToBytes(args[1])
   517  			if err != nil {
   518  				return err
   519  			}
   520  
   521  			msg, err := group.NewMsgUpdateGroupPolicyDecisionPolicy(
   522  				clientCtx.GetFromAddress(),
   523  				accountAddress,
   524  				policy,
   525  			)
   526  			if err != nil {
   527  				return err
   528  			}
   529  
   530  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   531  		},
   532  	}
   533  
   534  	flags.AddTxFlagsToCmd(cmd)
   535  
   536  	return cmd
   537  }
   538  
   539  // MsgUpdateGroupPolicyMetadataCmd creates a CLI command for Msg/UpdateGroupPolicyMetadata.
   540  func MsgUpdateGroupPolicyMetadataCmd() *cobra.Command {
   541  	cmd := &cobra.Command{
   542  		Use:   "update-group-policy-metadata [admin] [group-policy-account] [new-metadata]",
   543  		Short: "Update a group policy metadata",
   544  		Args:  cobra.ExactArgs(3),
   545  		RunE: func(cmd *cobra.Command, args []string) error {
   546  			if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil {
   547  				return err
   548  			}
   549  
   550  			clientCtx, err := client.GetClientTxContext(cmd)
   551  			if err != nil {
   552  				return err
   553  			}
   554  
   555  			msg := &group.MsgUpdateGroupPolicyMetadata{
   556  				Admin:              clientCtx.GetFromAddress().String(),
   557  				GroupPolicyAddress: args[1],
   558  				Metadata:           args[2],
   559  			}
   560  
   561  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   562  		},
   563  	}
   564  
   565  	flags.AddTxFlagsToCmd(cmd)
   566  
   567  	return cmd
   568  }
   569  
   570  // MsgSubmitProposalCmd creates a CLI command for Msg/SubmitProposal.
   571  func MsgSubmitProposalCmd() *cobra.Command {
   572  	cmd := &cobra.Command{
   573  		Use:   "submit-proposal [proposal_json_file]",
   574  		Short: "Submit a new proposal",
   575  		Long: `Submit a new proposal.
   576  Parameters:
   577  			msg_tx_json_file: path to json file with messages that will be executed if the proposal is accepted.`,
   578  		Example: fmt.Sprintf(`
   579  %s tx group submit-proposal path/to/proposal.json
   580  	
   581  	Where proposal.json contains:
   582  
   583  {
   584  	"group_policy_address": "cosmos1...",
   585  	// array of proto-JSON-encoded sdk.Msgs
   586  	"messages": [
   587  	{
   588  		"@type": "/cosmos.bank.v1beta1.MsgSend",
   589  		"from_address": "cosmos1...",
   590  		"to_address": "cosmos1...",
   591  		"amount":[{"denom": "stake","amount": "10"}]
   592  	}
   593  	],
   594  	// metadata can be any of base64 encoded, raw text, stringified json, IPFS link to json
   595  	// see below for example metadata
   596  	"metadata": "4pIMOgIGx1vZGU=", // base64-encoded metadata
   597  	"title": "My proposal",
   598  	"summary": "This is a proposal to send 10 stake to cosmos1...",
   599  	"proposers": ["cosmos1...", "cosmos1..."],
   600  }
   601  
   602  metadata example: 
   603  {
   604  	"title": "",
   605  	"authors": [""],
   606  	"summary": "",
   607  	"details": "", 
   608  	"proposal_forum_url": "",
   609  	"vote_option_context": "",
   610  } 
   611  `, version.AppName),
   612  		Args: cobra.ExactArgs(1),
   613  		RunE: func(cmd *cobra.Command, args []string) error {
   614  			prop, err := getCLIProposal(args[0])
   615  			if err != nil {
   616  				return err
   617  			}
   618  
   619  			// Since the --from flag is not required on this CLI command, we
   620  			// ignore it, and just use the 1st proposer in the JSON file.
   621  			if prop.Proposers == nil || len(prop.Proposers) == 0 {
   622  				return errors.New("no proposers specified in proposal")
   623  			}
   624  			cmd.Flags().Set(flags.FlagFrom, prop.Proposers[0])
   625  
   626  			clientCtx, err := client.GetClientTxContext(cmd)
   627  			if err != nil {
   628  				return err
   629  			}
   630  
   631  			msgs, err := parseMsgs(clientCtx.Codec, prop)
   632  			if err != nil {
   633  				return err
   634  			}
   635  
   636  			execStr, _ := cmd.Flags().GetString(FlagExec)
   637  			msg, err := group.NewMsgSubmitProposal(
   638  				prop.GroupPolicyAddress,
   639  				prop.Proposers,
   640  				msgs,
   641  				prop.Metadata,
   642  				execFromString(execStr),
   643  				prop.Title,
   644  				prop.Summary,
   645  			)
   646  			if err != nil {
   647  				return err
   648  			}
   649  
   650  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   651  		},
   652  	}
   653  
   654  	cmd.Flags().String(FlagExec, "", "Set to 1 to try to execute proposal immediately after creation (proposers signatures are considered as Yes votes)")
   655  	flags.AddTxFlagsToCmd(cmd)
   656  
   657  	return cmd
   658  }
   659  
   660  // MsgWithdrawProposalCmd creates a CLI command for Msg/WithdrawProposal.
   661  func MsgWithdrawProposalCmd() *cobra.Command {
   662  	cmd := &cobra.Command{
   663  		Use:   "withdraw-proposal [proposal-id] [group-policy-admin-or-proposer]",
   664  		Short: "Withdraw a submitted proposal",
   665  		Long: `Withdraw a submitted proposal.
   666  
   667  Parameters:
   668  			proposal-id: unique ID of the proposal.
   669  			group-policy-admin-or-proposer: either admin of the group policy or one the proposer of the proposal.
   670  			Note: --from flag will be ignored here.
   671  `,
   672  		Args: cobra.ExactArgs(2),
   673  		RunE: func(cmd *cobra.Command, args []string) error {
   674  			if err := cmd.Flags().Set(flags.FlagFrom, args[1]); err != nil {
   675  				return err
   676  			}
   677  
   678  			clientCtx, err := client.GetClientTxContext(cmd)
   679  			if err != nil {
   680  				return err
   681  			}
   682  
   683  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   684  			if err != nil {
   685  				return err
   686  			}
   687  
   688  			if proposalID == 0 {
   689  				return fmt.Errorf("invalid proposal id: %d", proposalID)
   690  			}
   691  
   692  			msg := &group.MsgWithdrawProposal{
   693  				ProposalId: proposalID,
   694  				Address:    clientCtx.GetFromAddress().String(),
   695  			}
   696  
   697  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   698  		},
   699  	}
   700  
   701  	flags.AddTxFlagsToCmd(cmd)
   702  
   703  	return cmd
   704  }
   705  
   706  // MsgVoteCmd creates a CLI command for Msg/Vote.
   707  func MsgVoteCmd() *cobra.Command {
   708  	cmd := &cobra.Command{
   709  		Use:   "vote [proposal-id] [voter] [vote-option] [metadata]",
   710  		Short: "Vote on a proposal",
   711  		Long: `Vote on a proposal.
   712  
   713  Parameters:
   714  			proposal-id: unique ID of the proposal
   715  			voter: voter account addresses.
   716  			vote-option: choice of the voter(s)
   717  				VOTE_OPTION_UNSPECIFIED: no-op
   718  				VOTE_OPTION_NO: no
   719  				VOTE_OPTION_YES: yes
   720  				VOTE_OPTION_ABSTAIN: abstain
   721  				VOTE_OPTION_NO_WITH_VETO: no-with-veto
   722  			Metadata: metadata for the vote
   723  `,
   724  		Args: cobra.ExactArgs(4),
   725  		RunE: func(cmd *cobra.Command, args []string) error {
   726  			err := cmd.Flags().Set(flags.FlagFrom, args[1])
   727  			if err != nil {
   728  				return err
   729  			}
   730  
   731  			clientCtx, err := client.GetClientTxContext(cmd)
   732  			if err != nil {
   733  				return err
   734  			}
   735  
   736  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   737  			if err != nil {
   738  				return err
   739  			}
   740  
   741  			voteOption, err := group.VoteOptionFromString(args[2])
   742  			if err != nil {
   743  				return err
   744  			}
   745  
   746  			execStr, _ := cmd.Flags().GetString(FlagExec)
   747  
   748  			msg := &group.MsgVote{
   749  				ProposalId: proposalID,
   750  				Voter:      args[1],
   751  				Option:     voteOption,
   752  				Metadata:   args[3],
   753  				Exec:       execFromString(execStr),
   754  			}
   755  
   756  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   757  		},
   758  	}
   759  
   760  	cmd.Flags().String(FlagExec, "", "Set to 1 to try to execute proposal immediately after voting")
   761  	flags.AddTxFlagsToCmd(cmd)
   762  
   763  	return cmd
   764  }
   765  
   766  // MsgExecCmd creates a CLI command for Msg/Exec.
   767  func MsgExecCmd() *cobra.Command {
   768  	cmd := &cobra.Command{
   769  		Use:   "exec [proposal-id]",
   770  		Short: "Execute a proposal",
   771  		Args:  cobra.ExactArgs(1),
   772  		RunE: func(cmd *cobra.Command, args []string) error {
   773  			clientCtx, err := client.GetClientTxContext(cmd)
   774  			if err != nil {
   775  				return err
   776  			}
   777  
   778  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   779  			if err != nil {
   780  				return err
   781  			}
   782  
   783  			msg := &group.MsgExec{
   784  				ProposalId: proposalID,
   785  				Executor:   clientCtx.GetFromAddress().String(),
   786  			}
   787  
   788  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   789  		},
   790  	}
   791  
   792  	flags.AddTxFlagsToCmd(cmd)
   793  
   794  	return cmd
   795  }
   796  
   797  // MsgLeaveGroupCmd creates a CLI command for Msg/LeaveGroup.
   798  func MsgLeaveGroupCmd() *cobra.Command {
   799  	cmd := &cobra.Command{
   800  		Use:   "leave-group [member-address] [group-id]",
   801  		Short: "Remove member from the group",
   802  		Long: `Remove member from the group
   803  
   804  Parameters:
   805  		   group-id: unique id of the group
   806  		   member-address: account address of the group member
   807  		   Note, the '--from' flag is ignored as it is implied from [member-address]
   808  		`,
   809  		Args: cobra.ExactArgs(2),
   810  		RunE: func(cmd *cobra.Command, args []string) error {
   811  			cmd.Flags().Set(flags.FlagFrom, args[0])
   812  			clientCtx, err := client.GetClientTxContext(cmd)
   813  			if err != nil {
   814  				return err
   815  			}
   816  
   817  			groupID, err := strconv.ParseUint(args[1], 10, 64)
   818  			if err != nil {
   819  				return err
   820  			}
   821  
   822  			if groupID == 0 {
   823  				return errZeroGroupID
   824  			}
   825  
   826  			msg := &group.MsgLeaveGroup{
   827  				Address: clientCtx.GetFromAddress().String(),
   828  				GroupId: groupID,
   829  			}
   830  
   831  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   832  		},
   833  	}
   834  
   835  	flags.AddTxFlagsToCmd(cmd)
   836  
   837  	return cmd
   838  }