diff --git a/osmomath/decimal.go b/osmomath/decimal.go index b6aa442ad2a..527bab9caa6 100644 --- a/osmomath/decimal.go +++ b/osmomath/decimal.go @@ -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 @@ -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 @@ -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!" diff --git a/osmomath/decimal_test.go b/osmomath/decimal_test.go index f02e581cbeb..b5441930eaa 100644 --- a/osmomath/decimal_test.go +++ b/osmomath/decimal_test.go @@ -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" @@ -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), }, { @@ -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 { @@ -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) @@ -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 @@ -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), }, { @@ -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), }, } @@ -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(), ) } @@ -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 { @@ -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 { @@ -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 @@ -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 { @@ -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() { @@ -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", }, } diff --git a/osmomath/int.go b/osmomath/int.go index 9d4f5e6501b..c3ae4ef288e 100644 --- a/osmomath/int.go +++ b/osmomath/int.go @@ -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) diff --git a/osmomath/int_test.go b/osmomath/int_test.go index 33fe5dbc7c5..a3e376a95d4 100644 --- a/osmomath/int_test.go +++ b/osmomath/int_test.go @@ -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) }) @@ -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()) }) diff --git a/x/gamm/pool-models/stableswap/amm.go b/x/gamm/pool-models/stableswap/amm.go index 30c92620841..186c39b4a67 100644 --- a/x/gamm/pool-models/stableswap/amm.go +++ b/x/gamm/pool-models/stableswap/amm.go @@ -5,17 +5,18 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/v12/osmomath" "github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/internal/cfmm_common" types "github.com/osmosis-labs/osmosis/v12/x/gamm/types" ) var ( - cubeRootTwo, _ = sdk.NewDec(2).ApproxRoot(3) + cubeRootTwo, _ = osmomath.NewBigDec(2).ApproxRoot(3) threeCubeRootTwo = cubeRootTwo.MulInt64(3) ) // solidly CFMM is xy(x^2 + y^2) = k -func cfmmConstant(xReserve, yReserve sdk.Dec) sdk.Dec { +func cfmmConstant(xReserve, yReserve osmomath.BigDec) osmomath.BigDec { if !xReserve.IsPositive() || !yReserve.IsPositive() { panic("invalid input: reserves must be positive") } @@ -30,7 +31,7 @@ func cfmmConstant(xReserve, yReserve sdk.Dec) sdk.Dec { // outside of x and y (e.g. u = wz), and v is the sum // of their squares (e.g. v = w^2 + z^2). // When u = 1 and v = 0, this is equivalent to solidly's CFMM -func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares sdk.Dec) sdk.Dec { +func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares osmomath.BigDec) osmomath.BigDec { if !xReserve.IsPositive() || !yReserve.IsPositive() || !uReserve.IsPositive() || vSumSquares.IsNegative() { panic("invalid input: reserves must be positive") } @@ -46,7 +47,7 @@ func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares sdk.Dec) sdk.De // how many units `a` of x do we get out. // So we solve the following expression for `a` // xy(x^2 + y^2) = (x - a)(y + b)((x - a)^2 + (y + b)^2) -func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec { +func solveCfmm(xReserve, yReserve, yIn osmomath.BigDec) osmomath.BigDec { if !xReserve.IsPositive() || !yReserve.IsPositive() || !yIn.IsPositive() { panic("invalid input: reserves and input must be positive") } @@ -94,7 +95,7 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec { // and maybe in state. x := xReserve y := yReserve - x2py2 := x.Mul(x).AddMut(y.Mul(y)) + x2py2 := x.Mul(x).Add(y.Mul(y)) xy := x.Mul(y) @@ -128,19 +129,19 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec { e := xy.Mul(x2py2) // xy(x^2 + y^2) // t1 = -27 (b + y)^2 e - t1 := e.Mul(bpy2).MulInt64Mut(-27) + t1 := e.Mul(bpy2).MulInt64(-27) // compute d = (b + y)^2 sqrt(729 e^2 + 108 (b+y)^8) bpy8 := bpy4.Mul(bpy4) - sqrt_inner := e.MulMut(e).MulInt64Mut(729).AddMut(bpy8.MulInt64Mut(108)) // 729 e^2 + 108 (b+y)^8 + sqrt_inner := e.Mul(e).MulInt64(729).Add(bpy8.MulInt64(108)) // 729 e^2 + 108 (b+y)^8 sqrt, err := sqrt_inner.ApproxSqrt() if err != nil { panic(err) } - d := sqrt.MulMut(bpy2) + d := sqrt.Mul(bpy2) // foo = (t1 + d)^(1/3) - foo3 := t1.AddMut(d) + foo3 := t1.Add(d) foo, _ := foo3.ApproxRoot(3) // a = {foo / (3 2^(1/3) (b + y))} @@ -171,7 +172,7 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec { // how many units `a` of x do we get out. // So we solve the following expression for `a` // xyz(x^2 + y^2 + w) = (x - a)(y + b)z((x - a)^2 + (y + b)^2 + w) -func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn sdk.Dec) sdk.Dec { +func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn osmomath.BigDec) osmomath.BigDec { if !xReserve.IsPositive() || !yReserve.IsPositive() || !yIn.IsPositive() { panic("invalid input: reserves and input must be positive") } @@ -275,23 +276,23 @@ func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn sdk.Dec) sdk.Dec { return a } -func approxDecEqual(a, b, tol sdk.Dec) bool { +func approxDecEqual(a, b, tol osmomath.BigDec) bool { diff := a.Sub(b).Abs() return diff.Quo(a).LTE(tol) && diff.Quo(b).LTE(tol) } var ( - twodec = sdk.MustNewDecFromStr("2.0") - threshold = sdk.NewDecWithPrec(1, 10) // Correct within a factor of 1 * 10^{-10} + twodec = osmomath.MustNewDecFromStr("2.0") + threshold = osmomath.NewDecWithPrec(1, 10) // Correct within a factor of 1 * 10^{-10} ) // solveCFMMBinarySearch searches the correct dx using binary search over constant K. // added for future extension -func solveCFMMBinarySearch(constantFunction func(sdk.Dec, sdk.Dec) sdk.Dec) func(sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec { - return func(xReserve, yReserve, yIn sdk.Dec) sdk.Dec { +func solveCFMMBinarySearch(constantFunction func(osmomath.BigDec, osmomath.BigDec) osmomath.BigDec) func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec { + return func(xReserve, yReserve, yIn osmomath.BigDec) osmomath.BigDec { k := constantFunction(xReserve, yReserve) yf := yReserve.Add(yIn) - x_low_est := sdk.ZeroDec() + x_low_est := osmomath.ZeroDec() x_high_est := xReserve x_est := (x_high_est.Add(x_low_est)).Quo(twodec) cur_k := constantFunction(x_est, yf) @@ -310,11 +311,11 @@ func solveCFMMBinarySearch(constantFunction func(sdk.Dec, sdk.Dec) sdk.Dec) func // solveCFMMBinarySearch searches the correct dx using binary search over constant K. // added for future extension -func solveCFMMBinarySearchMulti(constantFunction func(sdk.Dec, sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec) func(sdk.Dec, sdk.Dec, sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec { - return func(xReserve, yReserve, uReserve, wSumSquares, yIn sdk.Dec) sdk.Dec { +func solveCFMMBinarySearchMulti(constantFunction func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec) func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec { + return func(xReserve, yReserve, uReserve, wSumSquares, yIn osmomath.BigDec) osmomath.BigDec { k := constantFunction(xReserve, yReserve, uReserve, wSumSquares) yf := yReserve.Add(yIn) - x_low_est := sdk.ZeroDec() + x_low_est := osmomath.ZeroDec() x_high_est := xReserve x_est := (x_high_est.Add(x_low_est)).Quo(twodec) cur_k := constantFunction(x_est, yf, uReserve, wSumSquares) @@ -331,7 +332,7 @@ func solveCFMMBinarySearchMulti(constantFunction func(sdk.Dec, sdk.Dec, sdk.Dec, } } -func spotPrice(baseReserve, quoteReserve sdk.Dec) sdk.Dec { +func spotPrice(baseReserve, quoteReserve osmomath.BigDec) osmomath.BigDec { // y = baseAsset, x = quoteAsset // Define f_{y -> x}(a) as the function that outputs the amount of tokens X you'd get by // trading "a" units of Y against the pool, assuming 0 swap fee, at the current liquidity. @@ -346,7 +347,7 @@ func spotPrice(baseReserve, quoteReserve sdk.Dec) sdk.Dec { // We arbitrarily choose a = 1, and anticipate that this is a small value at the scale of // xReserve & yReserve. - a := sdk.OneDec() + a := osmomath.OneDec() // no need to divide by a, since a = 1. return solveCfmm(baseReserve, quoteReserve, a) } @@ -359,9 +360,9 @@ func (p *Pool) calcOutAmtGivenIn(tokenIn sdk.Coin, tokenOutDenom string, swapFee } tokenInSupply, tokenOutSupply := reserves[0], reserves[1] // We are solving for the amount of token out, hence x = tokenOutSupply, y = tokenInSupply - cfmmOut := solveCfmm(tokenOutSupply, tokenInSupply, tokenIn.Amount.ToDec()) + cfmmOut := solveCfmm(osmomath.BigDecFromSdkDec(tokenOutSupply), osmomath.BigDecFromSdkDec(tokenInSupply), osmomath.BigDecFromSdkDec(tokenIn.Amount.ToDec())) outAmt := p.getDescaledPoolAmt(tokenOutDenom, cfmmOut) - return outAmt, nil + return outAmt.SdkDec(), nil } // returns inAmt as a decimal @@ -373,9 +374,9 @@ func (p *Pool) calcInAmtGivenOut(tokenOut sdk.Coin, tokenInDenom string, swapFee tokenInSupply, tokenOutSupply := reserves[0], reserves[1] // We are solving for the amount of token in, cfmm(x,y) = cfmm(x + x_in, y - y_out) // x = tokenInSupply, y = tokenOutSupply, yIn = -tokenOutAmount - cfmmIn := solveCfmm(tokenInSupply, tokenOutSupply, tokenOut.Amount.ToDec().Neg()) - inAmt := p.getDescaledPoolAmt(tokenInDenom, cfmmIn.NegMut()) - return inAmt, nil + cfmmIn := solveCfmm(osmomath.BigDecFromSdkDec(tokenInSupply), osmomath.BigDecFromSdkDec(tokenOutSupply), osmomath.BigDecFromSdkDec(tokenOut.Amount.ToDec().Neg())) + inAmt := p.getDescaledPoolAmt(tokenInDenom, cfmmIn.Neg()) + return inAmt.SdkDec(), nil } func (p *Pool) calcSingleAssetJoinShares(tokenIn sdk.Coin, swapFee sdk.Dec) (sdk.Int, error) { diff --git a/x/gamm/pool-models/stableswap/amm_bench_test.go b/x/gamm/pool-models/stableswap/amm_bench_test.go index a235463763a..a868b8f8664 100644 --- a/x/gamm/pool-models/stableswap/amm_bench_test.go +++ b/x/gamm/pool-models/stableswap/amm_bench_test.go @@ -4,7 +4,7 @@ import ( "math/rand" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/v12/osmomath" ) func BenchmarkCFMM(b *testing.B) { @@ -20,9 +20,9 @@ func BenchmarkBinarySearch(b *testing.B) { } } -func runCalc(solve func(sdk.Dec, sdk.Dec, sdk.Dec) sdk.Dec) { - xReserve := sdk.NewDec(rand.Int63n(100000) + 50000) - yReserve := sdk.NewDec(rand.Int63n(100000) + 50000) - yIn := sdk.NewDec(rand.Int63n(100000)) +func runCalc(solve func(osmomath.BigDec, osmomath.BigDec, osmomath.BigDec) osmomath.BigDec) { + xReserve := osmomath.NewBigDec(rand.Int63n(100000) + 50000) + yReserve := osmomath.NewBigDec(rand.Int63n(100000) + 50000) + yIn := osmomath.NewBigDec(rand.Int63n(100000)) solve(xReserve, yReserve, yIn) } diff --git a/x/gamm/pool-models/stableswap/amm_test.go b/x/gamm/pool-models/stableswap/amm_test.go index afbf5b5a739..e2c67eadfed 100644 --- a/x/gamm/pool-models/stableswap/amm_test.go +++ b/x/gamm/pool-models/stableswap/amm_test.go @@ -1,13 +1,14 @@ package stableswap import ( - fmt "fmt" + "fmt" "testing" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/osmosis-labs/osmosis/v12/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v12/osmomath" "github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/internal/test_helpers" ) @@ -16,24 +17,24 @@ type StableSwapTestSuite struct { } func TestCFMMInvariantTwoAssets(t *testing.T) { - kErrTolerance := sdk.OneDec() + kErrTolerance := osmomath.OneDec() tests := map[string]struct { - xReserve sdk.Dec - yReserve sdk.Dec - yIn sdk.Dec + xReserve osmomath.BigDec + yReserve osmomath.BigDec + yIn osmomath.BigDec expectPanic bool }{ "small pool small input": { - sdk.NewDec(100), - sdk.NewDec(100), - sdk.NewDec(1), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(1), false, }, "small pool large input": { - sdk.NewDec(100), - sdk.NewDec(100), - sdk.NewDec(1000), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(1000), false, }, // This test fails due to a bug in our original solver @@ -45,21 +46,21 @@ func TestCFMMInvariantTwoAssets(t *testing.T) { // panic catching "xReserve negative": { - sdk.NewDec(-100), - sdk.NewDec(100), - sdk.NewDec(1), + osmomath.NewBigDec(-100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(1), true, }, "yReserve negative": { - sdk.NewDec(100), - sdk.NewDec(-100), - sdk.NewDec(1), + osmomath.NewBigDec(100), + osmomath.NewBigDec(-100), + osmomath.NewBigDec(1), true, }, "yIn negative": { - sdk.NewDec(100), - sdk.NewDec(100), - sdk.NewDec(-1), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(-1), true, }, } @@ -73,14 +74,14 @@ func TestCFMMInvariantTwoAssets(t *testing.T) { xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn) k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn)) - osmoassert.DecApproxEq(t, k0, k1, kErrTolerance) + osmomath.DecApproxEq(t, k0, k1, kErrTolerance) // using multi-asset cfmm (should be equivalent with u = 1, w = 0) - k2 := cfmmConstantMulti(test.xReserve, test.yReserve, sdk.OneDec(), sdk.ZeroDec()) - osmoassert.DecApproxEq(t, k2, k0, kErrTolerance) - xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, sdk.ZeroDec(), test.yIn) - k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), sdk.OneDec(), sdk.ZeroDec()) - osmoassert.DecApproxEq(t, k2, k3, kErrTolerance) + k2 := cfmmConstantMulti(test.xReserve, test.yReserve, osmomath.OneDec(), osmomath.ZeroDec()) + osmomath.DecApproxEq(t, k2, k0, kErrTolerance) + xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, osmomath.ZeroDec(), test.yIn) + k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), osmomath.OneDec(), osmomath.ZeroDec()) + osmomath.DecApproxEq(t, k2, k3, kErrTolerance) } osmoassert.ConditionalPanic(t, test.expectPanic, sut) @@ -89,31 +90,31 @@ func TestCFMMInvariantTwoAssets(t *testing.T) { } func TestCFMMInvariantMultiAssets(t *testing.T) { - kErrTolerance := sdk.OneDec() + kErrTolerance := osmomath.OneDec() tests := map[string]struct { - xReserve sdk.Dec - yReserve sdk.Dec - uReserve sdk.Dec - wSumSquares sdk.Dec - yIn sdk.Dec + xReserve osmomath.BigDec + yReserve osmomath.BigDec + uReserve osmomath.BigDec + wSumSquares osmomath.BigDec + yIn osmomath.BigDec expectPanic bool }{ "4-asset pool, small input": { - sdk.NewDec(100), - sdk.NewDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), // represents a 4-asset pool with 100 in each reserve - sdk.NewDec(200), - sdk.NewDec(20000), - sdk.NewDec(1), + osmomath.NewBigDec(200), + osmomath.NewBigDec(20000), + osmomath.NewBigDec(1), false, }, "4-asset pool, large input": { - sdk.NewDec(100), - sdk.NewDec(100), - sdk.NewDec(200), - sdk.NewDec(20000), - sdk.NewDec(1000), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(200), + osmomath.NewBigDec(20000), + osmomath.NewBigDec(1000), false, }, // This test fails due to a bug in our original solver @@ -125,48 +126,48 @@ func TestCFMMInvariantMultiAssets(t *testing.T) { // panic catching "negative xReserve": { - sdk.NewDec(-100), - sdk.NewDec(100), + osmomath.NewBigDec(-100), + osmomath.NewBigDec(100), // represents a 4-asset pool with 100 in each reserve - sdk.NewDec(200), - sdk.NewDec(20000), - sdk.NewDec(1), + osmomath.NewBigDec(200), + osmomath.NewBigDec(20000), + osmomath.NewBigDec(1), true, }, "negative yReserve": { - sdk.NewDec(100), - sdk.NewDec(-100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(-100), // represents a 4-asset pool with 100 in each reserve - sdk.NewDec(200), - sdk.NewDec(20000), - sdk.NewDec(1), + osmomath.NewBigDec(200), + osmomath.NewBigDec(20000), + osmomath.NewBigDec(1), true, }, "negative uReserve": { - sdk.NewDec(100), - sdk.NewDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), // represents a 4-asset pool with 100 in each reserve - sdk.NewDec(-200), - sdk.NewDec(20000), - sdk.NewDec(1), + osmomath.NewBigDec(-200), + osmomath.NewBigDec(20000), + osmomath.NewBigDec(1), true, }, "negative sumSquares": { - sdk.NewDec(100), - sdk.NewDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), // represents a 4-asset pool with 100 in each reserve - sdk.NewDec(200), - sdk.NewDec(-20000), - sdk.NewDec(1), + osmomath.NewBigDec(200), + osmomath.NewBigDec(-20000), + osmomath.NewBigDec(1), true, }, "negative yIn": { - sdk.NewDec(100), - sdk.NewDec(100), + osmomath.NewBigDec(100), + osmomath.NewBigDec(100), // represents a 4-asset pool with 100 in each reserve - sdk.NewDec(200), - sdk.NewDec(-20000), - sdk.NewDec(1), + osmomath.NewBigDec(200), + osmomath.NewBigDec(-20000), + osmomath.NewBigDec(1), true, }, } @@ -179,7 +180,7 @@ func TestCFMMInvariantMultiAssets(t *testing.T) { k2 := cfmmConstantMulti(test.xReserve, test.yReserve, test.uReserve, test.wSumSquares) xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, test.wSumSquares, test.yIn) k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), test.uReserve, test.wSumSquares) - osmoassert.DecApproxEq(t, k2, k3, kErrTolerance) + osmomath.DecApproxEq(t, k2, k3, kErrTolerance) } osmoassert.ConditionalPanic(t, test.expectPanic, sut) diff --git a/x/gamm/pool-models/stableswap/pool.go b/x/gamm/pool-models/stableswap/pool.go index 464c7a569eb..d88e63ab241 100644 --- a/x/gamm/pool-models/stableswap/pool.go +++ b/x/gamm/pool-models/stableswap/pool.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/osmosis-labs/osmosis/v12/osmomath" "github.com/osmosis-labs/osmosis/v12/x/gamm/pool-models/internal/cfmm_common" "github.com/osmosis-labs/osmosis/v12/x/gamm/types" ) @@ -128,7 +129,7 @@ func (p Pool) getScaledPoolAmts(denoms ...string) ([]sdk.Dec, error) { } // getDescaledPoolAmts gets descaled amount of given denom and amount -func (p Pool) getDescaledPoolAmt(denom string, amount sdk.Dec) sdk.Dec { +func (p Pool) getDescaledPoolAmt(denom string, amount osmomath.BigDec) osmomath.BigDec { liquidityIndexes := p.getLiquidityIndexMap() liquidityIndex := liquidityIndexes[denom] @@ -241,10 +242,12 @@ func (p Pool) SpotPrice(ctx sdk.Context, baseAssetDenom string, quoteAssetDenom if err != nil { return sdk.Dec{}, err } - scaledSpotPrice := spotPrice(reserves[0], reserves[1]) + + scaledSpotPrice := spotPrice(osmomath.BigDecFromSdkDec(reserves[0]), osmomath.BigDecFromSdkDec(reserves[1])) spotPrice := p.getDescaledPoolAmt(baseAssetDenom, scaledSpotPrice) + spotPriceSdkDec := spotPrice.SdkDec() - return spotPrice, nil + return spotPriceSdkDec, nil } func (p Pool) Copy() Pool {