541 lines
14 KiB
Go
541 lines
14 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client/context"
|
|
"github.com/cosmos/cosmos-sdk/client/utils"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
|
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
|
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
const (
|
|
flagProposalID = "proposal-id"
|
|
flagTitle = "title"
|
|
flagDescription = "description"
|
|
flagProposalType = "type"
|
|
flagDeposit = "deposit"
|
|
flagVoter = "voter"
|
|
flagOption = "option"
|
|
flagDepositer = "depositer"
|
|
flagStatus = "status"
|
|
flagLatestProposalIDs = "latest"
|
|
flagProposal = "proposal"
|
|
)
|
|
|
|
type proposal struct {
|
|
Title string
|
|
Description string
|
|
Type string
|
|
Deposit string
|
|
}
|
|
|
|
var proposalFlags = []string{
|
|
flagTitle,
|
|
flagDescription,
|
|
flagProposalType,
|
|
flagDeposit,
|
|
}
|
|
|
|
// GetCmdSubmitProposal implements submitting a proposal transaction command.
|
|
func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "submit-proposal",
|
|
Short: "Submit a proposal along with an initial deposit",
|
|
Long: strings.TrimSpace(`
|
|
Submit a proposal along with an initial deposit. Proposal title, description, type and deposit can be given directly or through a proposal JSON file. For example:
|
|
|
|
$ gaiacli gov submit-proposal --proposal="path/to/proposal.json"
|
|
|
|
where proposal.json contains:
|
|
|
|
{
|
|
"title": "Test Proposal",
|
|
"description": "My awesome proposal",
|
|
"type": "Text",
|
|
"deposit": "1000test"
|
|
}
|
|
|
|
is equivalent to
|
|
|
|
$ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="1000test"
|
|
`),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
proposal, err := parseSubmitProposalFlags()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
|
cliCtx := context.NewCLIContext().
|
|
WithCodec(cdc).
|
|
WithLogger(os.Stdout).
|
|
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
|
|
|
fromAddr, err := cliCtx.GetFromAddress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
amount, err := sdk.ParseCoins(proposal.Deposit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
proposalType, err := gov.ProposalTypeFromString(proposal.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount)
|
|
err = msg.ValidateBasic()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cliCtx.GenerateOnly {
|
|
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
|
|
}
|
|
|
|
// Build and sign the transaction, then broadcast to Tendermint
|
|
// proposalID must be returned, and it is a part of response.
|
|
cliCtx.PrintResponse = true
|
|
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagTitle, "", "title of proposal")
|
|
cmd.Flags().String(flagDescription, "", "description of proposal")
|
|
cmd.Flags().String(flagProposalType, "", "proposalType of proposal")
|
|
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
|
|
}
|
|
|
|
func parseSubmitProposalFlags() (*proposal, error) {
|
|
proposal := &proposal{}
|
|
proposalFile := viper.GetString(flagProposal)
|
|
|
|
if proposalFile == "" {
|
|
proposal.Title = viper.GetString(flagTitle)
|
|
proposal.Description = viper.GetString(flagDescription)
|
|
proposal.Type = viper.GetString(flagProposalType)
|
|
proposal.Deposit = viper.GetString(flagDeposit)
|
|
return proposal, nil
|
|
}
|
|
|
|
for _, flag := range proposalFlags {
|
|
if viper.GetString(flag) != "" {
|
|
return nil, fmt.Errorf("--%s flag provided alongside --proposal, which is a noop", flag)
|
|
}
|
|
}
|
|
|
|
contents, err := ioutil.ReadFile(proposalFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = json.Unmarshal(contents, proposal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return proposal, nil
|
|
}
|
|
|
|
// GetCmdDeposit implements depositing tokens for an active proposal.
|
|
func GetCmdDeposit(cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "deposit",
|
|
Short: "Deposit tokens for activing proposal",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
|
cliCtx := context.NewCLIContext().
|
|
WithCodec(cdc).
|
|
WithLogger(os.Stdout).
|
|
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
|
|
|
depositerAddr, err := cliCtx.GetFromAddress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
amount, err := sdk.ParseCoins(viper.GetString(flagDeposit))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := gov.NewMsgDeposit(depositerAddr, proposalID, amount)
|
|
err = msg.ValidateBasic()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cliCtx.GenerateOnly {
|
|
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
|
|
}
|
|
|
|
// Build and sign the transaction, then broadcast to a Tendermint
|
|
// node.
|
|
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of proposal depositing on")
|
|
cmd.Flags().String(flagDeposit, "", "amount of deposit")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// GetCmdVote implements creating a new vote command.
|
|
func GetCmdVote(cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "vote",
|
|
Short: "Vote for an active proposal, options: Yes/No/NoWithVeto/Abstain",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
|
cliCtx := context.NewCLIContext().
|
|
WithCodec(cdc).
|
|
WithLogger(os.Stdout).
|
|
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
|
|
|
voterAddr, err := cliCtx.GetFromAddress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
option := viper.GetString(flagOption)
|
|
|
|
byteVoteOption, err := gov.VoteOptionFromString(option)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := gov.NewMsgVote(voterAddr, proposalID, byteVoteOption)
|
|
err = msg.ValidateBasic()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cliCtx.GenerateOnly {
|
|
return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg})
|
|
}
|
|
|
|
fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]",
|
|
voterAddr.String(), msg.ProposalID, msg.Option.String(),
|
|
)
|
|
|
|
// Build and sign the transaction, then broadcast to a Tendermint
|
|
// node.
|
|
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on")
|
|
cmd.Flags().String(flagOption, "", "vote option {Yes, No, NoWithVeto, Abstain}")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// GetCmdQueryProposal implements the query proposal command.
|
|
func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "proposal",
|
|
Short: "Query details of a single proposal",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
params := gov.QueryProposalParams{
|
|
ProposalID: proposalID,
|
|
}
|
|
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(res))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of proposal being queried")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// GetCmdQueryProposals implements a query proposals command.
|
|
func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "proposals",
|
|
Short: "Query proposals with optional filters",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
bechDepositerAddr := viper.GetString(flagDepositer)
|
|
bechVoterAddr := viper.GetString(flagVoter)
|
|
strProposalStatus := viper.GetString(flagStatus)
|
|
latestProposalsIDs := viper.GetInt64(flagLatestProposalIDs)
|
|
|
|
params := gov.QueryProposalsParams{
|
|
NumLatestProposals: latestProposalsIDs,
|
|
}
|
|
|
|
if len(bechDepositerAddr) != 0 {
|
|
depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params.Depositer = depositerAddr
|
|
}
|
|
|
|
if len(bechVoterAddr) != 0 {
|
|
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params.Voter = voterAddr
|
|
}
|
|
|
|
if len(strProposalStatus) != 0 {
|
|
proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params.ProposalStatus = proposalStatus
|
|
}
|
|
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposals", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var matchingProposals []gov.Proposal
|
|
err = cdc.UnmarshalJSON(res, &matchingProposals)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(matchingProposals) == 0 {
|
|
fmt.Println("No matching proposals found")
|
|
return nil
|
|
}
|
|
|
|
for _, proposal := range matchingProposals {
|
|
fmt.Printf(" %d - %s\n", proposal.GetProposalID(), proposal.GetTitle())
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagLatestProposalIDs, "", "(optional) limit to latest [number] proposals. Defaults to all proposals")
|
|
cmd.Flags().String(flagDepositer, "", "(optional) filter by proposals deposited on by depositer")
|
|
cmd.Flags().String(flagVoter, "", "(optional) filter by proposals voted on by voted")
|
|
cmd.Flags().String(flagStatus, "", "(optional) filter proposals by proposal status")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// Command to Get a Proposal Information
|
|
// GetCmdQueryVote implements the query proposal vote command.
|
|
func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "vote",
|
|
Short: "Query details of a single vote",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
params := gov.QueryVoteParams{
|
|
Voter: voterAddr,
|
|
ProposalID: proposalID,
|
|
}
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/vote", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(res))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on")
|
|
cmd.Flags().String(flagVoter, "", "bech32 voter address")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// GetCmdQueryVotes implements the command to query for proposal votes.
|
|
func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "votes",
|
|
Short: "Query votes on a proposal",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
params := gov.QueryVotesParams{
|
|
ProposalID: proposalID,
|
|
}
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/votes", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(res))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's votes are being queried")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// Command to Get a specific Deposit Information
|
|
// GetCmdQueryDeposit implements the query proposal deposit command.
|
|
func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "deposit",
|
|
Short: "Query details of a deposit",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
depositerAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagDepositer))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
params := gov.QueryDepositParams{
|
|
Depositer: depositerAddr,
|
|
ProposalID: proposalID,
|
|
}
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/deposit", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(res))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of proposal deposited on")
|
|
cmd.Flags().String(flagDepositer, "", "bech32 depositer address")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// GetCmdQueryDeposits implements the command to query for proposal deposits.
|
|
func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "deposits",
|
|
Short: "Query deposits on a proposal",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
params := gov.QueryDepositsParams{
|
|
ProposalID: proposalID,
|
|
}
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/deposits", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(res))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's deposits are being queried")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// GetCmdQueryDeposits implements the command to query for proposal deposits.
|
|
func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "tally",
|
|
Short: "Get the tally of a proposal vote",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
proposalID := viper.GetInt64(flagProposalID)
|
|
|
|
params := gov.QueryTallyParams{
|
|
ProposalID: proposalID,
|
|
}
|
|
bz, err := cdc.MarshalJSON(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/tally", queryRoute), bz)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(res))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().String(flagProposalID, "", "proposalID of which proposal is being tallied")
|
|
|
|
return cmd
|
|
}
|