diff --git a/CHANGELOG.md b/CHANGELOG.md index 415c94fa..8c439665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ # Changelog +## v0.7.2 + +* [#100](https://github.com/osmosis-labs/sqs/pull/100) Format in over out spot price in quotes. + ## v0.7.0 * [#99](https://github.com/osmosis-labs/sqs/pull/99) Move candidate routes cache out of Redis. Remove route overwrite diff --git a/domain/mvc/tokens.go b/domain/mvc/tokens.go index b09cf780..e2acc829 100644 --- a/domain/mvc/tokens.go +++ b/domain/mvc/tokens.go @@ -24,6 +24,9 @@ type TokensUsecase interface { // A clone should be made for any mutative operation. GetChainScalingFactorByDenomMut(ctx context.Context, denom string) (osmomath.Dec, error) + // GetSpotPriceScalingFactorByDenomMut returns the scaling factor for spot price. + GetSpotPriceScalingFactorByDenom(ctx context.Context, baseDenom, quoteDenom string) (osmomath.Dec, error) + // GetPrices returns prices for all given base and quote denoms given a pricing strategy or, otherwise, error, if any. // The outer map consists of base denoms as keys. // The inner map consists of quote denoms as keys. diff --git a/domain/router.go b/domain/router.go index 45a74c68..c89efd66 100644 --- a/domain/router.go +++ b/domain/router.go @@ -55,7 +55,10 @@ type Quote interface { // PrepareResult mutates the quote to prepare // it with the data formatted for output to the client. - PrepareResult(ctx context.Context) ([]SplitRoute, osmomath.Dec) + // scalingFactor is the spot price scaling factor according to chain precision. + // scalingFactor of zero is a valid value. It might occur if we do not have precision information + // for the tokens. In that case, we invalidate spot price by setting it to zero. + PrepareResult(ctx context.Context, scalingFactor osmomath.Dec) ([]SplitRoute, osmomath.Dec) String() string } diff --git a/router/delivery/http/router_handler.go b/router/delivery/http/router_handler.go index ea456f8f..72fbfed5 100644 --- a/router/delivery/http/router_handler.go +++ b/router/delivery/http/router_handler.go @@ -1,6 +1,7 @@ package http import ( + "context" "errors" "net/http" "strconv" @@ -9,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/osmomath" "github.com/osmosis-labs/sqs/domain" "github.com/osmosis-labs/sqs/domain/mvc" "github.com/osmosis-labs/sqs/log" @@ -92,7 +94,9 @@ func (a *RouterHandler) GetOptimalQuote(c echo.Context) (err error) { return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) } - quote.PrepareResult(ctx) + scalingFactor := a.getSpotPriceScalingFactor(ctx, tokenInDenom, tokenOutDenom) + + quote.PrepareResult(ctx, scalingFactor) return c.JSON(http.StatusOK, quote) } @@ -111,7 +115,9 @@ func (a *RouterHandler) GetBestSingleRouteQuote(c echo.Context) error { return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) } - quote.PrepareResult(ctx) + scalingFactor := a.getSpotPriceScalingFactor(ctx, tokenIn.Denom, tokenOutDenom) + + quote.PrepareResult(ctx, scalingFactor) return c.JSON(http.StatusOK, quote) } @@ -142,7 +148,9 @@ func (a *RouterHandler) GetCustomQuote(c echo.Context) error { return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) } - quote.PrepareResult(ctx) + scalingFactor := a.getSpotPriceScalingFactor(ctx, tokenIn.Denom, tokenOutDenom) + + quote.PrepareResult(ctx, scalingFactor) return c.JSON(http.StatusOK, quote) } @@ -173,7 +181,9 @@ func (a *RouterHandler) GetDirectCustomQuote(c echo.Context) error { return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) } - quote.PrepareResult(ctx) + scalingFactor := a.getSpotPriceScalingFactor(ctx, tokenIn.Denom, tokenOutDenom) + + quote.PrepareResult(ctx, scalingFactor) return c.JSON(http.StatusOK, quote) } @@ -344,6 +354,18 @@ func getValidRoutingParameters(c echo.Context) (string, sdk.Coin, error) { return tokenOutStr, tokenIn, nil } +// getSpotPriceScalingFactor returns the spot price scaling factor for a given tokenIn and tokenOutDenom. +func (a *RouterHandler) getSpotPriceScalingFactor(ctx context.Context, tokenInDenom, tokenOutDenom string) osmomath.Dec { + scalingFactor, err := a.TUsecase.GetSpotPriceScalingFactorByDenom(ctx, tokenOutDenom, tokenInDenom) + if err != nil { + // Note that we do not fail the quote if scaling factor fetching fails. + // Instead, we simply set it to zero to validate future calculations downstream. + scalingFactor = sdk.ZeroDec() + } + + return scalingFactor +} + func getValidTokenInStr(c echo.Context) (string, error) { tokenInStr := c.QueryParam("tokenIn") diff --git a/router/usecase/quote.go b/router/usecase/quote.go index 8970186a..d5eb00a5 100644 --- a/router/usecase/quote.go +++ b/router/usecase/quote.go @@ -14,11 +14,12 @@ import ( ) type quoteImpl struct { - AmountIn sdk.Coin "json:\"amount_in\"" - AmountOut osmomath.Int "json:\"amount_out\"" - Route []domain.SplitRoute "json:\"route\"" - EffectiveFee osmomath.Dec "json:\"effective_fee\"" - PriceImpact osmomath.Dec "json:\"price_impact\"" + AmountIn sdk.Coin "json:\"amount_in\"" + AmountOut osmomath.Int "json:\"amount_out\"" + Route []domain.SplitRoute "json:\"route\"" + EffectiveFee osmomath.Dec "json:\"effective_fee\"" + PriceImpact osmomath.Dec "json:\"price_impact\"" + InOutSpotPrice osmomath.Dec "json:\"in_out_spot_price\"" } var ( @@ -35,7 +36,7 @@ var _ domain.Quote = "eImpl{} // Computes an effective spread factor from all routes. // // Returns the updated route and the effective spread factor. -func (q *quoteImpl) PrepareResult(ctx context.Context) ([]domain.SplitRoute, osmomath.Dec) { +func (q *quoteImpl) PrepareResult(ctx context.Context, scalingFactor osmomath.Dec) ([]domain.SplitRoute, osmomath.Dec) { totalAmountIn := q.AmountIn.Amount.ToLegacyDec() totalFeeAcrossRoutes := osmomath.ZeroDec() @@ -89,6 +90,7 @@ func (q *quoteImpl) PrepareResult(ctx context.Context) ([]domain.SplitRoute, osm q.EffectiveFee = totalFeeAcrossRoutes q.Route = resultRoutes + q.InOutSpotPrice = totalSpotPriceInOverOut.Mul(scalingFactor) return q.Route, q.EffectiveFee } diff --git a/router/usecase/quote_test.go b/router/usecase/quote_test.go index 4e90e3bf..6d0a878b 100644 --- a/router/usecase/quote_test.go +++ b/router/usecase/quote_test.go @@ -20,9 +20,10 @@ import ( ) var ( - defaultAmount = sdk.NewInt(100_000_00) - totalInAmount = defaultAmount - totalOutAmount = defaultAmount.MulRaw(4) + defaultAmount = sdk.NewInt(100_000_00) + totalInAmount = defaultAmount + totalOutAmount = defaultAmount.MulRaw(4) + defaultSpotPriceScalingFactor = osmomath.OneDec() ) // TestPrepareResult prepares the result of the quote for output to the client. @@ -226,7 +227,7 @@ func (s *RouterTestSuite) TestPrepareResult() { expectedEffectiveSpreadFactor := expectedRouteOneFee.Add(expectedRouteTwoFee) // System under test - routes, effectiveSpreadFactor := testQuote.PrepareResult(context.TODO()) + routes, effectiveSpreadFactor := testQuote.PrepareResult(context.TODO(), defaultSpotPriceScalingFactor) // Validate routes. s.validateRoutes(expectedRoutes, routes) @@ -301,7 +302,7 @@ func (s *RouterTestSuite) TestPrepareResult_PriceImpact() { } // System under test. - testQuote.PrepareResult(context.TODO()) + testQuote.PrepareResult(context.TODO(), defaultSpotPriceScalingFactor) // Validate price impact. s.Require().Equal(expectedPriceImpact.String(), testQuote.GetPriceImpact().String()) diff --git a/tokens/usecase/tokens_usecase.go b/tokens/usecase/tokens_usecase.go index 192080a3..f009dfd6 100644 --- a/tokens/usecase/tokens_usecase.go +++ b/tokens/usecase/tokens_usecase.go @@ -326,3 +326,22 @@ func GetTokensFromChainRegistry(chainRegistryAssetsFileURL string) (map[string]d return tokensByChainDenom, nil } + +// GetSpotPriceScalingFactorByDenomMut implements mvc.TokensUsecase. +func (t *tokensUseCase) GetSpotPriceScalingFactorByDenom(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.Dec, error) { + baseScalingFactor, err := t.GetChainScalingFactorByDenomMut(ctx, baseDenom) + if err != nil { + return osmomath.Dec{}, err + } + + quoteScalingFactor, err := t.GetChainScalingFactorByDenomMut(ctx, quoteDenom) + if err != nil { + return osmomath.Dec{}, err + } + + if quoteScalingFactor.IsZero() { + return osmomath.Dec{}, fmt.Errorf("scaling factor for quote denom (%s) is zero", quoteDenom) + } + + return baseScalingFactor.Quo(quoteScalingFactor), nil +}