Merge PR #2062: Support a proposal JSON file in submit-proposal
This commit is contained in:
commit
4b5bb45a09
|
@ -29,6 +29,7 @@ FEATURES
|
||||||
|
|
||||||
* Gaia CLI (`gaiacli`)
|
* Gaia CLI (`gaiacli`)
|
||||||
* [cli] Cmds to query staking pool and params
|
* [cli] Cmds to query staking pool and params
|
||||||
|
* [gov][cli] #2062 added `--proposal` flag to `submit-proposal` that allows a JSON file containing a proposal to be passed in
|
||||||
|
|
||||||
* Gaia
|
* Gaia
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,23 @@ Uuse the CLI to create a new proposal:
|
||||||
simplegovcli propose --title="Voting Period update" --description="Should we change the proposal voting period to 3 weeks?" --deposit=300Atoms
|
simplegovcli propose --title="Voting Period update" --description="Should we change the proposal voting period to 3 weeks?" --deposit=300Atoms
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or, via a json file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
simplegovcli propose --proposal="path/to/proposal.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Where proposal.json contains:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Voting Period Update",
|
||||||
|
"description": "Should we change the proposal voting period to 3 weeks?",
|
||||||
|
"type": "Text",
|
||||||
|
"deposit": "300Atoms"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Get the details of your newly created proposal:
|
Get the details of your newly created proposal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -15,6 +15,9 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"io/ioutil"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,18 +31,51 @@ const (
|
||||||
flagDepositer = "depositer"
|
flagDepositer = "depositer"
|
||||||
flagStatus = "status"
|
flagStatus = "status"
|
||||||
flagLatestProposalIDs = "latest"
|
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.
|
// GetCmdSubmitProposal implements submitting a proposal transaction command.
|
||||||
func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
|
func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "submit-proposal",
|
Use: "submit-proposal",
|
||||||
Short: "Submit a proposal along with an initial deposit",
|
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 {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
title := viper.GetString(flagTitle)
|
proposal, err := parseSubmitProposalFlags()
|
||||||
description := viper.GetString(flagDescription)
|
if err != nil {
|
||||||
strProposalType := viper.GetString(flagProposalType)
|
return err
|
||||||
initialDeposit := viper.GetString(flagDeposit)
|
}
|
||||||
|
|
||||||
txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc)
|
txCtx := authctx.NewTxContextFromCLI().WithCodec(cdc)
|
||||||
cliCtx := context.NewCLIContext().
|
cliCtx := context.NewCLIContext().
|
||||||
|
@ -52,17 +88,17 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
amount, err := sdk.ParseCoins(initialDeposit)
|
amount, err := sdk.ParseCoins(proposal.Deposit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
proposalType, err := gov.ProposalTypeFromString(strProposalType)
|
proposalType, err := gov.ProposalTypeFromString(proposal.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := gov.NewMsgSubmitProposal(title, description, proposalType, fromAddr, amount)
|
msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount)
|
||||||
|
|
||||||
err = msg.ValidateBasic()
|
err = msg.ValidateBasic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -80,10 +116,42 @@ func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
|
||||||
cmd.Flags().String(flagDescription, "", "description of proposal")
|
cmd.Flags().String(flagDescription, "", "description of proposal")
|
||||||
cmd.Flags().String(flagProposalType, "", "proposalType of proposal")
|
cmd.Flags().String(flagProposalType, "", "proposalType of proposal")
|
||||||
cmd.Flags().String(flagDeposit, "", "deposit 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
|
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.
|
// GetCmdDeposit implements depositing tokens for an active proposal.
|
||||||
func GetCmdDeposit(cdc *wire.Codec) *cobra.Command {
|
func GetCmdDeposit(cdc *wire.Codec) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"io/ioutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSubmitProposalFlags(t *testing.T) {
|
||||||
|
okJSON, err := ioutil.TempFile("", "proposal")
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
okJSON.WriteString(`
|
||||||
|
{
|
||||||
|
"title": "Test Proposal",
|
||||||
|
"description": "My awesome proposal",
|
||||||
|
"type": "Text",
|
||||||
|
"deposit": "1000test"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
badJSON, err := ioutil.TempFile("", "proposal")
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
badJSON.WriteString("bad json")
|
||||||
|
|
||||||
|
// nonexistent json
|
||||||
|
viper.Set(flagProposal, "fileDoesNotExist")
|
||||||
|
_, err = parseSubmitProposalFlags()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// invalid json
|
||||||
|
viper.Set(flagProposal, badJSON.Name())
|
||||||
|
_, err = parseSubmitProposalFlags()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// ok json
|
||||||
|
viper.Set(flagProposal, okJSON.Name())
|
||||||
|
proposal1, err := parseSubmitProposalFlags()
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
require.Equal(t, "Test Proposal", proposal1.Title)
|
||||||
|
require.Equal(t, "My awesome proposal", proposal1.Description)
|
||||||
|
require.Equal(t, "Text", proposal1.Type)
|
||||||
|
require.Equal(t, "1000test", proposal1.Deposit)
|
||||||
|
|
||||||
|
// flags that can't be used with --proposal
|
||||||
|
for _, incompatibleFlag := range proposalFlags {
|
||||||
|
viper.Set(incompatibleFlag, "some value")
|
||||||
|
_, err := parseSubmitProposalFlags()
|
||||||
|
require.Error(t, err)
|
||||||
|
viper.Set(incompatibleFlag, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// no --proposal, only flags
|
||||||
|
viper.Set(flagProposal, "")
|
||||||
|
viper.Set(flagTitle, proposal1.Title)
|
||||||
|
viper.Set(flagDescription, proposal1.Description)
|
||||||
|
viper.Set(flagProposalType, proposal1.Type)
|
||||||
|
viper.Set(flagDeposit, proposal1.Deposit)
|
||||||
|
proposal2, err := parseSubmitProposalFlags()
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
require.Equal(t, proposal1.Title, proposal2.Title)
|
||||||
|
require.Equal(t, proposal1.Description, proposal2.Description)
|
||||||
|
require.Equal(t, proposal1.Type, proposal2.Type)
|
||||||
|
require.Equal(t, proposal1.Deposit, proposal2.Deposit)
|
||||||
|
|
||||||
|
err = okJSON.Close()
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
err = badJSON.Close()
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
}
|
Loading…
Reference in New Issue