// nolint package cli import ( "bufio" "fmt" "strings" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" authclient "github.com/cosmos/cosmos-sdk/x/auth/client" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/distribution/client/common" "github.com/cosmos/cosmos-sdk/x/distribution/types" ) var ( flagOnlyFromValidator = "only-from-validator" flagIsValidator = "is-validator" flagCommission = "commission" flagMaxMessagesPerTx = "max-msgs" ) const ( MaxMessagesPerTxDefault = 5 ) // GetTxCmd returns the transaction commands for this module func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { distTxCmd := &cobra.Command{ Use: types.ModuleName, Short: "Distribution transactions subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } distTxCmd.AddCommand(flags.PostCommands( GetCmdWithdrawRewards(cdc), GetCmdSetWithdrawAddr(cdc), GetCmdWithdrawAllRewards(cdc, storeKey), GetCmdFundCommunityPool(cdc), )...) return distTxCmd } type generateOrBroadcastFunc func(context.CLIContext, auth.TxBuilder, []sdk.Msg) error func splitAndApply( generateOrBroadcast generateOrBroadcastFunc, cliCtx context.CLIContext, txBldr auth.TxBuilder, msgs []sdk.Msg, chunkSize int, ) error { if chunkSize == 0 { return generateOrBroadcast(cliCtx, txBldr, msgs) } // split messages into slices of length chunkSize totalMessages := len(msgs) for i := 0; i < len(msgs); i += chunkSize { sliceEnd := i + chunkSize if sliceEnd > totalMessages { sliceEnd = totalMessages } msgChunk := msgs[i:sliceEnd] if err := generateOrBroadcast(cliCtx, txBldr, msgChunk); err != nil { return err } } return nil } // command to withdraw rewards func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "withdraw-rewards [validator-addr]", Short: "Withdraw rewards from a given delegation address, and optionally withdraw validator commission if the delegation address given is a validator operator", Long: strings.TrimSpace( fmt.Sprintf(`Withdraw rewards from a given delegation address, and optionally withdraw validator commission if the delegation address given is a validator operator. Example: $ %s tx distribution withdraw-rewards cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj --from mykey $ %s tx distribution withdraw-rewards cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj --from mykey --commission `, version.ClientName, version.ClientName, ), ), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc)) cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc) delAddr := cliCtx.GetFromAddress() valAddr, err := sdk.ValAddressFromBech32(args[0]) if err != nil { return err } msgs := []sdk.Msg{types.NewMsgWithdrawDelegatorReward(delAddr, valAddr)} if viper.GetBool(flagCommission) { msgs = append(msgs, types.NewMsgWithdrawValidatorCommission(valAddr)) } return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, msgs) }, } cmd.Flags().Bool(flagCommission, false, "also withdraw validator's commission") return cmd } // command to withdraw all rewards func GetCmdWithdrawAllRewards(cdc *codec.Codec, queryRoute string) *cobra.Command { cmd := &cobra.Command{ Use: "withdraw-all-rewards", Short: "withdraw all delegations rewards for a delegator", Long: strings.TrimSpace( fmt.Sprintf(`Withdraw all rewards for a single delegator. Example: $ %s tx distribution withdraw-all-rewards --from mykey `, version.ClientName, ), ), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc)) cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc) delAddr := cliCtx.GetFromAddress() // The transaction cannot be generated offline since it requires a query // to get all the validators. if cliCtx.GenerateOnly { return fmt.Errorf("command disabled with the provided flag: %s", flags.FlagGenerateOnly) } msgs, err := common.WithdrawAllDelegatorRewards(cliCtx, queryRoute, delAddr) if err != nil { return err } chunkSize := viper.GetInt(flagMaxMessagesPerTx) return splitAndApply(authclient.GenerateOrBroadcastMsgs, cliCtx, txBldr, msgs, chunkSize) }, } cmd.Flags().Int(flagMaxMessagesPerTx, MaxMessagesPerTxDefault, "Limit the number of messages per tx (0 for unlimited)") return cmd } // command to replace a delegator's withdrawal address func GetCmdSetWithdrawAddr(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "set-withdraw-addr [withdraw-addr]", Short: "change the default withdraw address for rewards associated with an address", Long: strings.TrimSpace( fmt.Sprintf(`Set the withdraw address for rewards associated with a delegator address. Example: $ %s tx distribution set-withdraw-addr cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p --from mykey `, version.ClientName, ), ), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc)) cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc) delAddr := cliCtx.GetFromAddress() withdrawAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } // GetCmdSubmitProposal implements the command to submit a community-pool-spend proposal func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "community-pool-spend [proposal-file]", Args: cobra.ExactArgs(1), Short: "Submit a community pool spend proposal", Long: strings.TrimSpace( fmt.Sprintf(`Submit a community pool spend proposal along with an initial deposit. The proposal details must be supplied via a JSON file. Example: $ %s tx gov submit-proposal community-pool-spend --from= Where proposal.json contains: { "title": "Community Pool Spend", "description": "Pay me some Atoms!", "recipient": "cosmos1s5afhd6gxevu37mkqcvvsj8qeylhn0rz46zdlq", "amount": "1000stake", "deposit": "1000stake" } `, version.ClientName, ), ), RunE: func(cmd *cobra.Command, args []string) error { inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc)) cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc) proposal, err := ParseCommunityPoolSpendProposalJSON(cdc, args[0]) if err != nil { return err } from := cliCtx.GetFromAddress() amount, err := sdk.ParseCoins(proposal.Amount) if err != nil { return err } content := types.NewCommunityPoolSpendProposal(proposal.Title, proposal.Description, proposal.Recipient, amount) deposit, err := sdk.ParseCoins(proposal.Deposit) if err != nil { return err } msg := gov.NewMsgSubmitProposal(content, deposit, from) if err := msg.ValidateBasic(); err != nil { return err } return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } return cmd } // GetCmdFundCommunityPool returns a command implementation that supports directly // funding the community pool. func GetCmdFundCommunityPool(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "fund-community-pool [amount]", Args: cobra.ExactArgs(1), Short: "Funds the community pool with the specified amount", Long: strings.TrimSpace( fmt.Sprintf(`Funds the community pool with the specified amount Example: $ %s tx distribution fund-community-pool 100uatom --from mykey `, version.ClientName, ), ), RunE: func(cmd *cobra.Command, args []string) error { inBuf := bufio.NewReader(cmd.InOrStdin()) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc)) cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc) depositorAddr := cliCtx.GetFromAddress() amount, err := sdk.ParseCoins(args[0]) if err != nil { return err } msg := types.NewMsgFundCommunityPool(amount, depositorAddr) if err := msg.ValidateBasic(); err != nil { return err } return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } }