package cli import ( "fmt" "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" "cosmossdk.io/core/address" "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" ) // Transaction flags for the x/distribution module var ( FlagCommission = "commission" FlagMaxMessagesPerTx = "max-msgs" ) const ( MaxMessagesPerTxDefault = 0 ) // NewTxCmd returns a root CLI command handler for all x/distribution transaction commands. func NewTxCmd(valAc, ac address.Codec) *cobra.Command { distTxCmd := &cobra.Command{ Use: types.ModuleName, Short: "Distribution transactions subcommands", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, } distTxCmd.AddCommand( NewWithdrawRewardsCmd(valAc, ac), NewWithdrawAllRewardsCmd(valAc, ac), NewSetWithdrawAddrCmd(ac), NewFundCommunityPoolCmd(ac), NewDepositValidatorRewardsPoolCmd(valAc, ac), NewWithdrawValidatorCommissionCmd(valAc, ac), ) 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 := min(i+chunkSize, totalMessages) msgChunk := msgs[i:sliceEnd] if err := genOrBroadcastFn(clientCtx, fs, msgChunk...); err != nil { return err } } return nil } // NewWithdrawRewardsCmd returns a CLI command handler for creating a MsgWithdrawDelegatorReward transaction. func NewWithdrawRewardsCmd(valCodec, ac address.Codec) *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, err := ac.BytesToString(clientCtx.GetFromAddress()) if err != nil { return err } _, err = valCodec.StringToBytes(args[0]) if err != nil { return err } msgs := []sdk.Msg{types.NewMsgWithdrawDelegatorReward(delAddr, args[0])} if commission, _ := cmd.Flags().GetBool(FlagCommission); commission { msgs = append(msgs, types.NewMsgWithdrawValidatorCommission(args[0])) } 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 } // NewWithdrawAllRewardsCmd returns a CLI command handler for creating a MsgWithdrawDelegatorReward transaction. func NewWithdrawAllRewardsCmd(valCodec, ac address.Codec) *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. Note that if you use this command with --%[2]s=%[3]s or --%[2]s=%[4]s, the %[5]s flag will automatically be set to 0. Example: $ %[1]s tx distribution withdraw-all-rewards --from mykey `, version.AppName, flags.FlagBroadcastMode, flags.BroadcastSync, flags.BroadcastAsync, FlagMaxMessagesPerTx, ), ), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } delAddr, err := ac.BytesToString(clientCtx.GetFromAddress()) if err != nil { return err } // 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}) if err != nil { return err } validators := delValsRes.Validators // build multi-message transaction msgs := make([]sdk.Msg, 0, len(validators)) for _, valAddr := range validators { _, err := valCodec.StringToBytes(valAddr) if err != nil { return err } msg := types.NewMsgWithdrawDelegatorReward(delAddr, valAddr) 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 } // NewSetWithdrawAddrCmd returns a CLI command handler for creating a MsgSetWithdrawAddress transaction. func NewSetWithdrawAddrCmd(ac address.Codec) *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 := ac.StringToBytes(args[0]) if err != nil { return err } msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd } // NewFundCommunityPoolCmd returns a CLI command handler for creating a MsgFundCommunityPool transaction. func NewFundCommunityPoolCmd(ac address.Codec) *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, err := ac.BytesToString(clientCtx.GetFromAddress()) if err != nil { return err } amount, err := sdk.ParseCoinsNormalized(args[0]) if err != nil { return err } msg := types.NewMsgFundCommunityPool(amount, depositorAddr) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd } // NewDepositValidatorRewardsPoolCmd returns a CLI command handler for creating // a MsgDepositValidatorRewardsPool transaction. func NewDepositValidatorRewardsPoolCmd(valCodec, ac address.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "fund-validator-rewards-pool [val_addr] [amount]", Args: cobra.ExactArgs(2), Short: "Fund the validator rewards pool with the specified amount", Example: fmt.Sprintf( "%s tx distribution fund-validator-rewards-pool cosmosvaloper1x20lytyf6zkcrv5edpkfkn8sz578qg5sqfyqnp 100uatom --from mykey", version.AppName, ), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } depositorAddr, err := ac.BytesToString(clientCtx.GetFromAddress()) if err != nil { return err } _, err = valCodec.StringToBytes(args[0]) if err != nil { return err } amount, err := sdk.ParseCoinsNormalized(args[1]) if err != nil { return err } msg := types.NewMsgDepositValidatorRewardsPool(depositorAddr, args[0], amount) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd } // NewWithdrawValidatorCommissionCmd returns a CLI command handler for creating a MsgWithdrawValidatorCommission transaction. func NewWithdrawValidatorCommissionCmd(valCodec, ac address.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "withdraw-validator-commission [validator-addr]", Short: "Withdraw commissions from a validator address (must be a validator operator)", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } _, err = ac.BytesToString(clientCtx.GetFromAddress()) if err != nil { return err } _, err = valCodec.StringToBytes(args[0]) if err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), types.NewMsgWithdrawValidatorCommission(args[0])) }, } flags.AddTxFlagsToCmd(cmd) return cmd }