github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/util/util.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package util 7 8 import ( 9 "bytes" 10 "crypto/tls" 11 "fmt" 12 "math/big" 13 "os" 14 "os/signal" 15 "path/filepath" 16 "strconv" 17 "strings" 18 "syscall" 19 20 "github.com/ethereum/go-ethereum/common" 21 "github.com/spf13/cobra" 22 "go.uber.org/zap" 23 "golang.org/x/crypto/ssh/terminal" 24 "google.golang.org/grpc" 25 "google.golang.org/grpc/credentials" 26 "google.golang.org/grpc/metadata" 27 28 "github.com/iotexproject/iotex-address/address" 29 "github.com/iotexproject/iotex-core/ioctl/config" 30 "github.com/iotexproject/iotex-core/ioctl/output" 31 "github.com/iotexproject/iotex-core/ioctl/validator" 32 "github.com/iotexproject/iotex-core/pkg/log" 33 ) 34 35 const ( 36 // IotxDecimalNum defines the number of decimal digits for IoTeX 37 IotxDecimalNum = 18 38 // GasPriceDecimalNum defines the number of decimal digits for gas price 39 GasPriceDecimalNum = 12 40 ) 41 42 // ExecuteCmd executes cmd with args, and return system output, e.g., help info, and error 43 func ExecuteCmd(cmd *cobra.Command, args ...string) (string, error) { 44 buf := new(bytes.Buffer) 45 cmd.SetOut(buf) 46 cmd.SetErr(new(bytes.Buffer)) 47 cmd.SetArgs(args) 48 err := cmd.Execute() 49 return buf.String(), err 50 } 51 52 // ConnectToEndpoint starts a new connection 53 func ConnectToEndpoint(secure bool) (*grpc.ClientConn, error) { 54 endpoint := config.ReadConfig.Endpoint 55 if endpoint == "" { 56 return nil, output.NewError(output.ConfigError, `use "ioctl config set endpoint" to config endpoint first`, nil) 57 } 58 if !secure { 59 return grpc.Dial(endpoint, grpc.WithInsecure()) 60 } 61 return grpc.Dial(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12}))) 62 } 63 64 // StringToRau converts different unit string into Rau big int 65 func StringToRau(amount string, numDecimals int) (*big.Int, error) { 66 amountStrings := strings.Split(amount, ".") 67 if len(amountStrings) != 1 { 68 if len(amountStrings) > 2 || len(amountStrings[1]) > numDecimals { 69 return nil, output.NewError(output.ConvertError, "failed to convert string into big int", nil) 70 } 71 amountStrings[0] += amountStrings[1] 72 numDecimals -= len(amountStrings[1]) 73 } 74 if len(amountStrings[0]) == 0 { 75 return nil, output.NewError(output.ConvertError, "failed to convert string into big int", nil) 76 } 77 zeroString := strings.Repeat("0", numDecimals) 78 amountStrings[0] += zeroString 79 amountRau, ok := new(big.Int).SetString(amountStrings[0], 10) 80 if !ok { 81 return nil, output.NewError(output.ConvertError, "failed to convert string into big int", nil) 82 } 83 if amountRau.Sign() < 0 { 84 return nil, output.NewError(output.ConvertError, "invalid number that is minus", nil) 85 } 86 return amountRau, nil 87 } 88 89 // RauToString converts Rau big int into Iotx string 90 func RauToString(amount *big.Int, numDecimals int) string { 91 if numDecimals == 0 { 92 return amount.String() 93 } 94 targetUnit := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(numDecimals)), nil) 95 amountInt, amountDec := big.NewInt(0), big.NewInt(0) 96 amountInt.DivMod(amount, targetUnit, amountDec) 97 if amountDec.Sign() != 0 { 98 decString := strings.TrimRight(amountDec.String(), "0") 99 zeroString := strings.Repeat("0", numDecimals-len(amountDec.String())) 100 decString = zeroString + decString 101 return amountInt.String() + "." + decString 102 } 103 return amountInt.String() 104 } 105 106 // StringToIOTX converts Rau string to Iotx string 107 func StringToIOTX(amount string) (string, error) { 108 amountInt, err := StringToRau(amount, 0) 109 if err != nil { 110 return "", output.NewError(output.ConvertError, "", err) 111 } 112 return RauToString(amountInt, IotxDecimalNum), nil 113 } 114 115 // ReadSecretFromStdin used to safely get password input 116 func ReadSecretFromStdin() (string, error) { 117 signalListener := make(chan os.Signal, 1) 118 signal.Notify(signalListener, os.Interrupt) 119 routineTerminate := make(chan struct{}) 120 sta, err := terminal.GetState(int(syscall.Stdin)) 121 if err != nil { 122 return "", output.NewError(output.RuntimeError, "", err) 123 } 124 go func() { 125 for { 126 select { 127 case <-signalListener: 128 err = terminal.Restore(int(syscall.Stdin), sta) 129 if err != nil { 130 log.L().Error("failed restore terminal", zap.Error(err)) 131 return 132 } 133 os.Exit(130) 134 case <-routineTerminate: 135 return 136 } 137 } 138 }() 139 bytePass, err := terminal.ReadPassword(int(syscall.Stdin)) 140 close(routineTerminate) 141 if err != nil { 142 return "", output.NewError(output.RuntimeError, "failed to read password", nil) 143 } 144 return string(bytePass), nil 145 } 146 147 // GetAddress get address from address or alias or context 148 func GetAddress(in string) (string, error) { 149 addr, err := config.GetAddressOrAlias(in) 150 if err != nil { 151 return "", output.NewError(output.AddressError, "", err) 152 } 153 return Address(addr) 154 } 155 156 // Address returns the address corresponding to alias. if 'in' is an IoTeX address, returns 'in' 157 func Address(in string) (string, error) { 158 // if in is an eth address, convert it to IoTeX address 159 if common.IsHexAddress(in) { 160 add, err := address.FromHex(in) 161 if err != nil { 162 return "", output.NewError(output.AddressError, "", err) 163 } 164 return add.String(), nil 165 } 166 if len(in) >= validator.IoAddrLen { 167 if err := validator.ValidateAddress(in); err != nil { 168 return "", output.NewError(output.ValidationError, in, err) 169 } 170 return in, nil 171 } 172 addr, ok := config.ReadConfig.Aliases[in] 173 if ok { 174 return addr, nil 175 } 176 return "", output.NewError(output.ConfigError, "cannot find address for alias "+in, nil) 177 } 178 179 // JwtAuth used for ioctl set auth and send for every grpc request 180 func JwtAuth() (jwt metadata.MD, err error) { 181 jwtFile := os.Getenv("HOME") + "/.config/ioctl/default/auth.jwt" 182 jwtString, err := os.ReadFile(filepath.Clean(jwtFile)) 183 if err != nil { 184 return nil, err 185 } 186 return metadata.Pairs("authorization", "bearer "+string(jwtString)), nil 187 } 188 189 // CheckArgs used for check ioctl cmd arg(s)'s num 190 func CheckArgs(validNum ...int) cobra.PositionalArgs { 191 return func(cmd *cobra.Command, args []string) error { 192 for _, n := range validNum { 193 if len(args) == n { 194 return nil 195 } 196 } 197 nums := strings.Replace(strings.Trim(fmt.Sprint(validNum), "[]"), " ", " or ", -1) 198 return fmt.Errorf("accepts "+nums+" arg(s), received %d", len(args)) 199 } 200 } 201 202 // TrimHexPrefix removes 0x prefix from a string if it has 203 func TrimHexPrefix(s string) string { 204 return strings.TrimPrefix(s, "0x") 205 } 206 207 // ParseHdwPath parse hdwallet path 208 func ParseHdwPath(addressOrAlias string) (uint32, uint32, uint32, error) { 209 // parse derive path 210 // for hdw::1/1/2, return 1, 1, 2 211 // for hdw::1/2, treat as default account = 0, return 0, 1, 2 212 args := strings.Split(addressOrAlias[5:], "/") 213 if len(args) < 2 || len(args) > 3 { 214 return 0, 0, 0, output.NewError(output.ValidationError, "derivation path error", nil) 215 } 216 217 arg := make([]uint32, 3) 218 j := 0 219 for i := 3 - len(args); i < 3; i++ { 220 u64, err := strconv.ParseUint(args[j], 10, 32) 221 if err != nil { 222 return 0, 0, 0, output.NewError(output.InputError, fmt.Sprintf("%v must be integer value", args[j]), err) 223 } 224 arg[i] = uint32(u64) 225 j++ 226 } 227 return arg[0], arg[1], arg[2], nil 228 } 229 230 // AliasIsHdwalletKey check whether to use hdwallet key 231 func AliasIsHdwalletKey(addressOrAlias string) bool { 232 return strings.HasPrefix(strings.ToLower(addressOrAlias), "hdw::") 233 }