feat: Add CLI for new gov proposal (#11013)
* feat: Add CLI for new proposal * Add changelog * Add comment * Add metadata * Add tests for parsing * Add CLI test * Use legacy submit proposal in other modules * Update x/gov/client/cli/tx.go * Update x/gov/client/cli/tx.go * Update x/gov/client/cli/tx.go * Remove deprecated cobra field * Update x/gov/client/cli/tx.go Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com>
This commit is contained in:
parent
24c97d529f
commit
62d97907e2
|
@ -124,6 +124,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||||
|
|
||||||
* [\#9594](https://github.com/cosmos/cosmos-sdk/pull/9594) Remove legacy REST API. Please see the [REST Endpoints Migration guide](https://docs.cosmos.network/master/migrations/rest.html) to migrate to the new REST endpoints.
|
* [\#9594](https://github.com/cosmos/cosmos-sdk/pull/9594) Remove legacy REST API. Please see the [REST Endpoints Migration guide](https://docs.cosmos.network/master/migrations/rest.html) to migrate to the new REST endpoints.
|
||||||
* [\#9995](https://github.com/cosmos/cosmos-sdk/pull/9995) Increased gas cost for creating proposals.
|
* [\#9995](https://github.com/cosmos/cosmos-sdk/pull/9995) Increased gas cost for creating proposals.
|
||||||
|
* [\#11013](https://github.com/cosmos/cosmos-sdk/pull/) The `tx gov submit-proposal` command has changed syntax to support the new Msg-based gov proposals. To access the old CLI command, please use `tx gov submit-legacy-proposal`.
|
||||||
|
|
||||||
### CLI Breaking Changes
|
### CLI Breaking Changes
|
||||||
|
|
||||||
|
|
|
@ -1503,7 +1503,7 @@ func (s *IntegrationTestSuite) TestAuxSigner() {
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tc := tc
|
tc := tc
|
||||||
s.Run(tc.name, func() {
|
s.Run(tc.name, func() {
|
||||||
_, err := govtestutil.MsgSubmitProposal(
|
_, err := govtestutil.MsgSubmitLegacyProposal(
|
||||||
val.ClientCtx,
|
val.ClientCtx,
|
||||||
val.Address.String(),
|
val.Address.String(),
|
||||||
"test",
|
"test",
|
||||||
|
@ -1747,7 +1747,7 @@ func (s *IntegrationTestSuite) TestAuxToFee() {
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tc := tc
|
tc := tc
|
||||||
s.Run(tc.name, func() {
|
s.Run(tc.name, func() {
|
||||||
res, err := govtestutil.MsgSubmitProposal(
|
res, err := govtestutil.MsgSubmitLegacyProposal(
|
||||||
val.ClientCtx,
|
val.ClientCtx,
|
||||||
tipper.String(),
|
tipper.String(),
|
||||||
"test",
|
"test",
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
// create a proposal with deposit
|
// create a proposal with deposit
|
||||||
_, err = govtestutil.MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
_, err = govtestutil.MsgSubmitLegacyProposal(val.ClientCtx, val.Address.String(),
|
||||||
"Text Proposal 1", "Where is the title!?", govv1beta1.ProposalTypeText,
|
"Text Proposal 1", "Where is the title!?", govv1beta1.ProposalTypeText,
|
||||||
fmt.Sprintf("--%s=%s", govcli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, govv1beta2.DefaultMinDepositTokens).String()))
|
fmt.Sprintf("--%s=%s", govcli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, govv1beta2.DefaultMinDepositTokens).String()))
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
|
@ -752,7 +752,7 @@ func (s *IntegrationTestSuite) TestTxWithFeeGrant() {
|
||||||
|
|
||||||
// granted fee allowance for an account which is not in state and creating
|
// granted fee allowance for an account which is not in state and creating
|
||||||
// any tx with it by using --fee-account shouldn't fail
|
// any tx with it by using --fee-account shouldn't fail
|
||||||
out, err := govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(),
|
out, err := govtestutil.MsgSubmitLegacyProposal(val.ClientCtx, grantee.String(),
|
||||||
"Text Proposal", "No desc", govv1beta1.ProposalTypeText,
|
"Text Proposal", "No desc", govv1beta1.ProposalTypeText,
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()),
|
fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()),
|
||||||
)
|
)
|
||||||
|
@ -892,7 +892,7 @@ func (s *IntegrationTestSuite) TestFilteredFeeAllowance() {
|
||||||
{
|
{
|
||||||
"valid proposal tx",
|
"valid proposal tx",
|
||||||
func() (testutil.BufferWriter, error) {
|
func() (testutil.BufferWriter, error) {
|
||||||
return govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(),
|
return govtestutil.MsgSubmitLegacyProposal(val.ClientCtx, grantee.String(),
|
||||||
"Text Proposal", "No desc", govv1beta1.ProposalTypeText,
|
"Text Proposal", "No desc", govv1beta1.ProposalTypeText,
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()),
|
fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()),
|
||||||
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String()),
|
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String()),
|
||||||
|
|
|
@ -7,11 +7,20 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
govutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
govutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseSubmitProposalFlags(fs *pflag.FlagSet) (*proposal, error) {
|
type legacyProposal struct {
|
||||||
proposal := &proposal{}
|
Title string
|
||||||
|
Description string
|
||||||
|
Type string
|
||||||
|
Deposit string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) {
|
||||||
|
proposal := &legacyProposal{}
|
||||||
proposalFile, _ := fs.GetString(FlagProposal)
|
proposalFile, _ := fs.GetString(FlagProposal)
|
||||||
|
|
||||||
if proposalFile == "" {
|
if proposalFile == "" {
|
||||||
|
@ -42,3 +51,43 @@ func parseSubmitProposalFlags(fs *pflag.FlagSet) (*proposal, error) {
|
||||||
|
|
||||||
return proposal, nil
|
return proposal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// proposal defines the new Msg-based proposal.
|
||||||
|
type proposal struct {
|
||||||
|
// Msgs defines an array of sdk.Msgs proto-JSON-encoded as Anys.
|
||||||
|
Messages []json.RawMessage
|
||||||
|
Metadata []byte
|
||||||
|
Deposit string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSubmitProposal(cdc codec.Codec, path string) ([]sdk.Msg, []byte, sdk.Coins, error) {
|
||||||
|
var proposal proposal
|
||||||
|
|
||||||
|
contents, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(contents, &proposal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := make([]sdk.Msg, len(proposal.Messages))
|
||||||
|
for i, anyJSON := range proposal.Messages {
|
||||||
|
var msg sdk.Msg
|
||||||
|
err := cdc.UnmarshalInterfaceJSON(anyJSON, &msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs[i] = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
deposit, err := sdk.ParseCoinsNormalized(proposal.Deposit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgs, proposal.Metadata, deposit, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil"
|
"github.com/cosmos/cosmos-sdk/testutil"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta2"
|
||||||
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseSubmitProposalFlags(t *testing.T) {
|
func TestParseSubmitLegacyProposalFlags(t *testing.T) {
|
||||||
okJSON := testutil.WriteToNewTempFile(t, `
|
okJSON := testutil.WriteToNewTempFile(t, `
|
||||||
{
|
{
|
||||||
"title": "Test Proposal",
|
"title": "Test Proposal",
|
||||||
|
@ -19,21 +29,21 @@ func TestParseSubmitProposalFlags(t *testing.T) {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
badJSON := testutil.WriteToNewTempFile(t, "bad json")
|
badJSON := testutil.WriteToNewTempFile(t, "bad json")
|
||||||
fs := NewCmdSubmitProposal().Flags()
|
fs := NewCmdSubmitLegacyProposal().Flags()
|
||||||
|
|
||||||
// nonexistent json
|
// nonexistent json
|
||||||
fs.Set(FlagProposal, "fileDoesNotExist")
|
fs.Set(FlagProposal, "fileDoesNotExist")
|
||||||
_, err := parseSubmitProposalFlags(fs)
|
_, err := parseSubmitLegacyProposalFlags(fs)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// invalid json
|
// invalid json
|
||||||
fs.Set(FlagProposal, badJSON.Name())
|
fs.Set(FlagProposal, badJSON.Name())
|
||||||
_, err = parseSubmitProposalFlags(fs)
|
_, err = parseSubmitLegacyProposalFlags(fs)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// ok json
|
// ok json
|
||||||
fs.Set(FlagProposal, okJSON.Name())
|
fs.Set(FlagProposal, okJSON.Name())
|
||||||
proposal1, err := parseSubmitProposalFlags(fs)
|
proposal1, err := parseSubmitLegacyProposalFlags(fs)
|
||||||
require.Nil(t, err, "unexpected error")
|
require.Nil(t, err, "unexpected error")
|
||||||
require.Equal(t, "Test Proposal", proposal1.Title)
|
require.Equal(t, "Test Proposal", proposal1.Title)
|
||||||
require.Equal(t, "My awesome proposal", proposal1.Description)
|
require.Equal(t, "My awesome proposal", proposal1.Description)
|
||||||
|
@ -43,7 +53,7 @@ func TestParseSubmitProposalFlags(t *testing.T) {
|
||||||
// flags that can't be used with --proposal
|
// flags that can't be used with --proposal
|
||||||
for _, incompatibleFlag := range ProposalFlags {
|
for _, incompatibleFlag := range ProposalFlags {
|
||||||
fs.Set(incompatibleFlag, "some value")
|
fs.Set(incompatibleFlag, "some value")
|
||||||
_, err := parseSubmitProposalFlags(fs)
|
_, err := parseSubmitLegacyProposalFlags(fs)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
fs.Set(incompatibleFlag, "")
|
fs.Set(incompatibleFlag, "")
|
||||||
}
|
}
|
||||||
|
@ -54,7 +64,7 @@ func TestParseSubmitProposalFlags(t *testing.T) {
|
||||||
fs.Set(FlagDescription, proposal1.Description)
|
fs.Set(FlagDescription, proposal1.Description)
|
||||||
fs.Set(FlagProposalType, proposal1.Type)
|
fs.Set(FlagProposalType, proposal1.Type)
|
||||||
fs.Set(FlagDeposit, proposal1.Deposit)
|
fs.Set(FlagDeposit, proposal1.Deposit)
|
||||||
proposal2, err := parseSubmitProposalFlags(fs)
|
proposal2, err := parseSubmitLegacyProposalFlags(fs)
|
||||||
|
|
||||||
require.Nil(t, err, "unexpected error")
|
require.Nil(t, err, "unexpected error")
|
||||||
require.Equal(t, proposal1.Title, proposal2.Title)
|
require.Equal(t, proposal1.Title, proposal2.Title)
|
||||||
|
@ -67,3 +77,83 @@ func TestParseSubmitProposalFlags(t *testing.T) {
|
||||||
err = badJSON.Close()
|
err = badJSON.Close()
|
||||||
require.Nil(t, err, "unexpected error")
|
require.Nil(t, err, "unexpected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseSubmitProposal(t *testing.T) {
|
||||||
|
_, _, addr := testdata.KeyTestPubAddr()
|
||||||
|
interfaceRegistry := codectypes.NewInterfaceRegistry()
|
||||||
|
cdc := codec.NewProtoCodec(interfaceRegistry)
|
||||||
|
banktypes.RegisterInterfaces(interfaceRegistry)
|
||||||
|
stakingtypes.RegisterInterfaces(interfaceRegistry)
|
||||||
|
v1beta1.RegisterInterfaces(interfaceRegistry)
|
||||||
|
v1beta2.RegisterInterfaces(interfaceRegistry)
|
||||||
|
expectedMetadata := []byte{42}
|
||||||
|
|
||||||
|
okJSON := testutil.WriteToNewTempFile(t, fmt.Sprintf(`
|
||||||
|
{
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"@type": "/cosmos.bank.v1beta1.MsgSend",
|
||||||
|
"from_address": "%s",
|
||||||
|
"to_address": "%s",
|
||||||
|
"amount":[{"denom": "stake","amount": "10"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "/cosmos.staking.v1beta1.MsgDelegate",
|
||||||
|
"delegator_address": "%s",
|
||||||
|
"validator_address": "%s",
|
||||||
|
"amount":{"denom": "stake","amount": "10"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "/cosmos.gov.v1beta2.MsgExecLegacyContent",
|
||||||
|
"authority": "%s",
|
||||||
|
"content": {
|
||||||
|
"@type": "/cosmos.gov.v1beta1.TextProposal",
|
||||||
|
"title": "My awesome title",
|
||||||
|
"description": "My awesome description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": "%s",
|
||||||
|
"deposit": "1000test"
|
||||||
|
}
|
||||||
|
`, addr, addr, addr, addr, addr, base64.StdEncoding.EncodeToString(expectedMetadata)))
|
||||||
|
|
||||||
|
badJSON := testutil.WriteToNewTempFile(t, "bad json")
|
||||||
|
|
||||||
|
// nonexistent json
|
||||||
|
_, _, _, err := parseSubmitProposal(cdc, "fileDoesNotExist")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// invalid json
|
||||||
|
_, _, _, err = parseSubmitProposal(cdc, badJSON.Name())
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// ok json
|
||||||
|
msgs, metadata, deposit, err := parseSubmitProposal(cdc, okJSON.Name())
|
||||||
|
require.NoError(t, err, "unexpected error")
|
||||||
|
require.Equal(t, sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(1000))), deposit)
|
||||||
|
require.Equal(t, expectedMetadata, metadata)
|
||||||
|
require.Len(t, msgs, 3)
|
||||||
|
msg1, ok := msgs[0].(*banktypes.MsgSend)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, addr.String(), msg1.FromAddress)
|
||||||
|
require.Equal(t, addr.String(), msg1.ToAddress)
|
||||||
|
require.Equal(t, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10))), msg1.Amount)
|
||||||
|
msg2, ok := msgs[1].(*stakingtypes.MsgDelegate)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, addr.String(), msg2.DelegatorAddress)
|
||||||
|
require.Equal(t, addr.String(), msg2.ValidatorAddress)
|
||||||
|
require.Equal(t, sdk.NewCoin("stake", sdk.NewInt(10)), msg2.Amount)
|
||||||
|
msg3, ok := msgs[2].(*v1beta2.MsgExecLegacyContent)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, addr.String(), msg3.Authority)
|
||||||
|
textProp, ok := msg3.Content.GetCachedValue().(*v1beta1.TextProposal)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, "My awesome title", textProp.Title)
|
||||||
|
require.Equal(t, "My awesome description", textProp.Description)
|
||||||
|
|
||||||
|
err = okJSON.Close()
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
err = badJSON.Close()
|
||||||
|
require.Nil(t, err, "unexpected error")
|
||||||
|
}
|
||||||
|
|
|
@ -20,23 +20,21 @@ import (
|
||||||
|
|
||||||
// Proposal flags
|
// Proposal flags
|
||||||
const (
|
const (
|
||||||
|
// Deprecated: only used for v1beta1 legacy proposals.
|
||||||
FlagTitle = "title"
|
FlagTitle = "title"
|
||||||
|
// Deprecated: only used for v1beta1 legacy proposals.
|
||||||
FlagDescription = "description"
|
FlagDescription = "description"
|
||||||
|
// Deprecated: only used for v1beta1 legacy proposals.
|
||||||
FlagProposalType = "type"
|
FlagProposalType = "type"
|
||||||
|
// Deprecated: only used for v1beta1 legacy proposals.
|
||||||
FlagDeposit = "deposit"
|
FlagDeposit = "deposit"
|
||||||
flagVoter = "voter"
|
flagVoter = "voter"
|
||||||
flagDepositor = "depositor"
|
flagDepositor = "depositor"
|
||||||
flagStatus = "status"
|
flagStatus = "status"
|
||||||
|
// Deprecated: only used for v1beta1 legacy proposals.
|
||||||
FlagProposal = "proposal"
|
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
|
// 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
|
// verify that these values are not provided in conjunction with a JSON proposal
|
||||||
// file.
|
// file.
|
||||||
|
@ -61,19 +59,18 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command {
|
||||||
RunE: client.ValidateCmd,
|
RunE: client.ValidateCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Add CLI for new submit proposal
|
cmdSubmitLegacyProp := NewCmdSubmitLegacyProposal()
|
||||||
// https://github.com/cosmos/cosmos-sdk/issues/10952
|
|
||||||
cmdSubmitProp := NewCmdSubmitProposal()
|
|
||||||
for _, propCmd := range propCmds {
|
for _, propCmd := range propCmds {
|
||||||
flags.AddTxFlagsToCmd(propCmd)
|
flags.AddTxFlagsToCmd(propCmd)
|
||||||
cmdSubmitProp.AddCommand(propCmd)
|
cmdSubmitLegacyProp.AddCommand(propCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
govTxCmd.AddCommand(
|
govTxCmd.AddCommand(
|
||||||
NewCmdDeposit(),
|
NewCmdDeposit(),
|
||||||
NewCmdVote(),
|
NewCmdVote(),
|
||||||
NewCmdWeightedVote(),
|
NewCmdWeightedVote(),
|
||||||
cmdSubmitProp,
|
NewCmdSubmitProposal(),
|
||||||
|
cmdSubmitLegacyProp,
|
||||||
)
|
)
|
||||||
|
|
||||||
return govTxCmd
|
return govTxCmd
|
||||||
|
@ -83,13 +80,71 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command {
|
||||||
func NewCmdSubmitProposal() *cobra.Command {
|
func NewCmdSubmitProposal() *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 some messages and metadata",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
Long: strings.TrimSpace(
|
Long: strings.TrimSpace(
|
||||||
fmt.Sprintf(`Submit a proposal along with an initial deposit.
|
fmt.Sprintf(`Submit a proposal along with some messages and metadata.
|
||||||
|
Messages, metadata and deposit are defined in a JSON file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
$ %s tx gov submit-proposal path/to/proposal.json
|
||||||
|
|
||||||
|
Where proposal.json contains:
|
||||||
|
|
||||||
|
{
|
||||||
|
// array of proto-JSON-encoded sdk.Msgs
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"@type": "/cosmos.bank.v1beta1.MsgSend",
|
||||||
|
"from_address": "cosmos1...",
|
||||||
|
"to_address": "cosmos1...",
|
||||||
|
"amount":[{"denom": "stake","amount": "10"}]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata: "4pIMOgIGx1vZGU=", // base64-encoded metadata
|
||||||
|
"deposit": "10stake"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
version.AppName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCtx, err := client.GetClientTxContext(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, metadata, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := v1beta2.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String(), metadata)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.AddTxFlagsToCmd(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCmdSubmitLegacyProposal implements submitting a proposal transaction command.
|
||||||
|
// Deprecated: please use NewCmdSubmitProposal instead.
|
||||||
|
func NewCmdSubmitLegacyProposal() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "submit-legacy-proposal",
|
||||||
|
Short: "Submit a legacy proposal along with an initial deposit",
|
||||||
|
Long: strings.TrimSpace(
|
||||||
|
fmt.Sprintf(`Submit a legacy proposal along with an initial deposit.
|
||||||
Proposal title, description, type and deposit can be given directly or through a proposal JSON file.
|
Proposal title, description, type and deposit can be given directly or through a proposal JSON file.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
$ %s tx gov submit-proposal --proposal="path/to/proposal.json" --from mykey
|
$ %s tx gov submit-legacy-proposal --proposal="path/to/proposal.json" --from mykey
|
||||||
|
|
||||||
Where proposal.json contains:
|
Where proposal.json contains:
|
||||||
|
|
||||||
|
@ -102,7 +157,7 @@ Where proposal.json contains:
|
||||||
|
|
||||||
Which is equivalent to:
|
Which is equivalent to:
|
||||||
|
|
||||||
$ %s tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey
|
$ %s tx gov submit-legacy-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey
|
||||||
`,
|
`,
|
||||||
version.AppName, version.AppName,
|
version.AppName, version.AppName,
|
||||||
),
|
),
|
||||||
|
@ -113,7 +168,7 @@ $ %s tx gov submit-proposal --title="Test Proposal" --description="My awesome pr
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
proposal, err := parseSubmitProposalFlags(cmd.Flags())
|
proposal, err := parseSubmitLegacyProposalFlags(cmd.Flags())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse proposal: %w", err)
|
return fmt.Errorf("failed to parse proposal: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (s *DepositTestSuite) createProposal(val *network.Validator, initialDeposit
|
||||||
exactArgs = append(exactArgs, fmt.Sprintf("--%s=%s", cli.FlagDeposit, initialDeposit.String()))
|
exactArgs = append(exactArgs, fmt.Sprintf("--%s=%s", cli.FlagDeposit, initialDeposit.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := MsgSubmitProposal(
|
_, err := MsgSubmitLegacyProposal(
|
||||||
val.ClientCtx,
|
val.ClientCtx,
|
||||||
val.Address.String(),
|
val.Address.String(),
|
||||||
fmt.Sprintf("Text Proposal %d", id),
|
fmt.Sprintf("Text Proposal %d", id),
|
||||||
|
|
|
@ -31,6 +31,20 @@ func MsgSubmitProposal(clientCtx client.Context, from, title, description, propo
|
||||||
return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdSubmitProposal(), args)
|
return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdSubmitProposal(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MsgSubmitLegacyProposal creates a tx for submit legacy proposal
|
||||||
|
func MsgSubmitLegacyProposal(clientCtx client.Context, from, title, description, proposalType string, extraArgs ...string) (testutil.BufferWriter, error) {
|
||||||
|
args := append([]string{
|
||||||
|
fmt.Sprintf("--%s=%s", govcli.FlagTitle, title),
|
||||||
|
fmt.Sprintf("--%s=%s", govcli.FlagDescription, description),
|
||||||
|
fmt.Sprintf("--%s=%s", govcli.FlagProposalType, proposalType),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFrom, from),
|
||||||
|
}, commonArgs...)
|
||||||
|
|
||||||
|
args = append(args, extraArgs...)
|
||||||
|
|
||||||
|
return clitestutil.ExecTestCLICmd(clientCtx, govcli.NewCmdSubmitLegacyProposal(), args)
|
||||||
|
}
|
||||||
|
|
||||||
// MsgVote votes for a proposal
|
// MsgVote votes for a proposal
|
||||||
func MsgVote(clientCtx client.Context, from, id, vote string, extraArgs ...string) (testutil.BufferWriter, error) {
|
func MsgVote(clientCtx client.Context, from, id, vote string, extraArgs ...string) (testutil.BufferWriter, error) {
|
||||||
args := append([]string{
|
args := append([]string{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -15,7 +16,9 @@ import (
|
||||||
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
|
||||||
"github.com/cosmos/cosmos-sdk/testutil/network"
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta2"
|
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta2"
|
||||||
)
|
)
|
||||||
|
@ -44,7 +47,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
|
||||||
// create a proposal with deposit
|
// create a proposal with deposit
|
||||||
_, err = MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
_, err = MsgSubmitLegacyProposal(val.ClientCtx, val.Address.String(),
|
||||||
"Text Proposal 1", "Where is the title!?", v1beta1.ProposalTypeText,
|
"Text Proposal 1", "Where is the title!?", v1beta1.ProposalTypeText,
|
||||||
fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, v1beta2.DefaultMinDepositTokens).String()))
|
fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, v1beta2.DefaultMinDepositTokens).String()))
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
@ -56,14 +59,14 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
// create a proposal without deposit
|
// create a proposal without deposit
|
||||||
_, err = MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
_, err = MsgSubmitLegacyProposal(val.ClientCtx, val.Address.String(),
|
||||||
"Text Proposal 2", "Where is the title!?", v1beta1.ProposalTypeText)
|
"Text Proposal 2", "Where is the title!?", v1beta1.ProposalTypeText)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
_, err = s.network.WaitForHeight(1)
|
_, err = s.network.WaitForHeight(1)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
// create a proposal3 with deposit
|
// create a proposal3 with deposit
|
||||||
_, err = MsgSubmitProposal(val.ClientCtx, val.Address.String(),
|
_, err = MsgSubmitLegacyProposal(val.ClientCtx, val.Address.String(),
|
||||||
"Text Proposal 3", "Where is the title!?", v1beta1.ProposalTypeText,
|
"Text Proposal 3", "Where is the title!?", v1beta1.ProposalTypeText,
|
||||||
fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, v1beta2.DefaultMinDepositTokens).String()))
|
fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, v1beta2.DefaultMinDepositTokens).String()))
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
@ -278,6 +281,88 @@ func (s *IntegrationTestSuite) TestCmdTally() {
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestNewCmdSubmitProposal() {
|
func (s *IntegrationTestSuite) TestNewCmdSubmitProposal() {
|
||||||
val := s.network.Validators[0]
|
val := s.network.Validators[0]
|
||||||
|
|
||||||
|
// Create an legacy proposal JSON, make sure it doesn't pass this new CLI
|
||||||
|
// command.
|
||||||
|
invalidProp := `{
|
||||||
|
"title": "",
|
||||||
|
"description": "Where is the title!?",
|
||||||
|
"type": "Text",
|
||||||
|
"deposit": "-324foocoin"
|
||||||
|
}`
|
||||||
|
invalidPropFile := testutil.WriteToNewTempFile(s.T(), invalidProp)
|
||||||
|
|
||||||
|
// Create a valid new proposal JSON.
|
||||||
|
propMetadata := []byte{42}
|
||||||
|
validProp := fmt.Sprintf(`
|
||||||
|
{
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"@type": "/cosmos.gov.v1beta2.MsgExecLegacyContent",
|
||||||
|
"authority": "%s",
|
||||||
|
"content": {
|
||||||
|
"@type": "/cosmos.gov.v1beta1.TextProposal",
|
||||||
|
"title": "My awesome title",
|
||||||
|
"description": "My awesome description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": "%s",
|
||||||
|
"deposit": "%s"
|
||||||
|
}`, authtypes.NewModuleAddress(types.ModuleName), base64.StdEncoding.EncodeToString(propMetadata), sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(5431)))
|
||||||
|
validPropFile := testutil.WriteToNewTempFile(s.T(), validProp)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectErr bool
|
||||||
|
expectedCode uint32
|
||||||
|
respType proto.Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"invalid proposal",
|
||||||
|
[]string{
|
||||||
|
invalidPropFile.Name(),
|
||||||
|
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
||||||
|
},
|
||||||
|
true, 0, nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"valid proposal",
|
||||||
|
[]string{
|
||||||
|
validPropFile.Name(),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
|
||||||
|
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
|
||||||
|
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
|
||||||
|
},
|
||||||
|
false, 0, &sdk.TxResponse{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
|
s.Run(tc.name, func() {
|
||||||
|
cmd := cli.NewCmdSubmitProposal()
|
||||||
|
clientCtx := val.ClientCtx
|
||||||
|
|
||||||
|
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
|
||||||
|
if tc.expectErr {
|
||||||
|
s.Require().Error(err)
|
||||||
|
} else {
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
|
||||||
|
txResp := tc.respType.(*sdk.TxResponse)
|
||||||
|
s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationTestSuite) TestNewCmdSubmitLegacyProposal() {
|
||||||
|
val := s.network.Validators[0]
|
||||||
invalidProp := `{
|
invalidProp := `{
|
||||||
"title": "",
|
"title": "",
|
||||||
"description": "Where is the title!?",
|
"description": "Where is the title!?",
|
||||||
|
@ -352,7 +437,7 @@ func (s *IntegrationTestSuite) TestNewCmdSubmitProposal() {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
|
||||||
s.Run(tc.name, func() {
|
s.Run(tc.name, func() {
|
||||||
cmd := cli.NewCmdSubmitProposal()
|
cmd := cli.NewCmdSubmitLegacyProposal()
|
||||||
clientCtx := val.ClientCtx
|
clientCtx := val.ClientCtx
|
||||||
|
|
||||||
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
|
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
|
||||||
|
|
Loading…
Reference in New Issue