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 }