Merge PR #3747: Implement initial simple denom convert utils
This commit is contained in:
parent
5115dd4a6a
commit
25408e7856
|
@ -0,0 +1 @@
|
|||
Implement coin conversion and denomination registration utilities
|
|
@ -1,7 +1,6 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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{}
|
||||
}
|
Loading…
Reference in New Issue