From c211999848a3ae1c9ac50716949f01c3a1bb9cfe Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Tue, 7 Jun 2022 17:30:52 -0500 Subject: [PATCH] Fix spotprice sigfig rounding for small spot prices (#1699) * Fix spotprice sigfig rounding for small spot prices * Changelog * Fix off by one in precision * Address PR comment --- CHANGELOG.md | 2 ++ app/apptesting/gamm.go | 2 +- osmomath/sigfig_round.go | 24 ++++++++++++++++++++++++ x/gamm/pool-models/balancer/amm.go | 3 ++- x/gamm/pool-models/balancer/amm_test.go | 14 ++++++++++++++ x/gamm/types/constants.go | 2 +- 6 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 osmomath/sigfig_round.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f09a96d0ef0..9d524a98bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes +* [#1699](https://github.com/osmosis-labs/osmosis/pull/1699) Fixes bug in sig fig rounding on spot price queries for small values + #### golang API breaks * [#1671](https://github.com/osmosis-labs/osmosis/pull/1671) Remove methods that constitute AppModuleSimulation APIs for several modules' AppModules, which implemented no-ops diff --git a/app/apptesting/gamm.go b/app/apptesting/gamm.go index 40b34eb6de3..d431e70399d 100644 --- a/app/apptesting/gamm.go +++ b/app/apptesting/gamm.go @@ -45,7 +45,7 @@ func (suite *KeeperTestHelper) PrepareBalancerPool() uint64 { spotPrice, err = suite.App.GAMMKeeper.CalculateSpotPrice(suite.Ctx, poolId, "baz", "foo") suite.NoError(err) s := sdk.NewDec(1).Quo(sdk.NewDec(3)) - sp := s.Mul(gammtypes.SigFigs).RoundInt().ToDec().Quo(gammtypes.SigFigs) + sp := s.MulInt(gammtypes.SigFigs).RoundInt().ToDec().QuoInt(gammtypes.SigFigs) suite.Equal(sp.String(), spotPrice.String()) return poolId diff --git a/osmomath/sigfig_round.go b/osmomath/sigfig_round.go new file mode 100644 index 00000000000..cd8621bd18c --- /dev/null +++ b/osmomath/sigfig_round.go @@ -0,0 +1,24 @@ +package osmomath + +import sdk "github.com/cosmos/cosmos-sdk/types" + +var pointOne = sdk.OneDec().QuoInt64(10) + +func SigFigRound(d sdk.Dec, tenToSigFig sdk.Int) sdk.Dec { + // for d > .1, we do round(d * 10^sigfig) / 10^sigfig + // for k, where 10^k*d > .1 && 10^{k-1}*d < .1, we do: + // (round(10^k * d * 10^sigfig) / (10^sigfig * 10^k) + // take note of floor div, vs normal div + k := uint64(0) + dTimesK := d + for ; dTimesK.LT(pointOne); k += 1 { + dTimesK.MulInt64Mut(10) + } + // d * 10^k * 10^sigfig + dkSigFig := dTimesK.MulInt(tenToSigFig) + numerator := dkSigFig.RoundInt().ToDec() + + tenToK := sdk.NewInt(10).ToDec().Power(k) + denominator := tenToSigFig.Mul(tenToK.TruncateInt()) + return numerator.QuoInt(denominator) +} diff --git a/x/gamm/pool-models/balancer/amm.go b/x/gamm/pool-models/balancer/amm.go index 620ec50bf83..b81b9cb4c3c 100644 --- a/x/gamm/pool-models/balancer/amm.go +++ b/x/gamm/pool-models/balancer/amm.go @@ -195,7 +195,8 @@ func (p Pool) SpotPrice(ctx sdk.Context, baseAsset, quoteAsset string) (sdk.Dec, invWeightRatio := quote.Weight.ToDec().Quo(base.Weight.ToDec()) supplyRatio := base.Token.Amount.ToDec().Quo(quote.Token.Amount.ToDec()) fullRatio := supplyRatio.Mul(invWeightRatio) - ratio := (fullRatio.Mul(types.SigFigs).RoundInt()).ToDec().Quo(types.SigFigs) + // we want to round this to `SigFigs` of precision + ratio := osmomath.SigFigRound(fullRatio, types.SigFigs) return ratio, nil } diff --git a/x/gamm/pool-models/balancer/amm_test.go b/x/gamm/pool-models/balancer/amm_test.go index c4ebe433723..5d648a412a7 100644 --- a/x/gamm/pool-models/balancer/amm_test.go +++ b/x/gamm/pool-models/balancer/amm_test.go @@ -53,6 +53,20 @@ func (suite *KeeperTestSuite) TestBalancerSpotPrice() { expectError: false, expectedOutput: sdk.MustNewDecFromStr("1.913043480000000000"), // ans is 1.913043478260869565, rounded is 1.91304348 }, + { + name: "check number of sig figs", + baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 100), + quoteDenomPoolInput: sdk.NewInt64Coin(quoteDenom, 300), + expectError: false, + expectedOutput: sdk.MustNewDecFromStr("0.333333330000000000"), + }, + { + name: "check number of sig figs high sizes", + baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 343569192534), + quoteDenomPoolInput: sdk.NewCoin(quoteDenom, sdk.MustNewDecFromStr("186633424395479094888742").TruncateInt()), + expectError: false, + expectedOutput: sdk.MustNewDecFromStr("0.000000000001840877"), + }, } for _, tc := range tests { diff --git a/x/gamm/types/constants.go b/x/gamm/types/constants.go index 56cb99e2b0b..acdde3c66e2 100644 --- a/x/gamm/types/constants.go +++ b/x/gamm/types/constants.go @@ -22,5 +22,5 @@ var ( InitPoolSharesSupply = OneShare.MulRaw(100) // SigFigs is the amount of significant figures used to calculate SpotPrice - SigFigs = sdk.NewDec(10).Power(SigFigsExponent) + SigFigs = sdk.NewDec(10).Power(SigFigsExponent).TruncateInt() )