Skip to content

Commit b55f157

Browse files
BE-724 | Fix spot price calculation for orderbooks (#646)
* BE-724 | Fix spot price calculation for orderbooks Shallow orderbooks may compute spot price incorrectly leading to not accurate price impact. This change introduces a fall back of spot price calculation for orderbooks based on on-chain pricing that would better reflect actual spot price across all on-chain orderbooks.
1 parent 834e5d7 commit b55f157

24 files changed

+350
-111
lines changed

app/sidecar_query_server.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ func NewSideCarQueryServer(ctx context.Context, appCodec codec.Codec, config dom
194194
return nil, err
195195
}
196196

197+
// Set the default quote denom on the tokens use case
198+
tokensUseCase.SetDefaultQuoteDenom(defaultQuoteDenom)
199+
197200
// Get liquidity pricer
198201
liquidityPricer := pricingWorker.NewLiquidityPricer(defaultQuoteDenom, tokensUseCase.GetChainScalingFactorByDenomMut)
199202

domain/mocks/quote_mock.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (m *MockQuote) GetRoute() []domain.SplitRoute {
5858
}
5959

6060
// PrepareResult implements domain.Quote.
61-
func (m *MockQuote) PrepareResult(ctx context.Context, scalingFactor math.LegacyDec, logger log.Logger) ([]domain.SplitRoute, math.LegacyDec, error) {
61+
func (m *MockQuote) PrepareResult(ctx context.Context, scalingFactor math.LegacyDec, spotPriceCalculator domain.SpotPriceQuoteCalculator, logger log.Logger) ([]domain.SplitRoute, math.LegacyDec, error) {
6262
panic("unimplemented")
6363
}
6464

domain/mocks/route_mock.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type RouteMock struct {
1616
GetPoolsFunc func() []domain.RoutablePool
1717
GetTokenOutDenomFunc func() string
1818
GetTokenInDenomFunc func() string
19-
PrepareResultPoolsExactAmountInFunc func(ctx context.Context, tokenIn types.Coin, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error)
20-
PrepareResultPoolsExactAmountOutFunc func(ctx context.Context, tokenOut types.Coin, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error)
19+
PrepareResultPoolsExactAmountInFunc func(ctx context.Context, tokenIn types.Coin, spotPriceCalculator domain.SpotPriceQuoteCalculator, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error)
20+
PrepareResultPoolsExactAmountOutFunc func(ctx context.Context, tokenOut types.Coin, spotPriceCalculator domain.SpotPriceQuoteCalculator, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error)
2121
StringFunc func() string
2222

2323
GetAmountInFunc func() math.Int
@@ -79,16 +79,17 @@ func (r *RouteMock) GetTokenInDenom() string {
7979
}
8080

8181
// PrepareResultPoolsOutGivenIn implements domain.Route.
82-
func (r *RouteMock) PrepareResultPoolsOutGivenIn(ctx context.Context, tokenIn types.Coin, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error) {
82+
func (r *RouteMock) PrepareResultPoolsOutGivenIn(ctx context.Context, tokenIn types.Coin, spotPriceCalculator domain.SpotPriceQuoteCalculator, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error) {
8383
if r.PrepareResultPoolsExactAmountInFunc != nil {
84-
return r.PrepareResultPoolsExactAmountInFunc(ctx, tokenIn, logger)
84+
return r.PrepareResultPoolsExactAmountInFunc(ctx, tokenIn, spotPriceCalculator, logger)
8585
}
8686

8787
panic("unimplemented")
8888
}
89-
func (r *RouteMock) PrepareResultPoolsExactAmountOut(ctx context.Context, tokenIn types.Coin, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error) {
89+
90+
func (r *RouteMock) PrepareResultPoolsExactAmountOut(ctx context.Context, tokenIn types.Coin, spotPriceCalculator domain.SpotPriceQuoteCalculator, logger log.Logger) ([]domain.RoutablePool, math.LegacyDec, math.LegacyDec, error) {
9091
if r.PrepareResultPoolsExactAmountOutFunc != nil {
91-
return r.PrepareResultPoolsExactAmountOutFunc(ctx, tokenIn, logger)
92+
return r.PrepareResultPoolsExactAmountOutFunc(ctx, tokenIn, spotPriceCalculator, logger)
9293
}
9394

9495
panic("unimplemented")
@@ -119,5 +120,7 @@ func (r *RouteMock) GetAmountOut() math.Int {
119120
panic("unimplemented")
120121
}
121122

122-
var _ domain.Route = &RouteMock{}
123-
var _ domain.SplitRoute = &RouteMock{}
123+
var (
124+
_ domain.Route = &RouteMock{}
125+
_ domain.SplitRoute = &RouteMock{}
126+
)

domain/mocks/tokens_usecase_mock.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type TokensUsecaseMock struct {
3131
UpdateAssetsAtHeightIntervalSyncFunc func(height uint64) error
3232
SetTokenRegistryLoaderFunc func(loader domain.TokenRegistryLoader)
3333
ClearPoolDenomMetadataFunc func()
34+
CalcSpotPriceFunc func(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.BigDec, error)
35+
CalcScalingFactorFunc func(baseDenom string, quoteDenom string) (osmomath.BigDec, error)
3436
}
3537

3638
var _ mvc.TokensUsecase = &TokensUsecaseMock{}
@@ -172,3 +174,19 @@ func (m *TokensUsecaseMock) ClearPoolDenomMetadata() {
172174
}
173175
panic("unimplemented")
174176
}
177+
178+
// CalcSpotPrice implements mvc.TokensUsecase.
179+
func (m *TokensUsecaseMock) CalcSpotPrice(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.BigDec, error) {
180+
if m.CalcSpotPriceFunc != nil {
181+
return m.CalcSpotPriceFunc(ctx, baseDenom, quoteDenom)
182+
}
183+
return osmomath.BigDec{}, nil
184+
}
185+
186+
// CalcScalingFactor implements mvc.TokensUsecase.
187+
func (m *TokensUsecaseMock) CalcScalingFactor(baseDenom string, quoteDenom string) (osmomath.BigDec, error) {
188+
if m.CalcScalingFactorFunc != nil {
189+
return m.CalcScalingFactorFunc(baseDenom, quoteDenom)
190+
}
191+
return osmomath.BigDec{}, nil
192+
}

domain/mvc/tokens.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package mvc
22

33
import (
4-
"context"
54
"fmt"
65

76
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -50,14 +49,11 @@ type TokensUsecase interface {
5049
// GetSpotPriceScalingFactorByDenomMut returns the scaling factor for spot price.
5150
GetSpotPriceScalingFactorByDenom(baseDenom, quoteDenom string) (osmomath.Dec, error)
5251

52+
// CalcSpotPrice calculates the spot price between two denoms.
53+
domain.SpotPriceQuoteCalculator
54+
5355
// GetPrices returns prices for all given base and quote denoms given a pricing source type or, otherwise, error, if any.
54-
// The options configure some customization with regards to how prices are computed.
55-
// By default, the prices are computes by using cache and the default min liquidity parameter set via config.
56-
// The options are capable of overriding the defaults.
57-
// The outer map consists of base denoms as keys.
58-
// The inner map consists of quote denoms as keys.
59-
// The result of the inner map is prices of the outer base and inner quote.
60-
GetPrices(ctx context.Context, baseDenoms []string, quoteDenoms []string, pricingSourceType domain.PricingSourceType, opts ...domain.PricingOption) (domain.PricesResult, error)
56+
domain.TokensPriceFetcher
6157

6258
// GetPoolDenomMetadata returns the pool denom metadata of a pool denom.
6359
// This metadata is accumulated from all pools.

domain/pools.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,17 @@ type ScalingFactorGetterCb func(denom string) (osmomath.Dec, error)
3232
type QuoteEstimatorCb func(ctx context.Context, tokenIn sdk.Coin, tokenOutDenom string) (sdk.Coin, error)
3333

3434
// SpotPriceQuoteCalculator is an interface that defines a contract for computing spot price using
35-
// the quote method. Using this method, the calculator swaps 1 precision-scaled unit of the quote denom
36-
// For majority of the spot prices with USDC as a quote, this is a reliable method for computing spot price.
37-
// There are edge cases where this method might prove unreliable. For example, swaping 1 WBTC, might lead
38-
// to a severe price impact and an unreliable estimation method. On the other hand, swapping 1 PEPE might
39-
// be too small of an amount, leading to an output of zero.
40-
// To deal with these issues, we might introduce custom overwrites based on denom down the road.
41-
//
42-
// This method primarily exists to workaround a bug with Astroport PCL pools that fail to compute spot price
43-
// correctly due to downstream issues.
35+
// the quote method.
4436
type SpotPriceQuoteCalculator interface {
45-
// Calculate returns spot price for base denom and quote denom.
37+
// CalcSpotPrice returns spot price for base denom and quote denom.
4638
// Returns error if:
4739
// * Fails to retrieve scaling factor for the quote denom.
4840
// * Quote fails to be computed.
4941
// * Quote outputs nil coin.
5042
// * Quoute outputs coin with nil amount.
5143
// * Quote outputs coin with zero amount
5244
// * Truncation in intermediary calculations happens, leading to spot price of zero.
53-
Calculate(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.BigDec, error)
45+
CalcSpotPrice(ctx context.Context, baseDenom string, quoteDenom string) (osmomath.BigDec, error)
5446
}
5547

5648
// UnsetScalingFactorGetterCb is a callback that is used to unset the scaling factor getter callback.

domain/route_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func (s *RouterTestSuite) TestPrepareResultPools() {
132132
s.Run(name, func() {
133133

134134
// Note: token in is chosen arbitrarily since it is irrelevant for this test
135-
actualPools, _, _, err := tc.route.PrepareResultPoolsOutGivenIn(context.TODO(), sdk.NewCoin(DenomTwo, DefaultAmt0), &log.NoOpLogger{})
135+
actualPools, _, _, err := tc.route.PrepareResultPoolsOutGivenIn(context.TODO(), sdk.NewCoin(DenomTwo, DefaultAmt0), nil, &log.NoOpLogger{})
136136
s.Require().NoError(err)
137137

138138
s.ValidateRoutePools(tc.expectedPools, actualPools)

domain/router.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type Route interface {
5050
// Computes the spot price of the route.
5151
// Returns the spot price before swap and effective spot price.
5252
// The token in is the base token and the token out is the quote token.
53-
PrepareResultPoolsOutGivenIn(ctx context.Context, tokenIn sdk.Coin, logger log.Logger) ([]RoutablePool, osmomath.Dec, osmomath.Dec, error)
53+
PrepareResultPoolsOutGivenIn(ctx context.Context, tokenIn sdk.Coin, spotPriceCalculator SpotPriceQuoteCalculator, logger log.Logger) ([]RoutablePool, osmomath.Dec, osmomath.Dec, error)
5454

5555
String() string
5656
}
@@ -74,7 +74,7 @@ type Quote interface {
7474
// scalingFactor is the spot price scaling factor according to chain precision.
7575
// scalingFactor of zero is a valid value. It might occur if we do not have precision information
7676
// for the tokens. In that case, we invalidate spot price by setting it to zero.
77-
PrepareResult(ctx context.Context, scalingFactor osmomath.Dec, logger log.Logger) ([]SplitRoute, osmomath.Dec, error)
77+
PrepareResult(ctx context.Context, scalingFactor osmomath.Dec, spotPriceCalculator SpotPriceQuoteCalculator, logger log.Logger) ([]SplitRoute, osmomath.Dec, error)
7878

7979
// SetQuotePriceInfo sets the quote price info.
8080
SetQuotePriceInfo(info *TxFeeInfo)

domain/tokens.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package domain
22

33
import (
4+
"context"
5+
46
"github.com/osmosis-labs/osmosis/osmomath"
57
)
68

@@ -71,3 +73,15 @@ const (
7173
// Unknown swap method, used for error handling.
7274
TokenSwapMethodInvalid
7375
)
76+
77+
// TokensPriceFetcher is the contract for fetching prices of base and quote tokens from different sources.
78+
type TokensPriceFetcher interface {
79+
// GetPrices returns prices for all given base and quote denoms given a pricing source type or, otherwise, error, if any.
80+
// The options configure some customization with regards to how prices are computed.
81+
// By default, the prices are computes by using cache and the default min liquidity parameter set via config.
82+
// The options are capable of overriding the defaults.
83+
// The outer map consists of base denoms as keys.
84+
// The inner map consists of quote denoms as keys.
85+
// The result of the inner map is prices of the outer base and inner quote.
86+
GetPrices(ctx context.Context, baseDenoms []string, quoteDenoms []string, pricingSourceType PricingSourceType, opts ...PricingOption) (PricesResult, error)
87+
}

pools/usecase/pools_usecase_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,9 @@ func (s *PoolsUsecaseTestSuite) TestGetRoutesFromCandidates() {
256256
// helper method for validation.
257257
// Note token in is chosen arbitrarily since it is irrelevant for this test
258258
tokenIn := sdk.NewCoin(tc.tokenInDenom, osmomath.NewInt(100))
259-
actualPools, _, _, err := actualRoute.PrepareResultPoolsOutGivenIn(context.TODO(), tokenIn, logger)
259+
actualPools, _, _, err := actualRoute.PrepareResultPoolsOutGivenIn(context.TODO(), tokenIn, nil, logger)
260260
s.Require().NoError(err)
261-
expectedPools, _, _, err := expectedRoute.PrepareResultPoolsOutGivenIn(context.TODO(), tokenIn, logger)
261+
expectedPools, _, _, err := expectedRoute.PrepareResultPoolsOutGivenIn(context.TODO(), tokenIn, nil, logger)
262262
s.Require().NoError(err)
263263

264264
// Validates:

0 commit comments

Comments
 (0)