github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/client/cli/tx.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/spf13/cobra" 9 10 "github.com/cosmos/cosmos-sdk/client" 11 "github.com/cosmos/cosmos-sdk/client/flags" 12 "github.com/cosmos/cosmos-sdk/client/tx" 13 sdk "github.com/cosmos/cosmos-sdk/types" 14 "github.com/cosmos/cosmos-sdk/version" 15 govutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" 16 "github.com/cosmos/cosmos-sdk/x/gov/types" 17 v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 18 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" 19 ) 20 21 // Proposal flags 22 const ( 23 FlagTitle = "title" 24 FlagDeposit = "deposit" 25 flagVoter = "voter" 26 flagDepositor = "depositor" 27 flagStatus = "status" 28 FlagMetadata = "metadata" 29 FlagSummary = "summary" 30 // Deprecated: only used for v1beta1 legacy proposals. 31 FlagProposal = "proposal" 32 // Deprecated: only used for v1beta1 legacy proposals. 33 FlagDescription = "description" 34 // Deprecated: only used for v1beta1 legacy proposals. 35 FlagProposalType = "type" 36 ) 37 38 // ProposalFlags defines the core required fields of a legacy proposal. It is used to 39 // verify that these values are not provided in conjunction with a JSON proposal 40 // file. 41 var ProposalFlags = []string{ 42 FlagTitle, 43 FlagDescription, 44 FlagProposalType, 45 FlagDeposit, 46 } 47 48 // NewTxCmd returns the transaction commands for this module 49 // governance ModuleClient is slightly different from other ModuleClients in that 50 // it contains a slice of legacy "proposal" child commands. These commands are respective 51 // to the proposal type handlers that are implemented in other modules but are mounted 52 // under the governance CLI (eg. parameter change proposals). 53 func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command { 54 govTxCmd := &cobra.Command{ 55 Use: types.ModuleName, 56 Short: "Governance transactions subcommands", 57 DisableFlagParsing: true, 58 SuggestionsMinimumDistance: 2, 59 RunE: client.ValidateCmd, 60 } 61 62 cmdSubmitLegacyProp := NewCmdSubmitLegacyProposal() 63 for _, propCmd := range legacyPropCmds { 64 flags.AddTxFlagsToCmd(propCmd) 65 cmdSubmitLegacyProp.AddCommand(propCmd) 66 } 67 68 govTxCmd.AddCommand( 69 NewCmdDeposit(), 70 NewCmdVote(), 71 NewCmdWeightedVote(), 72 NewCmdSubmitProposal(), 73 NewCmdDraftProposal(), 74 NewCmdCancelProposal(), 75 76 // Deprecated 77 cmdSubmitLegacyProp, 78 ) 79 80 return govTxCmd 81 } 82 83 // NewCmdSubmitProposal implements submitting a proposal transaction command. 84 func NewCmdSubmitProposal() *cobra.Command { 85 cmd := &cobra.Command{ 86 Use: "submit-proposal [path/to/proposal.json]", 87 Short: "Submit a proposal along with some messages, metadata and deposit", 88 Args: cobra.ExactArgs(1), 89 Long: strings.TrimSpace( 90 fmt.Sprintf(`Submit a proposal along with some messages, metadata and deposit. 91 They should be defined in a JSON file. 92 93 Example: 94 $ %s tx gov submit-proposal path/to/proposal.json 95 96 Where proposal.json contains: 97 98 { 99 // array of proto-JSON-encoded sdk.Msgs 100 "messages": [ 101 { 102 "@type": "/cosmos.bank.v1beta1.MsgSend", 103 "from_address": "cosmos1...", 104 "to_address": "cosmos1...", 105 "amount":[{"denom": "stake","amount": "10"}] 106 } 107 ], 108 // metadata can be any of base64 encoded, raw text, stringified json, IPFS link to json 109 // see below for example metadata 110 "metadata": "4pIMOgIGx1vZGU=", 111 "deposit": "10stake", 112 "title": "My proposal", 113 "summary": "A short summary of my proposal", 114 "expedited": false 115 } 116 117 metadata example: 118 { 119 "title": "", 120 "authors": [""], 121 "summary": "", 122 "details": "", 123 "proposal_forum_url": "", 124 "vote_option_context": "", 125 } 126 `, 127 version.AppName, 128 ), 129 ), 130 RunE: func(cmd *cobra.Command, args []string) error { 131 clientCtx, err := client.GetClientTxContext(cmd) 132 if err != nil { 133 return err 134 } 135 136 proposal, msgs, deposit, err := parseSubmitProposal(clientCtx.Codec, args[0]) 137 if err != nil { 138 return err 139 } 140 141 msg, err := v1.NewMsgSubmitProposal(msgs, deposit, clientCtx.GetFromAddress().String(), proposal.Metadata, proposal.Title, proposal.Summary, proposal.Expedited) 142 if err != nil { 143 return fmt.Errorf("invalid message: %w", err) 144 } 145 146 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 147 }, 148 } 149 150 flags.AddTxFlagsToCmd(cmd) 151 152 return cmd 153 } 154 155 // NewCmdCancelProposal implements submitting a cancel proposal transaction command. 156 func NewCmdCancelProposal() *cobra.Command { 157 cmd := &cobra.Command{ 158 Use: "cancel-proposal [proposal-id]", 159 Short: "Cancel governance proposal before the voting period ends. Must be signed by the proposal creator.", 160 Args: cobra.ExactArgs(1), 161 Example: fmt.Sprintf(`$ %s tx gov cancel-proposal 1 --from mykey`, version.AppName), 162 RunE: func(cmd *cobra.Command, args []string) error { 163 clientCtx, err := client.GetClientTxContext(cmd) 164 if err != nil { 165 return err 166 } 167 168 // validate that the proposal id is a uint 169 proposalID, err := strconv.ParseUint(args[0], 10, 64) 170 if err != nil { 171 return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0]) 172 } 173 174 // Get proposer address 175 from := clientCtx.GetFromAddress() 176 msg := v1.NewMsgCancelProposal(proposalID, from.String()) 177 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 178 }, 179 } 180 181 flags.AddTxFlagsToCmd(cmd) 182 return cmd 183 } 184 185 // NewCmdSubmitLegacyProposal implements submitting a proposal transaction command. 186 // Deprecated: please use NewCmdSubmitProposal instead. 187 func NewCmdSubmitLegacyProposal() *cobra.Command { 188 cmd := &cobra.Command{ 189 Use: "submit-legacy-proposal", 190 Short: "Submit a legacy proposal along with an initial deposit", 191 Long: strings.TrimSpace( 192 fmt.Sprintf(`Submit a legacy proposal along with an initial deposit. 193 Proposal title, description, type and deposit can be given directly or through a proposal JSON file. 194 195 Example: 196 $ %s tx gov submit-legacy-proposal --proposal="path/to/proposal.json" --from mykey 197 198 Where proposal.json contains: 199 200 { 201 "title": "Test Proposal", 202 "description": "My awesome proposal", 203 "type": "Text", 204 "deposit": "10test" 205 } 206 207 Which is equivalent to: 208 209 $ %s tx gov submit-legacy-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey 210 `, 211 version.AppName, version.AppName, 212 ), 213 ), 214 RunE: func(cmd *cobra.Command, args []string) error { 215 clientCtx, err := client.GetClientTxContext(cmd) 216 if err != nil { 217 return err 218 } 219 220 proposal, err := parseSubmitLegacyProposal(cmd.Flags()) 221 if err != nil { 222 return fmt.Errorf("failed to parse proposal: %w", err) 223 } 224 225 amount, err := sdk.ParseCoinsNormalized(proposal.Deposit) 226 if err != nil { 227 return err 228 } 229 230 content, ok := v1beta1.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type) 231 if !ok { 232 return fmt.Errorf("failed to create proposal content: unknown proposal type %s", proposal.Type) 233 } 234 235 msg, err := v1beta1.NewMsgSubmitProposal(content, amount, clientCtx.GetFromAddress()) 236 if err != nil { 237 return fmt.Errorf("invalid message: %w", err) 238 } 239 240 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 241 }, 242 } 243 244 cmd.Flags().String(FlagTitle, "", "The proposal title") 245 cmd.Flags().String(FlagDescription, "", "The proposal description") 246 cmd.Flags().String(FlagProposalType, "", "The proposal Type") 247 cmd.Flags().String(FlagDeposit, "", "The proposal deposit") 248 cmd.Flags().String(FlagProposal, "", "Proposal file path (if this path is given, other proposal flags are ignored)") 249 flags.AddTxFlagsToCmd(cmd) 250 251 return cmd 252 } 253 254 // NewCmdDeposit implements depositing tokens for an active proposal. 255 func NewCmdDeposit() *cobra.Command { 256 cmd := &cobra.Command{ 257 Use: "deposit [proposal-id] [deposit]", 258 Args: cobra.ExactArgs(2), 259 Short: "Deposit tokens for an active proposal", 260 Long: strings.TrimSpace( 261 fmt.Sprintf(`Submit a deposit for an active proposal. You can 262 find the proposal-id by running "%s query gov proposals". 263 264 Example: 265 $ %s tx gov deposit 1 10stake --from mykey 266 `, 267 version.AppName, version.AppName, 268 ), 269 ), 270 RunE: func(cmd *cobra.Command, args []string) error { 271 clientCtx, err := client.GetClientTxContext(cmd) 272 if err != nil { 273 return err 274 } 275 276 // validate that the proposal id is a uint 277 proposalID, err := strconv.ParseUint(args[0], 10, 64) 278 if err != nil { 279 return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0]) 280 } 281 282 // Get depositor address 283 from := clientCtx.GetFromAddress() 284 285 // Get amount of coins 286 amount, err := sdk.ParseCoinsNormalized(args[1]) 287 if err != nil { 288 return err 289 } 290 291 msg := v1.NewMsgDeposit(from, proposalID, amount) 292 293 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 294 }, 295 } 296 297 flags.AddTxFlagsToCmd(cmd) 298 299 return cmd 300 } 301 302 // NewCmdVote implements creating a new vote command. 303 func NewCmdVote() *cobra.Command { 304 cmd := &cobra.Command{ 305 Use: "vote [proposal-id] [option]", 306 Args: cobra.ExactArgs(2), 307 Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", 308 Long: strings.TrimSpace( 309 fmt.Sprintf(`Submit a vote for an active proposal. You can 310 find the proposal-id by running "%s query gov proposals". 311 312 Example: 313 $ %s tx gov vote 1 yes --from mykey 314 `, 315 version.AppName, version.AppName, 316 ), 317 ), 318 RunE: func(cmd *cobra.Command, args []string) error { 319 clientCtx, err := client.GetClientTxContext(cmd) 320 if err != nil { 321 return err 322 } 323 // Get voting address 324 from := clientCtx.GetFromAddress() 325 326 // validate that the proposal id is a uint 327 proposalID, err := strconv.ParseUint(args[0], 10, 64) 328 if err != nil { 329 return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) 330 } 331 332 // Find out which vote option user chose 333 byteVoteOption, err := v1.VoteOptionFromString(govutils.NormalizeVoteOption(args[1])) 334 if err != nil { 335 return err 336 } 337 338 metadata, err := cmd.Flags().GetString(FlagMetadata) 339 if err != nil { 340 return err 341 } 342 343 // Build vote message and run basic validation 344 msg := v1.NewMsgVote(from, proposalID, byteVoteOption, metadata) 345 346 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 347 }, 348 } 349 350 cmd.Flags().String(FlagMetadata, "", "Specify metadata of the vote") 351 flags.AddTxFlagsToCmd(cmd) 352 353 return cmd 354 } 355 356 // NewCmdWeightedVote implements creating a new weighted vote command. 357 func NewCmdWeightedVote() *cobra.Command { 358 cmd := &cobra.Command{ 359 Use: "weighted-vote [proposal-id] [weighted-options]", 360 Args: cobra.ExactArgs(2), 361 Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", 362 Long: strings.TrimSpace( 363 fmt.Sprintf(`Submit a vote for an active proposal. You can 364 find the proposal-id by running "%s query gov proposals". 365 366 Example: 367 $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05 --from mykey 368 `, 369 version.AppName, version.AppName, 370 ), 371 ), 372 RunE: func(cmd *cobra.Command, args []string) error { 373 clientCtx, err := client.GetClientTxContext(cmd) 374 if err != nil { 375 return err 376 } 377 378 // Get voter address 379 from := clientCtx.GetFromAddress() 380 381 // validate that the proposal id is a uint 382 proposalID, err := strconv.ParseUint(args[0], 10, 64) 383 if err != nil { 384 return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) 385 } 386 387 // Figure out which vote options user chose 388 options, err := v1.WeightedVoteOptionsFromString(govutils.NormalizeWeightedVoteOptions(args[1])) 389 if err != nil { 390 return err 391 } 392 393 metadata, err := cmd.Flags().GetString(FlagMetadata) 394 if err != nil { 395 return err 396 } 397 398 // Build vote message and run basic validation 399 msg := v1.NewMsgVoteWeighted(from, proposalID, options, metadata) 400 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 401 }, 402 } 403 404 cmd.Flags().String(FlagMetadata, "", "Specify metadata of the weighted vote") 405 flags.AddTxFlagsToCmd(cmd) 406 407 return cmd 408 }