github.com/Finschia/finschia-sdk@v0.48.1/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/Finschia/finschia-sdk/client" 11 "github.com/Finschia/finschia-sdk/client/flags" 12 "github.com/Finschia/finschia-sdk/client/tx" 13 sdk "github.com/Finschia/finschia-sdk/types" 14 "github.com/Finschia/finschia-sdk/version" 15 govutils "github.com/Finschia/finschia-sdk/x/gov/client/utils" 16 "github.com/Finschia/finschia-sdk/x/gov/types" 17 ) 18 19 // Proposal flags 20 const ( 21 FlagTitle = "title" 22 FlagDescription = "description" 23 FlagProposalType = "type" 24 FlagDeposit = "deposit" 25 flagVoter = "voter" 26 flagDepositor = "depositor" 27 flagStatus = "status" 28 FlagProposal = "proposal" 29 ) 30 31 type proposal struct { 32 Title string 33 Description string 34 Type string 35 Deposit string 36 } 37 38 // ProposalFlags defines the core required fields of a 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 "proposal" child commands. These commands are respective 51 // to proposal type handlers that are implemented in other modules but are mounted 52 // under the governance CLI (eg. parameter change proposals). 53 func NewTxCmd(propCmds []*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 cmdSubmitProp := NewCmdSubmitProposal() 63 for _, propCmd := range propCmds { 64 flags.AddTxFlagsToCmd(propCmd) 65 cmdSubmitProp.AddCommand(propCmd) 66 } 67 68 govTxCmd.AddCommand( 69 NewCmdDeposit(), 70 NewCmdVote(), 71 NewCmdWeightedVote(), 72 cmdSubmitProp, 73 ) 74 75 return govTxCmd 76 } 77 78 // NewCmdSubmitProposal implements submitting a proposal transaction command. 79 func NewCmdSubmitProposal() *cobra.Command { 80 cmd := &cobra.Command{ 81 Use: "submit-proposal", 82 Short: "Submit a proposal along with an initial deposit", 83 Long: strings.TrimSpace( 84 fmt.Sprintf(`Submit a proposal along with an initial deposit. 85 Proposal title, description, type and deposit can be given directly or through a proposal JSON file. 86 87 Example: 88 $ %s tx gov submit-proposal --proposal="path/to/proposal.json" --from mykey 89 90 Where proposal.json contains: 91 92 { 93 "title": "Test Proposal", 94 "description": "My awesome proposal", 95 "type": "Text", 96 "deposit": "10test" 97 } 98 99 Which is equivalent to: 100 101 $ %s tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey 102 `, 103 version.AppName, version.AppName, 104 ), 105 ), 106 RunE: func(cmd *cobra.Command, args []string) error { 107 clientCtx, err := client.GetClientTxContext(cmd) 108 if err != nil { 109 return err 110 } 111 112 proposal, err := parseSubmitProposalFlags(cmd.Flags()) 113 if err != nil { 114 return fmt.Errorf("failed to parse proposal: %w", err) 115 } 116 117 amount, err := sdk.ParseCoinsNormalized(proposal.Deposit) 118 if err != nil { 119 return err 120 } 121 122 content := types.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type) 123 124 msg, err := types.NewMsgSubmitProposal(content, amount, clientCtx.GetFromAddress()) 125 if err != nil { 126 return fmt.Errorf("invalid message: %w", err) 127 } 128 129 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 130 }, 131 } 132 133 cmd.Flags().String(FlagTitle, "", "The proposal title") 134 cmd.Flags().String(FlagDescription, "", "The proposal description") 135 cmd.Flags().String(FlagProposalType, "", "The proposal Type") 136 cmd.Flags().String(FlagDeposit, "", "The proposal deposit") 137 cmd.Flags().String(FlagProposal, "", "Proposal file path (if this path is given, other proposal flags are ignored)") 138 flags.AddTxFlagsToCmd(cmd) 139 140 return cmd 141 } 142 143 // NewCmdDeposit implements depositing tokens for an active proposal. 144 func NewCmdDeposit() *cobra.Command { 145 cmd := &cobra.Command{ 146 Use: "deposit [proposal-id] [deposit]", 147 Args: cobra.ExactArgs(2), 148 Short: "Deposit tokens for an active proposal", 149 Long: strings.TrimSpace( 150 fmt.Sprintf(`Submit a deposit for an active proposal. You can 151 find the proposal-id by running "%s query gov proposals". 152 153 Example: 154 $ %s tx gov deposit 1 10stake --from mykey 155 `, 156 version.AppName, version.AppName, 157 ), 158 ), 159 RunE: func(cmd *cobra.Command, args []string) error { 160 clientCtx, err := client.GetClientTxContext(cmd) 161 if err != nil { 162 return err 163 } 164 165 // validate that the proposal id is a uint 166 proposalID, err := strconv.ParseUint(args[0], 10, 64) 167 if err != nil { 168 return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0]) 169 } 170 171 // Get depositor address 172 from := clientCtx.GetFromAddress() 173 174 // Get amount of coins 175 amount, err := sdk.ParseCoinsNormalized(args[1]) 176 if err != nil { 177 return err 178 } 179 180 msg := types.NewMsgDeposit(from, proposalID, amount) 181 182 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 183 }, 184 } 185 186 flags.AddTxFlagsToCmd(cmd) 187 188 return cmd 189 } 190 191 // NewCmdVote implements creating a new vote command. 192 func NewCmdVote() *cobra.Command { 193 cmd := &cobra.Command{ 194 Use: "vote [proposal-id] [option]", 195 Args: cobra.ExactArgs(2), 196 Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", 197 Long: strings.TrimSpace( 198 fmt.Sprintf(`Submit a vote for an active proposal. You can 199 find the proposal-id by running "%s query gov proposals". 200 201 Example: 202 $ %s tx gov vote 1 yes --from mykey 203 `, 204 version.AppName, version.AppName, 205 ), 206 ), 207 RunE: func(cmd *cobra.Command, args []string) error { 208 clientCtx, err := client.GetClientTxContext(cmd) 209 if err != nil { 210 return err 211 } 212 // Get voting address 213 from := clientCtx.GetFromAddress() 214 215 // validate that the proposal id is a uint 216 proposalID, err := strconv.ParseUint(args[0], 10, 64) 217 if err != nil { 218 return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) 219 } 220 221 // Find out which vote option user chose 222 byteVoteOption, err := types.VoteOptionFromString(govutils.NormalizeVoteOption(args[1])) 223 if err != nil { 224 return err 225 } 226 227 // Build vote message and run basic validation 228 msg := types.NewMsgVote(from, proposalID, byteVoteOption) 229 230 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 231 }, 232 } 233 234 flags.AddTxFlagsToCmd(cmd) 235 236 return cmd 237 } 238 239 // NewCmdWeightedVote implements creating a new weighted vote command. 240 func NewCmdWeightedVote() *cobra.Command { 241 cmd := &cobra.Command{ 242 Use: "weighted-vote [proposal-id] [weighted-options]", 243 Args: cobra.ExactArgs(2), 244 Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", 245 Long: strings.TrimSpace( 246 fmt.Sprintf(`Submit a vote for an active proposal. You can 247 find the proposal-id by running "%s query gov proposals". 248 249 Example: 250 $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05 --from mykey 251 `, 252 version.AppName, version.AppName, 253 ), 254 ), 255 RunE: func(cmd *cobra.Command, args []string) error { 256 clientCtx, err := client.GetClientTxContext(cmd) 257 if err != nil { 258 return err 259 } 260 261 // Get voter address 262 from := clientCtx.GetFromAddress() 263 264 // validate that the proposal id is a uint 265 proposalID, err := strconv.ParseUint(args[0], 10, 64) 266 if err != nil { 267 return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) 268 } 269 270 // Figure out which vote options user chose 271 options, err := types.WeightedVoteOptionsFromString(govutils.NormalizeWeightedVoteOptions(args[1])) 272 if err != nil { 273 return err 274 } 275 276 // Build vote message and run basic validation 277 msg := types.NewMsgVoteWeighted(from, proposalID, options) 278 err = msg.ValidateBasic() 279 if err != nil { 280 return err 281 } 282 283 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 284 }, 285 } 286 287 flags.AddTxFlagsToCmd(cmd) 288 289 return cmd 290 }