package cli import ( "fmt" "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/distribution/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) // Transaction flags for the x/distribution module var ( FlagCommission = "commission" FlagMaxMessagesPerTx = "max-msgs" ) const ( MaxMessagesPerTxDefault = 5 ) // NewTxCmd returns a root CLI command handler for all x/distribution transaction commands. func NewTxCmd() *cobra.Command { distTxCmd := &cobra.Command{ Use: types.ModuleName, Short: "Distribution transactions subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } distTxCmd.AddCommand( NewWithdrawRewardsCmd(), NewWithdrawAllRewardsCmd(), NewSetWithdrawAddrCmd(), NewFundCommunityPoolCmd(), ) return distTxCmd } type newGenerateOrBroadcastFunc func(client.Context, *pflag.FlagSet, ...sdk.Msg) error func newSplitAndApply( genOrBroadcastFn newGenerateOrBroadcastFunc, clientCtx client.Context, fs *pflag.FlagSet, msgs []sdk.Msg, chunkSize int, ) error { if chunkSize == 0 { return genOrBroadcastFn(clientCtx, fs, 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 := genOrBroadcastFn(clientCtx, fs, msgChunk...); err != nil { return err } } return nil } func NewWithdrawRewardsCmd() *cobra.Command { bech32PrefixValAddr := sdk.GetConfig().GetBech32ValidatorAddrPrefix() 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 %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj --from mykey $ %s tx distribution withdraw-rewards %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj --from mykey --commission `, version.AppName, bech32PrefixValAddr, version.AppName, bech32PrefixValAddr, ), ), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } delAddr := clientCtx.GetFromAddress() valAddr, err := sdk.ValAddressFromBech32(args[0]) if err != nil { return err } msgs := []sdk.Msg{types.NewMsgWithdrawDelegatorReward(delAddr, valAddr)} if commission, _ := cmd.Flags().GetBool(FlagCommission); commission { msgs = append(msgs, types.NewMsgWithdrawValidatorCommission(valAddr)) } for _, msg := range msgs { if err := msg.ValidateBasic(); err != nil { return err } } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msgs...) }, } cmd.Flags().Bool(FlagCommission, false, "Withdraw the validator's commission in addition to the rewards") flags.AddTxFlagsToCmd(cmd) return cmd } func NewWithdrawAllRewardsCmd() *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.AppName, ), ), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } delAddr := clientCtx.GetFromAddress() // The transaction cannot be generated offline since it requires a query // to get all the validators. if clientCtx.Offline { return fmt.Errorf("cannot generate tx in offline mode") } queryClient := types.NewQueryClient(clientCtx) delValsRes, err := queryClient.DelegatorValidators(cmd.Context(), &types.QueryDelegatorValidatorsRequest{DelegatorAddress: delAddr.String()}) if err != nil { return err } validators := delValsRes.Validators // build multi-message transaction msgs := make([]sdk.Msg, 0, len(validators)) for _, valAddr := range validators { val, err := sdk.ValAddressFromBech32(valAddr) if err != nil { return err } msg := types.NewMsgWithdrawDelegatorReward(delAddr, val) if err := msg.ValidateBasic(); err != nil { return err } msgs = append(msgs, msg) } chunkSize, _ := cmd.Flags().GetInt(FlagMaxMessagesPerTx) return newSplitAndApply(tx.GenerateOrBroadcastTxCLI, clientCtx, cmd.Flags(), msgs, chunkSize) }, } cmd.Flags().Int(FlagMaxMessagesPerTx, MaxMessagesPerTxDefault, "Limit the number of messages per tx (0 for unlimited)") flags.AddTxFlagsToCmd(cmd) return cmd } func NewSetWithdrawAddrCmd() *cobra.Command { bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() cmd := &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 %s1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p --from mykey `, version.AppName, bech32PrefixAccAddr, ), ), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } delAddr := clientCtx.GetFromAddress() withdrawAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) if err := msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd } func NewFundCommunityPoolCmd() *cobra.Command { cmd := &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.AppName, ), ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } depositorAddr := clientCtx.GetFromAddress() amount, err := sdk.ParseCoinsNormalized(args[0]) if err != nil { return err } msg := types.NewMsgFundCommunityPool(amount, depositorAddr) if err := msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd } // GetCmdSubmitProposal implements the command to submit a community-pool-spend proposal func GetCmdSubmitProposal() *cobra.Command { bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() 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": "%s1s5afhd6gxevu37mkqcvvsj8qeylhn0rz46zdlq", "amount": "1000stake", "deposit": "1000stake" } `, version.AppName, bech32PrefixAccAddr, ), ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } proposal, err := ParseCommunityPoolSpendProposalWithDeposit(clientCtx.JSONMarshaler, args[0]) if err != nil { return err } amount, err := sdk.ParseCoinsNormalized(proposal.Amount) if err != nil { return err } deposit, err := sdk.ParseCoinsNormalized(proposal.Deposit) if err != nil { return err } from := clientCtx.GetFromAddress() recpAddr, err := sdk.AccAddressFromBech32(proposal.Recipient) if err != nil { return err } content := types.NewCommunityPoolSpendProposal(proposal.Title, proposal.Description, recpAddr, amount) msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from) if err != nil { return err } if err := msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } return cmd }