github.com/Finschia/finschia-sdk@v0.48.1/x/foundation/client/cli/tx.go (about) 1 package cli 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strconv" 7 8 "github.com/spf13/cobra" 9 10 "github.com/Finschia/finschia-sdk/client" 11 "github.com/Finschia/finschia-sdk/client/flags" 12 "github.com/Finschia/finschia-sdk/client/tx" 13 "github.com/Finschia/finschia-sdk/codec" 14 sdk "github.com/Finschia/finschia-sdk/types" 15 "github.com/Finschia/finschia-sdk/x/foundation" 16 govcli "github.com/Finschia/finschia-sdk/x/gov/client/cli" 17 govtypes "github.com/Finschia/finschia-sdk/x/gov/types" 18 ) 19 20 // Proposal flags 21 const ( 22 FlagExec = "exec" 23 ExecTry = "try" 24 ) 25 26 func validateGenerateOnly(cmd *cobra.Command) error { 27 generateOnly, err := cmd.Flags().GetBool(flags.FlagGenerateOnly) 28 if err != nil { 29 return err 30 } 31 if !generateOnly { 32 return fmt.Errorf("you must use it with the flag --%s", flags.FlagGenerateOnly) 33 } 34 return nil 35 } 36 37 func parseMemberRequests(codec codec.Codec, membersJSON string) ([]foundation.MemberRequest, error) { 38 var cliMembers []json.RawMessage 39 if err := json.Unmarshal([]byte(membersJSON), &cliMembers); err != nil { 40 return nil, err 41 } 42 43 members := make([]foundation.MemberRequest, len(cliMembers)) 44 for i, cliMember := range cliMembers { 45 var member foundation.MemberRequest 46 if err := codec.UnmarshalJSON(cliMember, &member); err != nil { 47 return nil, err 48 } 49 members[i] = member 50 } 51 52 return members, nil 53 } 54 55 func parseAddresses(addressesJSON string) ([]string, error) { 56 var addresses []string 57 if err := json.Unmarshal([]byte(addressesJSON), &addresses); err != nil { 58 return nil, err 59 } 60 if len(addresses) == 0 { 61 return nil, fmt.Errorf("you must provide one address at least") 62 } 63 64 return addresses, nil 65 } 66 67 func parseDecisionPolicy(codec codec.Codec, policyJSON string) (foundation.DecisionPolicy, error) { 68 var policy foundation.DecisionPolicy 69 if err := codec.UnmarshalInterfaceJSON([]byte(policyJSON), &policy); err != nil { 70 return nil, err 71 } 72 73 return policy, nil 74 } 75 76 func parseAuthorization(codec codec.Codec, authorizationJSON string) (foundation.Authorization, error) { 77 var authorization foundation.Authorization 78 if err := codec.UnmarshalInterfaceJSON([]byte(authorizationJSON), &authorization); err != nil { 79 return nil, err 80 } 81 82 return authorization, nil 83 } 84 85 func execFromString(execStr string) foundation.Exec { 86 exec := foundation.Exec_EXEC_UNSPECIFIED 87 switch execStr { 88 case ExecTry: 89 exec = foundation.Exec_EXEC_TRY 90 } 91 return exec 92 } 93 94 // VoteOptionFromString returns a VoteOption from a string. It returns an error 95 // if the string is invalid. 96 func voteOptionFromString(str string) (foundation.VoteOption, error) { 97 vo, ok := foundation.VoteOption_value[str] 98 if !ok { 99 return foundation.VOTE_OPTION_UNSPECIFIED, fmt.Errorf("'%s' is not a valid vote option", str) 100 } 101 return foundation.VoteOption(vo), nil 102 } 103 104 func parseMsgs(cdc codec.Codec, msgsJSON string) ([]sdk.Msg, error) { 105 var cliMsgs []json.RawMessage 106 if err := json.Unmarshal([]byte(msgsJSON), &cliMsgs); err != nil { 107 return nil, err 108 } 109 110 msgs := make([]sdk.Msg, len(cliMsgs)) 111 for i, anyJSON := range cliMsgs { 112 var msg sdk.Msg 113 err := cdc.UnmarshalInterfaceJSON(anyJSON, &msg) 114 if err != nil { 115 return nil, err 116 } 117 118 msgs[i] = msg 119 } 120 121 return msgs, nil 122 } 123 124 // NewTxCmd returns the transaction commands for this module 125 func NewTxCmd() *cobra.Command { 126 txCmd := &cobra.Command{ 127 Use: foundation.ModuleName, 128 Short: fmt.Sprintf("%s transactions subcommands", foundation.ModuleName), 129 DisableFlagParsing: true, 130 SuggestionsMinimumDistance: 2, 131 RunE: client.ValidateCmd, 132 } 133 134 txCmd.AddCommand( 135 NewTxCmdFundTreasury(), 136 NewTxCmdWithdrawFromTreasury(), 137 NewTxCmdUpdateMembers(), 138 NewTxCmdUpdateDecisionPolicy(), 139 NewTxCmdSubmitProposal(), 140 NewTxCmdWithdrawProposal(), 141 NewTxCmdVote(), 142 NewTxCmdExec(), 143 NewTxCmdLeaveFoundation(), 144 NewTxCmdGrant(), 145 NewTxCmdRevoke(), 146 ) 147 148 return txCmd 149 } 150 151 func NewTxCmdFundTreasury() *cobra.Command { 152 cmd := &cobra.Command{ 153 Use: "fund-treasury [from] [amount]", 154 Args: cobra.ExactArgs(2), 155 Short: "Fund the treasury", 156 Long: `Fund the treasury 157 `, 158 RunE: func(cmd *cobra.Command, args []string) error { 159 from := args[0] 160 if err := cmd.Flags().Set(flags.FlagFrom, from); err != nil { 161 return err 162 } 163 164 clientCtx, err := client.GetClientTxContext(cmd) 165 if err != nil { 166 return err 167 } 168 169 amount, err := sdk.ParseCoinsNormalized(args[1]) 170 if err != nil { 171 return err 172 } 173 174 msg := foundation.MsgFundTreasury{ 175 From: from, 176 Amount: amount, 177 } 178 if err := msg.ValidateBasic(); err != nil { 179 return err 180 } 181 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 182 }, 183 } 184 185 flags.AddTxFlagsToCmd(cmd) 186 return cmd 187 } 188 189 func NewTxCmdWithdrawFromTreasury() *cobra.Command { 190 cmd := &cobra.Command{ 191 Use: "withdraw-from-treasury [authority] [to] [amount]", 192 Args: cobra.ExactArgs(3), 193 Short: "Withdraw coins from the treasury", 194 Long: `Withdraw coins from the treasury 195 `, 196 RunE: func(cmd *cobra.Command, args []string) error { 197 if err := validateGenerateOnly(cmd); err != nil { 198 return err 199 } 200 201 clientCtx, err := client.GetClientTxContext(cmd) 202 if err != nil { 203 return err 204 } 205 206 amount, err := sdk.ParseCoinsNormalized(args[2]) 207 if err != nil { 208 return err 209 } 210 211 msg := foundation.MsgWithdrawFromTreasury{ 212 Authority: args[0], 213 To: args[1], 214 Amount: amount, 215 } 216 if err := msg.ValidateBasic(); err != nil { 217 return err 218 } 219 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 220 }, 221 } 222 223 flags.AddTxFlagsToCmd(cmd) 224 return cmd 225 } 226 227 func NewTxCmdUpdateMembers() *cobra.Command { 228 cmd := &cobra.Command{ 229 Use: "update-members [authority] [members-json]", 230 Args: cobra.ExactArgs(2), 231 Short: "Update the foundation members", 232 Long: `Update the foundation members 233 234 Example of the content of members-json: 235 236 [ 237 { 238 "address": "addr1", 239 "participating": true, 240 "metadata": "some new metadata" 241 }, 242 { 243 "address": "addr2", 244 "participating": false, 245 "metadata": "some metadata" 246 } 247 ] 248 249 Set a member's participating to false to delete it. 250 `, 251 RunE: func(cmd *cobra.Command, args []string) error { 252 if err := validateGenerateOnly(cmd); err != nil { 253 return err 254 } 255 256 clientCtx, err := client.GetClientTxContext(cmd) 257 if err != nil { 258 return err 259 } 260 261 updates, err := parseMemberRequests(clientCtx.Codec, args[1]) 262 if err != nil { 263 return err 264 } 265 266 msg := foundation.MsgUpdateMembers{ 267 Authority: args[0], 268 MemberUpdates: updates, 269 } 270 if err := msg.ValidateBasic(); err != nil { 271 return err 272 } 273 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 274 }, 275 } 276 277 flags.AddTxFlagsToCmd(cmd) 278 return cmd 279 } 280 281 func NewTxCmdUpdateDecisionPolicy() *cobra.Command { 282 cmd := &cobra.Command{ 283 Use: "update-decision-policy [authority] [policy-json]", 284 Args: cobra.ExactArgs(2), 285 Short: "Update the foundation decision policy", 286 Long: `Update the foundation decision policy 287 288 Example of the content of policy-json: 289 290 { 291 "@type": "/lbm.foundation.v1.ThresholdDecisionPolicy", 292 "threshold": "10", 293 "windows": { 294 "voting_period": "24h", 295 "min_execution_period": "0s" 296 } 297 } 298 `, 299 RunE: func(cmd *cobra.Command, args []string) error { 300 if err := validateGenerateOnly(cmd); err != nil { 301 return err 302 } 303 304 clientCtx, err := client.GetClientTxContext(cmd) 305 if err != nil { 306 return err 307 } 308 309 msg := foundation.MsgUpdateDecisionPolicy{ 310 Authority: args[0], 311 } 312 policy, err := parseDecisionPolicy(clientCtx.Codec, args[1]) 313 if err != nil { 314 return err 315 } 316 if err := msg.SetDecisionPolicy(policy); err != nil { 317 return err 318 } 319 320 if err := msg.ValidateBasic(); err != nil { 321 return err 322 } 323 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 324 }, 325 } 326 327 flags.AddTxFlagsToCmd(cmd) 328 return cmd 329 } 330 331 func NewTxCmdSubmitProposal() *cobra.Command { 332 cmd := &cobra.Command{ 333 Use: "submit-proposal [metadata] [proposers-json] [messages-json]", 334 Args: cobra.ExactArgs(3), 335 Short: "Submit a new proposal", 336 Long: `Submit a new proposal 337 338 Parameters: 339 metadata: metadata of the proposal. 340 proposers-json: the addresses of the proposers in json format. 341 messages-json: messages in json format that will be executed if the proposal is accepted. 342 343 Example of the content of proposers-json: 344 345 [ 346 "addr1", 347 "addr2" 348 ] 349 350 Example of the content of messages-json: 351 352 [ 353 { 354 "@type": "/lbm.foundation.v1.MsgWithdrawFromTreasury", 355 "authority": "addr1", 356 "to": "addr2", 357 "amount": "10000stake" 358 } 359 ] 360 `, 361 RunE: func(cmd *cobra.Command, args []string) error { 362 proposers, err := parseAddresses(args[1]) 363 if err != nil { 364 return err 365 } 366 367 signer := proposers[0] 368 if err := cmd.Flags().Set(flags.FlagFrom, signer); err != nil { 369 return err 370 } 371 372 clientCtx, err := client.GetClientTxContext(cmd) 373 if err != nil { 374 return err 375 } 376 377 messages, err := parseMsgs(clientCtx.Codec, args[2]) 378 if err != nil { 379 return err 380 } 381 382 execStr, err := cmd.Flags().GetString(FlagExec) 383 if err != nil { 384 return err 385 } 386 exec := execFromString(execStr) 387 388 msg := foundation.MsgSubmitProposal{ 389 Proposers: proposers, 390 Metadata: args[0], 391 Exec: exec, 392 } 393 if err := msg.SetMsgs(messages); err != nil { 394 return err 395 } 396 if err := msg.ValidateBasic(); err != nil { 397 return err 398 } 399 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 400 }, 401 } 402 403 flags.AddTxFlagsToCmd(cmd) 404 cmd.Flags().String(FlagExec, "", "Set to 'try' to try to execute proposal immediately after creation (proposers signatures are considered as Yes votes)") 405 406 return cmd 407 } 408 409 func NewTxCmdWithdrawProposal() *cobra.Command { 410 cmd := &cobra.Command{ 411 Use: "withdraw-proposal [proposal-id] [address]", 412 Args: cobra.ExactArgs(2), 413 Short: "Withdraw a submitted proposal", 414 Long: `Withdraw a submitted proposal. 415 416 Parameters: 417 proposal-id: unique ID of the proposal. 418 address: one of the proposer of the proposal. 419 `, 420 RunE: func(cmd *cobra.Command, args []string) error { 421 address := args[1] 422 if err := cmd.Flags().Set(flags.FlagFrom, address); err != nil { 423 return err 424 } 425 426 clientCtx, err := client.GetClientTxContext(cmd) 427 if err != nil { 428 return err 429 } 430 431 proposalID, err := strconv.ParseUint(args[0], 10, 64) 432 if err != nil { 433 return err 434 } 435 436 msg := foundation.MsgWithdrawProposal{ 437 ProposalId: proposalID, 438 Address: address, 439 } 440 if err := msg.ValidateBasic(); err != nil { 441 return err 442 } 443 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 444 }, 445 } 446 447 flags.AddTxFlagsToCmd(cmd) 448 return cmd 449 } 450 451 func NewTxCmdVote() *cobra.Command { 452 cmd := &cobra.Command{ 453 Use: "vote [proposal-id] [voter] [option] [metadata]", 454 Args: cobra.ExactArgs(4), 455 Short: "Vote on a proposal", 456 Long: `Vote on a proposal. 457 458 Parameters: 459 proposal-id: unique ID of the proposal 460 voter: voter account addresses. 461 vote-option: choice of the voter(s) 462 VOTE_OPTION_UNSPECIFIED: no-op 463 VOTE_OPTION_NO: no 464 VOTE_OPTION_YES: yes 465 VOTE_OPTION_ABSTAIN: abstain 466 VOTE_OPTION_NO_WITH_VETO: no-with-veto 467 metadata: metadata for the vote 468 `, 469 RunE: func(cmd *cobra.Command, args []string) error { 470 voter := args[1] 471 if err := cmd.Flags().Set(flags.FlagFrom, voter); err != nil { 472 return err 473 } 474 475 clientCtx, err := client.GetClientTxContext(cmd) 476 if err != nil { 477 return err 478 } 479 480 proposalID, err := strconv.ParseUint(args[0], 10, 64) 481 if err != nil { 482 return err 483 } 484 485 option, err := voteOptionFromString(args[2]) 486 if err != nil { 487 return err 488 } 489 490 execStr, err := cmd.Flags().GetString(FlagExec) 491 if err != nil { 492 return err 493 } 494 exec := execFromString(execStr) 495 496 msg := foundation.MsgVote{ 497 ProposalId: proposalID, 498 Voter: voter, 499 Option: option, 500 Metadata: args[3], 501 Exec: exec, 502 } 503 if err := msg.ValidateBasic(); err != nil { 504 return err 505 } 506 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 507 }, 508 } 509 510 flags.AddTxFlagsToCmd(cmd) 511 cmd.Flags().String(FlagExec, "", "Set to 'try' to try to execute proposal immediately after voting") 512 513 return cmd 514 } 515 516 func NewTxCmdExec() *cobra.Command { 517 cmd := &cobra.Command{ 518 Use: "exec [proposal-id] [signer]", 519 Args: cobra.ExactArgs(2), 520 Short: "Execute a proposal", 521 RunE: func(cmd *cobra.Command, args []string) error { 522 signer := args[1] 523 if err := cmd.Flags().Set(flags.FlagFrom, signer); err != nil { 524 return err 525 } 526 527 clientCtx, err := client.GetClientTxContext(cmd) 528 if err != nil { 529 return err 530 } 531 532 proposalID, err := strconv.ParseUint(args[0], 10, 64) 533 if err != nil { 534 return err 535 } 536 537 msg := foundation.MsgExec{ 538 ProposalId: proposalID, 539 Signer: signer, 540 } 541 if err := msg.ValidateBasic(); err != nil { 542 return err 543 } 544 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 545 }, 546 } 547 548 flags.AddTxFlagsToCmd(cmd) 549 return cmd 550 } 551 552 func NewTxCmdLeaveFoundation() *cobra.Command { 553 cmd := &cobra.Command{ 554 Use: "leave-foundation [address]", 555 Args: cobra.ExactArgs(1), 556 Short: "Leave the foundation", 557 RunE: func(cmd *cobra.Command, args []string) error { 558 address := args[0] 559 if err := cmd.Flags().Set(flags.FlagFrom, address); err != nil { 560 return err 561 } 562 563 clientCtx, err := client.GetClientTxContext(cmd) 564 if err != nil { 565 return err 566 } 567 568 msg := foundation.MsgLeaveFoundation{ 569 Address: address, 570 } 571 if err := msg.ValidateBasic(); err != nil { 572 return err 573 } 574 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 575 }, 576 } 577 578 flags.AddTxFlagsToCmd(cmd) 579 return cmd 580 } 581 582 func NewTxCmdGrant() *cobra.Command { 583 cmd := &cobra.Command{ 584 Use: "grant [authority] [grantee] [authorization-json]", 585 Args: cobra.ExactArgs(3), 586 Short: "Grant an authorization to grantee", 587 Long: `Grant an authorization to grantee 588 589 Example of the content of authorization-json: 590 591 { 592 "@type": "/lbm.foundation.v1.ReceiveFromTreasuryAuthorization", 593 "receive_limit": [ 594 "denom": "stake", 595 "amount": "10000" 596 ] 597 } 598 `, 599 RunE: func(cmd *cobra.Command, args []string) error { 600 if err := validateGenerateOnly(cmd); err != nil { 601 return err 602 } 603 604 clientCtx, err := client.GetClientTxContext(cmd) 605 if err != nil { 606 return err 607 } 608 609 msg := foundation.MsgGrant{ 610 Authority: args[0], 611 Grantee: args[1], 612 } 613 authorization, err := parseAuthorization(clientCtx.Codec, args[2]) 614 if err != nil { 615 return err 616 } 617 if err := msg.SetAuthorization(authorization); err != nil { 618 return err 619 } 620 621 if err := msg.ValidateBasic(); err != nil { 622 return err 623 } 624 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 625 }, 626 } 627 628 flags.AddTxFlagsToCmd(cmd) 629 return cmd 630 } 631 632 func NewTxCmdRevoke() *cobra.Command { 633 cmd := &cobra.Command{ 634 Use: "revoke [authority] [grantee] [msg-type-url]", 635 Args: cobra.ExactArgs(3), 636 Short: "Revoke an authorization of grantee", 637 Long: `Revoke an authorization of grantee 638 `, 639 RunE: func(cmd *cobra.Command, args []string) error { 640 if err := validateGenerateOnly(cmd); err != nil { 641 return err 642 } 643 644 clientCtx, err := client.GetClientTxContext(cmd) 645 if err != nil { 646 return err 647 } 648 649 msg := foundation.MsgRevoke{ 650 Authority: args[0], 651 Grantee: args[1], 652 MsgTypeUrl: args[2], 653 } 654 if err := msg.ValidateBasic(); err != nil { 655 return err 656 } 657 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) 658 }, 659 } 660 661 flags.AddTxFlagsToCmd(cmd) 662 return cmd 663 } 664 665 // NewProposalCmdFoundationExecProposal returns a CLI command handler for 666 // creating a foundation exec proposal governance transaction. 667 func NewProposalCmdFoundationExec() *cobra.Command { 668 cmd := &cobra.Command{ 669 Use: "foundation-exec [messages-json]", 670 Args: cobra.ExactArgs(1), 671 Short: "Submit a foundation exec proposal", 672 Long: ` 673 Parameters: 674 messages-json: messages in json format that will be executed if the proposal is accepted. 675 676 Example of the content of messages-json: 677 678 [ 679 { 680 "@type": "/lbm.foundation.v1.MsgUpdateCensorship", 681 "authority": "addr1", 682 "censorship": { 683 "msg_type_url": "/cosmos.staking.v1beta1.MsgCreateValidator", 684 "authority": "CENSORSHIP_AUTHORITY_UNSPECIFIED" 685 } 686 } 687 ] 688 `, 689 RunE: func(cmd *cobra.Command, args []string) error { 690 clientCtx, err := client.GetClientTxContext(cmd) 691 if err != nil { 692 return err 693 } 694 695 from := clientCtx.GetFromAddress() 696 697 title, err := cmd.Flags().GetString(govcli.FlagTitle) 698 if err != nil { 699 return err 700 } 701 702 description, err := cmd.Flags().GetString(govcli.FlagDescription) 703 if err != nil { 704 return err 705 } 706 707 messages, err := parseMsgs(clientCtx.Codec, args[0]) 708 if err != nil { 709 return err 710 } 711 712 content := foundation.NewFoundationExecProposal(title, description, messages) 713 714 depositStr, err := cmd.Flags().GetString(govcli.FlagDeposit) 715 if err != nil { 716 return err 717 } 718 deposit, err := sdk.ParseCoinsNormalized(depositStr) 719 if err != nil { 720 return err 721 } 722 723 msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from) 724 if err != nil { 725 return err 726 } 727 if err := msg.ValidateBasic(); err != nil { 728 return err 729 } 730 731 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 732 }, 733 } 734 735 cmd.Flags().String(govcli.FlagTitle, "", "title of proposal") 736 cmd.Flags().String(govcli.FlagDescription, "", "description of proposal") 737 cmd.Flags().String(govcli.FlagDeposit, "", "deposit of proposal") 738 739 return cmd 740 }