Skip to content

Commit 7eb259f

Browse files
authored
feat: add draft-proposal for x/group (cosmos#13353)
* feat: add `draft-proposal` for x/group * add changelog * extract useful function * add `GetMsgFromTypeURL` tests
1 parent fc32ef1 commit 7eb259f

File tree

8 files changed

+237
-58
lines changed

8 files changed

+237
-58
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
3939

4040
### Features
4141

42-
* (cli) [#13304](https://github.com/cosmos/cosmos-sdk/pull/13304) Add `tx gov draft-proposal` command for generating proposal JSONs.
42+
* (cli) [#13353](https://github.com/cosmos/cosmos-sdk/pull/13353) Add `tx group draft-proposal` command for generating group proposal JSONs (skeleton).
43+
* (cli) [#13304](https://github.com/cosmos/cosmos-sdk/pull/13304) Add `tx gov draft-proposal` command for generating proposal JSONs (skeleton).
4344
* (cli) [#13207](https://github.com/cosmos/cosmos-sdk/pull/13207) Reduce user's password prompts when calling keyring `List()` function
4445
* (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.
4546
* (sdk.Coins) [#12627](https://github.com/cosmos/cosmos-sdk/pull/12627) Make a Denoms method on sdk.Coins.
@@ -487,7 +488,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
487488
* (x/params) [#12724](https://github.com/cosmos/cosmos-sdk/pull/12724) Add `GetParamSetIfExists` function to params `Subspace` to prevent panics on breaking changes.
488489
* [#12668](https://github.com/cosmos/cosmos-sdk/pull/12668) Add `authz_msg_index` event attribute to message events emitted when executing via `MsgExec` through `x/authz`.
489490
* [#12697](https://github.com/cosmos/cosmos-sdk/pull/12697) Upgrade IAVL to v0.19.0 with fast index and error propagation. NOTE: first start will take a while to propagate into new model.
490-
- Note: after upgrading to this version it may take up to 15 minutes to migrate from 0.17 to 0.19. This time is used to create the fast cache introduced into IAVL for performance
491+
* Note: after upgrading to this version it may take up to 15 minutes to migrate from 0.17 to 0.19. This time is used to create the fast cache introduced into IAVL for performance
491492
* [#12784](https://github.com/cosmos/cosmos-sdk/pull/12784) Upgrade Tendermint to 0.34.20.
492493
* (x/bank) [#12674](https://github.com/cosmos/cosmos-sdk/pull/12674) Add convenience function `CreatePrefixedAccountStoreKey()` to construct key to access account's balance for a given denom.
493494

types/tx_msg.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package types
22

33
import (
4+
"encoding/json"
5+
fmt "fmt"
6+
47
"github.com/cosmos/gogoproto/proto"
58

9+
"github.com/cosmos/cosmos-sdk/codec"
610
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
711
)
812

@@ -79,3 +83,22 @@ type TxEncoder func(tx Tx) ([]byte, error)
7983
func MsgTypeURL(msg Msg) string {
8084
return "/" + proto.MessageName(msg)
8185
}
86+
87+
// GetMsgFromTypeURL returns a `sdk.Msg` message type from a type URL
88+
func GetMsgFromTypeURL(cdc codec.Codec, input string) (Msg, error) {
89+
var msg Msg
90+
bz, err := json.Marshal(struct {
91+
Type string `json:"@type"`
92+
}{
93+
Type: input,
94+
})
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
if err := cdc.UnmarshalInterfaceJSON(bz, &msg); err != nil {
100+
return nil, fmt.Errorf("failed to determine sdk.Msg for %s URL : %w", input, err)
101+
}
102+
103+
return msg, nil
104+
}

types/tx_msg_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/stretchr/testify/suite"
77

8+
"github.com/cosmos/cosmos-sdk/codec"
89
"github.com/cosmos/cosmos-sdk/testutil/testdata"
910
sdk "github.com/cosmos/cosmos-sdk/types"
1011
)
@@ -33,3 +34,12 @@ func (s *testMsgSuite) TestMsg() {
3334
func (s *testMsgSuite) TestMsgTypeURL() {
3435
s.Require().Equal("/testdata.TestMsg", sdk.MsgTypeURL(new(testdata.TestMsg)))
3536
}
37+
38+
func (s *testMsgSuite) TestGetMsgFromTypeURL() {
39+
msg := new(testdata.TestMsg)
40+
cdc := codec.NewProtoCodec(testdata.NewTestInterfaceRegistry())
41+
42+
result, err := sdk.GetMsgFromTypeURL(cdc, "/testdata.TestMsg")
43+
s.Require().NoError(err)
44+
s.Require().Equal(msg, result)
45+
}

x/gov/client/cli/prompt.go

Lines changed: 26 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,6 @@ const (
2727
draftMetadataFileName = "draft_metadata.json"
2828
)
2929

30-
// ProposalMetadata is the metadata of a proposal
31-
// This metadata is supposed to live off-chain when submitted in a proposal
32-
type ProposalMetadata struct {
33-
Title string `json:"title"`
34-
Authors string `json:"authors"`
35-
Summary string `json:"summary"`
36-
Details string `json:"details"`
37-
ProposalForumUrl string `json:"proposal_forum_url"` // named 'Url' instead of 'URL' for avoiding the camel case split
38-
VoteOptionContext string `json:"vote_option_context"`
39-
}
40-
4130
// Prompt prompts the user for all values of the given type.
4231
// data is the struct to be filled
4332
// namePrefix is the name to be display as "Enter <namePrefix> <field>"
@@ -115,21 +104,22 @@ func Prompt[T any](data T, namePrefix string) (T, error) {
115104
return data, nil
116105
}
117106

118-
type proposalTypes struct {
119-
Type string
107+
type proposalType struct {
108+
Name string
120109
MsgType string
121110
Msg sdk.Msg
122111
}
123112

124113
// Prompt the proposal type values and return the proposal and its metadata
125-
func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, error) {
114+
func (p *proposalType) Prompt(cdc codec.Codec) (*proposal, types.ProposalMetadata, error) {
126115
proposal := &proposal{}
127116

128117
// set metadata
129-
metadata, err := Prompt(ProposalMetadata{}, "proposal")
118+
metadata, err := Prompt(types.ProposalMetadata{}, "proposal")
130119
if err != nil {
131120
return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err)
132121
}
122+
// the metadata must be saved on IPFS, set placeholder
133123
proposal.Metadata = "ipfs://CID"
134124

135125
// set deposit
@@ -160,60 +150,42 @@ func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, er
160150
return proposal, metadata, nil
161151
}
162152

163-
var supportedProposalTypes = []proposalTypes{
153+
var suggestedProposalTypes = []proposalType{
164154
{
165-
Type: proposalText,
155+
Name: proposalText,
166156
MsgType: "", // no message for text proposal
167157
},
168158
{
169-
Type: "community-pool-spend",
159+
Name: "community-pool-spend",
170160
MsgType: "/cosmos.distribution.v1beta1.MsgCommunityPoolSpend",
171161
},
172162
{
173-
Type: "software-upgrade",
163+
Name: "software-upgrade",
174164
MsgType: "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade",
175165
},
176166
{
177-
Type: "cancel-software-upgrade",
167+
Name: "cancel-software-upgrade",
178168
MsgType: "/cosmos.upgrade.v1beta1.MsgCancelUpgrade",
179169
},
180170
{
181-
Type: proposalOther,
171+
Name: proposalOther,
182172
MsgType: "", // user will input the message type
183173
},
184174
}
185175

186-
func getProposalTypes() []string {
187-
types := make([]string, len(supportedProposalTypes))
188-
for i, p := range supportedProposalTypes {
189-
types[i] = p.Type
176+
func getProposalSuggestions() []string {
177+
types := make([]string, len(suggestedProposalTypes))
178+
for i, p := range suggestedProposalTypes {
179+
types[i] = p.Name
190180
}
191181
return types
192182
}
193183

194-
func getProposalMsg(cdc codec.Codec, input string) (sdk.Msg, error) {
195-
var msg sdk.Msg
196-
bz, err := json.Marshal(struct {
197-
Type string `json:"@type"`
198-
}{
199-
Type: input,
200-
})
201-
if err != nil {
202-
return nil, err
203-
}
204-
205-
if err := cdc.UnmarshalInterfaceJSON(bz, &msg); err != nil {
206-
return nil, fmt.Errorf("failed to determined sdk.Msg from %s proposal type : %w", input, err)
207-
}
208-
209-
return msg, nil
210-
}
211-
212184
// NewCmdDraftProposal let a user generate a draft proposal.
213185
func NewCmdDraftProposal() *cobra.Command {
214186
cmd := &cobra.Command{
215187
Use: "draft-proposal",
216-
Short: "Generate a draft proposal json file. The generated proposal json contains only one message.",
188+
Short: "Generate a draft proposal json file. The generated proposal json contains only one message (skeleton).",
217189
SilenceUsage: true,
218190
RunE: func(cmd *cobra.Command, _ []string) error {
219191
clientCtx, err := client.GetClientTxContext(cmd)
@@ -224,24 +196,24 @@ func NewCmdDraftProposal() *cobra.Command {
224196
// prompt proposal type
225197
proposalTypesPrompt := promptui.Select{
226198
Label: "Select proposal type",
227-
Items: getProposalTypes(),
199+
Items: getProposalSuggestions(),
228200
}
229201

230-
_, proposalType, err := proposalTypesPrompt.Run()
202+
_, selectedProposalType, err := proposalTypesPrompt.Run()
231203
if err != nil {
232204
return fmt.Errorf("failed to prompt proposal types: %w", err)
233205
}
234206

235-
var proposal proposalTypes
236-
for _, p := range supportedProposalTypes {
237-
if strings.EqualFold(p.Type, proposalType) {
207+
var proposal proposalType
208+
for _, p := range suggestedProposalTypes {
209+
if strings.EqualFold(p.Name, selectedProposalType) {
238210
proposal = p
239211
break
240212
}
241213
}
242214

243215
// create any proposal type
244-
if proposal.Type == proposalOther {
216+
if proposal.Name == proposalOther {
245217
// prompt proposal type
246218
msgPrompt := promptui.Select{
247219
Label: "Select proposal message type:",
@@ -261,23 +233,23 @@ func NewCmdDraftProposal() *cobra.Command {
261233
}
262234

263235
if proposal.MsgType != "" {
264-
proposal.Msg, err = getProposalMsg(clientCtx.Codec, proposal.MsgType)
236+
proposal.Msg, err = sdk.GetMsgFromTypeURL(clientCtx.Codec, proposal.MsgType)
265237
if err != nil {
266238
// should never happen
267239
panic(err)
268240
}
269241
}
270242

271-
prop, metadata, err := proposal.Prompt(clientCtx.Codec)
243+
result, metadata, err := proposal.Prompt(clientCtx.Codec)
272244
if err != nil {
273245
return err
274246
}
275247

276-
if err := writeFile(draftMetadataFileName, metadata); err != nil {
248+
if err := writeFile(draftProposalFileName, result); err != nil {
277249
return err
278250
}
279251

280-
if err := writeFile(draftProposalFileName, prop); err != nil {
252+
if err := writeFile(draftMetadataFileName, metadata); err != nil {
281253
return err
282254
}
283255

x/gov/types/metadata.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package types
2+
3+
// ProposalMetadata is the metadata of a proposal
4+
// This metadata is supposed to live off-chain when submitted in a proposal
5+
type ProposalMetadata struct {
6+
Title string `json:"title"`
7+
Authors string `json:"authors"`
8+
Summary string `json:"summary"`
9+
Details string `json:"details"`
10+
ProposalForumUrl string `json:"proposal_forum_url"` // named 'Url' instead of 'URL' for avoiding the camel case split
11+
VoteOptionContext string `json:"vote_option_context"`
12+
}

0 commit comments

Comments
 (0)