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