diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 3df9ec36ed7..587e66a8762 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -62,30 +62,32 @@ func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command { RunE: client.ValidateCmd, } - cmdSubmitLegacyProp := NewCmdSubmitLegacyProposal() + cmdSubmitProp := NewCmdSubmitProposal() for _, propCmd := range legacyPropCmds { flags.AddTxFlagsToCmd(propCmd) - cmdSubmitLegacyProp.AddCommand(propCmd) + cmdSubmitProp.AddCommand(propCmd) } govTxCmd.AddCommand( NewCmdWeightedVote(), - NewCmdSubmitProposal(), + cmdSubmitProp, NewCmdDraftProposal(), - - // Deprecated - cmdSubmitLegacyProp, ) return govTxCmd } // NewCmdSubmitProposal implements submitting a proposal transaction command. +// NOTE: This command has been modified from its original to subsume both +// the "legacy" and the new (as of v0.46) invocation patterns. This modification +// will be dropped once all legacy call sites have been updated to either +// use the new pattern or call the legacy command explicitly. func NewCmdSubmitProposal() *cobra.Command { cmd := &cobra.Command{ - Use: "submit-proposal ", - Short: "Submit a proposal along with some messages, metadata and deposit", - Args: cobra.ExactArgs(1), + Use: "submit-proposal [path/to/proposal.json]", + Short: "Submit a proposal along with some messages, metadata and deposit", + Aliases: []string{"submit-legacy-proposal"}, + Args: cobra.MaximumNArgs(1), Long: strings.TrimSpace( fmt.Sprintf(`Submit a proposal along with some messages, metadata and deposit. They should be defined in a JSON file. @@ -123,8 +125,28 @@ metadata example: "proposal_forum_url": "", "vote_option_context": "", } + +Legacy Syntax: +Submit a legacy 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-legacy-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-legacy-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey `, - version.AppName, + version.AppName, version.AppName, version.AppName, ), ), RunE: func(cmd *cobra.Command, args []string) error { @@ -133,17 +155,39 @@ metadata example: return err } - proposal, msgs, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0]) + // try to interpret as a legacy submit-proposal call + proposal, err := parseSubmitLegacyProposalFlags(cmd.Flags()) + if err == nil { + amount, err := sdk.ParseCoinsNormalized(proposal.Deposit) + if err != nil { + return err + } + + content, ok := v1beta1.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type) + if !ok { + return fmt.Errorf("failed to create proposal content: unknown proposal type %s", proposal.Type) + } + + msg, err := v1beta1.NewMsgSubmitProposal(content, amount, clientCtx.GetFromAddress()) + if err != nil { + return fmt.Errorf("invalid message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + } + + // otherwise try to interpret as a new (0.46) submit-proposal + err = cobra.ExactArgs(1)(cmd, args) if err != nil { return err } - addr, err := clientCtx.AddressCodec.BytesToString(clientCtx.GetFromAddress()) + msgs, metadata, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0]) if err != nil { return err } - msg, err := v1.NewMsgSubmitProposal(msgs, deposit, addr, proposal.Metadata, proposal.Title, proposal.Summary, proposal.proposalType) + msg, err := v1.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String(), metadata) if err != nil { return fmt.Errorf("invalid message: %w", err) } @@ -152,6 +196,11 @@ metadata example: }, } + cmd.Flags().String(FlagTitle, "", "The proposal title") + cmd.Flags().String(FlagDescription, "", "The proposal description") + cmd.Flags().String(FlagProposalType, "", "The proposal Type") + cmd.Flags().String(FlagDeposit, "", "The proposal deposit") + cmd.Flags().String(FlagProposal, "", "Proposal file path (if this path is given, other proposal flags are ignored)") flags.AddTxFlagsToCmd(cmd) return cmd @@ -159,30 +208,71 @@ metadata example: // NewCmdSubmitLegacyProposal implements submitting a proposal transaction command. // Deprecated: please use NewCmdSubmitProposal instead. +// Preserved for tests. func NewCmdSubmitLegacyProposal() *cobra.Command { + return NewCmdSubmitProposal() +} + +// NewCmdDeposit implements depositing tokens for an active proposal. +func NewCmdDeposit() *cobra.Command { cmd := &cobra.Command{ - Use: "submit-legacy-proposal", - Aliases: []string{"exec-legacy-content"}, - Short: "Submit a legacy proposal along with an initial deposit", + Use: "deposit [proposal-id] [deposit]", + Args: cobra.ExactArgs(2), + Short: "Deposit tokens for an active proposal", Long: strings.TrimSpace( - fmt.Sprintf(`Submit a legacy proposal along with an initial deposit. -Proposal title, description, type and deposit can be given directly or through a proposal JSON file. + 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 submit-legacy-proposal --proposal="path/to/proposal.json" --from mykey +$ %s tx gov deposit 1 10stake --from mykey +`, + version.AppName, version.AppName, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } -Where proposal.json contains: + // 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]) + } -{ - "title": "Test Proposal", - "description": "My awesome proposal", - "type": "Text", - "deposit": "10test" + // Get depositor address + from := clientCtx.GetFromAddress() + + // Get amount of coins + amount, err := sdk.ParseCoinsNormalized(args[1]) + if err != nil { + return err + } + + msg := v1.NewMsgDeposit(from, proposalID, amount) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd } -Which is equivalent to: +// NewCmdVote implements creating a new vote command. +func NewCmdVote() *cobra.Command { + cmd := &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". -$ %s tx gov submit-legacy-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey +Example: +$ %s tx gov vote 1 yes --from mykey `, version.AppName, version.AppName, ), @@ -192,41 +282,34 @@ $ %s tx gov submit-legacy-proposal --title="Test Proposal" --description="My awe if err != nil { return err } + // Get voting address + from := clientCtx.GetFromAddress() - proposal, err := parseSubmitLegacyProposal(cmd.Flags()) + // validate that the proposal id is a uint + proposalID, err := strconv.ParseUint(args[0], 10, 64) if err != nil { - return fmt.Errorf("failed to parse proposal: %w", err) + return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) } - amount, err := sdk.ParseCoinsNormalized(proposal.Deposit) + // Find out which vote option user chose + byteVoteOption, err := v1.VoteOptionFromString(govutils.NormalizeVoteOption(args[1])) if err != nil { return err } - content, ok := v1beta1.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type) - if !ok { - return fmt.Errorf("failed to create proposal content: unknown proposal type %s", proposal.Type) - } - - proposer, err := clientCtx.AddressCodec.BytesToString(clientCtx.GetFromAddress()) + metadata, err := cmd.Flags().GetString(flagMetadata) if err != nil { return err } - msg, err := v1beta1.NewMsgSubmitProposal(content, amount, proposer) - if err != nil { - return fmt.Errorf("invalid message: %w", err) - } + // Build vote message and run basic validation + msg := v1.NewMsgVote(from, proposalID, byteVoteOption, metadata) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } - cmd.Flags().String(FlagTitle, "", "The proposal title") - cmd.Flags().String(FlagDescription, "", "The proposal description") - cmd.Flags().String(FlagProposalType, "", "The proposal Type") - cmd.Flags().String(FlagDeposit, "", "The proposal deposit") - cmd.Flags().String(FlagProposal, "", "Proposal file path (if this path is given, other proposal flags are ignored)") + cmd.Flags().String(flagMetadata, "", "Specify metadata of the vote") flags.AddTxFlagsToCmd(cmd) return cmd