diff --git a/core/common/misc.go b/core/common/misc.go index 572548163..86d7c31a1 100644 --- a/core/common/misc.go +++ b/core/common/misc.go @@ -2,32 +2,13 @@ package common import ( "fmt" + "regexp" "strconv" "strings" "github.com/0chain/errors" ) -const tokenUnit = 10000000000.0 - -// Balance represents an amount of tokens. -type Balance int64 - -// ToToken converts the Balance to ZCN tokens. -func (b Balance) ToToken() float64 { - return float64(b) / tokenUnit -} - -// ToBalance converts ZCN tokens to Balance. -func ToBalance(tok float64) Balance { - return Balance(tok * tokenUnit) -} - -// String implements fmt.Stringer interface. -func (b Balance) String() string { - return strconv.FormatFloat(b.ToToken(), 'f', -1, 64) -} - // A Key represents an identifier. It can be a pool ID, client ID, smart // contract address, etc. type Key string @@ -95,3 +76,134 @@ func (wp *WhoPays) Parse(val string) (err error) { } return } + +/* Balance */ + +// minimum token unit (sas) +const tokenUnit = 1e10 + +// reParseToken is a regexp to parse string representation of token +var reParseToken = regexp.MustCompile(`^((?:\d*\.)?\d+)\s+(SAS|sas|uZCN|uzcn|mZCN|mzcn|ZCN|zcn)$`) + +// Balance represents 0chain native token +type Balance int64 + +func (b Balance) ToToken() float64 { + return float64(b) / tokenUnit +} + +// String implements fmt.Stringer interface. +func (b Balance) String() string { + return b.AutoFormat() +} + +func (b Balance) Format(unit BalanceUnit) string { + v := float64(b) + switch unit { + case SAS: + return fmt.Sprintf("%d %v", b, unit) + case UZCN: + v /= 1e4 + case MZCN: + v /= 1e7 + case ZCN: + v /= 1e10 + } + return fmt.Sprintf("%.3f %v", v, unit) +} + +func (b Balance) AutoFormat() string { + switch { + case b/1e10 > 0: + return b.Format(ZCN) + case b/1e7 > 0: + return b.Format(MZCN) + case b/1e4 > 0: + return b.Format(UZCN) + } + return b.Format(SAS) +} + +// ToBalance converts ZCN tokens to Balance. +func ToBalance(token float64) Balance { + return Balance(token * tokenUnit) +} + +func FormatBalance(b Balance, unit BalanceUnit) string { + return b.Format(unit) +} + +func AutoFormatBalance(b Balance) string { + return b.AutoFormat() +} + +func ParseBalance(str string) (Balance, error) { + + matches := reParseToken.FindAllStringSubmatch(str, -1) + + if len(matches) != 1 || len(matches[0]) != 3 { + return 0, fmt.Errorf("invalid input: %s", str) + } + + b, err := strconv.ParseFloat(matches[0][1], 64) + if err != nil { + return 0, err + } + + var unit BalanceUnit + + err = unit.Parse(matches[0][2]) + if err != nil { + return 0, err + } + + switch unit { + case UZCN: + b *= 1e4 + case MZCN: + b *= 1e7 + case ZCN: + b *= 1e10 + } + + return Balance(b), nil +} + +const ( + SAS BalanceUnit = iota + UZCN + MZCN + ZCN +) + +type BalanceUnit byte + +func (unit BalanceUnit) String() string { + switch unit { + case SAS: + return "SAS" + case MZCN: + return "mZCN" + case UZCN: + return "uZCN" + case ZCN: + return "ZCN" + } + return "" +} + +func (unit *BalanceUnit) Parse(s string) error { + switch s { + case "SAS", "sas": + *unit = SAS + case "uZCN", "uzcn": + *unit = UZCN + case "mZCN", "mzcn": + *unit = MZCN + case "ZCN", "zcn": + *unit = ZCN + default: + return errors.New("", "undefined balance unit: "+s) + } + return nil +} diff --git a/core/common/misc_test.go b/core/common/misc_test.go new file mode 100644 index 000000000..f489564b3 --- /dev/null +++ b/core/common/misc_test.go @@ -0,0 +1,49 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFormat(t *testing.T) { + token := Balance(129382129321) + require.Equal(t, "12.938 ZCN", token.Format(ZCN)) + require.Equal(t, "12938.213 mZCN", token.Format(MZCN)) + require.Equal(t, "12938212.932 uZCN", token.Format(UZCN)) + require.Equal(t, "129382129321 SAS", token.Format(SAS)) +} + +func TestAutoFormat(t *testing.T) { + require.Equal(t, "239 SAS", Balance(239).AutoFormat()) + require.Equal(t, "2.736 uZCN", Balance(27361).AutoFormat()) + require.Equal(t, "2.387 mZCN", Balance(23872013).AutoFormat()) + require.Equal(t, "20.383 ZCN", Balance(203827162834).AutoFormat()) +} + +func TestParseBalance(t *testing.T) { + b, err := ParseBalance("12.938 ZCN") + require.NoError(t, err) + require.Equal(t, Balance(12.938*1e10), b) + + b, err = ParseBalance("12.938 mzcn") + require.NoError(t, err) + require.Equal(t, Balance(12.938*1e7), b) + + b, err = ParseBalance("12.938 uZCN") + require.NoError(t, err) + require.Equal(t, Balance(12.938*1e4), b) + + b, err = ParseBalance("122389 sas") + require.NoError(t, err) + require.Equal(t, Balance(122389*1e0), b) + + _, err = ParseBalance("10 ") + require.EqualError(t, err, "invalid input: 10 ") + + _, err = ParseBalance("10 zwe") + require.EqualError(t, err, "invalid input: 10 zwe") + + _, err = ParseBalance(" 10 zcn ") + require.EqualError(t, err, "invalid input: 10 zcn ") +}