diff --git a/.pending/bugfixes/sdk/3870-Fix-DecCoins-TruncateDecimal-to-never-return-zero-coins-in b/.pending/bugfixes/sdk/3870-Fix-DecCoins-TruncateDecimal-to-never-return-zero-coins-in new file mode 100644 index 000000000000..f84e6217c0d9 --- /dev/null +++ b/.pending/bugfixes/sdk/3870-Fix-DecCoins-TruncateDecimal-to-never-return-zero-coins-in @@ -0,0 +1,2 @@ +#3870 Fix DecCoins#TruncateDecimal to never return zero coins in +either the truncated coins or the change coins. diff --git a/types/dec_coin.go b/types/dec_coin.go index 4e734d2d7895..5986275f44ed 100644 --- a/types/dec_coin.go +++ b/types/dec_coin.go @@ -114,7 +114,8 @@ func (coin DecCoin) Sub(coinB DecCoin) DecCoin { return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)} } -// return the decimal coins with trunctated decimals, and return the change +// TruncateDecimal returns a Coin with a truncated decimal and a DecCoin for the +// change. Note, the change may be zero. func (coin DecCoin) TruncateDecimal() (Coin, DecCoin) { truncated := coin.Amount.TruncateInt() change := coin.Amount.Sub(truncated.ToDec()) @@ -171,20 +172,20 @@ func (coins DecCoins) String() string { } // TruncateDecimal returns the coins with truncated decimals and returns the -// change. -func (coins DecCoins) TruncateDecimal() (Coins, DecCoins) { - changeSum := DecCoins{} - out := make(Coins, len(coins)) - - for i, coin := range coins { +// change. Note, it will not return any zero-amount coins in either the truncated or +// change coins. +func (coins DecCoins) TruncateDecimal() (truncatedCoins Coins, changeCoins DecCoins) { + for _, coin := range coins { truncated, change := coin.TruncateDecimal() - out[i] = truncated + if !truncated.IsZero() { + truncatedCoins = truncatedCoins.Add(Coins{truncated}) + } if !change.IsZero() { - changeSum = changeSum.Add(DecCoins{change}) + changeCoins = changeCoins.Add(DecCoins{change}) } } - return out, changeSum + return truncatedCoins, changeCoins } // Add adds two sets of DecCoins. diff --git a/types/dec_coin_test.go b/types/dec_coin_test.go index b7b6f4d0c28e..f439484f926c 100644 --- a/types/dec_coin_test.go +++ b/types/dec_coin_test.go @@ -253,6 +253,40 @@ func TestDecCoinsIntersect(t *testing.T) { 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) - // require.Equal(t, tc.expectedResult, in1.Intersect(in2).String(), "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, + ) } } diff --git a/types/decimal.go b/types/decimal.go index bec27468c59b..8fdad0ce06b9 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -332,6 +332,10 @@ func (d Dec) Format(s fmt.State, verb rune) { } func (d Dec) String() string { + if d.Int == nil { + return d.Int.String() + } + isNeg := d.IsNegative() if d.IsNegative() { d = d.Neg() @@ -348,7 +352,6 @@ func (d Dec) String() string { // TODO: Remove trailing zeros // case 1, purely decimal if inputSize <= Precision { - bzStr = make([]byte, Precision+2) // 0. prefix @@ -377,6 +380,7 @@ func (d Dec) String() string { if isNeg { return "-" + string(bzStr) } + return string(bzStr) }