Skip to content

Commit

Permalink
Merge PR #3747: Implement initial simple denom convert utils
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored and cwgoes committed Mar 16, 2019
1 parent 5115dd4 commit 25408e7
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement coin conversion and denomination registration utilities
3 changes: 1 addition & 2 deletions types/coin.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package types

import (
"errors"
"fmt"
"regexp"
"sort"
Expand Down Expand Up @@ -552,7 +551,7 @@ var (

func validateDenom(denom string) error {
if !reDnm.MatchString(denom) {
return errors.New("illegal characters")
return fmt.Errorf("invalid denom: %s", denom)
}
return nil
}
Expand Down
64 changes: 64 additions & 0 deletions types/denom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package types

import (
"fmt"
)

// denomUnits contains a mapping of denomination mapped to their respective unit
// multipliers (e.g. 1atom = 10^-6uatom).
var denomUnits = map[string]Dec{}

// RegisterDenom registers a denomination with a corresponding unit. If the
// denomination is already registered, an error will be returned.
func RegisterDenom(denom string, unit Dec) error {
if err := validateDenom(denom); err != nil {
return err
}

if _, ok := denomUnits[denom]; ok {
return fmt.Errorf("denom %s already registered", denom)
}

denomUnits[denom] = unit
return nil
}

// GetDenomUnit returns a unit for a given denomination if it exists. A boolean
// is returned if the denomination is registered.
func GetDenomUnit(denom string) (Dec, bool) {
if err := validateDenom(denom); err != nil {
return ZeroDec(), false
}

unit, ok := denomUnits[denom]
if !ok {
return ZeroDec(), false
}

return unit, true
}

// ConvertCoin attempts to convert a coin to a given denomination. If the given
// denomination is invalid or if neither denomination is registered, an error
// is returned.
func ConvertCoin(coin Coin, denom string) (Coin, error) {
if err := validateDenom(denom); err != nil {
return Coin{}, err
}

srcUnit, ok := GetDenomUnit(coin.Denom)
if !ok {
return Coin{}, fmt.Errorf("source denom not registered: %s", coin.Denom)
}

dstUnit, ok := GetDenomUnit(denom)
if !ok {
return Coin{}, fmt.Errorf("destination denom not registered: %s", denom)
}

if srcUnit.Equal(dstUnit) {
return NewCoin(denom, coin.Amount), nil
}

return NewCoin(denom, coin.Amount.ToDec().Mul(srcUnit.Quo(dstUnit)).TruncateInt()), nil
}
83 changes: 83 additions & 0 deletions types/denom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package types

import (
"testing"

"github.com/stretchr/testify/require"
)

var (
atom = "atom" // 1 (base denom unit)
matom = "matom" // 10^-3 (milli)
uatom = "uatom" // 10^-6 (micro)
natom = "natom" // 10^-9 (nano)
)

func TestRegisterDenom(t *testing.T) {
atomUnit := OneDec() // 1 (base denom unit)

require.NoError(t, RegisterDenom(atom, atomUnit))
require.Error(t, RegisterDenom(atom, atomUnit))

res, ok := GetDenomUnit(atom)
require.True(t, ok)
require.Equal(t, atomUnit, res)

res, ok = GetDenomUnit(matom)
require.False(t, ok)
require.Equal(t, ZeroDec(), res)

// reset registration
denomUnits = map[string]Dec{}
}

func TestConvertCoins(t *testing.T) {
atomUnit := OneDec() // 1 (base denom unit)
require.NoError(t, RegisterDenom(atom, atomUnit))

matomUnit := NewDecWithPrec(1, 3) // 10^-3 (milli)
require.NoError(t, RegisterDenom(matom, matomUnit))

uatomUnit := NewDecWithPrec(1, 6) // 10^-6 (micro)
require.NoError(t, RegisterDenom(uatom, uatomUnit))

natomUnit := NewDecWithPrec(1, 9) // 10^-9 (nano)
require.NoError(t, RegisterDenom(natom, natomUnit))

testCases := []struct {
input Coin
denom string
result Coin
expErr bool
}{
{NewCoin("foo", ZeroInt()), atom, Coin{}, true},
{NewCoin(atom, ZeroInt()), "foo", Coin{}, true},
{NewCoin(atom, ZeroInt()), "FOO", Coin{}, true},

{NewCoin(atom, NewInt(5)), matom, NewCoin(matom, NewInt(5000)), false}, // atom => matom
{NewCoin(atom, NewInt(5)), uatom, NewCoin(uatom, NewInt(5000000)), false}, // atom => uatom
{NewCoin(atom, NewInt(5)), natom, NewCoin(natom, NewInt(5000000000)), false}, // atom => natom

{NewCoin(uatom, NewInt(5000000)), matom, NewCoin(matom, NewInt(5000)), false}, // uatom => matom
{NewCoin(uatom, NewInt(5000000)), natom, NewCoin(natom, NewInt(5000000000)), false}, // uatom => natom
{NewCoin(uatom, NewInt(5000000)), atom, NewCoin(atom, NewInt(5)), false}, // uatom => atom

{NewCoin(matom, NewInt(5000)), natom, NewCoin(natom, NewInt(5000000000)), false}, // matom => natom
{NewCoin(matom, NewInt(5000)), uatom, NewCoin(uatom, NewInt(5000000)), false}, // matom => uatom
}

for i, tc := range testCases {
res, err := ConvertCoin(tc.input, tc.denom)
require.Equal(
t, tc.expErr, err != nil,
"unexpected error; tc: #%d, input: %s, denom: %s", i+1, tc.input, tc.denom,
)
require.Equal(
t, tc.result, res,
"invalid result; tc: #%d, input: %s, denom: %s", i+1, tc.input, tc.denom,
)
}

// reset registration
denomUnits = map[string]Dec{}
}

0 comments on commit 25408e7

Please sign in to comment.