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 }