From be591c6616a38f8aaf8a420b9065dae4868c2372 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:47:08 +0100 Subject: [PATCH 01/12] feat: Add CLI for new proposal --- x/gov/client/cli/parse.go | 53 +++++++++++++++++++- x/gov/client/cli/parse_test.go | 12 ++--- x/gov/client/cli/tx.go | 92 +++++++++++++++++++++++++++------- 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/x/gov/client/cli/parse.go b/x/gov/client/cli/parse.go index eeb814eb98e7..d29b29e58591 100644 --- a/x/gov/client/cli/parse.go +++ b/x/gov/client/cli/parse.go @@ -7,11 +7,20 @@ import ( "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" ) -func parseSubmitProposalFlags(fs *pflag.FlagSet) (*proposal, error) { - proposal := &proposal{} +type legacyProposal struct { + Title string + Description string + Type string + Deposit string +} + +func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) { + proposal := &legacyProposal{} proposalFile, _ := fs.GetString(FlagProposal) if proposalFile == "" { @@ -42,3 +51,43 @@ func parseSubmitProposalFlags(fs *pflag.FlagSet) (*proposal, error) { return proposal, nil } + +type proposal struct { + Msgs []json.RawMessage + Metadata []byte + Deposit string +} + +func parseSubmitProposal(cdc codec.Codec, path string, fs *pflag.FlagSet) ([]sdk.Msg, []byte, sdk.Coins, error) { + proposal := &proposal{} + + proposal.Deposit, _ = fs.GetString(FlagDeposit) + + 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.Msgs)) + for i, any := range proposal.Msgs { + var msg sdk.Msg + err := cdc.UnmarshalInterfaceJSON(any, &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 +} diff --git a/x/gov/client/cli/parse_test.go b/x/gov/client/cli/parse_test.go index da6aeea71949..c7e9dfaaf343 100644 --- a/x/gov/client/cli/parse_test.go +++ b/x/gov/client/cli/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" ) -func TestParseSubmitProposalFlags(t *testing.T) { +func TestParseSubmitLegacyProposalFlags(t *testing.T) { okJSON := testutil.WriteToNewTempFile(t, ` { "title": "Test Proposal", @@ -23,17 +23,17 @@ func TestParseSubmitProposalFlags(t *testing.T) { // nonexistent json fs.Set(FlagProposal, "fileDoesNotExist") - _, err := parseSubmitProposalFlags(fs) + _, err := parseSubmitLegacyProposalFlags(fs) require.Error(t, err) // invalid json fs.Set(FlagProposal, badJSON.Name()) - _, err = parseSubmitProposalFlags(fs) + _, err = parseSubmitLegacyProposalFlags(fs) require.Error(t, err) // ok json fs.Set(FlagProposal, okJSON.Name()) - proposal1, err := parseSubmitProposalFlags(fs) + proposal1, err := parseSubmitLegacyProposalFlags(fs) require.Nil(t, err, "unexpected error") require.Equal(t, "Test Proposal", proposal1.Title) require.Equal(t, "My awesome proposal", proposal1.Description) @@ -43,7 +43,7 @@ func TestParseSubmitProposalFlags(t *testing.T) { // flags that can't be used with --proposal for _, incompatibleFlag := range ProposalFlags { fs.Set(incompatibleFlag, "some value") - _, err := parseSubmitProposalFlags(fs) + _, err := parseSubmitLegacyProposalFlags(fs) require.Error(t, err) fs.Set(incompatibleFlag, "") } @@ -54,7 +54,7 @@ func TestParseSubmitProposalFlags(t *testing.T) { fs.Set(FlagDescription, proposal1.Description) fs.Set(FlagProposalType, proposal1.Type) fs.Set(FlagDeposit, proposal1.Deposit) - proposal2, err := parseSubmitProposalFlags(fs) + proposal2, err := parseSubmitLegacyProposalFlags(fs) require.Nil(t, err, "unexpected error") require.Equal(t, proposal1.Title, proposal2.Title) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index ecec13bd6cd9..5031675e451c 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -20,23 +20,20 @@ import ( // Proposal flags const ( - FlagTitle = "title" - FlagDescription = "description" + // Deprecated: only used for v1beta1 legacy proposals. + FlagTitle = "title" + // Deprecated: only used for v1beta1 legacy proposals. + FlagDescription = "description" + // Deprecated: only used for v1beta1 legacy proposals. FlagProposalType = "type" FlagDeposit = "deposit" flagVoter = "voter" flagDepositor = "depositor" flagStatus = "status" - FlagProposal = "proposal" + // Deprecated: only used for v1beta1 legacy proposals. + 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 // verify that these values are not provided in conjunction with a JSON proposal // file. @@ -63,33 +60,90 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command { // TODO Add CLI for new submit proposal // https://github.com/cosmos/cosmos-sdk/issues/10952 - cmdSubmitProp := NewCmdSubmitProposal() + cmdSubmitLegacyProp := NewCmdSubmitLegacyProposal() for _, propCmd := range propCmds { flags.AddTxFlagsToCmd(propCmd) - cmdSubmitProp.AddCommand(propCmd) + cmdSubmitLegacyProp.AddCommand(propCmd) } govTxCmd.AddCommand( NewCmdDeposit(), NewCmdVote(), NewCmdWeightedVote(), - cmdSubmitProp, + NewCmdSubmitProposal(), + cmdSubmitLegacyProp, ) return govTxCmd } -// NewCmdSubmitProposal implements submitting a proposal transaction command. +// NewCmdSubmitLegacyProposal implements submitting a proposal transaction command. func NewCmdSubmitProposal() *cobra.Command { cmd := &cobra.Command{ 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( + fmt.Sprintf(`Submit a proposal along with some messages and metadata. +Messages and metadata are defined in a JSON file. + +Example: +$ %s tx gov submit-proposal path/to/proposal.json --deposit="10test" + +Where proposal.json contains: + +{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + // -- snip -- + } + ], + "metadata: "4pIMOgIGx1vZGU=" // base64-encoded metadata +} +`, + version.AppName, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + msgs, _, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0], cmd.Flags()) + if err != nil { + return err + } + + msg, err := v1beta2.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String()) + if err != nil { + return fmt.Errorf("invalid message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().String(FlagDeposit, "", "The proposal deposit") + 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", + Deprecated: "This command is deprecated, prefer to use `submit-proposal` instead", + Short: "Submit a legacy proposal along with an initial deposit", Long: strings.TrimSpace( - fmt.Sprintf(`Submit a proposal along with an initial deposit. + 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. 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: @@ -102,7 +156,7 @@ Where proposal.json contains: 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, ), @@ -113,7 +167,7 @@ $ %s tx gov submit-proposal --title="Test Proposal" --description="My awesome pr return err } - proposal, err := parseSubmitProposalFlags(cmd.Flags()) + proposal, err := parseSubmitLegacyProposalFlags(cmd.Flags()) if err != nil { return fmt.Errorf("failed to parse proposal: %w", err) } From 50241dae7f2dde7b5794341f94bbe02787cb9ddd Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:51:29 +0100 Subject: [PATCH 02/12] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eafd98da196..a1e68d96cfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,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. * [\#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 From 0629492c352fcf611f528c576a3bef86a034f4b8 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:53:37 +0100 Subject: [PATCH 03/12] Add comment --- x/gov/client/cli/parse.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/gov/client/cli/parse.go b/x/gov/client/cli/parse.go index d29b29e58591..1d21da324c68 100644 --- a/x/gov/client/cli/parse.go +++ b/x/gov/client/cli/parse.go @@ -53,6 +53,7 @@ func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) } type proposal struct { + // Msgs defines an array of sdk.Msgs proto-JSON-encoded as Anys. Msgs []json.RawMessage Metadata []byte Deposit string From 07c0b674f028b28e887eeba011f8b8b340eb1661 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:21:41 +0100 Subject: [PATCH 04/12] Add metadata --- x/gov/client/cli/parse_test.go | 71 +++++++++++++++++++++++++++++++++- x/gov/client/cli/tx.go | 4 +- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/x/gov/client/cli/parse_test.go b/x/gov/client/cli/parse_test.go index c7e9dfaaf343..eac0dfb4e471 100644 --- a/x/gov/client/cli/parse_test.go +++ b/x/gov/client/cli/parse_test.go @@ -1,11 +1,14 @@ package cli import ( + "fmt" "testing" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/testdata" ) func TestParseSubmitLegacyProposalFlags(t *testing.T) { @@ -19,7 +22,7 @@ func TestParseSubmitLegacyProposalFlags(t *testing.T) { `) badJSON := testutil.WriteToNewTempFile(t, "bad json") - fs := NewCmdSubmitProposal().Flags() + fs := NewCmdSubmitLegacyProposal().Flags() // nonexistent json fs.Set(FlagProposal, "fileDoesNotExist") @@ -67,3 +70,69 @@ func TestParseSubmitLegacyProposalFlags(t *testing.T) { err = badJSON.Close() require.Nil(t, err, "unexpected error") } + +func TestparseSubmitProposal(t *testing.T) { + _, _, addr := testdata.KeyTestPubAddr() + encCfg := simapp.MakeTestEncodingConfig() + + okJSON := testutil.WriteToNewTempFile(t, fmt.Sprintf(` +{ + "messages": [ + { + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"%s", + "to_address":"%s", + "amount":[{"denom":"stake","amount":"10"}] + } + ], + "deposit": "1000test" +} +`, addr, addr)) + + badJSON := testutil.WriteToNewTempFile(t, "bad json") + fs := NewCmdSubmitProposal().Flags() + + // nonexistent json + _, _, _, err := parseSubmitProposal(encCfg.Codec, "fileDoesNotExist", fs) + require.Error(t, err) + + // invalid json + _, _, _, err = parseSubmitProposal(encCfg.Codec, badJSON.Name(), fs) + require.Error(t, err) + + // ok json + fs.Set(FlagProposal, okJSON.Name()) + _, _, proposal1, err := parseSubmitProposal(fs) + 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 { + fs.Set(incompatibleFlag, "some value") + _, _, _, err := parseSubmitProposal(fs) + require.Error(t, err) + fs.Set(incompatibleFlag, "") + } + + // no --proposal, only flags + fs.Set(FlagProposal, "") + fs.Set(FlagTitle, proposal1.Title) + fs.Set(FlagDescription, proposal1.Description) + fs.Set(FlagProposalType, proposal1.Type) + fs.Set(FlagDeposit, proposal1.Deposit) + _, _, proposal2, err := parseSubmitProposal(fs) + + 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") +} diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 5031675e451c..6b5204c32fe4 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -111,12 +111,12 @@ Where proposal.json contains: return err } - msgs, _, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0], cmd.Flags()) + msgs, metadata, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0], cmd.Flags()) if err != nil { return err } - msg, err := v1beta2.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String()) + msg, err := v1beta2.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String(), metadata) if err != nil { return fmt.Errorf("invalid message: %w", err) } From 853f3a1bb5506f3aa7bb31abb42c25fa0718c18d Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:18:29 +0100 Subject: [PATCH 05/12] Add tests for parsing --- x/gov/client/cli/parse.go | 16 +++--- x/gov/client/cli/parse_test.go | 101 ++++++++++++++++++++------------- x/gov/client/cli/tx.go | 21 +++---- 3 files changed, 79 insertions(+), 59 deletions(-) diff --git a/x/gov/client/cli/parse.go b/x/gov/client/cli/parse.go index 1d21da324c68..e036bd598b8a 100644 --- a/x/gov/client/cli/parse.go +++ b/x/gov/client/cli/parse.go @@ -54,30 +54,28 @@ func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) type proposal struct { // Msgs defines an array of sdk.Msgs proto-JSON-encoded as Anys. - Msgs []json.RawMessage + Messages []json.RawMessage Metadata []byte Deposit string } -func parseSubmitProposal(cdc codec.Codec, path string, fs *pflag.FlagSet) ([]sdk.Msg, []byte, sdk.Coins, error) { - proposal := &proposal{} - - proposal.Deposit, _ = fs.GetString(FlagDeposit) +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) + err = json.Unmarshal(contents, &proposal) if err != nil { return nil, nil, nil, err } - msgs := make([]sdk.Msg, len(proposal.Msgs)) - for i, any := range proposal.Msgs { + msgs := make([]sdk.Msg, len(proposal.Messages)) + for i, anyJSON := range proposal.Messages { var msg sdk.Msg - err := cdc.UnmarshalInterfaceJSON(any, &msg) + err := cdc.UnmarshalInterfaceJSON(anyJSON, &msg) if err != nil { return nil, nil, nil, err } diff --git a/x/gov/client/cli/parse_test.go b/x/gov/client/cli/parse_test.go index eac0dfb4e471..12946b68c56a 100644 --- a/x/gov/client/cli/parse_test.go +++ b/x/gov/client/cli/parse_test.go @@ -1,14 +1,21 @@ package cli import ( + "encoding/base64" "fmt" "testing" "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/simapp" + "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/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 TestParseSubmitLegacyProposalFlags(t *testing.T) { @@ -71,65 +78,79 @@ func TestParseSubmitLegacyProposalFlags(t *testing.T) { require.Nil(t, err, "unexpected error") } -func TestparseSubmitProposal(t *testing.T) { +func TestParseSubmitProposal(t *testing.T) { _, _, addr := testdata.KeyTestPubAddr() - encCfg := simapp.MakeTestEncodingConfig() + 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.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, addr, addr, base64.StdEncoding.EncodeToString(expectedMetadata))) badJSON := testutil.WriteToNewTempFile(t, "bad json") - fs := NewCmdSubmitProposal().Flags() // nonexistent json - _, _, _, err := parseSubmitProposal(encCfg.Codec, "fileDoesNotExist", fs) + _, _, _, err := parseSubmitProposal(cdc, "fileDoesNotExist") require.Error(t, err) // invalid json - _, _, _, err = parseSubmitProposal(encCfg.Codec, badJSON.Name(), fs) + _, _, _, err = parseSubmitProposal(cdc, badJSON.Name()) require.Error(t, err) // ok json - fs.Set(FlagProposal, okJSON.Name()) - _, _, proposal1, err := parseSubmitProposal(fs) - 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 { - fs.Set(incompatibleFlag, "some value") - _, _, _, err := parseSubmitProposal(fs) - require.Error(t, err) - fs.Set(incompatibleFlag, "") - } - - // no --proposal, only flags - fs.Set(FlagProposal, "") - fs.Set(FlagTitle, proposal1.Title) - fs.Set(FlagDescription, proposal1.Description) - fs.Set(FlagProposalType, proposal1.Type) - fs.Set(FlagDeposit, proposal1.Deposit) - _, _, proposal2, err := parseSubmitProposal(fs) - - 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) + 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") diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 6b5204c32fe4..7be35d9fa189 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -58,8 +58,6 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command { RunE: client.ValidateCmd, } - // TODO Add CLI for new submit proposal - // https://github.com/cosmos/cosmos-sdk/issues/10952 cmdSubmitLegacyProp := NewCmdSubmitLegacyProposal() for _, propCmd := range propCmds { flags.AddTxFlagsToCmd(propCmd) @@ -85,21 +83,24 @@ func NewCmdSubmitProposal() *cobra.Command { Args: cobra.ExactArgs(1), Long: strings.TrimSpace( fmt.Sprintf(`Submit a proposal along with some messages and metadata. -Messages and metadata are defined in a JSON file. +Messages, metadata and deposit are defined in a JSON file. Example: -$ %s tx gov submit-proposal path/to/proposal.json --deposit="10test" +$ %s tx gov submit-proposal path/to/proposal.json Where proposal.json contains: { "messages": [ - { - "@type": "/cosmos.bank.v1beta1.MsgSend", - // -- snip -- - } + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1...", + "to_address": "cosmos1...", + "amount":[{"denom": "stake","amount": "10"}] + } ], - "metadata: "4pIMOgIGx1vZGU=" // base64-encoded metadata + "metadata: "4pIMOgIGx1vZGU=", // base64-encoded metadata + "deposit": "10stake" } `, version.AppName, @@ -111,7 +112,7 @@ Where proposal.json contains: return err } - msgs, metadata, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0], cmd.Flags()) + msgs, metadata, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0]) if err != nil { return err } From e1fa0056c4362c34c041dc2199531e85193314aa Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:39:25 +0100 Subject: [PATCH 06/12] Add CLI test --- x/gov/client/testutil/deposits.go | 2 +- x/gov/client/testutil/helpers.go | 14 +++++ x/gov/client/testutil/suite.go | 93 +++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/x/gov/client/testutil/deposits.go b/x/gov/client/testutil/deposits.go index d45aa13f3c76..eb2d5b1a8e5b 100644 --- a/x/gov/client/testutil/deposits.go +++ b/x/gov/client/testutil/deposits.go @@ -74,7 +74,7 @@ func (s *DepositTestSuite) createProposal(val *network.Validator, initialDeposit exactArgs = append(exactArgs, fmt.Sprintf("--%s=%s", cli.FlagDeposit, initialDeposit.String())) } - _, err := MsgSubmitProposal( + _, err := MsgSubmitLegacyProposal( val.ClientCtx, val.Address.String(), fmt.Sprintf("Text Proposal %d", id), diff --git a/x/gov/client/testutil/helpers.go b/x/gov/client/testutil/helpers.go index e16ac0e98e83..0e17ead4a553 100644 --- a/x/gov/client/testutil/helpers.go +++ b/x/gov/client/testutil/helpers.go @@ -31,6 +31,20 @@ func MsgSubmitProposal(clientCtx client.Context, from, title, description, propo 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 func MsgVote(clientCtx client.Context, from, id, vote string, extraArgs ...string) (testutil.BufferWriter, error) { args := append([]string{ diff --git a/x/gov/client/testutil/suite.go b/x/gov/client/testutil/suite.go index 420c7125c0ec..cb88db9a1bcc 100644 --- a/x/gov/client/testutil/suite.go +++ b/x/gov/client/testutil/suite.go @@ -1,6 +1,7 @@ package testutil import ( + "encoding/base64" "fmt" "strings" @@ -15,7 +16,9 @@ import ( clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" "github.com/cosmos/cosmos-sdk/testutil/network" 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/types" "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta2" ) @@ -44,7 +47,7 @@ func (s *IntegrationTestSuite) SetupSuite() { val := s.network.Validators[0] // 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, fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, v1beta2.DefaultMinDepositTokens).String())) s.Require().NoError(err) @@ -56,14 +59,14 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) // 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) s.Require().NoError(err) _, err = s.network.WaitForHeight(1) s.Require().NoError(err) // 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, fmt.Sprintf("--%s=%s", cli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, v1beta2.DefaultMinDepositTokens).String())) s.Require().NoError(err) @@ -278,6 +281,88 @@ func (s *IntegrationTestSuite) TestCmdTally() { func (s *IntegrationTestSuite) TestNewCmdSubmitProposal() { 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 := `{ "title": "", "description": "Where is the title!?", @@ -352,7 +437,7 @@ func (s *IntegrationTestSuite) TestNewCmdSubmitProposal() { tc := tc s.Run(tc.name, func() { - cmd := cli.NewCmdSubmitProposal() + cmd := cli.NewCmdSubmitLegacyProposal() clientCtx := val.ClientCtx out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) From 7beeeefcf0e77bcfa31861d13d8d2e5ca0da05ab Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:41:38 +0100 Subject: [PATCH 07/12] Use legacy submit proposal in other modules --- x/auth/client/testutil/suite.go | 4 ++-- x/authz/client/testutil/tx.go | 2 +- x/feegrant/client/testutil/suite.go | 4 ++-- x/gov/client/cli/parse.go | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/x/auth/client/testutil/suite.go b/x/auth/client/testutil/suite.go index 9120e21a5539..4c9d7da5d132 100644 --- a/x/auth/client/testutil/suite.go +++ b/x/auth/client/testutil/suite.go @@ -1503,7 +1503,7 @@ func (s *IntegrationTestSuite) TestAuxSigner() { for _, tc := range testCases { tc := tc s.Run(tc.name, func() { - _, err := govtestutil.MsgSubmitProposal( + _, err := govtestutil.MsgSubmitLegacyProposal( val.ClientCtx, val.Address.String(), "test", @@ -1747,7 +1747,7 @@ func (s *IntegrationTestSuite) TestAuxToFee() { for _, tc := range testCases { tc := tc s.Run(tc.name, func() { - res, err := govtestutil.MsgSubmitProposal( + res, err := govtestutil.MsgSubmitLegacyProposal( val.ClientCtx, tipper.String(), "test", diff --git a/x/authz/client/testutil/tx.go b/x/authz/client/testutil/tx.go index e883aa6fe848..e9c0f3881d8f 100644 --- a/x/authz/client/testutil/tx.go +++ b/x/authz/client/testutil/tx.go @@ -54,7 +54,7 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) // 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, fmt.Sprintf("--%s=%s", govcli.FlagDeposit, sdk.NewCoin(s.cfg.BondDenom, govv1beta2.DefaultMinDepositTokens).String())) s.Require().NoError(err) diff --git a/x/feegrant/client/testutil/suite.go b/x/feegrant/client/testutil/suite.go index df6defaa1ca0..38d01577b20c 100644 --- a/x/feegrant/client/testutil/suite.go +++ b/x/feegrant/client/testutil/suite.go @@ -752,7 +752,7 @@ func (s *IntegrationTestSuite) TestTxWithFeeGrant() { // granted fee allowance for an account which is not in state and creating // 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, fmt.Sprintf("--%s=%s", flags.FlagFeeGranter, granter.String()), ) @@ -892,7 +892,7 @@ func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { { "valid proposal tx", func() (testutil.BufferWriter, error) { - return govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(), + return govtestutil.MsgSubmitLegacyProposal(val.ClientCtx, grantee.String(), "Text Proposal", "No desc", govv1beta1.ProposalTypeText, 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()), diff --git a/x/gov/client/cli/parse.go b/x/gov/client/cli/parse.go index e036bd598b8a..306b4b10dde9 100644 --- a/x/gov/client/cli/parse.go +++ b/x/gov/client/cli/parse.go @@ -52,6 +52,7 @@ func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) 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 From e13f386e3c9fc687c16c3c996f42f143b52792cd Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:43:29 +0100 Subject: [PATCH 08/12] Update x/gov/client/cli/tx.go --- x/gov/client/cli/tx.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 7be35d9fa189..2bfe9e6aab30 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -26,6 +26,7 @@ const ( FlagDescription = "description" // Deprecated: only used for v1beta1 legacy proposals. FlagProposalType = "type" + // Deprecated: only used for v1beta1 legacy proposals. FlagDeposit = "deposit" flagVoter = "voter" flagDepositor = "depositor" From 3c9c2fdcc3866f2a95cc807e6a683b061d2e5c49 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:44:37 +0100 Subject: [PATCH 09/12] Update x/gov/client/cli/tx.go --- x/gov/client/cli/tx.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 2bfe9e6aab30..f57883dfcde0 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -92,6 +92,7 @@ $ %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 677a810a54e9ea8c47cdb8f8e6999022a7555db4 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 13:45:25 +0100 Subject: [PATCH 10/12] Update x/gov/client/cli/tx.go --- x/gov/client/cli/tx.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index f57883dfcde0..b9fbc063ee09 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -128,7 +128,6 @@ Where proposal.json contains: }, } - cmd.Flags().String(FlagDeposit, "", "The proposal deposit") flags.AddTxFlagsToCmd(cmd) return cmd From 936d75f51ea9f4a4680f054322e09989306e8a34 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 26 Jan 2022 14:55:05 +0100 Subject: [PATCH 11/12] Remove deprecated cobra field --- x/gov/client/cli/tx.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 7be35d9fa189..2bd05ff967c0 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -136,9 +136,8 @@ Where proposal.json contains: // Deprecated: please use NewCmdSubmitProposal instead. func NewCmdSubmitLegacyProposal() *cobra.Command { cmd := &cobra.Command{ - Use: "submit-legacy-proposal", - Deprecated: "This command is deprecated, prefer to use `submit-proposal` instead", - Short: "Submit a legacy proposal along with an initial deposit", + 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. From ab7ab3be3ee7d940538ca0f32711bb55b21be795 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Thu, 27 Jan 2022 11:42:12 +0100 Subject: [PATCH 12/12] Update x/gov/client/cli/tx.go --- x/gov/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 07c45625a0cd..17c3db75e4ee 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -76,7 +76,7 @@ func NewTxCmd(propCmds []*cobra.Command) *cobra.Command { return govTxCmd } -// NewCmdSubmitLegacyProposal implements submitting a proposal transaction command. +// NewCmdSubmitProposal implements submitting a proposal transaction command. func NewCmdSubmitProposal() *cobra.Command { cmd := &cobra.Command{ Use: "submit-proposal",