From 78c4fe088c54861dba88670a99b2a222184e585e Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 15 Sep 2022 15:35:20 +0200 Subject: [PATCH 01/12] feat: CLI tooling to generate proposal JSONs --- client/prompts.go | 68 +++++++ go.mod | 2 + go.sum | 5 + simapp/go.mod | 2 + simapp/go.sum | 5 + tests/go.mod | 2 + tests/go.sum | 5 + x/gov/README.md | 4 +- x/gov/client/cli/prompt.go | 180 ++++++++++++++++++ x/gov/client/cli/tx.go | 1 + x/gov/client/cli/{parse.go => util.go} | 6 +- .../cli/{parse_test.go => util_test.go} | 0 x/group/README.md | 8 +- 13 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 client/prompts.go create mode 100644 x/gov/client/cli/prompt.go rename x/gov/client/cli/{parse.go => util.go} (94%) rename x/gov/client/cli/{parse_test.go => util_test.go} (100%) diff --git a/client/prompts.go b/client/prompts.go new file mode 100644 index 000000000000..b803b9c36004 --- /dev/null +++ b/client/prompts.go @@ -0,0 +1,68 @@ +package client + +import ( + "fmt" + "net/url" + "unicode" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/manifoldco/promptui" +) + +// Prompts + +var ( + AddressPrompt = promptui.Prompt{ + Label: "Enter address", + Validate: func(input string) error { + if _, err := sdk.AccAddressFromBech32(input); err != nil { + return fmt.Errorf("invalid address: %w", err) + } + + return nil + }, + } + + CoinsAmountPrompt = promptui.Prompt{ + Label: "Enter coin(s) amount", + Validate: func(input string) error { + _, err := sdk.ParseCoinsNormalized(input) + if err != nil { + return fmt.Errorf("invalid coin amount: %w", err) + } + + return nil + }, + } +) + +// Validation + +func ValidatePromptNotEmpty(input string) error { + if input == "" { + return fmt.Errorf("input cannot be empty") + } + + return nil +} + +func ValidatePromptURL(input string) error { + _, err := url.ParseRequestURI(input) + if err != nil { + return fmt.Errorf("invalid URL: %w", err) + } + + return nil +} + +// Prompts Helpers + +func CamelCaseToString(str string) string { + w := []rune(str) + for i := len(w) - 1; i > 1; i-- { + if unicode.IsUpper(w[i]) { + w = append(w[:i], append([]rune{' '}, w[i:]...)...) + } + } + return string(w) +} diff --git a/go.mod b/go.mod index 579643fab702..5872d851288f 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 github.com/magiconair/properties v1.8.6 + github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-isatty v0.0.16 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.13.0 @@ -75,6 +76,7 @@ require ( github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect diff --git a/go.sum b/go.sum index a083f9a1c436..c49a2b7f1144 100644 --- a/go.sum +++ b/go.sum @@ -147,8 +147,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= @@ -560,6 +563,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/simapp/go.mod b/simapp/go.mod index de50c053aa86..fe67db269370 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -37,6 +37,7 @@ require ( github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.8.0 // indirect github.com/confio/ics23/go v0.7.0 // indirect @@ -97,6 +98,7 @@ require ( github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index a256b820dc75..99be4823d44d 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -147,8 +147,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= @@ -548,6 +551,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/tests/go.mod b/tests/go.mod index 7136eb6c9e40..0bd4038c6438 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -36,6 +36,7 @@ require ( github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.8.0 // indirect github.com/confio/ics23/go v0.7.0 // indirect @@ -96,6 +97,7 @@ require ( github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect diff --git a/tests/go.sum b/tests/go.sum index 0c0d14fd3fbd..8744e4fdae66 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -147,8 +147,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= @@ -550,6 +553,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/x/gov/README.md b/x/gov/README.md index 7a5344dba177..3e713003503e 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -2612,8 +2612,8 @@ Location: off-chain as json object stored on IPFS (mirrors [group proposal](../. "authors": "", "summary": "", "details": "", - "proposalForumURL": "", - "voteOptionContext": "", + "proposal_forum_url": "", + "vote_option_context": "", } ``` diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go new file mode 100644 index 000000000000..23c76cdc7364 --- /dev/null +++ b/x/gov/client/cli/prompt.go @@ -0,0 +1,180 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + "reflect" + "strings" + + "github.com/cosmos/gogoproto/proto" + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +const draftProposalFileName = "draft_proposal.json" + +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"` // named 'Url' instead of 'URL' for avoiding the camel case split + VoteOptionContext string `json:"vote_option_context"` +} + +// ProposalMetadataPrompt prompts the user for filling proposal metadata +func ProposalMetadataPrompt() (ProposalMetadata, error) { + proposalMetadata := ProposalMetadata{} + v := reflect.ValueOf(&proposalMetadata).Elem() + for i := 0; i < v.NumField(); i++ { + fieldName := strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name)) + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Enter proposal %s", fieldName), + Validate: client.ValidatePromptNotEmpty, + } + + if strings.Contains(fieldName, "url") { + prompt.Validate = client.ValidatePromptURL + } + + result, err := prompt.Run() + if err != nil { + return ProposalMetadata{}, fmt.Errorf("failed to prompt for %s: %w", fieldName, err) + } + + v.Field(i).SetString(result) + } + + return proposalMetadata, nil +} + +type proposalTypes struct { + Type string + Msg interface{} +} + +func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { + var proposal = &proposal{} + + // set metadata + metadata, err := ProposalMetadataPrompt() + if err != nil { + return nil, fmt.Errorf("failed to set proposal metadata: %w", err) + } + + rawMetadata, err := json.Marshal(metadata) + if err != nil { + return nil, fmt.Errorf("failed to marshal proposal metadata: %w", err) + } + + proposal.Metadata = string(rawMetadata) + + // set deposit + depositPrompt := client.CoinsAmountPrompt + depositPrompt.Label = "Enter proposal deposit" + + proposal.Deposit, err = depositPrompt.Run() + if err != nil { + return nil, fmt.Errorf("failed to set proposal deposit: %w", err) + } + + if p.Msg == nil { + return proposal, nil + } + + // set messages field + result, err := p.Msg.(proto.Message), nil // TODO - create prompt for this + if err != nil { + return nil, fmt.Errorf("failed to set proposal message: %w", err) + } + + proposal.Messages = append(proposal.Messages, cdc.MustMarshalJSON(result)) + return proposal, nil +} + +var supportedProposalTypes = []proposalTypes{ + { + Type: "text", + Msg: nil, // no message for text proposal + }, + { + Type: "community-pool-spend", + Msg: distrtypes.MsgCommunityPoolSpend{}, + }, + { + Type: "software-upgrade", + }, + { + Type: "cancel-software-upgrade", + }, + { + Type: "parameter-change", + }, +} + +func getProposalTypes() []string { + types := make([]string, len(supportedProposalTypes)) + for i, p := range supportedProposalTypes { + types[i] = p.Type + } + return types +} + +// NewCmdDraftProposal let a user generate a draft proposal. +func NewCmdDraftProposal() *cobra.Command { + cmd := &cobra.Command{ + Use: "draft-proposal [proposal-type]", + Short: "Generate a draft proposal json file. The generated proposal json contains only one message.", + Long: `Generate a draft proposal json file. The generated proposal json contains only one message. +The proposal-type can be one of the following:` + strings.Join(getProposalTypes(), ", "), + Args: cobra.ExactArgs(1), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + var proposal *proposalTypes + for _, p := range supportedProposalTypes { + if strings.EqualFold(p.Type, args[0]) { + proposal = &p + break + } + } + if proposal == nil { + return fmt.Errorf("unsupported proposal type \"%s\", supported types are:\n- %s", args[0], strings.Join(getProposalTypes(), "\n- ")) + } + + fmt.Printf("You are generating a draft %s proposal. Please fill in the following information:\n", proposal.Type) + + result, err := proposal.Prompt(clientCtx.Codec) + if err != nil { + return err + } + + rawProposal, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal proposal: %w", err) + } + + if err := os.WriteFile(draftProposalFileName, rawProposal, 0o600); err != nil { + return err + } + + fmt.Printf("A draft proposal has been generated.\nNote that proposal should contains off-chain metadata.\nPlease upload the metadata.json to IPFS.\nIf you have modified metadata.json, do not forget to update the metadata field in the proposal with the correct CID.\n") + + return nil + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 85e821ddebee..f18c14874984 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -70,6 +70,7 @@ func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command { NewCmdVote(), NewCmdWeightedVote(), NewCmdSubmitProposal(), + NewCmdDraftProposal(), // Deprecated cmdSubmitLegacyProp, diff --git a/x/gov/client/cli/parse.go b/x/gov/client/cli/util.go similarity index 94% rename from x/gov/client/cli/parse.go rename to x/gov/client/cli/util.go index a43d7f868cea..a71db018b453 100644 --- a/x/gov/client/cli/parse.go +++ b/x/gov/client/cli/util.go @@ -77,9 +77,9 @@ func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) // 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 string - Deposit string + Messages []json.RawMessage `json:"messages"` + Metadata string `json:"metadata"` + Deposit string `json:"deposit"` } func parseSubmitProposal(cdc codec.Codec, path string) ([]sdk.Msg, string, sdk.Coins, error) { diff --git a/x/gov/client/cli/parse_test.go b/x/gov/client/cli/util_test.go similarity index 100% rename from x/gov/client/cli/parse_test.go rename to x/gov/client/cli/util_test.go diff --git a/x/group/README.md b/x/group/README.md index f1aab2cdeaee..4ca2fae74c0d 100644 --- a/x/group/README.md +++ b/x/group/README.md @@ -2073,8 +2073,8 @@ Location: off-chain as json object stored on IPFS (mirrors [gov proposal](../../ "authors": "", "summary": "", "details": "", - "proposalForumURL": "", - "voteOptionContext": "", + "proposal_forum_url": "", + "vote_option_context": "", } ``` @@ -2096,8 +2096,8 @@ Location: off-chain as json object stored on IPFS { "name": "", "description": "", - "groupWebsiteURL": "", - "groupForumURL": "", + "group_website_url": "", + "group_forum_url": "", } ``` From 6d8ae8abe1fea52bfc4d38ef854fc247a5c0b391 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 00:56:33 +0200 Subject: [PATCH 02/12] updates --- client/prompts.go | 46 ++++------- x/gov/client/cli/prompt.go | 164 +++++++++++++++++++++++++++---------- x/gov/client/cli/util.go | 2 +- 3 files changed, 140 insertions(+), 72 deletions(-) diff --git a/client/prompts.go b/client/prompts.go index b803b9c36004..e57ffdb5d2cd 100644 --- a/client/prompts.go +++ b/client/prompts.go @@ -6,37 +6,9 @@ import ( "unicode" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/manifoldco/promptui" ) -// Prompts - -var ( - AddressPrompt = promptui.Prompt{ - Label: "Enter address", - Validate: func(input string) error { - if _, err := sdk.AccAddressFromBech32(input); err != nil { - return fmt.Errorf("invalid address: %w", err) - } - - return nil - }, - } - - CoinsAmountPrompt = promptui.Prompt{ - Label: "Enter coin(s) amount", - Validate: func(input string) error { - _, err := sdk.ParseCoinsNormalized(input) - if err != nil { - return fmt.Errorf("invalid coin amount: %w", err) - } - - return nil - }, - } -) - -// Validation +// Prompts Validation func ValidatePromptNotEmpty(input string) error { if input == "" { @@ -55,6 +27,22 @@ func ValidatePromptURL(input string) error { return nil } +func ValidatePromptAddress(input string) error { + if _, err := sdk.AccAddressFromBech32(input); err != nil { + return fmt.Errorf("invalid address: %w", err) + } + + return nil +} + +func ValidatePromptCoins(input string) error { + if _, err := sdk.ParseCoinsNormalized(input); err != nil { + return fmt.Errorf("invalid coins: %w", err) + } + + return nil +} + // Prompts Helpers func CamelCaseToString(str string) string { diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 23c76cdc7364..57e995719419 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -5,33 +5,45 @@ import ( "fmt" "os" "reflect" + "strconv" "strings" - "github.com/cosmos/gogoproto/proto" "github.com/manifoldco/promptui" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" - distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" ) -const draftProposalFileName = "draft_proposal.json" +const ( + proposalText = "text" + proposalOther = "other" + draftProposalFileName = "draft_proposal.json" +) 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"` // 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"` // named 'Url' instead of 'URL' for avoiding the camel case split + // VoteOptionContext string `json:"vote_option_context"` } -// ProposalMetadataPrompt prompts the user for filling proposal metadata -func ProposalMetadataPrompt() (ProposalMetadata, error) { - proposalMetadata := ProposalMetadata{} - v := reflect.ValueOf(&proposalMetadata).Elem() +// ProposalPrompt prompts the user for filling proposal data +func ProposalPrompt[T any](data T) (T, error) { + v := reflect.ValueOf(&data).Elem() + if v.Kind() == reflect.Interface { + v = reflect.ValueOf(data) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + } + for i := 0; i < v.NumField(); i++ { fieldName := strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name)) prompt := promptui.Prompt{ @@ -43,27 +55,42 @@ func ProposalMetadataPrompt() (ProposalMetadata, error) { prompt.Validate = client.ValidatePromptURL } + if strings.EqualFold(v.Type().Field(i).Name, "authority") { + // pre-fill with gov address + prompt.Default = authtypes.NewModuleAddress(types.ModuleName).String() + prompt.Validate = client.ValidatePromptAddress + } + result, err := prompt.Run() if err != nil { - return ProposalMetadata{}, fmt.Errorf("failed to prompt for %s: %w", fieldName, err) + return data, fmt.Errorf("failed to prompt for %s: %w", fieldName, err) } - v.Field(i).SetString(result) + switch v.Field(i).Kind() { + case reflect.String: + v.Field(i).SetString(result) + case reflect.Int64: + resultInt, _ := strconv.Atoi(result) + v.Field(i).SetInt(int64(resultInt)) + case reflect.Struct: + // TODO - manage all different types nicely :thinking_face: + } } - return proposalMetadata, nil + return data, nil } type proposalTypes struct { - Type string - Msg interface{} + Type string + MsgType string + Msg sdk.Msg } func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { var proposal = &proposal{} // set metadata - metadata, err := ProposalMetadataPrompt() + metadata, err := ProposalPrompt(ProposalMetadata{}) if err != nil { return nil, fmt.Errorf("failed to set proposal metadata: %w", err) } @@ -76,9 +103,10 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { proposal.Metadata = string(rawMetadata) // set deposit - depositPrompt := client.CoinsAmountPrompt - depositPrompt.Label = "Enter proposal deposit" - + depositPrompt := promptui.Prompt{ + Label: "Enter proposal deposit", + Validate: client.ValidatePromptCoins, + } proposal.Deposit, err = depositPrompt.Run() if err != nil { return nil, fmt.Errorf("failed to set proposal deposit: %w", err) @@ -89,32 +117,36 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { } // set messages field - result, err := p.Msg.(proto.Message), nil // TODO - create prompt for this + result, err := ProposalPrompt(p.Msg) if err != nil { return nil, fmt.Errorf("failed to set proposal message: %w", err) } + // TODO enrich message type proposal.Messages = append(proposal.Messages, cdc.MustMarshalJSON(result)) return proposal, nil } var supportedProposalTypes = []proposalTypes{ { - Type: "text", - Msg: nil, // no message for text proposal + Type: proposalText, + MsgType: "", // no message for text proposal }, { - Type: "community-pool-spend", - Msg: distrtypes.MsgCommunityPoolSpend{}, + Type: "community-pool-spend", + MsgType: "/cosmos.distribution.v1beta1.MsgCommunityPoolSpend", }, { - Type: "software-upgrade", + Type: "software-upgrade", + MsgType: "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", }, { - Type: "cancel-software-upgrade", + Type: "cancel-software-upgrade", + MsgType: "/cosmos.upgrade.v1beta1.MsgCancelUpgrade", }, { - Type: "parameter-change", + Type: proposalOther, + MsgType: "", // user will input the message type }, } @@ -126,33 +158,81 @@ func getProposalTypes() []string { return types } +func getProposalMsg(cdc codec.Codec, input string) (sdk.Msg, error) { + var msg sdk.Msg + bz, err := json.Marshal(struct { + Type string `json:"@type"` + }{ + Type: input, + }) + if err != nil { + return nil, err + } + + if err := cdc.UnmarshalInterfaceJSON(bz, &msg); err != nil { + return nil, fmt.Errorf("failed to determined sdk.Msg from %s proposal type : %w", input, err) + } + + return msg, nil +} + // NewCmdDraftProposal let a user generate a draft proposal. func NewCmdDraftProposal() *cobra.Command { cmd := &cobra.Command{ - Use: "draft-proposal [proposal-type]", - Short: "Generate a draft proposal json file. The generated proposal json contains only one message.", - Long: `Generate a draft proposal json file. The generated proposal json contains only one message. -The proposal-type can be one of the following:` + strings.Join(getProposalTypes(), ", "), - Args: cobra.ExactArgs(1), + Use: "draft-proposal", + Short: "Generate a draft proposal json file. The generated proposal json contains only one message.", SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } + // prompt proposal type + proposalTypesPrompt := promptui.Select{ + Label: "Select proposal type", + Items: getProposalTypes(), + } + + _, proposalType, err := proposalTypesPrompt.Run() + if err != nil { + return fmt.Errorf("failed to prompt proposal types: %w", err) + } + var proposal *proposalTypes for _, p := range supportedProposalTypes { - if strings.EqualFold(p.Type, args[0]) { + if strings.EqualFold(p.Type, proposalType) { proposal = &p break } } - if proposal == nil { - return fmt.Errorf("unsupported proposal type \"%s\", supported types are:\n- %s", args[0], strings.Join(getProposalTypes(), "\n- ")) - } - fmt.Printf("You are generating a draft %s proposal. Please fill in the following information:\n", proposal.Type) + // create any proposal type + if proposal.Type == proposalOther { + // prompt proposal type + msgPrompt := promptui.Prompt{ + Label: "Which message type do you want to use for the proposal", + Validate: func(input string) error { + _, err := getProposalMsg(clientCtx.Codec, input) + return err + }, + } + + result, err := msgPrompt.Run() + if err != nil { + return fmt.Errorf("failed to prompt proposal types: %w", err) + } + + proposal.Msg, err = getProposalMsg(clientCtx.Codec, result) + if err != nil { + return err + } + } else if proposal.MsgType != "" { + proposal.Msg, err = getProposalMsg(clientCtx.Codec, proposal.MsgType) + if err != nil { + return err + } + } result, err := proposal.Prompt(clientCtx.Codec) if err != nil { @@ -168,7 +248,7 @@ The proposal-type can be one of the following:` + strings.Join(getProposalTypes( return err } - fmt.Printf("A draft proposal has been generated.\nNote that proposal should contains off-chain metadata.\nPlease upload the metadata.json to IPFS.\nIf you have modified metadata.json, do not forget to update the metadata field in the proposal with the correct CID.\n") + fmt.Printf("A draft proposal has been generated.\nNote that proposal should contains off-chain metadata.\nPlease upload the metadata object to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") return nil }, diff --git a/x/gov/client/cli/util.go b/x/gov/client/cli/util.go index a71db018b453..64517fab22c6 100644 --- a/x/gov/client/cli/util.go +++ b/x/gov/client/cli/util.go @@ -77,7 +77,7 @@ func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error) // 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 `json:"messages"` + Messages []json.RawMessage `json:"messages,omitempty"` Metadata string `json:"metadata"` Deposit string `json:"deposit"` } From 4e3da104ff94659447a21e017eb25bf652b2572c Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 01:24:44 +0200 Subject: [PATCH 03/12] more generic --- x/gov/client/cli/prompt.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 57e995719419..64a53f4064f7 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -34,8 +34,10 @@ type ProposalMetadata struct { // VoteOptionContext string `json:"vote_option_context"` } -// ProposalPrompt prompts the user for filling proposal data -func ProposalPrompt[T any](data T) (T, error) { +// Prompt prompts the user for filling struct data +// data is the struct to be filled +// namePrefix is the name to be display as "Enter " +func Prompt[T any](data T, namePrefix string) (T, error) { v := reflect.ValueOf(&data).Elem() if v.Kind() == reflect.Interface { v = reflect.ValueOf(data) @@ -45,9 +47,19 @@ func ProposalPrompt[T any](data T) (T, error) { } for i := 0; i < v.NumField(); i++ { + if v.Field(i).Kind() == reflect.Struct { + // if the field is a struct, call recursively + result, err := Prompt(reflect.ValueOf(v.Field(i)).Interface(), strings.ToLower(v.Type().Field(i).Name)) + if err != nil { + return data, err + } + + v.Field(i).Set(reflect.ValueOf(result)) + } + fieldName := strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name)) prompt := promptui.Prompt{ - Label: fmt.Sprintf("Enter proposal %s", fieldName), + Label: fmt.Sprintf("Enter %s %s", namePrefix, fieldName), Validate: client.ValidatePromptNotEmpty, } @@ -87,10 +99,10 @@ type proposalTypes struct { } func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { - var proposal = &proposal{} + proposal := &proposal{} // set metadata - metadata, err := ProposalPrompt(ProposalMetadata{}) + metadata, err := Prompt(ProposalMetadata{}, "proposal") if err != nil { return nil, fmt.Errorf("failed to set proposal metadata: %w", err) } @@ -117,7 +129,7 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { } // set messages field - result, err := ProposalPrompt(p.Msg) + result, err := Prompt(p.Msg, "msg") if err != nil { return nil, fmt.Errorf("failed to set proposal message: %w", err) } From cdad7efb9b3cb9cc80d95a4025b14d247e754d86 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 01:31:43 +0200 Subject: [PATCH 04/12] improve message --- x/gov/client/cli/prompt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 64a53f4064f7..fe98b2cbba6c 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -260,7 +260,7 @@ func NewCmdDraftProposal() *cobra.Command { return err } - fmt.Printf("A draft proposal has been generated.\nNote that proposal should contains off-chain metadata.\nPlease upload the metadata object to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") + fmt.Printf("Your draft proposal has successfully been generated.\nBecause proposals should contain off-chain metadata, please upload the metadata object to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") return nil }, From a0304253c1d7eeb25b2dfa0a34ffd787b4c0bd4f Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 11:16:51 +0200 Subject: [PATCH 05/12] do not recursively call prompt yet --- api/cosmos/nft/v1beta1/tx.pulsar.go | 22 +++++++++++------ proto/cosmos/nft/v1beta1/tx.proto | 5 ++-- x/gov/client/cli/prompt.go | 38 ++++++++++++++++++----------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/api/cosmos/nft/v1beta1/tx.pulsar.go b/api/cosmos/nft/v1beta1/tx.pulsar.go index 2a7fbd181fe4..246a0a34c1e2 100644 --- a/api/cosmos/nft/v1beta1/tx.pulsar.go +++ b/api/cosmos/nft/v1beta1/tx.pulsar.go @@ -4,6 +4,7 @@ package nftv1beta1 import ( _ "cosmossdk.io/api/cosmos/msg/v1" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" runtime "github.com/cosmos/cosmos-proto/runtime" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoiface "google.golang.org/protobuf/runtime/protoiface" @@ -1091,14 +1092,19 @@ var file_cosmos_nft_v1beta1_tx_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6e, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x6e, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x1a, 0x17, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x73, 0x67, 0x2f, 0x76, 0x31, - 0x2f, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x75, 0x0a, 0x07, 0x4d, 0x73, - 0x67, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x49, 0x64, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, + 0x31, 0x1a, 0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x63, 0x6f, + 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x73, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x73, 0x67, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x07, 0x4d, 0x73, 0x67, 0x53, 0x65, 0x6e, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x06, + 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, + 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x34, + 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x3a, 0x0b, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x22, 0x11, 0x0a, 0x0f, 0x4d, 0x73, 0x67, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x56, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x48, 0x0a, 0x04, 0x53, diff --git a/proto/cosmos/nft/v1beta1/tx.proto b/proto/cosmos/nft/v1beta1/tx.proto index 52bbeb0104b9..0637cd8d8956 100644 --- a/proto/cosmos/nft/v1beta1/tx.proto +++ b/proto/cosmos/nft/v1beta1/tx.proto @@ -3,6 +3,7 @@ package cosmos.nft.v1beta1; option go_package = "github.com/cosmos/cosmos-sdk/x/nft"; +import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; // Msg defines the nft Msg service. @@ -24,10 +25,10 @@ message MsgSend { string id = 2; // sender is the address of the owner of nft - string sender = 3; + string sender = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // receiver is the receiver address of nft - string receiver = 4; + string receiver = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } // MsgSendResponse defines the Msg/Send response type. message MsgSendResponse {} \ No newline at end of file diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index fe98b2cbba6c..9abc3257f553 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -47,32 +47,40 @@ func Prompt[T any](data T, namePrefix string) (T, error) { } for i := 0; i < v.NumField(); i++ { - if v.Field(i).Kind() == reflect.Struct { - // if the field is a struct, call recursively - result, err := Prompt(reflect.ValueOf(v.Field(i)).Interface(), strings.ToLower(v.Type().Field(i).Name)) - if err != nil { - return data, err - } - - v.Field(i).Set(reflect.ValueOf(result)) + 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 + continue } - fieldName := strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name)) + // create prompts prompt := promptui.Prompt{ - Label: fmt.Sprintf("Enter %s %s", namePrefix, fieldName), + Label: fmt.Sprintf("Enter %s %s", namePrefix, strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name))), Validate: client.ValidatePromptNotEmpty, } + fieldName := strings.ToLower(v.Type().Field(i).Name) + // validation per field name if strings.Contains(fieldName, "url") { prompt.Validate = client.ValidatePromptURL } - if strings.EqualFold(v.Type().Field(i).Name, "authority") { + if strings.EqualFold(fieldName, "authority") { // pre-fill with gov address prompt.Default = authtypes.NewModuleAddress(types.ModuleName).String() prompt.Validate = client.ValidatePromptAddress } + if strings.Contains(fieldName, "addr") || + strings.Contains(fieldName, "sender") || + strings.Contains(fieldName, "voter") || + strings.Contains(fieldName, "depositor") || + strings.Contains(fieldName, "granter") || + strings.Contains(fieldName, "grantee") || + strings.Contains(fieldName, "recipient") { + prompt.Validate = client.ValidatePromptAddress + } + result, err := prompt.Run() if err != nil { return data, fmt.Errorf("failed to prompt for %s: %w", fieldName, err) @@ -81,11 +89,13 @@ func Prompt[T any](data T, namePrefix string) (T, error) { switch v.Field(i).Kind() { case reflect.String: v.Field(i).SetString(result) - case reflect.Int64: + case reflect.Int: resultInt, _ := strconv.Atoi(result) v.Field(i).SetInt(int64(resultInt)) - case reflect.Struct: - // TODO - manage all different types nicely :thinking_face: + default: + // skip other types + // possibly in the future we can add more types (like slices) + continue } } From 0ea8edf23f095c92c1877ebb1575edf12ceb8cd4 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 11:49:03 +0200 Subject: [PATCH 06/12] fix doc and add message type --- x/gov/README.md | 144 +++++++++++++++++++++++++------------ x/gov/client/cli/prompt.go | 63 +++++++++------- 2 files changed, 136 insertions(+), 71 deletions(-) diff --git a/x/gov/README.md b/x/gov/README.md index 3e713003503e..63c6cdc61ff0 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -35,51 +35,95 @@ The following specification uses *ATOM* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *ATOM* with the native staking token of the chain. -* [Concepts](#concepts) - * [Proposal submission](#proposal-submission) - * [Right to submit a proposal](#right-to-submit-a-proposal) - * [Proposal Messages](#proposal-messages) - * [Deposit](#deposit) - * [Vote](#vote) - * [Participants](#participants) - * [Voting period](#voting-period) - * [Option set](#option-set) - * [Weighted Votes](#weighted-votes) - * [Quorum](#quorum) - * [Threshold](#threshold) - * [Inheritance](#inheritance) - * [Validator’s punishment for non-voting](#validators-punishment-for-non-voting) - * [Governance address](#governance-address) - * [Software Upgrade](#software-upgrade) -* [State](#state) - * [Proposals](#proposals) - * [Parameters and base types](#parameters-and-base-types) - * [DepositParams](#depositparams) - * [VotingParams](#votingparams) - * [TallyParams](#tallyparams) - * [Deposit](#deposit-1) - * [ValidatorGovInfo](#validatorgovinfo) - * [Stores](#stores) - * [Proposal Processing Queue](#proposal-processing-queue) - * [Legacy Proposal](#legacy-proposal) -* [Messages](#messages) - * [Proposal Submission](#proposal-submission-1) - * [Deposit](#deposit-2) - * [Vote](#vote-1) -* [Events](#events) - * [EndBlocker](#endblocker) - * [Handlers](#handlers) - * [MsgSubmitProposal](#msgsubmitproposal) - * [MsgVote](#msgvote) - * [MsgVoteWeighted](#msgvoteweighted) - * [MsgDeposit](#msgdeposit) -* [Future Improvements](#future-improvements) -* [Parameters](#parameters) -* [Client](#client) - * [CLI](#cli) - * [gRPC](#grpc) - * [REST](#rest) -* [Metadata](#metadata) +- [`x/gov`](#xgov) + - [Abstract](#abstract) + - [Contents](#contents) +- [Concepts](#concepts) + - [Proposal submission](#proposal-submission) + - [Right to submit a proposal](#right-to-submit-a-proposal) + - [Proposal Messages](#proposal-messages) + - [Deposit](#deposit) + - [Deposit refund and burn](#deposit-refund-and-burn) + - [Vote](#vote) + - [Participants](#participants) + - [Voting period](#voting-period) + - [Option set](#option-set) + - [Weighted Votes](#weighted-votes) + - [Quorum](#quorum) + - [Threshold](#threshold) + - [Inheritance](#inheritance) + - [Validator’s punishment for non-voting](#validators-punishment-for-non-voting) + - [Governance address](#governance-address) + - [Software Upgrade](#software-upgrade) + - [Signal](#signal) + - [Switch](#switch) +- [State](#state) + - [Proposals](#proposals) + - [Writing a module that uses governance](#writing-a-module-that-uses-governance) + - [Parameters and base types](#parameters-and-base-types) + - [DepositParams](#depositparams) + - [VotingParams](#votingparams) + - [TallyParams](#tallyparams) + - [Deposit](#deposit-1) + - [ValidatorGovInfo](#validatorgovinfo) + - [Stores](#stores) + - [Proposal Processing Queue](#proposal-processing-queue) + - [Legacy Proposal](#legacy-proposal) +- [Messages](#messages) + - [Proposal Submission](#proposal-submission-1) + - [Deposit](#deposit-2) + - [Vote](#vote-1) +- [Events](#events) + - [EndBlocker](#endblocker) + - [Handlers](#handlers) + - [MsgSubmitProposal](#msgsubmitproposal) + - [MsgVote](#msgvote) + - [MsgVoteWeighted](#msgvoteweighted) + - [MsgDeposit](#msgdeposit) +- [Future Improvements](#future-improvements) +- [Parameters](#parameters) + - [SubKeys](#subkeys) +- [Client](#client) + - [CLI](#cli) + - [Query](#query) + - [deposit](#deposit-3) + - [deposits](#deposits) + - [param](#param) + - [params](#params) + - [proposal](#proposal) + - [proposals](#proposals-1) + - [proposer](#proposer) + - [tally](#tally) + - [vote](#vote-2) + - [votes](#votes) + - [Transactions](#transactions) + - [deposit](#deposit-4) + - [draft-proposal](#draft-proposal) + - [submit-proposal](#submit-proposal) + - [submit-legacy-proposal](#submit-legacy-proposal) + - [vote](#vote-3) + - [weighted-vote](#weighted-vote) + - [gRPC](#grpc) + - [Proposal](#proposal-1) + - [Proposals](#proposals-2) + - [Vote](#vote-4) + - [Votes](#votes-1) + - [Params](#params-1) + - [Deposit](#deposit-5) + - [deposits](#deposits-1) + - [TallyResult](#tallyresult) + - [REST](#rest) + - [proposal](#proposal-2) + - [proposals](#proposals-3) + - [voter vote](#voter-vote) + - [votes](#votes-2) + - [params](#params-2) + - [deposits](#deposits-2) + - [proposal deposits](#proposal-deposits) + - [tally](#tally-1) +- [Metadata](#metadata) + - [Proposal](#proposal-3) + - [Vote](#vote-5) @@ -1149,6 +1193,16 @@ Example: simd tx gov deposit 1 10000000stake --from cosmos1.. ``` +#### draft-proposal + +The `draft-proposal` command allows users to draft any type of proposal. +The command returns a `draft_proposal.json`, to be used by `submit-proposal` after being completed. +The `draft_metadata.json` is mean to be uploaded to [IPFS](#metadata). + +```bash +simd tx gov draft-proposal +``` + #### submit-proposal The `submit-proposal` command allows users to submit a governance proposal along with some messages and metadata. diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 9abc3257f553..9ba125cc1265 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -23,6 +23,7 @@ const ( proposalText = "text" proposalOther = "other" draftProposalFileName = "draft_proposal.json" + draftMetadataFileName = "draft_metadata.json" ) type ProposalMetadata struct { @@ -108,21 +109,16 @@ type proposalTypes struct { Msg sdk.Msg } -func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { +func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, error) { proposal := &proposal{} // set metadata metadata, err := Prompt(ProposalMetadata{}, "proposal") if err != nil { - return nil, fmt.Errorf("failed to set proposal metadata: %w", err) + return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } - rawMetadata, err := json.Marshal(metadata) - if err != nil { - return nil, fmt.Errorf("failed to marshal proposal metadata: %w", err) - } - - proposal.Metadata = string(rawMetadata) + proposal.Metadata = fmt.Sprintf("%s CID", draftMetadataFileName) // set deposit depositPrompt := promptui.Prompt{ @@ -131,22 +127,25 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, error) { } proposal.Deposit, err = depositPrompt.Run() if err != nil { - return nil, fmt.Errorf("failed to set proposal deposit: %w", err) + return nil, metadata, fmt.Errorf("failed to set proposal deposit: %w", err) } if p.Msg == nil { - return proposal, nil + return proposal, metadata, nil } // set messages field result, err := Prompt(p.Msg, "msg") if err != nil { - return nil, fmt.Errorf("failed to set proposal message: %w", err) + return nil, metadata, fmt.Errorf("failed to set proposal message: %w", err) } - // TODO enrich message type - proposal.Messages = append(proposal.Messages, cdc.MustMarshalJSON(result)) - return proposal, nil + message, err := cdc.MarshalInterfaceJSON(result) + if err != nil { + return nil, metadata, fmt.Errorf("failed to marshal proposal message: %w", err) + } + proposal.Messages = append(proposal.Messages, message) + return proposal, metadata, nil } var supportedProposalTypes = []proposalTypes{ @@ -245,32 +244,31 @@ func NewCmdDraftProposal() *cobra.Command { return fmt.Errorf("failed to prompt proposal types: %w", err) } - proposal.Msg, err = getProposalMsg(clientCtx.Codec, result) - if err != nil { - return err - } - } else if proposal.MsgType != "" { + proposal.MsgType = result + } + + if proposal.MsgType != "" { proposal.Msg, err = getProposalMsg(clientCtx.Codec, proposal.MsgType) if err != nil { - return err + // should never happen + panic(err) } } - result, err := proposal.Prompt(clientCtx.Codec) + prop, metadata, err := proposal.Prompt(clientCtx.Codec) if err != nil { return err } - rawProposal, err := json.MarshalIndent(result, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal proposal: %w", err) + if err := writeFile(draftMetadataFileName, metadata); err != nil { + return err } - if err := os.WriteFile(draftProposalFileName, rawProposal, 0o600); err != nil { + if err := writeFile(draftProposalFileName, prop); err != nil { return err } - fmt.Printf("Your draft proposal has successfully been generated.\nBecause proposals should contain off-chain metadata, please upload the metadata object to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n") + 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") return nil }, @@ -280,3 +278,16 @@ func NewCmdDraftProposal() *cobra.Command { return cmd } + +func writeFile(fileName string, input any) error { + raw, err := json.MarshalIndent(input, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal proposal: %w", err) + } + + if err := os.WriteFile(fileName, raw, 0o600); err != nil { + return err + } + + return nil +} From bed895059e2221b88d0f41aadfef708043f9008d Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 11:52:10 +0200 Subject: [PATCH 07/12] fix golangci --- x/gov/client/cli/prompt.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 9ba125cc1265..da9b2afddcef 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -27,12 +27,12 @@ const ( ) 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"` // 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"` // named 'Url' instead of 'URL' for avoiding the camel case split + VoteOptionContext string `json:"vote_option_context"` } // Prompt prompts the user for filling struct data @@ -118,7 +118,7 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, er return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } - proposal.Metadata = fmt.Sprintf("%s CID", draftMetadataFileName) + proposal.Metadata = fmt.Sprintf("Insert here %s IPFS CID", draftMetadataFileName) // set deposit depositPrompt := promptui.Prompt{ @@ -220,10 +220,10 @@ func NewCmdDraftProposal() *cobra.Command { return fmt.Errorf("failed to prompt proposal types: %w", err) } - var proposal *proposalTypes + var proposal proposalTypes for _, p := range supportedProposalTypes { if strings.EqualFold(p.Type, proposalType) { - proposal = &p + proposal = p break } } From fc4bfeaedff146473587991de6fb633ed51c87ea Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 12:10:43 +0200 Subject: [PATCH 08/12] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b7073e8820..335c593ab3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +* (cli) [#13304](https://github.com/cosmos/cosmos-sdk/pull/13304) Add `tx gov draft-proposal` command for generating proposal JSONs. * (cli) [#13207](https://github.com/cosmos/cosmos-sdk/pull/13207) Reduce user's password prompts when calling keyring `List()` function * (x/authz) [#12648](https://github.com/cosmos/cosmos-sdk/pull/12648) Add an allow list, an optional list of addresses allowed to receive bank assets via authz MsgSend grant. * (sdk.Coins) [#12627](https://github.com/cosmos/cosmos-sdk/pull/12627) Make a Denoms method on sdk.Coins. From 4c4a0ed62409a943753a9ed36ba3e30f11ee3513 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Fri, 16 Sep 2022 17:40:51 +0200 Subject: [PATCH 09/12] fix godocs --- client/prompts.go | 9 +++++---- x/gov/README.md | 2 +- x/gov/client/cli/prompt.go | 5 ++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/client/prompts.go b/client/prompts.go index e57ffdb5d2cd..050d806c49a8 100644 --- a/client/prompts.go +++ b/client/prompts.go @@ -8,8 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Prompts Validation - +// ValidatePromptNotEmpty validates that the input is not empty. func ValidatePromptNotEmpty(input string) error { if input == "" { return fmt.Errorf("input cannot be empty") @@ -18,6 +17,7 @@ func ValidatePromptNotEmpty(input string) error { return nil } +// ValidatePromptURL validates that the input is a valid URL. func ValidatePromptURL(input string) error { _, err := url.ParseRequestURI(input) if err != nil { @@ -27,6 +27,7 @@ func ValidatePromptURL(input string) error { return nil } +// ValidatePromptAddress validates that the input is a valid Bech32 address. func ValidatePromptAddress(input string) error { if _, err := sdk.AccAddressFromBech32(input); err != nil { return fmt.Errorf("invalid address: %w", err) @@ -35,6 +36,7 @@ func ValidatePromptAddress(input string) error { return nil } +// ValidatePromptYesNo validates that the input is valid sdk.COins func ValidatePromptCoins(input string) error { if _, err := sdk.ParseCoinsNormalized(input); err != nil { return fmt.Errorf("invalid coins: %w", err) @@ -43,8 +45,7 @@ func ValidatePromptCoins(input string) error { return nil } -// Prompts Helpers - +// CamelCaseToString converts a camel case string to a string with spaces. func CamelCaseToString(str string) string { w := []rune(str) for i := len(w) - 1; i > 1; i-- { diff --git a/x/gov/README.md b/x/gov/README.md index 63c6cdc61ff0..d14530ff57c8 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -1197,7 +1197,7 @@ simd tx gov deposit 1 10000000stake --from cosmos1.. The `draft-proposal` command allows users to draft any type of proposal. The command returns a `draft_proposal.json`, to be used by `submit-proposal` after being completed. -The `draft_metadata.json` is mean to be uploaded to [IPFS](#metadata). +The `draft_metadata.json` is meant to be uploaded to [IPFS](#metadata). ```bash simd tx gov draft-proposal diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index da9b2afddcef..8fd5177ca14a 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -26,6 +26,8 @@ const ( draftMetadataFileName = "draft_metadata.json" ) +// 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"` @@ -35,7 +37,7 @@ type ProposalMetadata struct { VoteOptionContext string `json:"vote_option_context"` } -// Prompt prompts the user for filling struct data +// Prompt prompts the user for all values of the given type. // data is the struct to be filled // namePrefix is the name to be display as "Enter " func Prompt[T any](data T, namePrefix string) (T, error) { @@ -109,6 +111,7 @@ type proposalTypes struct { Msg sdk.Msg } +// Prompt the proposal type values and return the proposal and its metadata func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, error) { proposal := &proposal{} From ec5a704c2acc4a706d35d369d76e7e85871d6718 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Sat, 17 Sep 2022 19:27:38 +0200 Subject: [PATCH 10/12] list all messages types --- x/gov/client/cli/prompt.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 8fd5177ca14a..024a170c0bd4 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "reflect" + "sort" "strconv" "strings" @@ -234,15 +235,16 @@ func NewCmdDraftProposal() *cobra.Command { // create any proposal type if proposal.Type == proposalOther { // prompt proposal type - msgPrompt := promptui.Prompt{ - Label: "Which message type do you want to use for the proposal", - Validate: func(input string) error { - _, err := getProposalMsg(clientCtx.Codec, input) - return err - }, + msgPrompt := promptui.Select{ + Label: "Select proposal message type:", + Items: func() []string { + msgs := clientCtx.InterfaceRegistry.ListImplementations(sdk.MsgInterfaceProtoName) + sort.Strings(msgs) + return msgs + }(), } - result, err := msgPrompt.Run() + _, result, err := msgPrompt.Run() if err != nil { return fmt.Errorf("failed to prompt proposal types: %w", err) } From a2c8d6bf3881a1515e53585f365aa6802e3fee6a Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Sun, 18 Sep 2022 18:17:33 +0200 Subject: [PATCH 11/12] updates --- x/gov/README.md | 178 ++++++++++++++++++------------------- x/gov/client/cli/prompt.go | 3 +- 2 files changed, 90 insertions(+), 91 deletions(-) diff --git a/x/gov/README.md b/x/gov/README.md index d14530ff57c8..286c1ff56e16 100644 --- a/x/gov/README.md +++ b/x/gov/README.md @@ -35,95 +35,95 @@ The following specification uses *ATOM* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *ATOM* with the native staking token of the chain. -- [`x/gov`](#xgov) - - [Abstract](#abstract) - - [Contents](#contents) -- [Concepts](#concepts) - - [Proposal submission](#proposal-submission) - - [Right to submit a proposal](#right-to-submit-a-proposal) - - [Proposal Messages](#proposal-messages) - - [Deposit](#deposit) - - [Deposit refund and burn](#deposit-refund-and-burn) - - [Vote](#vote) - - [Participants](#participants) - - [Voting period](#voting-period) - - [Option set](#option-set) - - [Weighted Votes](#weighted-votes) - - [Quorum](#quorum) - - [Threshold](#threshold) - - [Inheritance](#inheritance) - - [Validator’s punishment for non-voting](#validators-punishment-for-non-voting) - - [Governance address](#governance-address) - - [Software Upgrade](#software-upgrade) - - [Signal](#signal) - - [Switch](#switch) -- [State](#state) - - [Proposals](#proposals) - - [Writing a module that uses governance](#writing-a-module-that-uses-governance) - - [Parameters and base types](#parameters-and-base-types) - - [DepositParams](#depositparams) - - [VotingParams](#votingparams) - - [TallyParams](#tallyparams) - - [Deposit](#deposit-1) - - [ValidatorGovInfo](#validatorgovinfo) - - [Stores](#stores) - - [Proposal Processing Queue](#proposal-processing-queue) - - [Legacy Proposal](#legacy-proposal) -- [Messages](#messages) - - [Proposal Submission](#proposal-submission-1) - - [Deposit](#deposit-2) - - [Vote](#vote-1) -- [Events](#events) - - [EndBlocker](#endblocker) - - [Handlers](#handlers) - - [MsgSubmitProposal](#msgsubmitproposal) - - [MsgVote](#msgvote) - - [MsgVoteWeighted](#msgvoteweighted) - - [MsgDeposit](#msgdeposit) -- [Future Improvements](#future-improvements) -- [Parameters](#parameters) - - [SubKeys](#subkeys) -- [Client](#client) - - [CLI](#cli) - - [Query](#query) - - [deposit](#deposit-3) - - [deposits](#deposits) - - [param](#param) - - [params](#params) - - [proposal](#proposal) - - [proposals](#proposals-1) - - [proposer](#proposer) - - [tally](#tally) - - [vote](#vote-2) - - [votes](#votes) - - [Transactions](#transactions) - - [deposit](#deposit-4) - - [draft-proposal](#draft-proposal) - - [submit-proposal](#submit-proposal) - - [submit-legacy-proposal](#submit-legacy-proposal) - - [vote](#vote-3) - - [weighted-vote](#weighted-vote) - - [gRPC](#grpc) - - [Proposal](#proposal-1) - - [Proposals](#proposals-2) - - [Vote](#vote-4) - - [Votes](#votes-1) - - [Params](#params-1) - - [Deposit](#deposit-5) - - [deposits](#deposits-1) - - [TallyResult](#tallyresult) - - [REST](#rest) - - [proposal](#proposal-2) - - [proposals](#proposals-3) - - [voter vote](#voter-vote) - - [votes](#votes-2) - - [params](#params-2) - - [deposits](#deposits-2) - - [proposal deposits](#proposal-deposits) - - [tally](#tally-1) -- [Metadata](#metadata) - - [Proposal](#proposal-3) - - [Vote](#vote-5) +* [`x/gov`](#xgov) + * [Abstract](#abstract) + * [Contents](#contents) +* [Concepts](#concepts) + * [Proposal submission](#proposal-submission) + * [Right to submit a proposal](#right-to-submit-a-proposal) + * [Proposal Messages](#proposal-messages) + * [Deposit](#deposit) + * [Deposit refund and burn](#deposit-refund-and-burn) + * [Vote](#vote) + * [Participants](#participants) + * [Voting period](#voting-period) + * [Option set](#option-set) + * [Weighted Votes](#weighted-votes) + * [Quorum](#quorum) + * [Threshold](#threshold) + * [Inheritance](#inheritance) + * [Validator’s punishment for non-voting](#validators-punishment-for-non-voting) + * [Governance address](#governance-address) + * [Software Upgrade](#software-upgrade) + * [Signal](#signal) + * [Switch](#switch) +* [State](#state) + * [Proposals](#proposals) + * [Writing a module that uses governance](#writing-a-module-that-uses-governance) + * [Parameters and base types](#parameters-and-base-types) + * [DepositParams](#depositparams) + * [VotingParams](#votingparams) + * [TallyParams](#tallyparams) + * [Deposit](#deposit-1) + * [ValidatorGovInfo](#validatorgovinfo) + * [Stores](#stores) + * [Proposal Processing Queue](#proposal-processing-queue) + * [Legacy Proposal](#legacy-proposal) +* [Messages](#messages) + * [Proposal Submission](#proposal-submission-1) + * [Deposit](#deposit-2) + * [Vote](#vote-1) +* [Events](#events) + * [EndBlocker](#endblocker) + * [Handlers](#handlers) + * [MsgSubmitProposal](#msgsubmitproposal) + * [MsgVote](#msgvote) + * [MsgVoteWeighted](#msgvoteweighted) + * [MsgDeposit](#msgdeposit) +* [Future Improvements](#future-improvements) +* [Parameters](#parameters) + * [SubKeys](#subkeys) +* [Client](#client) + * [CLI](#cli) + * [Query](#query) + * [deposit](#deposit-3) + * [deposits](#deposits) + * [param](#param) + * [params](#params) + * [proposal](#proposal) + * [proposals](#proposals-1) + * [proposer](#proposer) + * [tally](#tally) + * [vote](#vote-2) + * [votes](#votes) + * [Transactions](#transactions) + * [deposit](#deposit-4) + * [draft-proposal](#draft-proposal) + * [submit-proposal](#submit-proposal) + * [submit-legacy-proposal](#submit-legacy-proposal) + * [vote](#vote-3) + * [weighted-vote](#weighted-vote) + * [gRPC](#grpc) + * [Proposal](#proposal-1) + * [Proposals](#proposals-2) + * [Vote](#vote-4) + * [Votes](#votes-1) + * [Params](#params-1) + * [Deposit](#deposit-5) + * [deposits](#deposits-1) + * [TallyResult](#tallyresult) + * [REST](#rest) + * [proposal](#proposal-2) + * [proposals](#proposals-3) + * [voter vote](#voter-vote) + * [votes](#votes-2) + * [params](#params-2) + * [deposits](#deposits-2) + * [proposal deposits](#proposal-deposits) + * [tally](#tally-1) +* [Metadata](#metadata) + * [Proposal](#proposal-3) + * [Vote](#vote-5) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 024a170c0bd4..44d73ff97eb0 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -121,8 +121,7 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, er if err != nil { return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } - - proposal.Metadata = fmt.Sprintf("Insert here %s IPFS CID", draftMetadataFileName) + proposal.Metadata = fmt.Sprintf("ipfs://<%s CID>", draftMetadataFileName) // set deposit depositPrompt := promptui.Prompt{ From bb9ec466e19952a1e879b84c11bf80ef3761cce4 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Sun, 18 Sep 2022 18:35:40 +0200 Subject: [PATCH 12/12] simplify --- x/gov/client/cli/prompt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/gov/client/cli/prompt.go b/x/gov/client/cli/prompt.go index 44d73ff97eb0..b997f5a4a8b9 100644 --- a/x/gov/client/cli/prompt.go +++ b/x/gov/client/cli/prompt.go @@ -121,7 +121,7 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, er if err != nil { return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err) } - proposal.Metadata = fmt.Sprintf("ipfs://<%s CID>", draftMetadataFileName) + proposal.Metadata = "ipfs://CID" // set deposit depositPrompt := promptui.Prompt{