304 lines
8.3 KiB
Go
304 lines
8.3 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(propCmds []*cobra.Command) *cobra.Command {
|
|
govTxCmd := &cobra.Command{
|
|
Use: types.ModuleName,
|
|
Short: "Governance transactions subcommands",
|
|
DisableFlagParsing: true,
|
|
SuggestionsMinimumDistance: 2,
|
|
RunE: client.ValidateCmd,
|
|
}
|
|
|
|
cmdSubmitProp := NewCmdSubmitProposal()
|
|
for _, propCmd := range propCmds {
|
|
flags.AddTxFlagsToCmd(propCmd)
|
|
cmdSubmitProp.AddCommand(propCmd)
|
|
}
|
|
|
|
govTxCmd.AddCommand(
|
|
NewCmdDeposit(),
|
|
NewCmdVote(),
|
|
NewCmdWeightedVote(),
|
|
cmdSubmitProp,
|
|
)
|
|
|
|
return govTxCmd
|
|
}
|
|
|
|
// NewCmdSubmitProposal implements submitting a proposal transaction command.
|
|
func NewCmdSubmitProposal() *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, err := client.GetClientTxContext(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
proposal, err := parseSubmitProposalFlags(cmd.Flags())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse proposal: %w", err)
|
|
}
|
|
|
|
amount, err := sdk.ParseCoinsNormalized(proposal.Deposit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
content := types.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type)
|
|
|
|
msg, err := types.NewMsgSubmitProposal(content, amount, clientCtx.GetFromAddress())
|
|
if err != nil {
|
|
return fmt.Errorf("invalid message: %w", err)
|
|
}
|
|
|
|
if err = msg.ValidateBasic(); err != nil {
|
|
return fmt.Errorf("message validation failed: %w", err)
|
|
}
|
|
|
|
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)")
|
|
flags.AddTxFlagsToCmd(cmd)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewCmdDeposit implements depositing tokens for an active proposal.
|
|
func NewCmdDeposit() *cobra.Command {
|
|
cmd := &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, err := client.GetClientTxContext(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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.ParseCoinsNormalized(args[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := types.NewMsgDeposit(from, proposalID, amount)
|
|
err = msg.ValidateBasic()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
|
},
|
|
}
|
|
|
|
flags.AddTxFlagsToCmd(cmd)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// 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".
|
|
|
|
Example:
|
|
$ %s tx gov vote 1 yes --from mykey
|
|
`,
|
|
version.AppName, version.AppName,
|
|
),
|
|
),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
clientCtx, err := client.GetClientTxContext(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// 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.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
|
},
|
|
}
|
|
|
|
flags.AddTxFlagsToCmd(cmd)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewCmdWeightedVote implements creating a new weighted vote command.
|
|
func NewCmdWeightedVote() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "weighted-vote [proposal-id] [weighted-options]",
|
|
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=0.6,no=0.3,abstain=0.05,no_with_veto=0.05 --from mykey
|
|
`,
|
|
version.AppName, version.AppName,
|
|
),
|
|
),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
clientCtx := client.GetClientContextFromCmd(cmd)
|
|
clientCtx, err := client.ReadTxCommandFlags(clientCtx, cmd.Flags())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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])
|
|
}
|
|
|
|
// Figure out which vote options user chose
|
|
options, err := types.WeightedVoteOptionsFromString(govutils.NormalizeWeightedVoteOptions(args[1]))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build vote message and run basic validation
|
|
msg := types.NewMsgWeightedVote(from, proposalID, options)
|
|
err = msg.ValidateBasic()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
|
},
|
|
}
|
|
|
|
flags.AddTxFlagsToCmd(cmd)
|
|
|
|
return cmd
|
|
}
|