From 701aaa8c32d92718672d5a06d334b6402e756080 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 5 Jan 2023 18:04:56 +0100 Subject: [PATCH] feat: Add support for `[]string` and `[]int` in `draft-proposal` prompt. (#14483) --- CHANGELOG.md | 1 + x/auth/module.go | 2 +- x/gov/README.md | 25 ++++++++++++---- x/gov/client/cli/prompt.go | 29 ++++++++++++++---- x/gov/types/metadata.go | 12 ++++---- x/group/README.md | 57 +++++++++++++++++++++++------------- x/group/client/cli/prompt.go | 16 ++++++++-- x/group/client/cli/tx.go | 4 +++ x/group/client/cli/util.go | 2 +- 9 files changed, 106 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dddd89135c32..51abb84dc5ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (x/group, x/gov) [#14483](https://github.com/cosmos/cosmos-sdk/pull/14483) Add support for `[]string` and `[]int` in `draft-proposal` prompt. * (protobuf) [#14476](https://github.com/cosmos/cosmos-sdk/pull/14476) Clean up protobuf annotations `{accepts,implements}_interface`. * (module) [#14415](https://github.com/cosmos/cosmos-sdk/pull/14415) Loosen assertions in SetOrderBeginBlockers() and SetOrderEndBlockers() * (context)[#14384](https://github.com/cosmos/cosmos-sdk/pull/14384) refactor(context): Pass EventManager to the context as an interface. diff --git a/x/auth/module.go b/x/auth/module.go index 26e54997177f..ca849b2eb581 100644 --- a/x/auth/module.go +++ b/x/auth/module.go @@ -31,7 +31,7 @@ import ( ) // ConsensusVersion defines the current x/auth module consensus version. -const ConsensusVersion = 5 +const ConsensusVersion = 4 var ( _ module.AppModule = AppModule{} diff --git a/x/gov/README.md b/x/gov/README.md index 817c01390bec..8bafe5279f31 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -1464,7 +1464,9 @@ Example Output: ], "votingStartTime": "2022-03-28T14:25:26.644857113Z", "votingEndTime": "2022-03-30T14:25:26.644857113Z", - "metadata": "AQ==" + "metadata": "AQ==", + "title": "Proposal Title", + "summary": "Proposal Summary" }, { "id": "2", @@ -1486,7 +1488,9 @@ Example Output: "amount": "10" } ], - "metadata": "AQ==" + "metadata": "AQ==", + "title": "Proposal Title", + "summary": "Proposal Summary" } ], "pagination": { @@ -2009,7 +2013,9 @@ Example Output: ], "voting_start_time": "2022-03-28T14:25:26.644857113Z", "voting_end_time": "2022-03-30T14:25:26.644857113Z", - "metadata": "AQ==" + "metadata": "AQ==", + "title": "Proposal Title", + "summary": "Proposal Summary" } } ``` @@ -2134,7 +2140,9 @@ Example Output: ], "voting_start_time": "2022-03-28T14:25:26.644857113Z", "voting_end_time": "2022-03-30T14:25:26.644857113Z", - "metadata": "AQ==" + "metadata": "AQ==", + "title": "Proposal Title", + "summary": "Proposal Summary" }, { "id": "2", @@ -2168,7 +2176,9 @@ Example Output: ], "voting_start_time": null, "voting_end_time": null, - "metadata": "AQ==" + "metadata": "AQ==", + "title": "Proposal Title", + "summary": "Proposal Summary" } ], "pagination": { @@ -2598,6 +2608,11 @@ Location: off-chain as json object stored on IPFS (mirrors [group proposal](../g } ``` +:::note +The `authors` field is an array of strings, this is to allow for multiple authors to be listed in the metadata. +In v0.46, the `authors` field is a comma-separated string. Frontends are encouraged to support both formats for backwards compatibility. +::: + ### Vote Location: on-chain as json within 255 character limit (mirrors [group vote](../group/README.md#metadata)) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index bb9b0936979a..a75d7c9eda87 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -63,10 +63,15 @@ func Prompt[T any](data T, namePrefix string) (T, error) { } for i := 0; i < v.NumField(); i++ { - if v.Field(i).Kind() == reflect.Struct || v.Field(i).Kind() == reflect.Slice { - // if the field is a struct skip - // in a future we can add a recursive call to Prompt + // if the field is a struct skip or not slice of string or int then skip + switch v.Field(i).Kind() { + case reflect.Struct: + // TODO(@julienrbrt) in the future we can add a recursive call to Prompt continue + case reflect.Slice: + if v.Field(i).Type().Elem().Kind() != reflect.String && v.Field(i).Type().Elem().Kind() != reflect.Int { + continue + } } // create prompts @@ -117,9 +122,20 @@ func Prompt[T any](data T, namePrefix string) (T, error) { // of which on 64-bit machines, which are most common, // int==int64 v.Field(i).SetInt(resultInt) + case reflect.Slice: + switch v.Field(i).Type().Elem().Kind() { + case reflect.String: + v.Field(i).Set(reflect.ValueOf([]string{result})) + case reflect.Int: + resultInt, err := strconv.ParseInt(result, 10, 0) + if err != nil { + return data, fmt.Errorf("invalid value for int: %w", err) + } + + v.Field(i).Set(reflect.ValueOf([]int{int(resultInt)})) + } default: - // skip other types - // possibly in the future we can add more types (like slices) + // skip any other types continue } } @@ -172,6 +188,7 @@ func (p *proposalType) Prompt(cdc codec.Codec) (*proposal, types.ProposalMetadat return nil, metadata, fmt.Errorf("failed to marshal proposal message: %w", err) } proposal.Messages = append(proposal.Messages, message) + return proposal, metadata, nil } @@ -256,7 +273,7 @@ func NewCmdDraftProposal() *cobra.Command { return err } - fmt.Printf("Your draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") + fmt.Printf("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") return nil }, diff --git a/x/gov/types/metadata.go b/x/gov/types/metadata.go index 1d4669b96db5..504ed72a1b54 100644 --- a/x/gov/types/metadata.go +++ b/x/gov/types/metadata.go @@ -3,10 +3,10 @@ package types // ProposalMetadata is the metadata of a proposal // This metadata is supposed to live off-chain when submitted in a proposal type ProposalMetadata struct { - Title string `json:"title"` - Authors string `json:"authors"` - Summary string `json:"summary"` - Details string `json:"details"` - ProposalForumUrl string `json:"proposal_forum_url"` //nolint:revive // named 'Url' instead of 'URL' for avoiding the camel case split - VoteOptionContext string `json:"vote_option_context"` + Title string `json:"title"` + Authors []string `json:"authors"` + Summary string `json:"summary"` + Details string `json:"details"` + ProposalForumUrl string `json:"proposal_forum_url"` //nolint:revive // named 'Url' instead of 'URL' for avoiding the camel case split + VoteOptionContext string `json:"vote_option_context"` } diff --git a/x/group/README.md b/x/group/README.md index 6837bcc2b9ce..645aa5bbecd4 100644 --- a/x/group/README.md +++ b/x/group/README.md @@ -848,6 +848,8 @@ proposal: no_count: "0" veto_count: "0" yes_count: "0" + summary: "Summary" + title: "Title" ``` ##### proposals-by-group-policy @@ -897,6 +899,8 @@ proposals: no_count: "0" veto_count: "0" yes_count: "0" + summary: "Summary" + title: "Title" ``` ##### vote @@ -988,7 +992,7 @@ The `tx` commands allow users to interact with the `group` module. simd tx group --help ``` -##### create-group +#### create-group The `create-group` command allows users to create a group which is an aggregation of member accounts with associated weights and an administrator account. @@ -1003,7 +1007,7 @@ Example: simd tx group create-group cosmos1.. "AQ==" members.json ``` -##### update-group-admin +#### update-group-admin The `update-group-admin` command allows users to update a group's admin. @@ -1017,7 +1021,7 @@ Example: simd tx group update-group-admin cosmos1.. 1 cosmos1.. ``` -##### update-group-members +#### update-group-members The `update-group-members` command allows users to update a group's members. @@ -1031,7 +1035,7 @@ Example: simd tx group update-group-members cosmos1.. 1 members.json ``` -##### update-group-metadata +#### update-group-metadata The `update-group-metadata` command allows users to update a group's metadata. @@ -1045,7 +1049,7 @@ Example: simd tx group update-group-metadata cosmos1.. 1 "AQ==" ``` -##### create-group-policy +#### create-group-policy The `create-group-policy` command allows users to create a group policy which is an account associated with a group and a decision policy. @@ -1059,7 +1063,7 @@ Example: simd tx group create-group-policy cosmos1.. 1 "AQ==" '{"@type":"/cosmos.group.v1.ThresholdDecisionPolicy", "threshold":"1", "windows": {"voting_period": "120h", "min_execution_period": "0s"}}' ``` -##### create-group-with-policy +#### create-group-with-policy The `create-group-with-policy` command allows users to create a group which is an aggregation of member accounts with associated weights and an administrator account with decision policy. If the `--group-policy-as-admin` flag is set to `true`, the group policy address becomes the group and group policy admin. @@ -1073,7 +1077,7 @@ Example: simd tx group create-group-with-policy cosmos1.. "AQ==" "AQ==" members.json '{"@type":"/cosmos.group.v1.ThresholdDecisionPolicy", "threshold":"1", "windows": {"voting_period": "120h", "min_execution_period": "0s"}}' ``` -##### update-group-policy-admin +#### update-group-policy-admin The `update-group-policy-admin` command allows users to update a group policy admin. @@ -1087,7 +1091,7 @@ Example: simd tx group update-group-policy-admin cosmos1.. cosmos1.. cosmos1.. ``` -##### update-group-policy-metadata +#### update-group-policy-metadata The `update-group-policy-metadata` command allows users to update a group policy metadata. @@ -1101,7 +1105,7 @@ Example: simd tx group update-group-policy-metadata cosmos1.. cosmos1.. "AQ==" ``` -##### update-group-policy-decision-policy +#### update-group-policy-decision-policy The `update-group-policy-decision-policy` command allows users to update a group policy's decision policy. @@ -1115,7 +1119,7 @@ Example: simd tx group update-group-policy-decision-policy cosmos1.. cosmos1.. '{"@type":"/cosmos.group.v1.ThresholdDecisionPolicy", "threshold":"2", "windows": {"voting_period": "120h", "min_execution_period": "0s"}}' ``` -##### create-proposal +#### create-proposal The `create-proposal` command allows users to submit a new proposal. @@ -1129,7 +1133,7 @@ Example: simd tx group create-proposal cosmos1.. cosmos1.. msg_tx.json "AQ==" ``` -##### withdraw-proposal +#### withdraw-proposal The `withdraw-proposal` command allows users to withdraw a proposal. @@ -1143,7 +1147,7 @@ Example: simd tx group withdraw-proposal 1 cosmos1.. ``` -##### vote +#### vote The `vote` command allows users to vote on a proposal. @@ -1157,7 +1161,7 @@ Example: simd tx group vote 1 cosmos1.. CHOICE_YES "AQ==" ``` -##### exec +#### exec The `exec` command allows users to execute a proposal. @@ -1171,7 +1175,7 @@ Example: simd tx group exec 1 ``` -##### leave-group +#### leave-group The `leave-group` command allows group member to leave the group. @@ -1452,9 +1456,11 @@ Example Output: "voting_period": "432000s" }, "executorResult": "EXECUTOR_RESULT_NOT_RUN", - "msgs": [ + "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100000000"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} - ] + ], + "title": "Title", + "summary": "Summary", } } ``` @@ -1501,9 +1507,11 @@ Example Output: "voting_period": "432000s" }, "executorResult": "EXECUTOR_RESULT_NOT_RUN", - "msgs": [ + "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100000000"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} - ] + ], + "title": "Title", + "summary": "Summary", } ], "pagination": { @@ -1911,7 +1919,7 @@ Example Output: "voting_period": "432000s" }, "executor_result": "EXECUTOR_RESULT_NOT_RUN", - "msgs": [ + "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", @@ -1923,7 +1931,9 @@ Example Output: } ] } - ] + ], + "title": "Title", + "summary": "Summary", } } ``` @@ -1970,7 +1980,7 @@ Example Output: "voting_period": "432000s" }, "executor_result": "EXECUTOR_RESULT_NOT_RUN", - "msgs": [ + "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", @@ -2107,6 +2117,11 @@ Location: off-chain as json object stored on IPFS (mirrors [gov proposal](../gov } ``` +:::note +The `authors` field is an array of strings, this is to allow for multiple authors to be listed in the metadata. +In v0.46, the `authors` field is a comma-separated string. Frontends are encouraged to support both formats for backwards compatibility. +::: + ### Vote Location: on-chain as json within 255 character limit (mirrors [gov vote](../gov/README.md#metadata)) diff --git a/x/group/client/cli/prompt.go b/x/group/client/cli/prompt.go index aa962e9671c7..7b4a2fc4507c 100644 --- a/x/group/client/cli/prompt.go +++ b/x/group/client/cli/prompt.go @@ -36,7 +36,7 @@ func (p *proposalType) Prompt(cdc codec.Codec) (*Proposal, govtypes.ProposalMeta if err != nil { return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } - // the metadata must be saved on IPFS, set placeholder + proposal := &Proposal{ Metadata: "ipfs://CID", // the metadata must be saved on IPFS, set placeholder Title: metadata.Title, @@ -54,6 +54,17 @@ func (p *proposalType) Prompt(cdc codec.Codec) (*Proposal, govtypes.ProposalMeta } proposal.GroupPolicyAddress = groupPolicyAddress + // set proposer address + proposerPrompt := promptui.Prompt{ + Label: "Enter proposer address", + Validate: client.ValidatePromptAddress, + } + proposerAddress, err := proposerPrompt.Run() + if err != nil { + return nil, metadata, fmt.Errorf("failed to set proposer address: %w", err) + } + proposal.Proposers = []string{proposerAddress} + if p.Msg == nil { return proposal, metadata, nil } @@ -69,6 +80,7 @@ func (p *proposalType) Prompt(cdc codec.Codec) (*Proposal, govtypes.ProposalMeta return nil, metadata, fmt.Errorf("failed to marshal proposal message: %w", err) } proposal.Messages = append(proposal.Messages, message) + return proposal, metadata, nil } @@ -138,7 +150,7 @@ func NewCmdDraftProposal() *cobra.Command { return err } - fmt.Printf("Your draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") + fmt.Printf("The draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") return nil }, diff --git a/x/group/client/cli/tx.go b/x/group/client/cli/tx.go index 2b62ea536ff0..36e7e8941d7c 100644 --- a/x/group/client/cli/tx.go +++ b/x/group/client/cli/tx.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" "strconv" @@ -581,6 +582,9 @@ Parameters: // Since the --from flag is not required on this CLI command, we // ignore it, and just use the 1st proposer in the JSON file. + if prop.Proposers == nil || len(prop.Proposers) == 0 { + return errors.New("no proposers specified in proposal") + } cmd.Flags().Set(flags.FlagFrom, prop.Proposers[0]) clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/group/client/cli/util.go b/x/group/client/cli/util.go index 81a3c63a76d6..7bd2bdb3b55f 100644 --- a/x/group/client/cli/util.go +++ b/x/group/client/cli/util.go @@ -65,7 +65,7 @@ type Proposal struct { // Messages defines an array of sdk.Msgs proto-JSON-encoded as Anys. Messages []json.RawMessage `json:"messages,omitempty"` Metadata string `json:"metadata"` - Proposers []string `json:"proposers,omitempty"` + Proposers []string `json:"proposers"` Title string `json:"title"` Summary string `json:"summary"` }