diff --git a/PENDING.md b/PENDING.md index 3a92041c333a..eda622202c1c 100644 --- a/PENDING.md +++ b/PENDING.md @@ -81,6 +81,7 @@ BREAKING CHANGES * [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding * [x/stake] \#2500 Block conflicting redelegations until we add an index * [x/params] Global Paramstore refactored + * [types] \#2506 sdk.Dec MarshalJSON now marshals as a normal Decimal, with 10 digits of decimal precision * [x/stake] \#2508 Utilize Tendermint power for validator power key * [x/stake] \#2531 Remove all inflation logic * [x/mint] \#2531 Add minting module and inflation logic diff --git a/types/decimal.go b/types/decimal.go index e9623995f008..c981f6dca63b 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -248,29 +248,33 @@ func (d Dec) QuoInt(i Int) Dec { } func (d Dec) String() string { - str := d.ToLeftPaddedWithDecimals(Precision) - placement := len(str) - Precision - if placement < 0 { - panic("too few decimal digits") + bz, err := d.Int.MarshalText() + if err != nil { + return "" + } + var bzWDec []byte + inputSize := len(bz) + // TODO: Remove trailing zeros + // case 1, purely decimal + if inputSize <= 10 { + bzWDec = make([]byte, 12) + // 0. prefix + bzWDec[0] = byte('0') + bzWDec[1] = byte('.') + // set relevant digits to 0 + for i := 0; i < 10-inputSize; i++ { + bzWDec[i+2] = byte('0') + } + // set last few digits + copy(bzWDec[2+(10-inputSize):], bz) + } else { + // inputSize + 1 to account for the decimal point that is being added + bzWDec = make([]byte, inputSize+1) + copy(bzWDec, bz[:inputSize-10]) + bzWDec[inputSize-10] = byte('.') + copy(bzWDec[inputSize-9:], bz[inputSize-10:]) } - return str[:placement] + "." + str[placement:] -} - -// TODO panic if negative or if totalDigits < len(initStr)??? -// evaluate as an integer and return left padded string -func (d Dec) ToLeftPaddedWithDecimals(totalDigits int8) string { - intStr := d.Int.String() - fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s` - return fmt.Sprintf(fcode, intStr) -} - -// TODO panic if negative or if totalDigits < len(initStr)??? -// evaluate as an integer and return left padded string -func (d Dec) ToLeftPadded(totalDigits int8) string { - chopped := chopPrecisionAndRoundNonMutative(d.Int) - intStr := chopped.String() - fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s` - return fmt.Sprintf(fcode, intStr) + return string(bzWDec) } // ____ @@ -407,17 +411,13 @@ func (d *Dec) UnmarshalAmino(text string) (err error) { return nil } -// MarshalJSON defines custom encoding scheme +// MarshalJSON marshals the decimal func (d Dec) MarshalJSON() ([]byte, error) { if d.Int == nil { return nilJSON, nil } - bz, err := d.Int.MarshalText() - if err != nil { - return nil, err - } - return json.Marshal(string(bz)) + return json.Marshal(d.String()) } // UnmarshalJSON defines custom decoding scheme @@ -431,7 +431,13 @@ func (d *Dec) UnmarshalJSON(bz []byte) error { if err != nil { return err } - return d.Int.UnmarshalText([]byte(text)) + // TODO: Reuse dec allocation + newDec, err := NewDecFromStr(text) + if err != nil { + return err + } + d.Int = newDec.Int + return nil } //___________________________________________________________________________________ diff --git a/types/decimal_test.go b/types/decimal_test.go index a6ec0740e819..07329c7dc7da 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -4,6 +4,8 @@ import ( "math/big" "testing" + "github.com/stretchr/testify/assert" + "github.com/cosmos/cosmos-sdk/codec" "github.com/stretchr/testify/require" ) @@ -228,26 +230,46 @@ func TestTruncate(t *testing.T) { } } -func TestToLeftPadded(t *testing.T) { +var cdc = codec.New() + +func TestDecMarshalJSON(t *testing.T) { + decimal := func(i int64) Dec { + d := NewDec(0) + d.Int = new(big.Int).SetInt64(i) + return d + } tests := []struct { - dec Dec - digits int8 - exp string + name string + d Dec + want string + wantErr bool // if wantErr = false, will also attempt unmarshaling }{ - {mustNewDecFromStr(t, "33.3"), 8, "00000033"}, - {mustNewDecFromStr(t, "50"), 8, "00000050"}, - {mustNewDecFromStr(t, "333"), 8, "00000333"}, - {mustNewDecFromStr(t, "333"), 12, "000000000333"}, - {mustNewDecFromStr(t, "0.3333"), 8, "00000000"}, + {"zero", decimal(0), "\"0.0000000000\"", false}, + {"one", decimal(1), "\"0.0000000001\"", false}, + {"ten", decimal(10), "\"0.0000000010\"", false}, + {"12340", decimal(12340), "\"0.0000012340\"", false}, + {"zeroInt", NewDec(0), "\"0.0000000000\"", false}, + {"oneInt", NewDec(1), "\"1.0000000000\"", false}, + {"tenInt", NewDec(10), "\"10.0000000000\"", false}, + {"12340Int", NewDec(12340), "\"12340.0000000000\"", false}, } - for tcIndex, tc := range tests { - res := tc.dec.ToLeftPadded(tc.digits) - require.Equal(t, tc.exp, res, "incorrect left padding, tc %d", tcIndex) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.d.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("Dec.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + assert.Equal(t, tt.want, string(got), "incorrect marshalled value") + unmarshalledDec := NewDec(0) + unmarshalledDec.UnmarshalJSON(got) + assert.Equal(t, tt.d, unmarshalledDec, "incorrect unmarshalled value") + } + }) } } -var cdc = codec.New() - func TestZeroDeserializationJSON(t *testing.T) { d := Dec{new(big.Int)} err := cdc.UnmarshalJSON([]byte(`"0"`), &d)