diff --git a/PENDING.md b/PENDING.md index 557a5e457b45..5f920ae0d57e 100644 --- a/PENDING.md +++ b/PENDING.md @@ -38,6 +38,7 @@ FEATURES * [tests] Remotenet commands for AWS (awsnet) * [store] Add transient store * [gov] Add slashing for validators who do not vote on a proposal +* [cli] added `gov query-proposals` command to CLI. Can filter by `depositer`, `voter`, and `status` IMPROVEMENTS * [baseapp] Allow any alphanumeric character in route diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 7fa438e9dcb1..20d11435b3e3 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -176,7 +176,10 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - // unbond a single share + proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags)) + require.Equal(t, "No matching proposals found", proposalsQuery) + + // submit a test proposal spStr := fmt.Sprintf("gaiacli gov submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") spStr += fmt.Sprintf(" --deposit=%s", "5steak") @@ -194,6 +197,9 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, int64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags)) + require.Equal(t, " 1 - Test", proposalsQuery) + depositStr := fmt.Sprintf("gaiacli gov deposit %v", flags) depositStr += fmt.Sprintf(" --from=%s", "foo") depositStr += fmt.Sprintf(" --deposit=%s", "10steak") @@ -224,6 +230,26 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Len(t, votes, 1) require.Equal(t, int64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) + + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=DepositPeriod %v", flags)) + require.Equal(t, "No matching proposals found", proposalsQuery) + + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=VotingPeriod %v", flags)) + require.Equal(t, " 1 - Test", proposalsQuery) + + // submit a second test proposal + spStr = fmt.Sprintf("gaiacli gov submit-proposal %v", flags) + spStr += fmt.Sprintf(" --from=%s", "foo") + spStr += fmt.Sprintf(" --deposit=%s", "5steak") + spStr += fmt.Sprintf(" --type=%s", "Text") + spStr += fmt.Sprintf(" --title=%s", "Apples") + spStr += fmt.Sprintf(" --description=%s", "test") + + executeWrite(t, spStr, pass) + tests.WaitForNextNBlocksTM(2, port) + + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --latest=1 %v", flags)) + require.Equal(t, " 2 - Apples", proposalsQuery) } //___________________________________________________________________________________ diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 7c66cb9ef7bf..9c4d67b8c4a8 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -113,6 +113,7 @@ func main() { govcmd.GetCmdQueryProposal("gov", cdc), govcmd.GetCmdQueryVote("gov", cdc), govcmd.GetCmdQueryVotes("gov", cdc), + govcmd.GetCmdQueryProposals("gov", cdc), )...) govCmd.AddCommand( client.PostCommands( diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 45b49a54223b..968c66de7c18 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -15,13 +15,16 @@ import ( ) const ( - flagProposalID = "proposal-id" - flagTitle = "title" - flagDescription = "description" - flagProposalType = "type" - flagDeposit = "deposit" - flagVoter = "voter" - flagOption = "option" + flagProposalID = "proposal-id" + flagTitle = "title" + flagDescription = "description" + flagProposalType = "type" + flagDeposit = "deposit" + flagVoter = "voter" + flagOption = "option" + flagDepositer = "depositer" + flagStatus = "status" + flagLatestProposalIDs = "latest" ) // submit a proposal tx @@ -203,6 +206,111 @@ func GetCmdQueryProposal(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } +// nolint: gocyclo +// Command to Query Proposals +func GetCmdQueryProposals(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "query-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) + + var err error + var voterAddr sdk.AccAddress + var depositerAddr sdk.AccAddress + var proposalStatus gov.ProposalStatus + + if len(bechDepositerAddr) != 0 { + depositerAddr, err = sdk.AccAddressFromBech32(bechDepositerAddr) + if err != nil { + return err + } + } + + if len(bechVoterAddr) != 0 { + voterAddr, err = sdk.AccAddressFromBech32(bechVoterAddr) + if err != nil { + return err + } + } + + if len(strProposalStatus) != 0 { + proposalStatus, err = gov.ProposalStatusFromString(strProposalStatus) + if err != nil { + return err + } + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyNextProposalID, storeName) + if err != nil { + return err + } + var maxProposalID int64 + cdc.MustUnmarshalBinary(res, &maxProposalID) + + matchingProposals := []gov.Proposal{} + + if latestProposalsIDs == 0 { + latestProposalsIDs = maxProposalID + } + + for proposalID := maxProposalID - latestProposalsIDs; proposalID < maxProposalID; proposalID++ { + if voterAddr != nil { + res, err = ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName) + if err != nil || len(res) == 0 { + continue + } + } + + if depositerAddr != nil { + res, err = ctx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName) + if err != nil || len(res) == 0 { + continue + } + } + + res, err = ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if err != nil || len(res) == 0 { + continue + } + + var proposal gov.Proposal + cdc.MustUnmarshalBinary(res, &proposal) + + if len(strProposalStatus) != 0 { + if proposal.GetStatus() != proposalStatus { + continue + } + } + + matchingProposals = append(matchingProposals, proposal) + } + + 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 func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{