github.com/Finschia/finschia-sdk@v0.48.1/x/staking/client/cli/tx.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/spf13/cobra"
     9  	flag "github.com/spf13/pflag"
    10  
    11  	"github.com/Finschia/finschia-sdk/client"
    12  	"github.com/Finschia/finschia-sdk/client/flags"
    13  	"github.com/Finschia/finschia-sdk/client/tx"
    14  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    15  	sdk "github.com/Finschia/finschia-sdk/types"
    16  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    17  	"github.com/Finschia/finschia-sdk/version"
    18  	"github.com/Finschia/finschia-sdk/x/staking/types"
    19  )
    20  
    21  // default values
    22  var (
    23  	DefaultTokens                  = sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction)
    24  	defaultAmount                  = DefaultTokens.String() + sdk.DefaultBondDenom
    25  	defaultCommissionRate          = "0.1"
    26  	defaultCommissionMaxRate       = "0.2"
    27  	defaultCommissionMaxChangeRate = "0.01"
    28  	defaultMinSelfDelegation       = "1"
    29  )
    30  
    31  // NewTxCmd returns a root CLI command handler for all x/staking transaction commands.
    32  func NewTxCmd() *cobra.Command {
    33  	stakingTxCmd := &cobra.Command{
    34  		Use:                        types.ModuleName,
    35  		Short:                      "Staking transaction subcommands",
    36  		DisableFlagParsing:         true,
    37  		SuggestionsMinimumDistance: 2,
    38  		RunE:                       client.ValidateCmd,
    39  	}
    40  
    41  	stakingTxCmd.AddCommand(
    42  		NewCreateValidatorCmd(),
    43  		NewEditValidatorCmd(),
    44  		NewDelegateCmd(),
    45  		NewRedelegateCmd(),
    46  		NewUnbondCmd(),
    47  	)
    48  
    49  	return stakingTxCmd
    50  }
    51  
    52  func NewCreateValidatorCmd() *cobra.Command {
    53  	cmd := &cobra.Command{
    54  		Use:   "create-validator",
    55  		Short: "create new validator initialized with a self-delegation to it",
    56  		RunE: func(cmd *cobra.Command, args []string) error {
    57  			clientCtx, err := client.GetClientTxContext(cmd)
    58  			if err != nil {
    59  				return err
    60  			}
    61  
    62  			txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).
    63  				WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever)
    64  			txf, msg, err := newBuildCreateValidatorMsg(clientCtx, txf, cmd.Flags())
    65  			if err != nil {
    66  				return err
    67  			}
    68  
    69  			return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg)
    70  		},
    71  	}
    72  
    73  	cmd.Flags().AddFlagSet(FlagSetPublicKey())
    74  	cmd.Flags().AddFlagSet(FlagSetAmount())
    75  	cmd.Flags().AddFlagSet(flagSetDescriptionCreate())
    76  	cmd.Flags().AddFlagSet(FlagSetCommissionCreate())
    77  	cmd.Flags().AddFlagSet(FlagSetMinSelfDelegation())
    78  
    79  	cmd.Flags().String(FlagIP, "", fmt.Sprintf("The node's public IP. It takes effect only when used in combination with --%s", flags.FlagGenerateOnly))
    80  	cmd.Flags().String(FlagNodeID, "", "The node's ID")
    81  	flags.AddTxFlagsToCmd(cmd)
    82  
    83  	_ = cmd.MarkFlagRequired(flags.FlagFrom)
    84  	_ = cmd.MarkFlagRequired(FlagAmount)
    85  	_ = cmd.MarkFlagRequired(FlagPubKey)
    86  	_ = cmd.MarkFlagRequired(FlagMoniker)
    87  
    88  	return cmd
    89  }
    90  
    91  func NewEditValidatorCmd() *cobra.Command {
    92  	cmd := &cobra.Command{
    93  		Use:   "edit-validator",
    94  		Short: "edit an existing validator account",
    95  		RunE: func(cmd *cobra.Command, args []string) error {
    96  			clientCtx, err := client.GetClientTxContext(cmd)
    97  			if err != nil {
    98  				return err
    99  			}
   100  			valAddr := clientCtx.GetFromAddress()
   101  			moniker, _ := cmd.Flags().GetString(FlagEditMoniker)
   102  			identity, _ := cmd.Flags().GetString(FlagIdentity)
   103  			website, _ := cmd.Flags().GetString(FlagWebsite)
   104  			security, _ := cmd.Flags().GetString(FlagSecurityContact)
   105  			details, _ := cmd.Flags().GetString(FlagDetails)
   106  			description := types.NewDescription(moniker, identity, website, security, details)
   107  
   108  			var newRate *sdk.Dec
   109  
   110  			commissionRate, _ := cmd.Flags().GetString(FlagCommissionRate)
   111  			if commissionRate != "" {
   112  				rate, err := sdk.NewDecFromStr(commissionRate)
   113  				if err != nil {
   114  					return fmt.Errorf("invalid new commission rate: %v", err)
   115  				}
   116  
   117  				newRate = &rate
   118  			}
   119  
   120  			var newMinSelfDelegation *sdk.Int
   121  
   122  			minSelfDelegationString, _ := cmd.Flags().GetString(FlagMinSelfDelegation)
   123  			if minSelfDelegationString != "" {
   124  				msb, ok := sdk.NewIntFromString(minSelfDelegationString)
   125  				if !ok {
   126  					return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer")
   127  				}
   128  
   129  				newMinSelfDelegation = &msb
   130  			}
   131  
   132  			msg := types.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate, newMinSelfDelegation)
   133  
   134  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   135  		},
   136  	}
   137  
   138  	cmd.Flags().AddFlagSet(flagSetDescriptionEdit())
   139  	cmd.Flags().AddFlagSet(flagSetCommissionUpdate())
   140  	cmd.Flags().AddFlagSet(FlagSetMinSelfDelegation())
   141  	flags.AddTxFlagsToCmd(cmd)
   142  
   143  	return cmd
   144  }
   145  
   146  func NewDelegateCmd() *cobra.Command {
   147  	bech32PrefixValAddr := sdk.GetConfig().GetBech32ValidatorAddrPrefix()
   148  
   149  	cmd := &cobra.Command{
   150  		Use:   "delegate [validator-addr] [amount]",
   151  		Args:  cobra.ExactArgs(2),
   152  		Short: "Delegate liquid tokens to a validator",
   153  		Long: strings.TrimSpace(
   154  			fmt.Sprintf(`Delegate an amount of liquid coins to a validator from your wallet.
   155  
   156  Example:
   157  $ %s tx staking delegate %s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 1000stake --from mykey
   158  `,
   159  				version.AppName, bech32PrefixValAddr,
   160  			),
   161  		),
   162  		RunE: func(cmd *cobra.Command, args []string) error {
   163  			clientCtx, err := client.GetClientTxContext(cmd)
   164  			if err != nil {
   165  				return err
   166  			}
   167  			amount, err := sdk.ParseCoinNormalized(args[1])
   168  			if err != nil {
   169  				return err
   170  			}
   171  
   172  			delAddr := clientCtx.GetFromAddress()
   173  			valAddr, err := sdk.ValAddressFromBech32(args[0])
   174  			if err != nil {
   175  				return err
   176  			}
   177  
   178  			msg := types.NewMsgDelegate(delAddr, valAddr, amount)
   179  
   180  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   181  		},
   182  	}
   183  
   184  	flags.AddTxFlagsToCmd(cmd)
   185  
   186  	return cmd
   187  }
   188  
   189  func NewRedelegateCmd() *cobra.Command {
   190  	bech32PrefixValAddr := sdk.GetConfig().GetBech32ValidatorAddrPrefix()
   191  
   192  	cmd := &cobra.Command{
   193  		Use:   "redelegate [src-validator-addr] [dst-validator-addr] [amount]",
   194  		Short: "Redelegate illiquid tokens from one validator to another",
   195  		Args:  cobra.ExactArgs(3),
   196  		Long: strings.TrimSpace(
   197  			fmt.Sprintf(`Redelegate an amount of illiquid staking tokens from one validator to another.
   198  
   199  Example:
   200  $ %s tx staking redelegate %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj %s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 100stake --from mykey
   201  `,
   202  				version.AppName, bech32PrefixValAddr, bech32PrefixValAddr,
   203  			),
   204  		),
   205  		RunE: func(cmd *cobra.Command, args []string) error {
   206  			clientCtx, err := client.GetClientTxContext(cmd)
   207  			if err != nil {
   208  				return err
   209  			}
   210  			delAddr := clientCtx.GetFromAddress()
   211  			valSrcAddr, err := sdk.ValAddressFromBech32(args[0])
   212  			if err != nil {
   213  				return err
   214  			}
   215  
   216  			valDstAddr, err := sdk.ValAddressFromBech32(args[1])
   217  			if err != nil {
   218  				return err
   219  			}
   220  
   221  			amount, err := sdk.ParseCoinNormalized(args[2])
   222  			if err != nil {
   223  				return err
   224  			}
   225  
   226  			msg := types.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, amount)
   227  
   228  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   229  		},
   230  	}
   231  
   232  	flags.AddTxFlagsToCmd(cmd)
   233  
   234  	return cmd
   235  }
   236  
   237  func NewUnbondCmd() *cobra.Command {
   238  	bech32PrefixValAddr := sdk.GetConfig().GetBech32ValidatorAddrPrefix()
   239  
   240  	cmd := &cobra.Command{
   241  		Use:   "unbond [validator-addr] [amount]",
   242  		Short: "Unbond shares from a validator",
   243  		Args:  cobra.ExactArgs(2),
   244  		Long: strings.TrimSpace(
   245  			fmt.Sprintf(`Unbond an amount of bonded shares from a validator.
   246  
   247  Example:
   248  $ %s tx staking unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake --from mykey
   249  `,
   250  				version.AppName, bech32PrefixValAddr,
   251  			),
   252  		),
   253  		RunE: func(cmd *cobra.Command, args []string) error {
   254  			clientCtx, err := client.GetClientTxContext(cmd)
   255  			if err != nil {
   256  				return err
   257  			}
   258  			delAddr := clientCtx.GetFromAddress()
   259  			valAddr, err := sdk.ValAddressFromBech32(args[0])
   260  			if err != nil {
   261  				return err
   262  			}
   263  
   264  			amount, err := sdk.ParseCoinNormalized(args[1])
   265  			if err != nil {
   266  				return err
   267  			}
   268  
   269  			msg := types.NewMsgUndelegate(delAddr, valAddr, amount)
   270  
   271  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   272  		},
   273  	}
   274  
   275  	flags.AddTxFlagsToCmd(cmd)
   276  
   277  	return cmd
   278  }
   279  
   280  func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet) (tx.Factory, *types.MsgCreateValidator, error) {
   281  	fAmount, _ := fs.GetString(FlagAmount)
   282  	amount, err := sdk.ParseCoinNormalized(fAmount)
   283  	if err != nil {
   284  		return txf, nil, err
   285  	}
   286  
   287  	valAddr := clientCtx.GetFromAddress()
   288  	pkStr, err := fs.GetString(FlagPubKey)
   289  	if err != nil {
   290  		return txf, nil, err
   291  	}
   292  
   293  	var pk cryptotypes.PubKey
   294  	if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(pkStr), &pk); err != nil {
   295  		return txf, nil, err
   296  	}
   297  
   298  	moniker, _ := fs.GetString(FlagMoniker)
   299  	identity, _ := fs.GetString(FlagIdentity)
   300  	website, _ := fs.GetString(FlagWebsite)
   301  	security, _ := fs.GetString(FlagSecurityContact)
   302  	details, _ := fs.GetString(FlagDetails)
   303  	description := types.NewDescription(
   304  		moniker,
   305  		identity,
   306  		website,
   307  		security,
   308  		details,
   309  	)
   310  
   311  	// get the initial validator commission parameters
   312  	rateStr, _ := fs.GetString(FlagCommissionRate)
   313  	maxRateStr, _ := fs.GetString(FlagCommissionMaxRate)
   314  	maxChangeRateStr, _ := fs.GetString(FlagCommissionMaxChangeRate)
   315  
   316  	commissionRates, err := buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr)
   317  	if err != nil {
   318  		return txf, nil, err
   319  	}
   320  
   321  	// get the initial validator min self delegation
   322  	msbStr, _ := fs.GetString(FlagMinSelfDelegation)
   323  
   324  	minSelfDelegation, ok := sdk.NewIntFromString(msbStr)
   325  	if !ok {
   326  		return txf, nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer")
   327  	}
   328  
   329  	msg, err := types.NewMsgCreateValidator(
   330  		sdk.ValAddress(valAddr), pk, amount, description, commissionRates, minSelfDelegation,
   331  	)
   332  	if err != nil {
   333  		return txf, nil, err
   334  	}
   335  	if err := msg.ValidateBasic(); err != nil {
   336  		return txf, nil, err
   337  	}
   338  
   339  	genOnly, _ := fs.GetBool(flags.FlagGenerateOnly)
   340  	if genOnly {
   341  		ip, _ := fs.GetString(FlagIP)
   342  		nodeID, _ := fs.GetString(FlagNodeID)
   343  
   344  		if nodeID != "" && ip != "" {
   345  			txf = txf.WithMemo(fmt.Sprintf("%s@%s:26656", nodeID, ip))
   346  		}
   347  	}
   348  
   349  	return txf, msg, nil
   350  }
   351  
   352  // Return the flagset, particular flags, and a description of defaults
   353  // this is anticipated to be used with the gen-tx
   354  func CreateValidatorMsgFlagSet(ipDefault string) (fs *flag.FlagSet, defaultsDesc string) {
   355  	fsCreateValidator := flag.NewFlagSet("", flag.ContinueOnError)
   356  	fsCreateValidator.String(FlagIP, ipDefault, "The node's public IP")
   357  	fsCreateValidator.String(FlagNodeID, "", "The node's NodeID")
   358  	fsCreateValidator.String(FlagMoniker, "", "The validator's (optional) moniker")
   359  	fsCreateValidator.String(FlagWebsite, "", "The validator's (optional) website")
   360  	fsCreateValidator.String(FlagSecurityContact, "", "The validator's (optional) security contact email")
   361  	fsCreateValidator.String(FlagDetails, "", "The validator's (optional) details")
   362  	fsCreateValidator.String(FlagIdentity, "", "The (optional) identity signature (ex. UPort or Keybase)")
   363  	fsCreateValidator.AddFlagSet(FlagSetCommissionCreate())
   364  	fsCreateValidator.AddFlagSet(FlagSetMinSelfDelegation())
   365  	fsCreateValidator.AddFlagSet(FlagSetAmount())
   366  	fsCreateValidator.AddFlagSet(FlagSetPublicKey())
   367  
   368  	defaultsDesc = fmt.Sprintf(`
   369  	delegation amount:           %s
   370  	commission rate:             %s
   371  	commission max rate:         %s
   372  	commission max change rate:  %s
   373  	minimum self delegation:     %s
   374  `, defaultAmount, defaultCommissionRate,
   375  		defaultCommissionMaxRate, defaultCommissionMaxChangeRate,
   376  		defaultMinSelfDelegation)
   377  
   378  	return fsCreateValidator, defaultsDesc
   379  }
   380  
   381  type TxCreateValidatorConfig struct {
   382  	ChainID string
   383  	NodeID  string
   384  	Moniker string
   385  
   386  	Amount string
   387  
   388  	CommissionRate          string
   389  	CommissionMaxRate       string
   390  	CommissionMaxChangeRate string
   391  	MinSelfDelegation       string
   392  
   393  	PubKey cryptotypes.PubKey
   394  
   395  	IP              string
   396  	Website         string
   397  	SecurityContact string
   398  	Details         string
   399  	Identity        string
   400  }
   401  
   402  func PrepareConfigForTxCreateValidator(flagSet *flag.FlagSet, moniker, nodeID, chainID string, valPubKey cryptotypes.PubKey) (TxCreateValidatorConfig, error) {
   403  	c := TxCreateValidatorConfig{}
   404  
   405  	ip, err := flagSet.GetString(FlagIP)
   406  	if err != nil {
   407  		return c, err
   408  	}
   409  	if ip == "" {
   410  		_, _ = fmt.Fprintf(os.Stderr, "couldn't retrieve an external IP; "+
   411  			"the tx's memo field will be unset")
   412  	}
   413  	c.IP = ip
   414  
   415  	website, err := flagSet.GetString(FlagWebsite)
   416  	if err != nil {
   417  		return c, err
   418  	}
   419  	c.Website = website
   420  
   421  	securityContact, err := flagSet.GetString(FlagSecurityContact)
   422  	if err != nil {
   423  		return c, err
   424  	}
   425  	c.SecurityContact = securityContact
   426  
   427  	details, err := flagSet.GetString(FlagDetails)
   428  	if err != nil {
   429  		return c, err
   430  	}
   431  	c.SecurityContact = details
   432  
   433  	identity, err := flagSet.GetString(FlagIdentity)
   434  	if err != nil {
   435  		return c, err
   436  	}
   437  	c.Identity = identity
   438  
   439  	c.Amount, err = flagSet.GetString(FlagAmount)
   440  	if err != nil {
   441  		return c, err
   442  	}
   443  
   444  	c.CommissionRate, err = flagSet.GetString(FlagCommissionRate)
   445  	if err != nil {
   446  		return c, err
   447  	}
   448  
   449  	c.CommissionMaxRate, err = flagSet.GetString(FlagCommissionMaxRate)
   450  	if err != nil {
   451  		return c, err
   452  	}
   453  
   454  	c.CommissionMaxChangeRate, err = flagSet.GetString(FlagCommissionMaxChangeRate)
   455  	if err != nil {
   456  		return c, err
   457  	}
   458  
   459  	c.MinSelfDelegation, err = flagSet.GetString(FlagMinSelfDelegation)
   460  	if err != nil {
   461  		return c, err
   462  	}
   463  
   464  	c.NodeID = nodeID
   465  	c.PubKey = valPubKey
   466  	c.Website = website
   467  	c.SecurityContact = securityContact
   468  	c.Details = details
   469  	c.Identity = identity
   470  	c.ChainID = chainID
   471  	c.Moniker = moniker
   472  
   473  	if c.Amount == "" {
   474  		c.Amount = defaultAmount
   475  	}
   476  
   477  	if c.CommissionRate == "" {
   478  		c.CommissionRate = defaultCommissionRate
   479  	}
   480  
   481  	if c.CommissionMaxRate == "" {
   482  		c.CommissionMaxRate = defaultCommissionMaxRate
   483  	}
   484  
   485  	if c.CommissionMaxChangeRate == "" {
   486  		c.CommissionMaxChangeRate = defaultCommissionMaxChangeRate
   487  	}
   488  
   489  	if c.MinSelfDelegation == "" {
   490  		c.MinSelfDelegation = defaultMinSelfDelegation
   491  	}
   492  
   493  	return c, nil
   494  }
   495  
   496  // BuildCreateValidatorMsg makes a new MsgCreateValidator.
   497  func BuildCreateValidatorMsg(clientCtx client.Context, config TxCreateValidatorConfig, txBldr tx.Factory, generateOnly bool) (tx.Factory, sdk.Msg, error) {
   498  	amounstStr := config.Amount
   499  	amount, err := sdk.ParseCoinNormalized(amounstStr)
   500  	if err != nil {
   501  		return txBldr, nil, err
   502  	}
   503  
   504  	valAddr := clientCtx.GetFromAddress()
   505  	description := types.NewDescription(
   506  		config.Moniker,
   507  		config.Identity,
   508  		config.Website,
   509  		config.SecurityContact,
   510  		config.Details,
   511  	)
   512  
   513  	// get the initial validator commission parameters
   514  	rateStr := config.CommissionRate
   515  	maxRateStr := config.CommissionMaxRate
   516  	maxChangeRateStr := config.CommissionMaxChangeRate
   517  	commissionRates, err := buildCommissionRates(rateStr, maxRateStr, maxChangeRateStr)
   518  	if err != nil {
   519  		return txBldr, nil, err
   520  	}
   521  
   522  	// get the initial validator min self delegation
   523  	msbStr := config.MinSelfDelegation
   524  	minSelfDelegation, ok := sdk.NewIntFromString(msbStr)
   525  
   526  	if !ok {
   527  		return txBldr, nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "minimum self delegation must be a positive integer")
   528  	}
   529  
   530  	msg, err := types.NewMsgCreateValidator(
   531  		sdk.ValAddress(valAddr), config.PubKey, amount, description, commissionRates, minSelfDelegation,
   532  	)
   533  	if err != nil {
   534  		return txBldr, msg, err
   535  	}
   536  	if generateOnly {
   537  		ip := config.IP
   538  		nodeID := config.NodeID
   539  
   540  		if nodeID != "" && ip != "" {
   541  			txBldr = txBldr.WithMemo(fmt.Sprintf("%s@%s:26656", nodeID, ip))
   542  		}
   543  	}
   544  
   545  	return txBldr, msg, nil
   546  }