Skip to content

BE-692 | Implement GetCustomDirectQuoteInGivenOut API #618

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

Open
wants to merge 1 commit into
base: BE-691
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions domain/mvc/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ type RouterUsecase interface {
// It does not search for the route. It directly computes the quote for the given poolID.
// This allows to bypass a min liquidity requirement in the router when attempting to swap over a specific pool.
GetCustomDirectQuoteOutGivenIn(ctx context.Context, tokenIn sdk.Coin, tokenOutDenom string, poolID uint64) (domain.Quote, error)

// GetCustomDirectQuoteInGivenOut returns the custom direct quote for the given tokenOut, tokenInDenom and poolID.
// It does not search for the route. It directly computes the quote for the given poolID.
// This allows to bypass a min liquidity requirement in the router when attempting to swap over a specific pool.
GetCustomDirectQuoteInGivenOut(ctx context.Context, tokenOut sdk.Coin, tokenInDenom string, poolID uint64) (domain.Quote, error)
// GetCustomDirectQuoteMultiPoolOutGivenIn calculates direct custom quote for given tokenIn and tokenOutDenom over given poolID route.
// Underlying implementation uses GetCustomDirectQuote.
GetCustomDirectQuoteMultiPoolOutGivenIn(ctx context.Context, tokenIn sdk.Coin, tokenOutDenom []string, poolIDs []uint64) (domain.Quote, error)
Expand Down
38 changes: 37 additions & 1 deletion router/usecase/optimized_routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ func (s *RouterTestSuite) TestGetOptimalQuoteExactAmounOut_Mainnet() {
// Validates custom quotes for UOSMO to UION.
// That is, with the given pool ID, we expect the quote to be routed through the route
// that matches these pool IDs. Errors otherwise.
func (s *RouterTestSuite) TestGetCustomQuote_GetCustomDirectQuote_Mainnet_UOSMOUION() {
func (s *RouterTestSuite) TestGetCustomQuote_GetCustomDirectQuoteOutGivenIn_Mainnet_UOSMOUION() {
config := routertesting.DefaultRouterConfig
config.MaxPoolsPerRoute = 5
config.MaxRoutes = 10
Expand Down Expand Up @@ -826,6 +826,42 @@ func (s *RouterTestSuite) TestGetCustomQuote_GetCustomDirectQuote_Mainnet_UOSMOU
s.validateExpectedPoolIDOneRouteOneHopQuote(quote, expectedPoolID)
}

func (s *RouterTestSuite) TestGetCustomQuote_GetCustomDirectQuoteInGivenOut_Mainnet_UOSMOUION() {
config := routertesting.DefaultRouterConfig
config.MaxPoolsPerRoute = 5
config.MaxRoutes = 10

var (
amountIn = osmomath.NewInt(5000000)
)

mainnetState := s.SetupMainnetState()

// Setup router repository mock
tokensRepositoryMock := routerrepo.New(&log.NoOpLogger{})
tokensRepositoryMock.SetTakerFees(mainnetState.TakerFeeMap)

// Setup pools usecase mock.
poolsUsecase, err := poolsusecase.NewPoolsUsecase(&domain.PoolsConfig{}, "node-uri-placeholder", tokensRepositoryMock, domain.UnsetScalingFactorGetterCb, nil, &log.NoOpLogger{})
s.Require().NoError(err)
poolsUsecase.StorePools(mainnetState.Pools)

tokenMetaDataHolderMock := &mocks.TokenMetadataHolderMock{}
candidateRouteFinderMock := &mocks.CandidateRouteFinderMock{}

routerUsecase := routerusecase.NewRouterUsecase(tokensRepositoryMock, poolsUsecase, candidateRouteFinderMock, tokenMetaDataHolderMock, config, emptyCosmWasmPoolsRouterConfig, &log.NoOpLogger{}, cache.New(), cache.New())

// This pool ID is second best: https://app.osmosis.zone/pool/2
// The top one is https://app.osmosis.zone/pool/1110 which is not selected
// due to custom parameter.
const expectedPoolID = uint64(2)

// System under test 2
quote, err := routerUsecase.GetCustomDirectQuoteInGivenOut(context.Background(), sdk.NewCoin(UOSMO, amountIn), UION, expectedPoolID)
s.Require().NoError(err)
s.validateExpectedPoolIDOneRouteOneHopQuote(quote, expectedPoolID)
}

// Validates that the logic skips errors from individual routes
// and only fails if all routes error.
// Additionally, validates that the highest amount route is chosen, routes
Expand Down
39 changes: 37 additions & 2 deletions router/usecase/router_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ func (r *routerUseCaseImpl) GetCustomDirectQuoteOutGivenIn(ctx context.Context,
}

// create candidate routes with given token out denom and pool ID.
candidateRoutes := r.createCandidateRouteByPoolID(tokenOutDenom, poolID)
candidateRoutes := r.createCandidateRouteByPoolID(tokenIn.Denom, tokenOutDenom, poolID)

// Convert candidate route into a route with all the pool data
routes, err := r.poolsUsecase.GetRoutesFromCandidates(candidateRoutes, tokenIn.Denom, tokenOutDenom)
Expand All @@ -467,6 +467,40 @@ func (r *routerUseCaseImpl) GetCustomDirectQuoteOutGivenIn(ctx context.Context,
return bestSingleRouteQuote, nil
}

// GetCustomDirectQuoteOutGivenIn implements mvc.RouterUsecase.
func (r *routerUseCaseImpl) GetCustomDirectQuoteInGivenOut(ctx context.Context, tokenOut sdk.Coin, tokenInDenom string, poolID uint64) (domain.Quote, error) {
pool, err := r.poolsUsecase.GetPool(poolID)
if err != nil {
return nil, err
}

poolDenoms := pool.GetPoolDenoms()

if !osmoutils.Contains(poolDenoms, tokenOut.Denom) {
return nil, fmt.Errorf("denom %s in pool %d: %w", tokenOut.Denom, poolID, ErrTokenInDenomPoolNotFound)
}
if !osmoutils.Contains(poolDenoms, tokenInDenom) {
return nil, fmt.Errorf("denom %s in pool %d: %w", tokenInDenom, poolID, ErrTokenOutDenomPoolNotFound)
}

// create candidate routes with given token out denom and pool ID.
candidateRoutes := r.createCandidateRouteByPoolID(tokenInDenom, tokenOut.Denom, poolID)

// Convert candidate route into a route with all the pool data
routes, err := r.poolsUsecase.GetRoutesFromCandidates(candidateRoutes, tokenOut.Denom, tokenInDenom)
if err != nil {
return nil, err
}

// Compute direct quote
bestSingleRouteQuote, _, err := r.estimateAndRankSingleRouteQuoteOutGivenIn(ctx, routes, tokenOut, r.logger)
if err != nil {
return nil, err
}
Comment on lines +495 to +499
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a comment that we can use the "out given in" implementation because this is a single pool route and we are providing "token out" as "token in"?


return bestSingleRouteQuote, nil
}

// GetCustomDirectQuoteMultiPoolOutGivenIn implements mvc.RouterUsecase.
func (r *routerUseCaseImpl) GetCustomDirectQuoteMultiPoolOutGivenIn(ctx context.Context, tokenIn sdk.Coin, tokenOutDenom []string, poolIDs []uint64) (domain.Quote, error) {
if len(poolIDs) == 0 {
Expand Down Expand Up @@ -986,14 +1020,15 @@ func filterOutGeneralizedCosmWasmPoolRoutes(rankedRoutes []route.RouteImpl) []ro
}

// createCandidateRouteByPoolID constructs a candidate route with the desired pool.
func (r *routerUseCaseImpl) createCandidateRouteByPoolID(tokenOutDenom string, poolID uint64) ingesttypes.CandidateRoutes {
func (r *routerUseCaseImpl) createCandidateRouteByPoolID(tokenInDenom string, tokenOutDenom string, poolID uint64) ingesttypes.CandidateRoutes {
// Create a candidate route with the desired pool
return ingesttypes.CandidateRoutes{
Routes: []ingesttypes.CandidateRoute{
{
Pools: []ingesttypes.CandidatePool{
{
ID: poolID,
TokenInDenom: tokenInDenom,
TokenOutDenom: tokenOutDenom,
},
},
Expand Down