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.NotPanics(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.NotPanics(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.NotPanics(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 TestFilteredZeroDecCoins(t *testing.T) { cases := []struct { name string input DecCoins original string expected string }{ { name: "all greater than zero", input: DecCoins{ {"testa", NewDec(1)}, {"testb", NewDec(2)}, {"testc", NewDec(3)}, {"testd", NewDec(4)}, {"teste", NewDec(5)}, }, original: "1.000000000000000000testa,2.000000000000000000testb,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", expected: "1.000000000000000000testa,2.000000000000000000testb,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", }, { name: "zero coin in middle", input: DecCoins{ {"testa", NewDec(1)}, {"testb", NewDec(2)}, {"testc", NewDec(0)}, {"testd", NewDec(4)}, {"teste", NewDec(5)}, }, original: "1.000000000000000000testa,2.000000000000000000testb,0.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", expected: "1.000000000000000000testa,2.000000000000000000testb,4.000000000000000000testd,5.000000000000000000teste", }, { name: "zero coin end (unordered)", input: DecCoins{ {"teste", NewDec(5)}, {"testc", NewDec(3)}, {"testa", NewDec(1)}, {"testd", NewDec(4)}, {"testb", NewDec(0)}, }, original: "5.000000000000000000teste,3.000000000000000000testc,1.000000000000000000testa,4.000000000000000000testd,0.000000000000000000testb", expected: "1.000000000000000000testa,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", }, } for _, tt := range cases { tt := tt t.Run(tt.name, func(t *testing.T) { undertest := NewDecCoins(tt.input...) require.Equal(t, tt.expected, undertest.String(), "NewDecCoins must return expected results") require.Equal(t, tt.original, tt.input.String(), "input must be unmodified and match original") }) } } 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)}, true, "valid uppercase denom", }, { DecCoin{Denom: "Bitcoin", Amount: NewDec(10)}, true, "valid mixed case denom", }, { 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 { name string coins DecCoins before, after bool // valid before/after sort }{ {"valid coins", good, true, true}, {"empty coins", empty, false, false}, {"unsorted coins (1)", badSort1, false, true}, {"unsorted coins (2)", badSort2, false, true}, {"zero amount coins", badAmt, false, false}, {"duplicate coins", dup, false, false}, } for _, tc := range cases { require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting; %s", tc.name) tc.coins.Sort() require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting; %s", tc.name) } } func TestDecCoinsValidate(t *testing.T) { testCases := []struct { input DecCoins expectedPass 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{"BTC", NewDec(5)}}, true}, {DecCoins{DecCoin{"0BTC", 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{"BTC", NewDec(5)}, DecCoin{testDenom2, NewDec(100000)}}, true}, {DecCoins{DecCoin{"0BTC", NewDec(5)}, DecCoin{testDenom2, NewDec(100000)}}, false}, } for i, tc := range testCases { err := tc.input.Validate() if tc.expectedPass { require.NoError(t, err, "unexpected result for test case #%d, input: %v", i, tc.input) } else { require.Error(t, err, "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", DecCoins{NewDecCoinFromDec("STAKE", NewDecWithPrec(4000000000000000, Precision))}, false, }, { "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: "10BTC", 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: "0BTC", 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) } } }