github.com/cosmos/cosmos-sdk@v0.50.10/x/authz/client/cli/tx.go (about)

     1  package cli
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/spf13/cobra"
    10  
    11  	"cosmossdk.io/core/address"
    12  
    13  	"github.com/cosmos/cosmos-sdk/client"
    14  	"github.com/cosmos/cosmos-sdk/client/flags"
    15  	"github.com/cosmos/cosmos-sdk/client/tx"
    16  	sdk "github.com/cosmos/cosmos-sdk/types"
    17  	"github.com/cosmos/cosmos-sdk/version"
    18  	authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
    19  	"github.com/cosmos/cosmos-sdk/x/authz"
    20  	bank "github.com/cosmos/cosmos-sdk/x/bank/types"
    21  	staking "github.com/cosmos/cosmos-sdk/x/staking/types"
    22  )
    23  
    24  // Flag names and values
    25  const (
    26  	FlagSpendLimit        = "spend-limit"
    27  	FlagMsgType           = "msg-type"
    28  	FlagExpiration        = "expiration"
    29  	FlagAllowedValidators = "allowed-validators"
    30  	FlagDenyValidators    = "deny-validators"
    31  	FlagAllowList         = "allow-list"
    32  	delegate              = "delegate"
    33  	redelegate            = "redelegate"
    34  	unbond                = "unbond"
    35  )
    36  
    37  // GetTxCmd returns the transaction commands for this module
    38  func GetTxCmd(ac address.Codec) *cobra.Command {
    39  	AuthorizationTxCmd := &cobra.Command{
    40  		Use:                        authz.ModuleName,
    41  		Short:                      "Authorization transactions subcommands",
    42  		Long:                       "Authorize and revoke access to execute transactions on behalf of your address",
    43  		DisableFlagParsing:         true,
    44  		SuggestionsMinimumDistance: 2,
    45  		RunE:                       client.ValidateCmd,
    46  	}
    47  
    48  	AuthorizationTxCmd.AddCommand(
    49  		NewCmdGrantAuthorization(ac),
    50  		NewCmdRevokeAuthorization(ac),
    51  		NewCmdExecAuthorization(),
    52  	)
    53  
    54  	return AuthorizationTxCmd
    55  }
    56  
    57  // NewCmdGrantAuthorization returns a CLI command handler for creating a MsgGrant transaction.
    58  func NewCmdGrantAuthorization(ac address.Codec) *cobra.Command {
    59  	cmd := &cobra.Command{
    60  		Use:   "grant <grantee> <authorization_type=\"send\"|\"generic\"|\"delegate\"|\"unbond\"|\"redelegate\"> --from <granter>",
    61  		Short: "Grant authorization to an address",
    62  		Long: strings.TrimSpace(
    63  			fmt.Sprintf(`create a new grant authorization to an address to execute a transaction on your behalf:
    64  
    65  Examples:
    66   $ %s tx %s grant cosmos1skjw.. send --spend-limit=1000stake --from=cosmos1skl..
    67   $ %s tx %s grant cosmos1skjw.. generic --msg-type=/cosmos.gov.v1.MsgVote --from=cosmos1sk..
    68  	`, version.AppName, authz.ModuleName, version.AppName, authz.ModuleName),
    69  		),
    70  		Args: cobra.ExactArgs(2),
    71  		RunE: func(cmd *cobra.Command, args []string) error {
    72  			clientCtx, err := client.GetClientTxContext(cmd)
    73  			if err != nil {
    74  				return err
    75  			}
    76  
    77  			if strings.EqualFold(args[0], clientCtx.GetFromAddress().String()) {
    78  				return errors.New("grantee and granter should be different")
    79  			}
    80  
    81  			grantee, err := ac.StringToBytes(args[0])
    82  			if err != nil {
    83  				return err
    84  			}
    85  
    86  			var authorization authz.Authorization
    87  			switch args[1] {
    88  			case "send":
    89  				limit, err := cmd.Flags().GetString(FlagSpendLimit)
    90  				if err != nil {
    91  					return err
    92  				}
    93  
    94  				spendLimit, err := sdk.ParseCoinsNormalized(limit)
    95  				if err != nil {
    96  					return err
    97  				}
    98  
    99  				if !spendLimit.IsAllPositive() {
   100  					return fmt.Errorf("spend-limit should be greater than zero")
   101  				}
   102  
   103  				allowList, err := cmd.Flags().GetStringSlice(FlagAllowList)
   104  				if err != nil {
   105  					return err
   106  				}
   107  
   108  				// check for duplicates
   109  				for i := 0; i < len(allowList); i++ {
   110  					for j := i + 1; j < len(allowList); j++ {
   111  						if allowList[i] == allowList[j] {
   112  							return fmt.Errorf("duplicate address %s in allow-list", allowList[i])
   113  						}
   114  					}
   115  				}
   116  
   117  				allowed, err := bech32toAccAddresses(allowList, ac)
   118  				if err != nil {
   119  					return err
   120  				}
   121  
   122  				authorization = bank.NewSendAuthorization(spendLimit, allowed)
   123  
   124  			case "generic":
   125  				msgType, err := cmd.Flags().GetString(FlagMsgType)
   126  				if err != nil {
   127  					return err
   128  				}
   129  
   130  				authorization = authz.NewGenericAuthorization(msgType)
   131  			case delegate, unbond, redelegate:
   132  				limit, err := cmd.Flags().GetString(FlagSpendLimit)
   133  				if err != nil {
   134  					return err
   135  				}
   136  
   137  				allowValidators, err := cmd.Flags().GetStringSlice(FlagAllowedValidators)
   138  				if err != nil {
   139  					return err
   140  				}
   141  
   142  				denyValidators, err := cmd.Flags().GetStringSlice(FlagDenyValidators)
   143  				if err != nil {
   144  					return err
   145  				}
   146  
   147  				var delegateLimit *sdk.Coin
   148  				if limit != "" {
   149  					spendLimit, err := sdk.ParseCoinNormalized(limit)
   150  					if err != nil {
   151  						return err
   152  					}
   153  					queryClient := staking.NewQueryClient(clientCtx)
   154  
   155  					res, err := queryClient.Params(cmd.Context(), &staking.QueryParamsRequest{})
   156  					if err != nil {
   157  						return err
   158  					}
   159  
   160  					if spendLimit.Denom != res.Params.BondDenom {
   161  						return fmt.Errorf("invalid denom %s; coin denom should match the current bond denom %s", spendLimit.Denom, res.Params.BondDenom)
   162  					}
   163  
   164  					if !spendLimit.IsPositive() {
   165  						return fmt.Errorf("spend-limit should be greater than zero")
   166  					}
   167  					delegateLimit = &spendLimit
   168  				}
   169  
   170  				allowed, err := bech32toValAddresses(allowValidators)
   171  				if err != nil {
   172  					return err
   173  				}
   174  
   175  				denied, err := bech32toValAddresses(denyValidators)
   176  				if err != nil {
   177  					return err
   178  				}
   179  
   180  				switch args[1] {
   181  				case delegate:
   182  					authorization, err = staking.NewStakeAuthorization(allowed, denied, staking.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, delegateLimit)
   183  				case unbond:
   184  					authorization, err = staking.NewStakeAuthorization(allowed, denied, staking.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, delegateLimit)
   185  				default:
   186  					authorization, err = staking.NewStakeAuthorization(allowed, denied, staking.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, delegateLimit)
   187  				}
   188  				if err != nil {
   189  					return err
   190  				}
   191  
   192  			default:
   193  				return fmt.Errorf("invalid authorization type, %s", args[1])
   194  			}
   195  
   196  			expire, err := getExpireTime(cmd)
   197  			if err != nil {
   198  				return err
   199  			}
   200  
   201  			msg, err := authz.NewMsgGrant(clientCtx.GetFromAddress(), grantee, authorization, expire)
   202  			if err != nil {
   203  				return err
   204  			}
   205  
   206  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
   207  		},
   208  	}
   209  	flags.AddTxFlagsToCmd(cmd)
   210  	cmd.Flags().String(FlagMsgType, "", "The Msg method name for which we are creating a GenericAuthorization")
   211  	cmd.Flags().String(FlagSpendLimit, "", "SpendLimit for Send Authorization, an array of Coins allowed spend")
   212  	cmd.Flags().StringSlice(FlagAllowedValidators, []string{}, "Allowed validators addresses separated by ,")
   213  	cmd.Flags().StringSlice(FlagDenyValidators, []string{}, "Deny validators addresses separated by ,")
   214  	cmd.Flags().StringSlice(FlagAllowList, []string{}, "Allowed addresses grantee is allowed to send funds separated by ,")
   215  	cmd.Flags().Int64(FlagExpiration, 0, "Expire time as Unix timestamp. Set zero (0) for no expiry. Default is 0.")
   216  	return cmd
   217  }
   218  
   219  func getExpireTime(cmd *cobra.Command) (*time.Time, error) {
   220  	exp, err := cmd.Flags().GetInt64(FlagExpiration)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	if exp == 0 {
   225  		return nil, nil
   226  	}
   227  	e := time.Unix(exp, 0)
   228  	return &e, nil
   229  }
   230  
   231  // NewCmdRevokeAuthorization returns a CLI command handler for creating a MsgRevoke transaction.
   232  func NewCmdRevokeAuthorization(ac address.Codec) *cobra.Command {
   233  	cmd := &cobra.Command{
   234  		Use:   "revoke [grantee] [msg-type-url] --from=[granter]",
   235  		Short: "revoke authorization",
   236  		Long: strings.TrimSpace(
   237  			fmt.Sprintf(`revoke authorization from a granter to a grantee:
   238  Example:
   239   $ %s tx %s revoke cosmos1skj.. %s --from=cosmos1skj..
   240  			`, version.AppName, authz.ModuleName, bank.SendAuthorization{}.MsgTypeURL()),
   241  		),
   242  		Args: cobra.ExactArgs(2),
   243  		RunE: func(cmd *cobra.Command, args []string) error {
   244  			clientCtx, err := client.GetClientTxContext(cmd)
   245  			if err != nil {
   246  				return err
   247  			}
   248  
   249  			grantee, err := ac.StringToBytes(args[0])
   250  			if err != nil {
   251  				return err
   252  			}
   253  
   254  			granter := clientCtx.GetFromAddress()
   255  			msgAuthorized := args[1]
   256  			msg := authz.NewMsgRevoke(granter, grantee, msgAuthorized)
   257  
   258  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
   259  		},
   260  	}
   261  	flags.AddTxFlagsToCmd(cmd)
   262  	return cmd
   263  }
   264  
   265  // NewCmdExecAuthorization returns a CLI command handler for creating a MsgExec transaction.
   266  func NewCmdExecAuthorization() *cobra.Command {
   267  	cmd := &cobra.Command{
   268  		Use:   "exec [tx-json-file] --from [grantee]",
   269  		Short: "execute tx on behalf of granter account",
   270  		Long: strings.TrimSpace(
   271  			fmt.Sprintf(`execute tx on behalf of granter account:
   272  Example:
   273   $ %s tx %s exec tx.json --from grantee
   274   $ %s tx bank send <granter> <recipient> --from <granter> --chain-id <chain-id> --generate-only > tx.json && %s tx %s exec tx.json --from grantee
   275  			`, version.AppName, authz.ModuleName, version.AppName, version.AppName, authz.ModuleName),
   276  		),
   277  		Args: cobra.ExactArgs(1),
   278  		RunE: func(cmd *cobra.Command, args []string) error {
   279  			clientCtx, err := client.GetClientTxContext(cmd)
   280  			if err != nil {
   281  				return err
   282  			}
   283  			grantee := clientCtx.GetFromAddress()
   284  
   285  			if offline, _ := cmd.Flags().GetBool(flags.FlagOffline); offline {
   286  				return errors.New("cannot broadcast tx during offline mode")
   287  			}
   288  
   289  			theTx, err := authclient.ReadTxFromFile(clientCtx, args[0])
   290  			if err != nil {
   291  				return err
   292  			}
   293  			msg := authz.NewMsgExec(grantee, theTx.GetMsgs())
   294  
   295  			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
   296  		},
   297  	}
   298  
   299  	flags.AddTxFlagsToCmd(cmd)
   300  
   301  	return cmd
   302  }
   303  
   304  // bech32toValAddresses returns []ValAddress from a list of Bech32 string addresses.
   305  func bech32toValAddresses(validators []string) ([]sdk.ValAddress, error) {
   306  	vals := make([]sdk.ValAddress, len(validators))
   307  	for i, validator := range validators {
   308  		addr, err := sdk.ValAddressFromBech32(validator)
   309  		if err != nil {
   310  			return nil, err
   311  		}
   312  		vals[i] = addr
   313  	}
   314  	return vals, nil
   315  }
   316  
   317  // bech32toAccAddresses returns []AccAddress from a list of Bech32 string addresses.
   318  func bech32toAccAddresses(accAddrs []string, ac address.Codec) ([]sdk.AccAddress, error) {
   319  	addrs := make([]sdk.AccAddress, len(accAddrs))
   320  	for i, addr := range accAddrs {
   321  		accAddr, err := ac.StringToBytes(addr)
   322  		if err != nil {
   323  			return nil, err
   324  		}
   325  		addrs[i] = accAddr
   326  	}
   327  	return addrs, nil
   328  }