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

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/spf13/cobra"
     9  
    10  	"github.com/cosmos/cosmos-sdk/client"
    11  	"github.com/cosmos/cosmos-sdk/client/flags"
    12  	"github.com/cosmos/cosmos-sdk/client/tx"
    13  	sdk "github.com/cosmos/cosmos-sdk/types"
    14  	"github.com/cosmos/cosmos-sdk/version"
    15  	govutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
    16  	"github.com/cosmos/cosmos-sdk/x/gov/types"
    17  	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
    18  	"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
    19  )
    20  
    21  // Proposal flags
    22  const (
    23  	FlagTitle     = "title"
    24  	FlagDeposit   = "deposit"
    25  	flagVoter     = "voter"
    26  	flagDepositor = "depositor"
    27  	flagStatus    = "status"
    28  	FlagMetadata  = "metadata"
    29  	FlagSummary   = "summary"
    30  	// Deprecated: only used for v1beta1 legacy proposals.
    31  	FlagProposal = "proposal"
    32  	// Deprecated: only used for v1beta1 legacy proposals.
    33  	FlagDescription = "description"
    34  	// Deprecated: only used for v1beta1 legacy proposals.
    35  	FlagProposalType = "type"
    36  )
    37  
    38  // ProposalFlags defines the core required fields of a legacy proposal. It is used to
    39  // verify that these values are not provided in conjunction with a JSON proposal
    40  // file.
    41  var ProposalFlags = []string{
    42  	FlagTitle,
    43  	FlagDescription,
    44  	FlagProposalType,
    45  	FlagDeposit,
    46  }
    47  
    48  // NewTxCmd returns the transaction commands for this module
    49  // governance ModuleClient is slightly different from other ModuleClients in that
    50  // it contains a slice of legacy "proposal" child commands. These commands are respective
    51  // to the proposal type handlers that are implemented in other modules but are mounted
    52  // under the governance CLI (eg. parameter change proposals).
    53  func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command {
    54  	govTxCmd := &cobra.Command{
    55  		Use:                        types.ModuleName,
    56  		Short:                      "Governance transactions subcommands",
    57  		DisableFlagParsing:         true,
    58  		SuggestionsMinimumDistance: 2,
    59  		RunE:                       client.ValidateCmd,
    60  	}
    61  
    62  	cmdSubmitLegacyProp := NewCmdSubmitLegacyProposal()
    63  	for _, propCmd := range legacyPropCmds {
    64  		flags.AddTxFlagsToCmd(propCmd)
    65  		cmdSubmitLegacyProp.AddCommand(propCmd)
    66  	}
    67  
    68  	govTxCmd.AddCommand(
    69  		NewCmdDeposit(),
    70  		NewCmdVote(),
    71  		NewCmdWeightedVote(),
    72  		NewCmdSubmitProposal(),
    73  		NewCmdDraftProposal(),
    74  		NewCmdCancelProposal(),
    75  
    76  		// Deprecated
    77  		cmdSubmitLegacyProp,
    78  	)
    79  
    80  	return govTxCmd
    81  }
    82  
    83  // NewCmdSubmitProposal implements submitting a proposal transaction command.
    84  func NewCmdSubmitProposal() *cobra.Command {
    85  	cmd := &cobra.Command{
    86  		Use:   "submit-proposal [path/to/proposal.json]",
    87  		Short: "Submit a proposal along with some messages, metadata and deposit",
    88  		Args:  cobra.ExactArgs(1),
    89  		Long: strings.TrimSpace(
    90  			fmt.Sprintf(`Submit a proposal along with some messages, metadata and deposit.
    91  They should be defined in a JSON file.
    92  
    93  Example:
    94  $ %s tx gov submit-proposal path/to/proposal.json
    95  
    96  Where proposal.json contains:
    97  
    98  {
    99    // array of proto-JSON-encoded sdk.Msgs
   100    "messages": [
   101      {
   102        "@type": "/cosmos.bank.v1beta1.MsgSend",
   103        "from_address": "cosmos1...",
   104        "to_address": "cosmos1...",
   105        "amount":[{"denom": "stake","amount": "10"}]
   106      }
   107    ],
   108    // metadata can be any of base64 encoded, raw text, stringified json, IPFS link to json
   109    // see below for example metadata
   110    "metadata": "4pIMOgIGx1vZGU=",
   111    "deposit": "10stake",
   112    "title": "My proposal",
   113    "summary": "A short summary of my proposal",
   114    "expedited": false
   115  }
   116  
   117  metadata example: 
   118  {
   119  	"title": "",
   120  	"authors": [""],
   121  	"summary": "",
   122  	"details": "", 
   123  	"proposal_forum_url": "",
   124  	"vote_option_context": "",
   125  }
   126  `,
   127  				version.AppName,
   128  			),
   129  		),
   130  		RunE: func(cmd *cobra.Command, args []string) error {
   131  			clientCtx, err := client.GetClientTxContext(cmd)
   132  			if err != nil {
   133  				return err
   134  			}
   135  
   136  			proposal, msgs, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0])
   137  			if err != nil {
   138  				return err
   139  			}
   140  
   141  			msg, err := v1.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String(), proposal.Metadata, proposal.Title, proposal.Summary, proposal.Expedited)
   142  			if err != nil {
   143  				return fmt.Errorf("invalid message: %w", err)
   144  			}
   145  
   146  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   147  		},
   148  	}
   149  
   150  	flags.AddTxFlagsToCmd(cmd)
   151  
   152  	return cmd
   153  }
   154  
   155  // NewCmdCancelProposal implements submitting a cancel proposal transaction command.
   156  func NewCmdCancelProposal() *cobra.Command {
   157  	cmd := &cobra.Command{
   158  		Use:     "cancel-proposal [proposal-id]",
   159  		Short:   "Cancel governance proposal before the voting period ends. Must be signed by the proposal creator.",
   160  		Args:    cobra.ExactArgs(1),
   161  		Example: fmt.Sprintf(`$ %s tx gov cancel-proposal 1 --from mykey`, version.AppName),
   162  		RunE: func(cmd *cobra.Command, args []string) error {
   163  			clientCtx, err := client.GetClientTxContext(cmd)
   164  			if err != nil {
   165  				return err
   166  			}
   167  
   168  			// validate that the proposal id is a uint
   169  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   170  			if err != nil {
   171  				return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
   172  			}
   173  
   174  			// Get proposer address
   175  			from := clientCtx.GetFromAddress()
   176  			msg := v1.NewMsgCancelProposal(proposalID, from.String())
   177  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   178  		},
   179  	}
   180  
   181  	flags.AddTxFlagsToCmd(cmd)
   182  	return cmd
   183  }
   184  
   185  // NewCmdSubmitLegacyProposal implements submitting a proposal transaction command.
   186  // Deprecated: please use NewCmdSubmitProposal instead.
   187  func NewCmdSubmitLegacyProposal() *cobra.Command {
   188  	cmd := &cobra.Command{
   189  		Use:   "submit-legacy-proposal",
   190  		Short: "Submit a legacy proposal along with an initial deposit",
   191  		Long: strings.TrimSpace(
   192  			fmt.Sprintf(`Submit a legacy proposal along with an initial deposit.
   193  Proposal title, description, type and deposit can be given directly or through a proposal JSON file.
   194  
   195  Example:
   196  $ %s tx gov submit-legacy-proposal --proposal="path/to/proposal.json" --from mykey
   197  
   198  Where proposal.json contains:
   199  
   200  {
   201    "title": "Test Proposal",
   202    "description": "My awesome proposal",
   203    "type": "Text",
   204    "deposit": "10test"
   205  }
   206  
   207  Which is equivalent to:
   208  
   209  $ %s tx gov submit-legacy-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey
   210  `,
   211  				version.AppName, version.AppName,
   212  			),
   213  		),
   214  		RunE: func(cmd *cobra.Command, args []string) error {
   215  			clientCtx, err := client.GetClientTxContext(cmd)
   216  			if err != nil {
   217  				return err
   218  			}
   219  
   220  			proposal, err := parseSubmitLegacyProposal(cmd.Flags())
   221  			if err != nil {
   222  				return fmt.Errorf("failed to parse proposal: %w", err)
   223  			}
   224  
   225  			amount, err := sdk.ParseCoinsNormalized(proposal.Deposit)
   226  			if err != nil {
   227  				return err
   228  			}
   229  
   230  			content, ok := v1beta1.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type)
   231  			if !ok {
   232  				return fmt.Errorf("failed to create proposal content: unknown proposal type %s", proposal.Type)
   233  			}
   234  
   235  			msg, err := v1beta1.NewMsgSubmitProposal(content, amount, clientCtx.GetFromAddress())
   236  			if err != nil {
   237  				return fmt.Errorf("invalid message: %w", err)
   238  			}
   239  
   240  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   241  		},
   242  	}
   243  
   244  	cmd.Flags().String(FlagTitle, "", "The proposal title")
   245  	cmd.Flags().String(FlagDescription, "", "The proposal description")
   246  	cmd.Flags().String(FlagProposalType, "", "The proposal Type")
   247  	cmd.Flags().String(FlagDeposit, "", "The proposal deposit")
   248  	cmd.Flags().String(FlagProposal, "", "Proposal file path (if this path is given, other proposal flags are ignored)")
   249  	flags.AddTxFlagsToCmd(cmd)
   250  
   251  	return cmd
   252  }
   253  
   254  // NewCmdDeposit implements depositing tokens for an active proposal.
   255  func NewCmdDeposit() *cobra.Command {
   256  	cmd := &cobra.Command{
   257  		Use:   "deposit [proposal-id] [deposit]",
   258  		Args:  cobra.ExactArgs(2),
   259  		Short: "Deposit tokens for an active proposal",
   260  		Long: strings.TrimSpace(
   261  			fmt.Sprintf(`Submit a deposit for an active proposal. You can
   262  find the proposal-id by running "%s query gov proposals".
   263  
   264  Example:
   265  $ %s tx gov deposit 1 10stake --from mykey
   266  `,
   267  				version.AppName, version.AppName,
   268  			),
   269  		),
   270  		RunE: func(cmd *cobra.Command, args []string) error {
   271  			clientCtx, err := client.GetClientTxContext(cmd)
   272  			if err != nil {
   273  				return err
   274  			}
   275  
   276  			// validate that the proposal id is a uint
   277  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   278  			if err != nil {
   279  				return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
   280  			}
   281  
   282  			// Get depositor address
   283  			from := clientCtx.GetFromAddress()
   284  
   285  			// Get amount of coins
   286  			amount, err := sdk.ParseCoinsNormalized(args[1])
   287  			if err != nil {
   288  				return err
   289  			}
   290  
   291  			msg := v1.NewMsgDeposit(from, proposalID, amount)
   292  
   293  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   294  		},
   295  	}
   296  
   297  	flags.AddTxFlagsToCmd(cmd)
   298  
   299  	return cmd
   300  }
   301  
   302  // NewCmdVote implements creating a new vote command.
   303  func NewCmdVote() *cobra.Command {
   304  	cmd := &cobra.Command{
   305  		Use:   "vote [proposal-id] [option]",
   306  		Args:  cobra.ExactArgs(2),
   307  		Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain",
   308  		Long: strings.TrimSpace(
   309  			fmt.Sprintf(`Submit a vote for an active proposal. You can
   310  find the proposal-id by running "%s query gov proposals".
   311  
   312  Example:
   313  $ %s tx gov vote 1 yes --from mykey
   314  `,
   315  				version.AppName, version.AppName,
   316  			),
   317  		),
   318  		RunE: func(cmd *cobra.Command, args []string) error {
   319  			clientCtx, err := client.GetClientTxContext(cmd)
   320  			if err != nil {
   321  				return err
   322  			}
   323  			// Get voting address
   324  			from := clientCtx.GetFromAddress()
   325  
   326  			// validate that the proposal id is a uint
   327  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   328  			if err != nil {
   329  				return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
   330  			}
   331  
   332  			// Find out which vote option user chose
   333  			byteVoteOption, err := v1.VoteOptionFromString(govutils.NormalizeVoteOption(args[1]))
   334  			if err != nil {
   335  				return err
   336  			}
   337  
   338  			metadata, err := cmd.Flags().GetString(FlagMetadata)
   339  			if err != nil {
   340  				return err
   341  			}
   342  
   343  			// Build vote message and run basic validation
   344  			msg := v1.NewMsgVote(from, proposalID, byteVoteOption, metadata)
   345  
   346  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   347  		},
   348  	}
   349  
   350  	cmd.Flags().String(FlagMetadata, "", "Specify metadata of the vote")
   351  	flags.AddTxFlagsToCmd(cmd)
   352  
   353  	return cmd
   354  }
   355  
   356  // NewCmdWeightedVote implements creating a new weighted vote command.
   357  func NewCmdWeightedVote() *cobra.Command {
   358  	cmd := &cobra.Command{
   359  		Use:   "weighted-vote [proposal-id] [weighted-options]",
   360  		Args:  cobra.ExactArgs(2),
   361  		Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain",
   362  		Long: strings.TrimSpace(
   363  			fmt.Sprintf(`Submit a vote for an active proposal. You can
   364  find the proposal-id by running "%s query gov proposals".
   365  
   366  Example:
   367  $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05 --from mykey
   368  `,
   369  				version.AppName, version.AppName,
   370  			),
   371  		),
   372  		RunE: func(cmd *cobra.Command, args []string) error {
   373  			clientCtx, err := client.GetClientTxContext(cmd)
   374  			if err != nil {
   375  				return err
   376  			}
   377  
   378  			// Get voter address
   379  			from := clientCtx.GetFromAddress()
   380  
   381  			// validate that the proposal id is a uint
   382  			proposalID, err := strconv.ParseUint(args[0], 10, 64)
   383  			if err != nil {
   384  				return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
   385  			}
   386  
   387  			// Figure out which vote options user chose
   388  			options, err := v1.WeightedVoteOptionsFromString(govutils.NormalizeWeightedVoteOptions(args[1]))
   389  			if err != nil {
   390  				return err
   391  			}
   392  
   393  			metadata, err := cmd.Flags().GetString(FlagMetadata)
   394  			if err != nil {
   395  				return err
   396  			}
   397  
   398  			// Build vote message and run basic validation
   399  			msg := v1.NewMsgVoteWeighted(from, proposalID, options, metadata)
   400  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   401  		},
   402  	}
   403  
   404  	cmd.Flags().String(FlagMetadata, "", "Specify metadata of the weighted vote")
   405  	flags.AddTxFlagsToCmd(cmd)
   406  
   407  	return cmd
   408  }