github.com/0chain/gosdk@v1.17.11/core/common/misc.go (about)

     1  package common
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math"
     7  	"regexp"
     8  	"strconv"
     9  
    10  	"github.com/shopspring/decimal"
    11  )
    12  
    13  const (
    14  	ZCNExponent = 10
    15  	// TokenUnit represents the minimum token unit (sas)
    16  	TokenUnit = 1e10
    17  )
    18  
    19  var (
    20  	// ErrNegativeValue is returned if a float value is a negative number
    21  	ErrNegativeValue = errors.New("negative coin value")
    22  	// ErrTooManyDecimals is returned if a value has more than 10 decimal places
    23  	ErrTooManyDecimals = errors.New("too many decimal places")
    24  	// ErrTooLarge is returned if a value is greater than math.MaxInt64
    25  	ErrTooLarge = errors.New("value is too large")
    26  	// ErrUint64OverflowsFloat64 is returned if when converting a uint64 to a float64 overflow float64
    27  	ErrUint64OverflowsFloat64 = errors.New("uint64 overflows float64")
    28  	// ErrUint64AddOverflow is returned if when adding uint64 values overflow uint64
    29  	ErrUint64AddOverflow = errors.New("uint64 addition overflow")
    30  )
    31  
    32  // A Key represents an identifier. It can be a pool ID, client ID, smart
    33  // contract address, etc.
    34  type Key string
    35  
    36  // A Size represents a size in bytes.
    37  type Size int64
    38  
    39  func byteCountIEC(b int64) string {
    40  	const unit = 1024
    41  	if b < unit {
    42  		return fmt.Sprintf("%d B", b)
    43  	}
    44  	div, exp := int64(unit), 0
    45  	for n := b / unit; n >= unit; n /= unit {
    46  		div *= unit
    47  		exp++
    48  	}
    49  	return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
    50  }
    51  
    52  // String implements fmt.Stringer interface
    53  func (s Size) String() string {
    54  	return byteCountIEC(int64(s))
    55  }
    56  
    57  /* Balance */
    58  
    59  // reParseToken is a regexp to parse string representation of token
    60  var reParseToken = regexp.MustCompile(`^((?:\d*\.)?\d+)\s+(SAS|sas|uZCN|uzcn|mZCN|mzcn|ZCN|zcn)$`)
    61  
    62  // Balance represents client's balance in Züs native token fractions (SAS = 10^-10 ZCN).
    63  type Balance uint64
    64  
    65  // ToToken converts Balance to ZCN tokens.
    66  func (b Balance) ToToken() (float64, error) {
    67  	if b > math.MaxInt64 {
    68  		return 0.0, ErrTooLarge
    69  	}
    70  
    71  	f, _ := decimal.New(int64(b), -ZCNExponent).Float64()
    72  	return f, nil
    73  }
    74  
    75  // String implements fmt.Stringer interface.
    76  func (b Balance) String() string {
    77  	if val, err := b.AutoFormat(); err == nil {
    78  		return val
    79  	}
    80  	return ""
    81  }
    82  
    83  // Format returns a string representation of the balance with the given unit.
    84  //   - unit is the balance unit.
    85  func (b Balance) Format(unit BalanceUnit) (string, error) {
    86  	v := float64(b)
    87  	if v < 0 {
    88  		return "", ErrUint64OverflowsFloat64
    89  	}
    90  	switch unit {
    91  	case SAS:
    92  		return fmt.Sprintf("%d %v", b, unit), nil
    93  	case UZCN:
    94  		v /= 1e4
    95  	case MZCN:
    96  		v /= 1e7
    97  	case ZCN:
    98  		v /= 1e10
    99  	default:
   100  		return "", fmt.Errorf("undefined balance unit: %d", unit)
   101  	}
   102  	return fmt.Sprintf("%.3f %v", v, unit), nil
   103  }
   104  
   105  // AutoFormat returns a string representation of the balance with the most
   106  func (b Balance) AutoFormat() (string, error) {
   107  	switch {
   108  	case b/1e10 > 0:
   109  		return b.Format(ZCN)
   110  	case b/1e7 > 0:
   111  		return b.Format(MZCN)
   112  	case b/1e4 > 0:
   113  		return b.Format(UZCN)
   114  	}
   115  	return b.Format(SAS)
   116  }
   117  
   118  // ToBalance converts ZCN tokens to Balance.
   119  //   - token amount of ZCN tokens.
   120  func ToBalance(token float64) (Balance, error) {
   121  	d := decimal.NewFromFloat(token)
   122  	if d.Sign() == -1 {
   123  		return 0, ErrNegativeValue
   124  	}
   125  
   126  	// ZCN have a maximum of 10 decimal places
   127  	if d.Exponent() < -ZCNExponent {
   128  		return 0, ErrTooManyDecimals
   129  	}
   130  
   131  	// Multiply the coin balance by 1e10 to obtain coin amount
   132  	e := d.Shift(ZCNExponent)
   133  
   134  	// Check that there are no decimal places remaining. This error should not
   135  	// occur, because of the earlier check of ZCNExponent()
   136  	if e.Exponent() < 0 {
   137  		return 0, ErrTooManyDecimals
   138  	}
   139  
   140  	maxDecimal := decimal.NewFromInt(math.MaxInt64)
   141  	// Values greater than math.MaxInt64 will overflow after conversion to int64
   142  	if e.GreaterThan(maxDecimal) {
   143  		return 0, ErrTooLarge
   144  	}
   145  
   146  	return Balance(e.IntPart()), nil
   147  }
   148  
   149  // AddBalance adds c and b, returning an error if the values overflow
   150  func AddBalance(c, b Balance) (Balance, error) {
   151  	sum := c + b
   152  	if sum < c || sum < b {
   153  		return 0, ErrUint64AddOverflow
   154  	}
   155  	return sum, nil
   156  }
   157  
   158  // FormatBalance returns a string representation of the balance with the given unit.
   159  func FormatBalance(b Balance, unit BalanceUnit) (string, error) {
   160  	return b.Format(unit)
   161  }
   162  
   163  // AutoFormatBalance returns a string representation of the balance with the most
   164  func AutoFormatBalance(b Balance) (string, error) {
   165  	return b.AutoFormat()
   166  }
   167  
   168  func ParseBalance(str string) (Balance, error) {
   169  
   170  	matches := reParseToken.FindAllStringSubmatch(str, -1)
   171  
   172  	if len(matches) != 1 || len(matches[0]) != 3 {
   173  		return 0, fmt.Errorf("invalid input: %s", str)
   174  	}
   175  
   176  	b, err := strconv.ParseFloat(matches[0][1], 64)
   177  	if err != nil {
   178  		return 0, err
   179  	}
   180  
   181  	var unit BalanceUnit
   182  
   183  	err = unit.Parse(matches[0][2])
   184  	if err != nil {
   185  		return 0, err
   186  	}
   187  
   188  	switch unit {
   189  	case UZCN:
   190  		b *= 1e4
   191  	case MZCN:
   192  		b *= 1e7
   193  	case ZCN:
   194  		b *= 1e10
   195  	}
   196  
   197  	return Balance(b), nil
   198  }
   199  
   200  const (
   201  	SAS BalanceUnit = iota
   202  	UZCN
   203  	MZCN
   204  	ZCN
   205  )
   206  
   207  type BalanceUnit byte
   208  
   209  func (unit BalanceUnit) String() string {
   210  	switch unit {
   211  	case SAS:
   212  		return "SAS"
   213  	case MZCN:
   214  		return "mZCN"
   215  	case UZCN:
   216  		return "uZCN"
   217  	case ZCN:
   218  		return "ZCN"
   219  	}
   220  	return ""
   221  }
   222  
   223  func (unit *BalanceUnit) Parse(s string) error {
   224  	switch s {
   225  	case "SAS", "sas":
   226  		*unit = SAS
   227  	case "uZCN", "uzcn":
   228  		*unit = UZCN
   229  	case "mZCN", "mzcn":
   230  		*unit = MZCN
   231  	case "ZCN", "zcn":
   232  		*unit = ZCN
   233  	default:
   234  		return errors.New("undefined balance unit: " + s)
   235  	}
   236  	return nil
   237  }
   238  
   239  func ParseBalanceStatic(str string) (int64, error) {
   240  	bal, err := ParseBalance(str)
   241  	return int64(bal), err
   242  }
   243  
   244  func FormatStatic(amount int64, unit string) (string, error) {
   245  	token := Balance(amount)
   246  
   247  	var unitB BalanceUnit
   248  	err := unitB.Parse(unit)
   249  	if err != nil {
   250  		return "", err
   251  	}
   252  
   253  	return token.Format(unitB)
   254  }
   255  
   256  func AutoFormatStatic(amount int64) (string, error) {
   257  	token := Balance(amount)
   258  	return token.AutoFormat()
   259  }