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  }