Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[x/stableswap]: Increase precision of osmomath BigDec to prevent stableswap CFMM precision issues #2778

Merged
merged 5 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions osmomath/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strconv"
"strings"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// NOTE: never use new(BigDec) or else we will panic unmarshalling into the
Expand All @@ -18,11 +20,11 @@ type BigDec struct {

const (
// number of decimal places
Precision = 18
Precision = 36

// bytes required to represent the above precision
// Ceiling[Log2[999 999 999 999 999 999]]
DecimalPrecisionBits = 60
// Ceiling[Log2[10**Precision - 1]]
DecimalPrecisionBits = 120

maxDecBitLen = maxBitLen + DecimalPrecisionBits

Expand Down Expand Up @@ -494,6 +496,27 @@ func (d BigDec) MustFloat64() float64 {
}
}

// SdkDec returns the Sdk.Dec representation of a BigDec.
// Values in any additional decimal places are truncated.
func (d BigDec) SdkDec() sdk.Dec {
precisionDiff := Precision - sdk.Precision
precisionFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(precisionDiff)), nil)

if precisionDiff < 0 {
panic("invalid decimal precision")
}

truncatedDec := sdk.NewDecFromBigIntWithPrec(new(big.Int).Quo(d.BigInt(), precisionFactor), sdk.Precision)

return truncatedDec
}

// BigDecFromSdkDec returns the BigDec representation of an SdkDec.
// Values in any additional decimal places are truncated.
func BigDecFromSdkDec(d sdk.Dec) BigDec {
return NewDecFromBigIntWithPrec(d.BigInt(), sdk.Precision)
}

// ____
// __| |__ "chop 'em
// ` \ round!"
Expand Down
166 changes: 109 additions & 57 deletions osmomath/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gopkg.in/yaml.v2"
Expand Down Expand Up @@ -68,9 +69,9 @@ func (s *decimalTestSuite) TestNewDecFromStr() {
{"0.75", false, NewDecWithPrec(75, 2)},
{"0.8", false, NewDecWithPrec(8, 1)},
{"0.11111", false, NewDecWithPrec(11111, 5)},
{"314460551102969.3144278234343371835", true, NewBigDec(3141203149163817869)},
{"314460551102969.31442782343433718353144278234343371835", true, NewBigDec(3141203149163817869)},
{
"314460551102969314427823434337.1835718092488231350",
"314460551102969314427823434337.18357180924882313501835718092488231350",
true, NewDecFromBigIntWithPrec(largeBigInt, 4),
},
{
Expand All @@ -83,7 +84,7 @@ func (s *decimalTestSuite) TestNewDecFromStr() {
{"foobar", true, BigDec{}},
{"0.foobar", true, BigDec{}},
{"0.foobar.", true, BigDec{}},
{"23258839177459420497578361852416145099316523541994177929007686373780457219628733546438113622840434097944400691400517693873107252115668992", true, BigDec{}},
{"179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216", true, BigDec{}},
}

for tcIndex, tc := range tests {
Expand Down Expand Up @@ -112,14 +113,15 @@ func (s *decimalTestSuite) TestDecString() {
d BigDec
want string
}{
{NewBigDec(0), "0.000000000000000000"},
{NewBigDec(1), "1.000000000000000000"},
{NewBigDec(10), "10.000000000000000000"},
{NewBigDec(12340), "12340.000000000000000000"},
{NewDecWithPrec(12340, 4), "1.234000000000000000"},
{NewDecWithPrec(12340, 5), "0.123400000000000000"},
{NewDecWithPrec(12340, 8), "0.000123400000000000"},
{NewDecWithPrec(1009009009009009009, 17), "10.090090090090090090"},
{NewBigDec(0), "0.000000000000000000000000000000000000"},
{NewBigDec(1), "1.000000000000000000000000000000000000"},
{NewBigDec(10), "10.000000000000000000000000000000000000"},
{NewBigDec(12340), "12340.000000000000000000000000000000000000"},
{NewDecWithPrec(12340, 4), "1.234000000000000000000000000000000000"},
{NewDecWithPrec(12340, 5), "0.123400000000000000000000000000000000"},
{NewDecWithPrec(12340, 8), "0.000123400000000000000000000000000000"},
{NewDecWithPrec(1009009009009009009, 17), "10.090090090090090090000000000000000000"},
{MustNewDecFromStr("10.090090090090090090090090090090090090"), "10.090090090090090090090090090090090090"},
}
for tcIndex, tc := range tests {
s.Require().Equal(tc.want, tc.d.String(), "bad String(), index: %v", tcIndex)
Expand Down Expand Up @@ -148,6 +150,56 @@ func (s *decimalTestSuite) TestDecFloat64() {
}
}

func (s *decimalTestSuite) TestSdkDec() {
tests := []struct {
d BigDec
want sdk.Dec
expPanic bool
}{
{NewBigDec(0), sdk.MustNewDecFromStr("0.000000000000000000"), false},
{NewBigDec(1), sdk.MustNewDecFromStr("1.000000000000000000"), false},
{NewBigDec(10), sdk.MustNewDecFromStr("10.000000000000000000"), false},
{NewBigDec(12340), sdk.MustNewDecFromStr("12340.000000000000000000"), false},
{NewDecWithPrec(12340, 4), sdk.MustNewDecFromStr("1.234000000000000000"), false},
{NewDecWithPrec(12340, 5), sdk.MustNewDecFromStr("0.123400000000000000"), false},
{NewDecWithPrec(12340, 8), sdk.MustNewDecFromStr("0.000123400000000000"), false},
{NewDecWithPrec(1009009009009009009, 17), sdk.MustNewDecFromStr("10.090090090090090090"), false},
}
for tcIndex, tc := range tests {
if tc.expPanic {
s.Require().Panics(func() { tc.d.SdkDec() })
} else {
value := tc.d.SdkDec()
s.Require().Equal(tc.want, value, "bad SdkDec(), index: %v", tcIndex)
}
}
}

func (s *decimalTestSuite) TestBigDecFromSdkDec() {
tests := []struct {
d sdk.Dec
want BigDec
expPanic bool
}{
{sdk.MustNewDecFromStr("0.000000000000000000"), NewBigDec(0), false},
{sdk.MustNewDecFromStr("1.000000000000000000"), NewBigDec(1), false},
{sdk.MustNewDecFromStr("10.000000000000000000"), NewBigDec(10), false},
{sdk.MustNewDecFromStr("12340.000000000000000000"), NewBigDec(12340), false},
{sdk.MustNewDecFromStr("1.234000000000000000"), NewDecWithPrec(12340, 4), false},
{sdk.MustNewDecFromStr("0.123400000000000000"), NewDecWithPrec(12340, 5), false},
{sdk.MustNewDecFromStr("0.000123400000000000"), NewDecWithPrec(12340, 8), false},
{sdk.MustNewDecFromStr("10.090090090090090090"), NewDecWithPrec(1009009009009009009, 17), false},
}
for tcIndex, tc := range tests {
if tc.expPanic {
s.Require().Panics(func() { BigDecFromSdkDec(tc.d) })
} else {
value := BigDecFromSdkDec(tc.d)
s.Require().Equal(tc.want, value, "bad BigDecFromSdkDec(), index: %v", tcIndex)
}
}
}

func (s *decimalTestSuite) TestEqualities() {
tests := []struct {
d1, d2 BigDec
Expand Down Expand Up @@ -227,7 +279,7 @@ func (s *decimalTestSuite) TestArithmetic() {

{
NewBigDec(3), NewBigDec(7), NewBigDec(21), NewBigDec(21),
NewDecWithPrec(428571428571428571, 18), NewDecWithPrec(428571428571428572, 18), NewDecWithPrec(428571428571428571, 18),
MustNewDecFromStr("0.428571428571428571428571428571428571"), MustNewDecFromStr("0.428571428571428571428571428571428572"), MustNewDecFromStr("0.428571428571428571428571428571428571"),
NewBigDec(10), NewBigDec(-4),
},
{
Expand All @@ -243,7 +295,7 @@ func (s *decimalTestSuite) TestArithmetic() {
},
{
NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), NewDecWithPrec(1109889, 8),
MustNewDecFromStr("10.009009009009009009"), MustNewDecFromStr("10.009009009009009010"), MustNewDecFromStr("10.009009009009009009"),
MustNewDecFromStr("10.009009009009009009009009009009009009"), MustNewDecFromStr("10.009009009009009009009009009009009010"), MustNewDecFromStr("10.009009009009009009009009009009009009"),
NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1),
},
}
Expand Down Expand Up @@ -336,7 +388,7 @@ func (s *decimalTestSuite) TestStringOverflow() {
s.Require().NoError(err)
dec3 := dec1.Add(dec2)
s.Require().Equal(
"19844653375691057515930281852116324640.000000000000000000",
"19844653375691057515930281852116324640.000000000000000000000000000000000000",
dec3.String(),
)
}
Expand All @@ -363,14 +415,14 @@ func (s *decimalTestSuite) TestDecCeil() {
input BigDec
expected BigDec
}{
{NewDecWithPrec(1000000000000000, Precision), NewBigDec(1)}, // 0.001 => 1.0
{NewDecWithPrec(-1000000000000000, Precision), ZeroDec()}, // -0.001 => 0.0
{MustNewDecFromStr("0.001"), NewBigDec(1)}, // 0.001 => 1.0
{MustNewDecFromStr("-0.001"), ZeroDec()}, // -0.001 => 0.0
{ZeroDec(), ZeroDec()}, // 0.0 => 0.0
{NewDecWithPrec(900000000000000000, Precision), NewBigDec(1)}, // 0.9 => 1.0
{NewDecWithPrec(4001000000000000000, Precision), NewBigDec(5)}, // 4.001 => 5.0
{NewDecWithPrec(-4001000000000000000, Precision), NewBigDec(-4)}, // -4.001 => -4.0
{NewDecWithPrec(4700000000000000000, Precision), NewBigDec(5)}, // 4.7 => 5.0
{NewDecWithPrec(-4700000000000000000, Precision), NewBigDec(-4)}, // -4.7 => -4.0
{MustNewDecFromStr("0.9"), NewBigDec(1)}, // 0.9 => 1.0
{MustNewDecFromStr("4.001"), NewBigDec(5)}, // 4.001 => 5.0
{MustNewDecFromStr("-4.001"), NewBigDec(-4)}, // -4.001 => -4.0
{MustNewDecFromStr("4.7"), NewBigDec(5)}, // 4.7 => 5.0
{MustNewDecFromStr("-4.7"), NewBigDec(-4)}, // -4.7 => -4.0
}

for i, tc := range testCases {
Expand All @@ -390,7 +442,7 @@ func (s *decimalTestSuite) TestPower() {
{NewDecWithPrec(2, 1), 2, NewDecWithPrec(4, 2)}, // 0.2 ^ 2 => 0.04
{NewDecFromInt(NewInt(3)), 3, NewDecFromInt(NewInt(27))}, // 3 ^ 3 => 27
{NewDecFromInt(NewInt(-3)), 4, NewDecFromInt(NewInt(81))}, // -3 ^ 4 = 81
{NewDecWithPrec(1414213562373095049, 18), 2, NewDecFromInt(NewInt(2))}, // 1.414213562373095049 ^ 2 = 2
{MustNewDecFromStr("1.414213562373095048801688724209698079"), 2, NewDecFromInt(NewInt(2))}, // 1.414213562373095048801688724209698079 ^ 2 = 2
}

for i, tc := range testCases {
Expand All @@ -410,11 +462,11 @@ func (s *decimalTestSuite) TestApproxRoot() {
{NewDecWithPrec(4, 2), 2, NewDecWithPrec(2, 1)}, // 0.04 ^ (0.5) => 0.2
{NewDecFromInt(NewInt(27)), 3, NewDecFromInt(NewInt(3))}, // 27 ^ (1/3) => 3
{NewDecFromInt(NewInt(-81)), 4, NewDecFromInt(NewInt(-3))}, // -81 ^ (0.25) => -3
{NewDecFromInt(NewInt(2)), 2, NewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049
{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) ≈ 1.00000000016
{SmallestDec(), 2, NewDecWithPrec(1, 9)}, // 1e-18 ^ (0.5) => 1e-9
{SmallestDec(), 3, MustNewDecFromStr("0.000000999999999997")}, // 1e-18 ^ (1/3) => 1e-6
{NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031900")}, // 1e-8 ^ (1/3) ≈ 0.00215443469
{NewDecFromInt(NewInt(2)), 2, MustNewDecFromStr("1.414213562373095048801688724209698079")}, // 2 ^ (0.5) => 1.414213562373095048801688724209698079
{NewDecWithPrec(1005, 3), 31536000, MustNewDecFromStr("1.000000000158153903837946258002096839")}, // 1.005 ^ (1/31536000) ≈ 1.000000000158153903837946258002096839
{SmallestDec(), 2, NewDecWithPrec(1, 18)}, // 1e-36 ^ (0.5) => 1e-18
{SmallestDec(), 3, MustNewDecFromStr("0.000000000001000000000000000002431786")}, // 1e-36 ^ (1/3) => 1e-12
{NewDecWithPrec(1, 8), 3, MustNewDecFromStr("0.002154434690031883721759293566519280")}, // 1e-8 ^ (1/3) ≈ 0.002154434690031883721759293566519
}

// In the case of 1e-8 ^ (1/3), the result repeats every 5 iterations starting from iteration 24
Expand All @@ -438,7 +490,7 @@ func (s *decimalTestSuite) TestApproxSqrt() {
{NewDecWithPrec(4, 2), NewDecWithPrec(2, 1)}, // 0.09 => 0.3
{NewDecFromInt(NewInt(9)), NewDecFromInt(NewInt(3))}, // 9 => 3
{NewDecFromInt(NewInt(-9)), NewDecFromInt(NewInt(-3))}, // -9 => -3
{NewDecFromInt(NewInt(2)), NewDecWithPrec(1414213562373095049, 18)}, // 2 => 1.414213562373095049
{NewDecFromInt(NewInt(2)), MustNewDecFromStr("1.414213562373095048801688724209698079")}, // 2 => 1.414213562373095048801688724209698079
}

for i, tc := range testCases {
Expand All @@ -453,24 +505,24 @@ func (s *decimalTestSuite) TestDecSortableBytes() {
d BigDec
want []byte
}{
{NewBigDec(0), []byte("000000000000000000.000000000000000000")},
{NewBigDec(1), []byte("000000000000000001.000000000000000000")},
{NewBigDec(10), []byte("000000000000000010.000000000000000000")},
{NewBigDec(12340), []byte("000000000000012340.000000000000000000")},
{NewDecWithPrec(12340, 4), []byte("000000000000000001.234000000000000000")},
{NewDecWithPrec(12340, 5), []byte("000000000000000000.123400000000000000")},
{NewDecWithPrec(12340, 8), []byte("000000000000000000.000123400000000000")},
{NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000010.090090090090090090")},
{NewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000010.090090090090090090")},
{NewBigDec(1000000000000000000), []byte("max")},
{NewBigDec(-1000000000000000000), []byte("--")},
{NewBigDec(0), []byte("000000000000000000000000000000000000.000000000000000000000000000000000000")},
{NewBigDec(1), []byte("000000000000000000000000000000000001.000000000000000000000000000000000000")},
{NewBigDec(10), []byte("000000000000000000000000000000000010.000000000000000000000000000000000000")},
{NewBigDec(12340), []byte("000000000000000000000000000000012340.000000000000000000000000000000000000")},
{NewDecWithPrec(12340, 4), []byte("000000000000000000000000000000000001.234000000000000000000000000000000000")},
{NewDecWithPrec(12340, 5), []byte("000000000000000000000000000000000000.123400000000000000000000000000000000")},
{NewDecWithPrec(12340, 8), []byte("000000000000000000000000000000000000.000123400000000000000000000000000000")},
{NewDecWithPrec(1009009009009009009, 17), []byte("000000000000000000000000000000000010.090090090090090090000000000000000000")},
{NewDecWithPrec(-1009009009009009009, 17), []byte("-000000000000000000000000000000000010.090090090090090090000000000000000000")},
{MustNewDecFromStr("1000000000000000000000000000000000000"), []byte("max")},
{MustNewDecFromStr("-1000000000000000000000000000000000000"), []byte("--")},
}
for tcIndex, tc := range tests {
s.Require().Equal(tc.want, SortableDecBytes(tc.d), "bad String(), index: %v", tcIndex)
}

s.Require().Panics(func() { SortableDecBytes(NewBigDec(1000000000000000001)) })
s.Require().Panics(func() { SortableDecBytes(NewBigDec(-1000000000000000001)) })
s.Require().Panics(func() { SortableDecBytes(MustNewDecFromStr("1000000000000000000000000000000000001")) })
s.Require().Panics(func() { SortableDecBytes(MustNewDecFromStr("-1000000000000000000000000000000000001")) })
}

func (s *decimalTestSuite) TestDecEncoding() {
Expand All @@ -482,32 +534,32 @@ func (s *decimalTestSuite) TestDecEncoding() {
}{
{
NewBigDec(0), "30",
"\"0.000000000000000000\"",
"\"0.000000000000000000\"\n",
"\"0.000000000000000000000000000000000000\"",
"\"0.000000000000000000000000000000000000\"\n",
},
{
NewDecWithPrec(4, 2),
"3430303030303030303030303030303030",
"\"0.040000000000000000\"",
"\"0.040000000000000000\"\n",
"3430303030303030303030303030303030303030303030303030303030303030303030",
"\"0.040000000000000000000000000000000000\"",
"\"0.040000000000000000000000000000000000\"\n",
},
{
NewDecWithPrec(-4, 2),
"2D3430303030303030303030303030303030",
"\"-0.040000000000000000\"",
"\"-0.040000000000000000\"\n",
"2D3430303030303030303030303030303030303030303030303030303030303030303030",
"\"-0.040000000000000000000000000000000000\"",
"\"-0.040000000000000000000000000000000000\"\n",
},
{
NewDecWithPrec(1414213562373095049, 18),
"31343134323133353632333733303935303439",
"\"1.414213562373095049\"",
"\"1.414213562373095049\"\n",
MustNewDecFromStr("1.414213562373095048801688724209698079"),
"31343134323133353632333733303935303438383031363838373234323039363938303739",
"\"1.414213562373095048801688724209698079\"",
"\"1.414213562373095048801688724209698079\"\n",
},
{
NewDecWithPrec(-1414213562373095049, 18),
"2D31343134323133353632333733303935303439",
"\"-1.414213562373095049\"",
"\"-1.414213562373095049\"\n",
MustNewDecFromStr("-1.414213562373095048801688724209698079"),
"2D31343134323133353632333733303935303438383031363838373234323039363938303739",
"\"-1.414213562373095048801688724209698079\"",
"\"-1.414213562373095048801688724209698079\"\n",
},
}

Expand Down
2 changes: 1 addition & 1 deletion osmomath/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"testing"
)

const maxBitLen = 512
const maxBitLen = 1024

func newIntegerFromString(s string) (*big.Int, bool) {
return new(big.Int).SetString(s, 0)
Expand Down
24 changes: 12 additions & 12 deletions osmomath/int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ func (s *intTestSuite) TestFromUint64() {
}

func (s *intTestSuite) TestIntPanic() {
// Max Int = 2^512-1 = 1.340781e+154
// Min Int = -(2^512-1) = -1.340781e+154
s.Require().NotPanics(func() { NewIntWithDecimal(4, 153) })
i1 := NewIntWithDecimal(5, 153)
s.Require().NotPanics(func() { NewIntWithDecimal(5, 153) })
i2 := NewIntWithDecimal(6, 153)
s.Require().NotPanics(func() { NewIntWithDecimal(6, 153) })
i3 := NewIntWithDecimal(7, 153)

s.Require().Panics(func() { NewIntWithDecimal(2, 154) })
s.Require().Panics(func() { NewIntWithDecimal(9, 160) })
// Max Int = 2^1024-1 = 8.988466e+308
// Min Int = -(2^1024-1) = -8.988466e+308
s.Require().NotPanics(func() { NewIntWithDecimal(4, 307) })
i1 := NewIntWithDecimal(4, 307)
s.Require().NotPanics(func() { NewIntWithDecimal(5, 307) })
i2 := NewIntWithDecimal(5, 307)
s.Require().NotPanics(func() { NewIntWithDecimal(92, 306) })
i3 := NewIntWithDecimal(92, 306)

s.Require().Panics(func() { NewIntWithDecimal(2, 308) })
s.Require().Panics(func() { NewIntWithDecimal(9, 340) })

// Overflow check
s.Require().NotPanics(func() { i1.Add(i1) })
Expand Down Expand Up @@ -84,7 +84,7 @@ func (s *intTestSuite) TestIntPanic() {
s.Require().Panics(func() { i3.Mul(i3.Neg()) })

// Bound check
intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(512), nil), big.NewInt(1)))
intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(1024), nil), big.NewInt(1)))
intmin := intmax.Neg()
s.Require().NotPanics(func() { intmax.Add(ZeroInt()) })
s.Require().NotPanics(func() { intmin.Sub(ZeroInt()) })
Expand Down
Loading