From 886bd3567097ea2f3bab4d163c02d27d474984f0 Mon Sep 17 00:00:00 2001 From: Jack Zampolin Date: Mon, 3 Dec 2018 12:48:51 -0800 Subject: [PATCH] Address gov cli ux issues and add additional input validation for better errors (#2938) * Fix governance cli ux issues and add additional transaction validation --- cmd/gaia/cli_test/cli_test.go | 22 ++-- docs/gaia/gaiacli.md | 36 +++---- x/gov/client/cli/query.go | 185 +++++++++++++++++++++++++++------- x/gov/client/cli/tx.go | 118 +++++++++++++++------- x/gov/client/module_client.go | 4 +- 5 files changed, 263 insertions(+), 102 deletions(-) diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 2bc4b7098..524b5aac5 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -359,7 +359,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal --proposal-id=1 --output=json %v", flags)) + proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal 1 --output=json %v", flags)) require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) @@ -367,14 +367,12 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, " 1 - Test", proposalsQuery) deposit := executeGetDeposit(t, - fmt.Sprintf("gaiacli query gov deposit --proposal-id=1 --depositor=%s --output=json %v", + fmt.Sprintf("gaiacli query gov deposit 1 %s --output=json %v", fooAddr, flags)) require.Equal(t, int64(5), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) - depositStr := fmt.Sprintf("gaiacli tx gov deposit %v", flags) + depositStr := fmt.Sprintf("gaiacli tx gov deposit 1 %s %v", fmt.Sprintf("10%s", stakeTypes.DefaultBondDenom), flags) depositStr += fmt.Sprintf(" --from=%s", "foo") - depositStr += fmt.Sprintf(" --deposit=%s", fmt.Sprintf("10%s", stakeTypes.DefaultBondDenom)) - depositStr += fmt.Sprintf(" --proposal-id=%s", "1") // Test generate only success, stdout, stderr = executeWriteRetStdStreams(t, depositStr+" --generate-only", app.DefaultKeyPass) @@ -391,12 +389,12 @@ func TestGaiaCLISubmitProposal(t *testing.T) { // test query deposit deposits := executeGetDeposits(t, - fmt.Sprintf("gaiacli query gov deposits --proposal-id=1 --output=json %v", flags)) + fmt.Sprintf("gaiacli query gov deposits 1 --output=json %v", flags)) require.Len(t, deposits, 1) require.Equal(t, int64(15), deposits[0].Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) deposit = executeGetDeposit(t, - fmt.Sprintf("gaiacli query gov deposit --proposal-id=1 --depositor=%s --output=json %v", + fmt.Sprintf("gaiacli query gov deposit 1 %s --output=json %v", fooAddr, flags)) require.Equal(t, int64(15), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64()) @@ -406,14 +404,12 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64()) - proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal --proposal-id=1 --output=json %v", flags)) + proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query gov proposal 1 --output=json %v", flags)) require.Equal(t, uint64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus()) - voteStr := fmt.Sprintf("gaiacli tx gov vote %v", flags) + voteStr := fmt.Sprintf("gaiacli tx gov vote 1 Yes %v", flags) voteStr += fmt.Sprintf(" --from=%s", "foo") - voteStr += fmt.Sprintf(" --proposal-id=%s", "1") - voteStr += fmt.Sprintf(" --option=%s", "Yes") // Test generate only success, stdout, stderr = executeWriteRetStdStreams(t, voteStr+" --generate-only", app.DefaultKeyPass) @@ -428,11 +424,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, voteStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(1, port) - vote := executeGetVote(t, fmt.Sprintf("gaiacli query gov vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) + vote := executeGetVote(t, fmt.Sprintf("gaiacli query gov vote 1 %s --output=json %v", fooAddr, flags)) require.Equal(t, uint64(1), vote.ProposalID) require.Equal(t, gov.OptionYes, vote.Option) - votes := executeGetVotes(t, fmt.Sprintf("gaiacli query gov votes --proposal-id=1 --output=json %v", flags)) + votes := executeGetVotes(t, fmt.Sprintf("gaiacli query gov votes 1 --output=json %v", flags)) require.Len(t, votes, 1) require.Equal(t, uint64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) diff --git a/docs/gaia/gaiacli.md b/docs/gaia/gaiacli.md index 5dd340250..41b1ca5e1 100644 --- a/docs/gaia/gaiacli.md +++ b/docs/gaia/gaiacli.md @@ -216,7 +216,7 @@ gaiacli query txs --tags=':&:' ::: tip Note -The action tag always equals the message type returned by the `Type()` function of the relevant message. +The action tag always equals the message type returned by the `Type()` function of the relevant message. You can find a list of available `tags` on each of the SDK modules: @@ -461,7 +461,7 @@ gaiacli tx gov submit-proposal \ Once created, you can now query information of the proposal: ```bash -gaiacli query gov proposal --proposal-id= +gaiacli query gov proposal ``` Or query all available proposals: @@ -477,9 +477,7 @@ You can also query proposals filtered by `voter` or `depositor` by using the cor In order for a proposal to be broadcasted to the network, the amount deposited must be above a `minDeposit` value (default: `10 steak`). If the proposal you previously created didn't meet this requirement, you can still increase the total amount deposited to activate it. Once the minimum deposit is reached, the proposal enters voting period: ```bash -gaiacli tx gov deposit \ - --proposal-id= \ - --deposit=<200steak> \ +gaiacli tx gov deposit <200steak> \ --from= \ --chain-id= ``` @@ -491,15 +489,13 @@ gaiacli tx gov deposit \ Once a new proposal is created, you can query all the deposits submitted to it: ```bash -gaiacli query gov deposits --proposal-id= +gaiacli query gov deposits ``` You can also query a deposit submitted by a specific address: ```bash -gaiacli query gov deposit \ - --proposal-id= \ - --depositor= +gaiacli query gov deposit ``` #### Vote on a proposal @@ -507,9 +503,7 @@ gaiacli query gov deposit \ After a proposal's deposit reaches the `MinDeposit` value, the voting period opens. Bonded `Atom` holders can then cast vote on it: ```bash -gaiacli tx gov vote \ - --proposal-id= \ - --option= \ +gaiacli tx gov vote \ --from= \ --chain-id= ``` @@ -519,15 +513,13 @@ gaiacli tx gov vote \ Check the vote with the option you just submitted: ```bash -gaiacli query gov vote \ - --proposal-id= \ - --voter= +gaiacli query gov vote ``` You can also get all the previous votes submitted to the proposal with: ```bash -gaiacli query gov votes --proposal-id= +gaiacli query gov votes ``` #### Query proposal tally results @@ -535,5 +527,15 @@ gaiacli query gov votes --proposal-id= To check the current tally of a given proposal you can use the `tally` command: ```bash -gaiacli query gov tally --proposal-id= +gaiacli query gov tally +``` + +#### Query governance parameters + +To check the current governance parameters run: + +```bash +gaiacli query gov param voting +gaiacli query gov param tallying +gaiacli query gov param deposit ``` diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index b301c3b1b..4f0c86fea 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + "strconv" + "strings" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" @@ -15,19 +17,25 @@ import ( // GetCmdQueryProposal implements the query proposal command. func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "proposal", + Use: "proposal [proposal-id]", + Args: cobra.ExactArgs(1), Short: "Query details of a single proposal", + Long: strings.TrimSpace(` +Query details for a proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli query gov proposal 1 +`), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := uint64(viper.GetInt64(flagProposalID)) - params := gov.NewQueryProposalParams(proposalID) - bz, err := cdc.MarshalJSON(params) + // validate that the proposal id is a uint + proposalID, err := strconv.ParseUint(args[0], 10, 64) if err != nil { - return err + return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0]) } - res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz) + // Query the proposal + res, err := queryProposal(proposalID, cliCtx, cdc, queryRoute) if err != nil { return err } @@ -37,16 +45,37 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagProposalID, "", "proposalID of proposal being queried") - return cmd } +func queryProposal(proposalID uint64, cliCtx context.CLIContext, cdc *codec.Codec, queryRoute string) ([]byte, error) { + // Construct query + params := gov.NewQueryProposalParams(proposalID) + bz, err := cdc.MarshalJSON(params) + if err != nil { + return nil, err + } + + // Query store + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz) + if err != nil { + return nil, err + } + return res, err +} + // 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", + Long: strings.TrimSpace(` +Query for a all proposals. You can filter the returns with the following flags: + +$ gaiacli query gov proposals --depositor cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk +$ gaiacli query gov proposals --voter cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk +$ gaiacli query gov proposals --status (DepositPeriod|VotingPeriod|Passed|Rejected) +`), RunE: func(cmd *cobra.Command, args []string) error { bechDepositorAddr := viper.GetString(flagDepositor) bechVoterAddr := viper.GetString(flagVoter) @@ -126,23 +155,43 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryVote implements the query proposal vote command. func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "vote", + Use: "vote [proposal-id] [voter-address]", + Args: cobra.ExactArgs(2), Short: "Query details of a single vote", + Long: strings.TrimSpace(` +Query details for a single vote on a proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli query gov vote 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk +`), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := uint64(viper.GetInt64(flagProposalID)) - voterAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagVoter)) + // 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]) + } + + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // get voter address + voterAddr, err := sdk.AccAddressFromBech32(args[1]) if err != nil { return err } + // Construct query params := gov.NewQueryVoteParams(proposalID, voterAddr) bz, err := cdc.MarshalJSON(params) if err != nil { return err } + // Query store res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/vote", queryRoute), bz) if err != nil { return err @@ -153,27 +202,43 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - 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", + Use: "votes [proposal-id]", + Args: cobra.ExactArgs(1), Short: "Query votes on a proposal", + Long: strings.TrimSpace(` +Query vote details for a single proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli query gov votes 1 +`), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := uint64(viper.GetInt64(flagProposalID)) + // 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]) + } + + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // Construct query params := gov.NewQueryProposalParams(proposalID) bz, err := cdc.MarshalJSON(params) if err != nil { return err } + // Query store res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/votes", queryRoute), bz) if err != nil { return err @@ -184,8 +249,6 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's votes are being queried") - return cmd } @@ -193,23 +256,43 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryDeposit implements the query proposal deposit command. func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "deposit", + Use: "deposit [proposal-id] [depositer-address]", + Args: cobra.ExactArgs(2), Short: "Query details of a deposit", + Long: strings.TrimSpace(` +Query details for a single proposal deposit on a proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk +`), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := uint64(viper.GetInt64(flagProposalID)) - depositorAddr, err := sdk.AccAddressFromBech32(viper.GetString(flagDepositor)) + // 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]) + } + + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // Get the depositer address + depositorAddr, err := sdk.AccAddressFromBech32(args[1]) if err != nil { return err } + // Construct query params := gov.NewQueryDepositParams(proposalID, depositorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { return err } + // Query store res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/deposit", queryRoute), bz) if err != nil { return err @@ -220,27 +303,43 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagProposalID, "", "proposalID of proposal deposited on") - cmd.Flags().String(flagDepositor, "", "bech32 depositor 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", + Use: "deposits [proposal-id]", + Args: cobra.ExactArgs(1), Short: "Query deposits on a proposal", + Long: strings.TrimSpace(` +Query details for all deposits on a proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli query gov deposits 1 +`), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := uint64(viper.GetInt64(flagProposalID)) + // 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]) + } + + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // Construct query params := gov.NewQueryProposalParams(proposalID) bz, err := cdc.MarshalJSON(params) if err != nil { return err } + // Query store res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/deposits", queryRoute), bz) if err != nil { return err @@ -251,26 +350,43 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's deposits are being queried") - return cmd } // GetCmdQueryTally implements the command to query for proposal tally result. func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "tally", + Use: "tally [proposal-id]", + Args: cobra.ExactArgs(1), Short: "Get the tally of a proposal vote", + Long: strings.TrimSpace(` +Query tally of votes on a proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli query gov tally 1 +`), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - proposalID := uint64(viper.GetInt64(flagProposalID)) + // 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]) + } + + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // Construct query params := gov.NewQueryProposalParams(proposalID) bz, err := cdc.MarshalJSON(params) if err != nil { return err } + // Query store res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/tally", queryRoute), bz) if err != nil { return err @@ -281,8 +397,6 @@ func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().String(flagProposalID, "", "proposalID of which proposal is being tallied") - return cmd } @@ -290,14 +404,13 @@ func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "param [param-type]", - Short: "Query the parameters (voting|tallying|deposit) of the governance process", Args: cobra.ExactArgs(1), + Short: "Query the parameters (voting|tallying|deposit) of the governance process", RunE: func(cmd *cobra.Command, args []string) error { - paramType := args[0] - cliCtx := context.NewCLIContext().WithCodec(cdc) - res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/%s", queryRoute, paramType), nil) + // Query store + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/%s", queryRoute, args[0]), nil) if err != nil { return err } diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 000aef68d..58ce91352 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "os" + "strconv" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" @@ -10,6 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/pkg/errors" "encoding/json" "io/ioutil" @@ -21,7 +23,6 @@ import ( ) const ( - flagProposalID = "proposal-id" flagTitle = "title" flagDescription = "description" flagProposalType = "type" @@ -56,7 +57,7 @@ func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command { 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" +$ gaiacli gov submit-proposal --proposal="path/to/proposal.json" --from mykey where proposal.json contains: @@ -64,12 +65,12 @@ where proposal.json contains: "title": "Test Proposal", "description": "My awesome proposal", "type": "Text", - "deposit": "1000test" + "deposit": "10test" } is equivalent to -$ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="1000test" +$ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey `), RunE: func(cmd *cobra.Command, args []string) error { proposal, err := parseSubmitProposalFlags() @@ -82,22 +83,35 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome WithCodec(cdc). WithAccountDecoder(cdc) - fromAddr, err := cliCtx.GetFromAddress() + // Get from address + from, err := cliCtx.GetFromAddress() if err != nil { return err } + // Pull associated account + account, err := cliCtx.GetAccount(from) + if err != nil { + return err + } + + // Find deposit amount amount, err := sdk.ParseCoins(proposal.Deposit) if err != nil { return err } + // ensure account has enough coins + if !account.GetCoins().IsAllGTE(amount) { + return errors.Errorf("Address %s doesn't have enough coins to pay for this transaction.", from) + } + proposalType, err := gov.ProposalTypeFromString(proposal.Type) if err != nil { return err } - msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount) + msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, from, amount) err = msg.ValidateBasic() if err != nil { return err @@ -155,29 +169,58 @@ func parseSubmitProposalFlags() (*proposal, error) { } // GetCmdDeposit implements depositing tokens for an active proposal. -func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { +func GetCmdDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "deposit", + Use: "deposit [proposal-id] [deposit]", + Args: cobra.ExactArgs(2), Short: "Deposit tokens for activing proposal", + Long: strings.TrimSpace(` +Submit a deposit for an acive proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli tx gov deposit 1 10STAKE --from mykey +`), RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). WithCodec(cdc). WithAccountDecoder(cdc) - depositorAddr, err := cliCtx.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 uint, please input a valid proposal-id", args[0]) + } + + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // Get from address + from, err := cliCtx.GetFromAddress() if err != nil { return err } - proposalID := uint64(viper.GetInt64(flagProposalID)) - - amount, err := sdk.ParseCoins(viper.GetString(flagDeposit)) + // Fetch associated account + account, err := cliCtx.GetAccount(from) if err != nil { return err } - msg := gov.NewMsgDeposit(depositorAddr, proposalID, amount) + // Get amount of coins + amount, err := sdk.ParseCoins(args[1]) + if err != nil { + return err + } + + // ensure account has enough coins + if !account.GetCoins().IsAllGTE(amount) { + return errors.Errorf("Address %s doesn't have enough coins to pay for this transaction.", from) + } + + msg := gov.NewMsgDeposit(from, proposalID, amount) err = msg.ValidateBasic() if err != nil { return err @@ -187,64 +230,71 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } - // Build and sign the transaction, then broadcast to a Tendermint - // node. + // 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 { +func GetCmdVote(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "vote", + 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(` +Submit a vote for an acive proposal. You can find the proposal-id by running gaiacli query gov proposals: + +$ gaiacli tx gov vote 1 yes --from mykey +`), RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). WithCodec(cdc). WithAccountDecoder(cdc) - voterAddr, err := cliCtx.GetFromAddress() + // Get voting address + from, err := cliCtx.GetFromAddress() if err != nil { return err } - proposalID := uint64(viper.GetInt64(flagProposalID)) - option := viper.GetString(flagOption) + // 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]) + } - byteVoteOption, err := gov.VoteOptionFromString(govClientUtils.NormalizeVoteOption(option)) + // check to see if the proposal is in the store + _, err = queryProposal(proposalID, cliCtx, cdc, queryRoute) + if err != nil { + return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + } + + // Find out which vote option user chose + byteVoteOption, err := gov.VoteOptionFromString(govClientUtils.NormalizeVoteOption(args[1])) if err != nil { return err } - msg := gov.NewMsgVote(voterAddr, proposalID, byteVoteOption) + // Build vote message and run basic validation + msg := gov.NewMsgVote(from, proposalID, byteVoteOption) err = msg.ValidateBasic() if err != nil { return err } + // If generate only print the transaction if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, []sdk.Msg{msg}, false) } - 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. + // 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, no_with_veto, abstain}") - return cmd } diff --git a/x/gov/client/module_client.go b/x/gov/client/module_client.go index 7cabc6884..456ddfbe7 100644 --- a/x/gov/client/module_client.go +++ b/x/gov/client/module_client.go @@ -46,8 +46,8 @@ func (mc ModuleClient) GetTxCmd() *cobra.Command { } govTxCmd.AddCommand(client.PostCommands( - govCli.GetCmdDeposit(mc.cdc), - govCli.GetCmdVote(mc.cdc), + govCli.GetCmdDeposit(mc.storeKey, mc.cdc), + govCli.GetCmdVote(mc.storeKey, mc.cdc), govCli.GetCmdSubmitProposal(mc.cdc), )...)