cosmos-sdk/x/gov/client/cli/tx.go

237 lines
6.4 KiB
Go

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