package types import ( "strings" "testing" "github.com/stretchr/testify/require" ) func TestNewDecCoin(t *testing.T) { require.NotPanics(t, func() { NewInt64DecCoin(testDenom1, 5) }) require.NotPanics(t, func() { NewInt64DecCoin(testDenom1, 0) }) require.Panics(t, func() { NewInt64DecCoin(strings.ToUpper(testDenom1), 5) }) require.Panics(t, func() { NewInt64DecCoin(testDenom1, -5) }) } func TestNewDecCoinFromDec(t *testing.T) { require.NotPanics(t, func() { NewDecCoinFromDec(testDenom1, NewDec(5)) }) require.NotPanics(t, func() { NewDecCoinFromDec(testDenom1, ZeroDec()) }) require.Panics(t, func() { NewDecCoinFromDec(strings.ToUpper(testDenom1), NewDec(5)) }) require.Panics(t, func() { NewDecCoinFromDec(testDenom1, NewDec(-5)) }) } func TestNewDecCoinFromCoin(t *testing.T) { require.NotPanics(t, func() { NewDecCoinFromCoin(Coin{testDenom1, NewInt(5)}) }) require.NotPanics(t, func() { NewDecCoinFromCoin(Coin{testDenom1, NewInt(0)}) }) require.Panics(t, func() { NewDecCoinFromCoin(Coin{strings.ToUpper(testDenom1), NewInt(5)}) }) require.Panics(t, func() { NewDecCoinFromCoin(Coin{testDenom1, NewInt(-5)}) }) } func TestDecCoinIsPositive(t *testing.T) { dc := NewInt64DecCoin(testDenom1, 5) require.True(t, dc.IsPositive()) dc = NewInt64DecCoin(testDenom1, 0) require.False(t, dc.IsPositive()) } func TestAddDecCoin(t *testing.T) { decCoinA1 := NewDecCoinFromDec(testDenom1, NewDecWithPrec(11, 1)) decCoinA2 := NewDecCoinFromDec(testDenom1, NewDecWithPrec(22, 1)) decCoinB1 := NewDecCoinFromDec(testDenom2, NewDecWithPrec(11, 1)) // regular add res := decCoinA1.Add(decCoinA1) require.Equal(t, decCoinA2, res, "sum of coins is incorrect") // bad denom add require.Panics(t, func() { decCoinA1.Add(decCoinB1) }, "expected panic on sum of different denoms") } func TestAddDecCoins(t *testing.T) { one := NewDec(1) zero := NewDec(0) two := NewDec(2) cases := []struct { inputOne DecCoins inputTwo DecCoins expected DecCoins }{ {DecCoins{{testDenom1, one}, {testDenom2, one}}, DecCoins{{testDenom1, one}, {testDenom2, one}}, DecCoins{{testDenom1, two}, {testDenom2, two}}}, {DecCoins{{testDenom1, zero}, {testDenom2, one}}, DecCoins{{testDenom1, zero}, {testDenom2, zero}}, DecCoins{{testDenom2, one}}}, {DecCoins{{testDenom1, zero}, {testDenom2, zero}}, DecCoins{{testDenom1, zero}, {testDenom2, zero}}, DecCoins(nil)}, } for tcIndex, tc := range cases { res := tc.inputOne.Add(tc.inputTwo...) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } func TestIsValid(t *testing.T) { tests := []struct { coin DecCoin expectPass bool msg string }{ { NewDecCoin("mytoken", NewInt(10)), true, "valid coins should have passed", }, { DecCoin{Denom: "BTC", Amount: NewDec(10)}, false, "invalid denoms", }, { DecCoin{Denom: "BTC", Amount: NewDec(-10)}, false, "negative amount", }, } for _, tc := range tests { tc := tc if tc.expectPass { require.True(t, tc.coin.IsValid(), tc.msg) } else { require.False(t, tc.coin.IsValid(), tc.msg) } } } func TestSubDecCoin(t *testing.T) { tests := []struct { coin DecCoin expectPass bool msg string }{ { NewDecCoin("mytoken", NewInt(20)), true, "valid coins should have passed", }, { NewDecCoin("othertoken", NewInt(20)), false, "denom mismatch", }, { NewDecCoin("mytoken", NewInt(9)), false, "negative amount", }, } decCoin := NewDecCoin("mytoken", NewInt(10)) for _, tc := range tests { tc := tc if tc.expectPass { equal := tc.coin.Sub(decCoin) require.Equal(t, equal, decCoin, tc.msg) } else { require.Panics(t, func() { tc.coin.Sub(decCoin) }, tc.msg) } } } func TestSubDecCoins(t *testing.T) { tests := []struct { coins DecCoins expectPass bool msg string }{ { NewDecCoinsFromCoins(NewCoin("mytoken", NewInt(10)), NewCoin("btc", NewInt(20)), NewCoin("eth", NewInt(30))), true, "sorted coins should have passed", }, { DecCoins{NewDecCoin("mytoken", NewInt(10)), NewDecCoin("btc", NewInt(20)), NewDecCoin("eth", NewInt(30))}, false, "unorted coins should panic", }, { DecCoins{DecCoin{Denom: "BTC", Amount: NewDec(10)}, NewDecCoin("eth", NewInt(15)), NewDecCoin("mytoken", NewInt(5))}, false, "invalid denoms", }, } decCoins := NewDecCoinsFromCoins(NewCoin("btc", NewInt(10)), NewCoin("eth", NewInt(15)), NewCoin("mytoken", NewInt(5))) for _, tc := range tests { tc := tc if tc.expectPass { equal := tc.coins.Sub(decCoins) require.Equal(t, equal, decCoins, tc.msg) } else { require.Panics(t, func() { tc.coins.Sub(decCoins) }, tc.msg) } } } func TestSortDecCoins(t *testing.T) { good := DecCoins{ NewInt64DecCoin("gas", 1), NewInt64DecCoin("mineral", 1), NewInt64DecCoin("tree", 1), } empty := DecCoins{ NewInt64DecCoin("gold", 0), } badSort1 := DecCoins{ NewInt64DecCoin("tree", 1), NewInt64DecCoin("gas", 1), NewInt64DecCoin("mineral", 1), } badSort2 := DecCoins{ // both are after the first one, but the second and third are in the wrong order NewInt64DecCoin("gas", 1), NewInt64DecCoin("tree", 1), NewInt64DecCoin("mineral", 1), } badAmt := DecCoins{ NewInt64DecCoin("gas", 1), NewInt64DecCoin("tree", 0), NewInt64DecCoin("mineral", 1), } dup := DecCoins{ NewInt64DecCoin("gas", 1), NewInt64DecCoin("gas", 1), NewInt64DecCoin("mineral", 1), } cases := []struct { coins DecCoins before, after bool // valid before/after sort }{ {good, true, true}, {empty, false, false}, {badSort1, false, true}, {badSort2, false, true}, {badAmt, false, false}, {dup, false, false}, } for tcIndex, tc := range cases { require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting, tc #%d", tcIndex) tc.coins.Sort() require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting, tc #%d", tcIndex) } } func TestDecCoinsIsValid(t *testing.T) { testCases := []struct { input DecCoins expected bool }{ {DecCoins{}, true}, {DecCoins{DecCoin{testDenom1, NewDec(5)}}, true}, {DecCoins{DecCoin{testDenom1, NewDec(5)}, DecCoin{testDenom2, NewDec(100000)}}, true}, {DecCoins{DecCoin{testDenom1, NewDec(-5)}}, false}, {DecCoins{DecCoin{"AAA", NewDec(5)}}, false}, {DecCoins{DecCoin{testDenom1, NewDec(5)}, DecCoin{"B", NewDec(100000)}}, false}, {DecCoins{DecCoin{testDenom1, NewDec(5)}, DecCoin{testDenom2, NewDec(-100000)}}, false}, {DecCoins{DecCoin{testDenom1, NewDec(-5)}, DecCoin{testDenom2, NewDec(100000)}}, false}, {DecCoins{DecCoin{"AAA", NewDec(5)}, DecCoin{testDenom2, NewDec(100000)}}, false}, } for i, tc := range testCases { res := tc.input.IsValid() require.Equal(t, tc.expected, res, "unexpected result for test case #%d, input: %v", i, tc.input) } } func TestParseDecCoins(t *testing.T) { testCases := []struct { input string expectedResult DecCoins expectedErr bool }{ {"", nil, false}, {"4stake", nil, true}, {"5.5atom,4stake", nil, true}, {"0.0stake", nil, true}, {"0.004STAKE", nil, true}, { "0.004stake", DecCoins{NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision))}, false, }, { "5.04atom,0.004stake", DecCoins{ NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)), NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)), }, false, }, } for i, tc := range testCases { res, err := ParseDecCoins(tc.input) if tc.expectedErr { require.Error(t, err, "expected error for test case #%d, input: %v", i, tc.input) } else { require.NoError(t, err, "unexpected error for test case #%d, input: %v", i, tc.input) require.Equal(t, tc.expectedResult, res, "unexpected result for test case #%d, input: %v", i, tc.input) } } } func TestDecCoinsString(t *testing.T) { testCases := []struct { input DecCoins expected string }{ {DecCoins{}, ""}, { DecCoins{ NewDecCoinFromDec("atom", NewDecWithPrec(5040000000000000000, Precision)), NewDecCoinFromDec("stake", NewDecWithPrec(4000000000000000, Precision)), }, "5.040000000000000000atom,0.004000000000000000stake", }, } for i, tc := range testCases { out := tc.input.String() require.Equal(t, tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input) } } func TestDecCoinsIntersect(t *testing.T) { testCases := []struct { input1 string input2 string expectedResult string }{ {"", "", ""}, {"1.0stake", "", ""}, {"1.0stake", "1.0stake", "1.0stake"}, {"", "1.0stake", ""}, {"1.0stake", "", ""}, {"2.0stake,1.0trope", "1.9stake", "1.9stake"}, {"2.0stake,1.0trope", "2.1stake", "2.0stake"}, {"2.0stake,1.0trope", "0.9trope", "0.9trope"}, {"2.0stake,1.0trope", "1.9stake,0.9trope", "1.9stake,0.9trope"}, {"2.0stake,1.0trope", "1.9stake,0.9trope,20.0other", "1.9stake,0.9trope"}, {"2.0stake,1.0trope", "1.0other", ""}, } for i, tc := range testCases { in1, err := ParseDecCoins(tc.input1) require.NoError(t, err, "unexpected parse error in %v", i) in2, err := ParseDecCoins(tc.input2) require.NoError(t, err, "unexpected parse error in %v", i) exr, err := ParseDecCoins(tc.expectedResult) require.NoError(t, err, "unexpected parse error in %v", i) require.True(t, in1.Intersect(in2).IsEqual(exr), "in1.cap(in2) != exr in %v", i) } } func TestDecCoinsTruncateDecimal(t *testing.T) { decCoinA := NewDecCoinFromDec("bar", MustNewDecFromStr("5.41")) decCoinB := NewDecCoinFromDec("foo", MustNewDecFromStr("6.00")) testCases := []struct { input DecCoins truncatedCoins Coins changeCoins DecCoins }{ {DecCoins{}, Coins(nil), DecCoins(nil)}, { DecCoins{decCoinA, decCoinB}, Coins{NewInt64Coin(decCoinA.Denom, 5), NewInt64Coin(decCoinB.Denom, 6)}, DecCoins{NewDecCoinFromDec(decCoinA.Denom, MustNewDecFromStr("0.41"))}, }, { DecCoins{decCoinB}, Coins{NewInt64Coin(decCoinB.Denom, 6)}, DecCoins(nil), }, } for i, tc := range testCases { truncatedCoins, changeCoins := tc.input.TruncateDecimal() require.Equal( t, tc.truncatedCoins, truncatedCoins, "unexpected truncated coins; tc #%d, input: %s", i, tc.input, ) require.Equal( t, tc.changeCoins, changeCoins, "unexpected change coins; tc #%d, input: %s", i, tc.input, ) } } func TestDecCoinsQuoDecTruncate(t *testing.T) { x := MustNewDecFromStr("1.00") y := MustNewDecFromStr("10000000000000000000.00") testCases := []struct { coins DecCoins input Dec result DecCoins panics bool }{ {DecCoins{}, ZeroDec(), DecCoins(nil), true}, {DecCoins{NewDecCoinFromDec("foo", x)}, y, DecCoins(nil), false}, {DecCoins{NewInt64DecCoin("foo", 5)}, NewDec(2), DecCoins{NewDecCoinFromDec("foo", MustNewDecFromStr("2.5"))}, false}, } for i, tc := range testCases { tc := tc if tc.panics { require.Panics(t, func() { tc.coins.QuoDecTruncate(tc.input) }) } else { res := tc.coins.QuoDecTruncate(tc.input) require.Equal(t, tc.result, res, "unexpected result; tc #%d, coins: %s, input: %s", i, tc.coins, tc.input) } } } func TestNewDecCoinsWithIsValid(t *testing.T) { fake1 := append(NewDecCoins(NewDecCoin("mytoken", NewInt(10))), DecCoin{Denom: "BTC", Amount: NewDec(10)}) fake2 := append(NewDecCoins(NewDecCoin("mytoken", NewInt(10))), DecCoin{Denom: "BTC", Amount: NewDec(-10)}) tests := []struct { coin DecCoins expectPass bool msg string }{ { NewDecCoins(NewDecCoin("mytoken", NewInt(10))), true, "valid coins should have passed", }, { fake1, false, "invalid denoms", }, { fake2, false, "negative amount", }, } for _, tc := range tests { tc := tc if tc.expectPass { require.True(t, tc.coin.IsValid(), tc.msg) } else { require.False(t, tc.coin.IsValid(), tc.msg) } } } func TestDecCoins_AddDecCoinWithIsValid(t *testing.T) { lengthTestDecCoins := NewDecCoins().Add(NewDecCoin("mytoken", NewInt(10))).Add(DecCoin{Denom: "BTC", Amount: NewDec(10)}) require.Equal(t, 2, len(lengthTestDecCoins), "should be 2") tests := []struct { coin DecCoins expectPass bool msg string }{ { NewDecCoins().Add(NewDecCoin("mytoken", NewInt(10))), true, "valid coins should have passed", }, { NewDecCoins().Add(NewDecCoin("mytoken", NewInt(10))).Add(DecCoin{Denom: "BTC", Amount: NewDec(10)}), false, "invalid denoms", }, { NewDecCoins().Add(NewDecCoin("mytoken", NewInt(10))).Add(DecCoin{Denom: "BTC", Amount: NewDec(-10)}), false, "negative amount", }, } for _, tc := range tests { tc := tc if tc.expectPass { require.True(t, tc.coin.IsValid(), tc.msg) } else { require.False(t, tc.coin.IsValid(), tc.msg) } } }