package cli import ( "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" "github.com/cosmos/cosmos-sdk/version" govutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" "github.com/cosmos/cosmos-sdk/x/gov/types" ) // Proposal flags const ( FlagTitle = "title" FlagDescription = "description" flagProposalType = "type" FlagDeposit = "deposit" flagVoter = "voter" flagDepositor = "depositor" flagStatus = "status" FlagProposal = "proposal" ) type proposal struct { Title string Description string Type string Deposit string } // ProposalFlags defines the core required fields of a proposal. It is used to // verify that these values are not provided in conjunction with a JSON proposal // file. var ProposalFlags = []string{ FlagTitle, FlagDescription, flagProposalType, FlagDeposit, } // NewTxCmd returns the transaction commands for this module // governance ModuleClient is slightly different from other ModuleClients in that // it contains a slice of "proposal" child commands. These commands are respective // to proposal type handlers that are implemented in other modules but are mounted // under the governance CLI (eg. parameter change proposals). func NewTxCmd( ctx client.Context, pcmds []*cobra.Command, ) *cobra.Command { govTxCmd := &cobra.Command{ Use: types.ModuleName, Short: "Governance transactions subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } cmdSubmitProp := NewCmdSubmitProposal(ctx) for _, pcmd := range pcmds { cmdSubmitProp.AddCommand(flags.PostCommands(pcmd)[0]) } govTxCmd.AddCommand(flags.PostCommands( NewCmdDeposit(ctx), NewCmdVote(ctx), cmdSubmitProp, )...) return govTxCmd } // NewCmdSubmitProposal implements submitting a proposal transaction command. func NewCmdSubmitProposal(clientCtx client.Context) *cobra.Command { cmd := &cobra.Command{ Use: "submit-proposal", Short: "Submit a proposal along with an initial deposit", Long: strings.TrimSpace( fmt.Sprintf(`Submit a proposal along with an initial deposit. Proposal title, description, type and deposit can be given directly or through a proposal JSON file. Example: $ %s tx gov submit-proposal --proposal="path/to/proposal.json" --from mykey Where proposal.json contains: { "title": "Test Proposal", "description": "My awesome proposal", "type": "Text", "deposit": "10test" } Which is equivalent to: $ %s tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey `, version.AppName, version.AppName, ), ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := clientCtx.InitWithInput(cmd.InOrStdin()) proposal, err := parseSubmitProposalFlags(cmd.Flags()) if err != nil { return err } amount, err := sdk.ParseCoins(proposal.Deposit) if err != nil { return err } content := types.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type) msg, err := types.NewMsgSubmitProposal(content, amount, clientCtx.FromAddress) if err != nil { return err } if err = msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTx(clientCtx, msg) }, } cmd.Flags().String(FlagTitle, "", "title of proposal") cmd.Flags().String(FlagDescription, "", "description of proposal") cmd.Flags().String(flagProposalType, "", "proposalType of proposal, types: text/parameter_change/software_upgrade") cmd.Flags().String(FlagDeposit, "", "deposit of proposal") cmd.Flags().String(FlagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)") return cmd } // NewCmdDeposit implements depositing tokens for an active proposal. func NewCmdDeposit(clientCtx client.Context) *cobra.Command { return &cobra.Command{ Use: "deposit [proposal-id] [deposit]", Args: cobra.ExactArgs(2), Short: "Deposit tokens for an active proposal", Long: strings.TrimSpace( fmt.Sprintf(`Submit a deposit for an active proposal. You can find the proposal-id by running "%s query gov proposals". Example: $ %s tx gov deposit 1 10stake --from mykey `, version.AppName, version.AppName, ), ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := clientCtx.InitWithInput(cmd.InOrStdin()) // validate that the proposal id is a uint proposalID, err := strconv.ParseUint(args[0], 10, 64) if err != nil { return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0]) } // Get depositor address from := clientCtx.GetFromAddress() // Get amount of coins amount, err := sdk.ParseCoins(args[1]) if err != nil { return err } msg := types.NewMsgDeposit(from, proposalID, amount) err = msg.ValidateBasic() if err != nil { return err } return tx.GenerateOrBroadcastTx(clientCtx, msg) }, } } // NewCmdVote implements creating a new vote command. func NewCmdVote(clientCtx client.Context) *cobra.Command { return &cobra.Command{ Use: "vote [proposal-id] [option]", Args: cobra.ExactArgs(2), Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", Long: strings.TrimSpace( fmt.Sprintf(`Submit a vote for an active proposal. You can find the proposal-id by running "%s query gov proposals". Example: $ %s tx gov vote 1 yes --from mykey `, version.AppName, version.AppName, ), ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx := clientCtx.InitWithInput(cmd.InOrStdin()) // Get voting address from := clientCtx.GetFromAddress() // validate that the proposal id is a uint proposalID, err := strconv.ParseUint(args[0], 10, 64) if err != nil { return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) } // Find out which vote option user chose byteVoteOption, err := types.VoteOptionFromString(govutils.NormalizeVoteOption(args[1])) if err != nil { return err } // Build vote message and run basic validation msg := types.NewMsgVote(from, proposalID, byteVoteOption) err = msg.ValidateBasic() if err != nil { return err } return tx.GenerateOrBroadcastTx(clientCtx, msg) }, } } // DONTCOVER