Skip to content

Commit

Permalink
feat: add support for exact output (#5240)
Browse files Browse the repository at this point in the history
  • Loading branch information
chefjackson authored Nov 9, 2022
1 parent e031ca3 commit 2265307
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 87 deletions.
84 changes: 56 additions & 28 deletions packages/smart-router/evm/getBestTradeFromV2.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
import { Currency, CurrencyAmount, Trade, TradeType } from '@pancakeswap/sdk'
import { Currency, CurrencyAmount, Pair, Trade, TradeType } from '@pancakeswap/sdk'

import { BETTER_TRADE_LESS_HOPS_THRESHOLD } from './constants'
import { getAllCommonPairs } from './getAllCommonPairs'
import { BestTradeOptions } from './types'
import { isTradeBetter } from './utils/trade'

export async function getBestTradeFromV2<TInput extends Currency, TOutput extends Currency>(
amountIn: CurrencyAmount<TInput>,
output: TOutput,
options: BestTradeOptions,
): Promise<Trade<TInput, TOutput, TradeType> | null> {
const { provider, ...restOptions } = options
const { maxHops = 3 } = restOptions
const allowedPairs = await getAllCommonPairs(amountIn.currency, output, { provider })

if (!allowedPairs.length) {
return null
}
export const getBestTradeFromV2ExactIn = createGetBestTradeFromV2(TradeType.EXACT_INPUT)

export const getBestTradeFromV2ExactOut = createGetBestTradeFromV2(TradeType.EXACT_OUTPUT)

export async function getBestTradeFromV2<
TInput extends Currency,
TOutput extends Currency,
TTradeType extends TradeType,
>(amountIn: CurrencyAmount<TInput>, output: TOutput, tradeType: TTradeType, options: BestTradeOptions) {
const getBestTrade = tradeType === TradeType.EXACT_INPUT ? getBestTradeFromV2ExactIn : getBestTradeFromV2ExactOut

if (maxHops === 1) {
return Trade.bestTradeExactIn(allowedPairs, amountIn, output, restOptions)[0] ?? null
return getBestTrade(amountIn, output, options)
}

function createGetBestTradeFromV2<TTradeType extends TradeType>(tradeType: TTradeType) {
function getBestTrade<In extends Currency, Out extends Currency>(
pairs: Pair[],
amountIn: CurrencyAmount<In>,
output: Out,
options: Omit<BestTradeOptions, 'provider'>,
) {
if (tradeType === TradeType.EXACT_INPUT) {
return Trade.bestTradeExactIn(pairs, amountIn, output, options)
}
return Trade.bestTradeExactOut(pairs, output, amountIn, options)
}

// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade<TInput, TOutput, TradeType> | null = null
for (let i = 1; i <= maxHops; i++) {
const currentTrade: Trade<TInput, TOutput, TradeType> | null =
Trade.bestTradeExactIn(allowedPairs, amountIn, output, {
...restOptions,
maxHops: i,
maxNumResults: 1,
})[0] ?? null
// if current trade is best yet, save it
if (isTradeBetter(bestTradeSoFar, currentTrade, BETTER_TRADE_LESS_HOPS_THRESHOLD)) {
bestTradeSoFar = currentTrade
return async function bestTradeFromV2<In extends Currency, Out extends Currency>(
amountIn: CurrencyAmount<In>,
output: Out,
options: BestTradeOptions,
) {
const { provider, ...restOptions } = options
const { maxHops = 3 } = restOptions
const allowedPairs = await getAllCommonPairs(amountIn.currency, output, { provider })

if (!allowedPairs.length) {
return null
}

if (maxHops === 1) {
return getBestTrade(allowedPairs, amountIn, output, restOptions)[0] ?? null
}

// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: ReturnType<typeof getBestTrade<In, Out>>[number] | null = null
for (let i = 1; i <= maxHops; i++) {
const currentTrade: ReturnType<typeof getBestTrade<In, Out>>[number] | null =
getBestTrade(allowedPairs, amountIn, output, {
...restOptions,
maxHops: i,
maxNumResults: 1,
})[0] ?? null
// if current trade is best yet, save it
if (isTradeBetter(bestTradeSoFar, currentTrade, BETTER_TRADE_LESS_HOPS_THRESHOLD)) {
bestTradeSoFar = currentTrade
}
}
return bestTradeSoFar
}
return bestTradeSoFar
}
23 changes: 16 additions & 7 deletions packages/smart-router/evm/getBestTradeWithStableSwap.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/* eslint-disable no-await-in-loop, no-continue */
import { Currency, CurrencyAmount, Pair, Trade, TradeType } from '@pancakeswap/sdk'

import { getBestTradeFromV2 } from './getBestTradeFromV2'
import { getBestTradeFromV2ExactIn } from './getBestTradeFromV2'
import { getStableSwapOutputAmount } from './onchain'
import { createTradeWithStableSwap } from './stableSwap'
import { BestTradeOptions, StableSwapPair, TradeWithStableSwap } from './types'
import { getOutputToken, isSamePair } from './utils/pair'

export async function getBestTradeWithStableSwap<TInput extends Currency, TOutput extends Currency>(
baseTrade: Trade<TInput, TOutput, TradeType>,
baseTrade: Trade<TInput, TOutput, TradeType.EXACT_INPUT> | Trade<TOutput, TInput, TradeType.EXACT_OUTPUT>,
stableSwapPairs: StableSwapPair[],
options: BestTradeOptions,
): Promise<TradeWithStableSwap<TInput, TOutput, TradeType>> {
) {
const { provider } = options
const { inputAmount, route, tradeType } = baseTrade
// Early return if there's no stableswap available
Expand Down Expand Up @@ -52,21 +52,30 @@ export async function getBestTradeWithStableSwap<TInput extends Currency, TOutpu
pairsWithStableSwap.push(pair)
}

if (tradeType === TradeType.EXACT_INPUT) {
return createTradeWithStableSwap({
pairs: pairsWithStableSwap,
inputAmount,
outputAmount,
tradeType,
}) as TradeWithStableSwap<TInput, TOutput, TradeType.EXACT_INPUT>
}
return createTradeWithStableSwap({
pairs: pairsWithStableSwap,
inputAmount,
// TODO add invariant check to make sure output amount has the same currency as the baseTrade output
outputAmount: outputAmount as CurrencyAmount<TOutput>,
outputAmount,
tradeType,
})
}) as TradeWithStableSwap<TOutput, TInput, TradeType.EXACT_OUTPUT>
}

async function getOutputAmountFromV2<TInput extends Currency, TOutput extends Currency>(
inputAmount: CurrencyAmount<TInput>,
outputToken: TOutput,
options: BestTradeOptions,
) {
const trade = await getBestTradeFromV2(inputAmount, outputToken, options)
// Since stable swap only supports exact in, we stick with exact in to
// calculate the estimated output when replacing pairs with stable swap pair
const trade = await getBestTradeFromV2ExactIn(inputAmount, outputToken, options)

if (!trade) {
throw new Error(`Cannot get valid trade from ${inputAmount.currency.name} to ${outputToken.name}`)
Expand Down
60 changes: 34 additions & 26 deletions packages/smart-router/evm/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import { Currency, CurrencyAmount, TradeType } from '@pancakeswap/sdk'

import { getBestTradeFromV2 } from './getBestTradeFromV2'
import { getBestTradeFromV2ExactIn, getBestTradeFromV2ExactOut } from './getBestTradeFromV2'
import { getBestTradeWithStableSwap } from './getBestTradeWithStableSwap'
import { getStableSwapPairs } from './getStableSwapPairs'
import { TradeWithStableSwap, BestTradeOptions } from './types'

export async function getBestTrade<TInput extends Currency, TOutput extends Currency>(
amountIn: CurrencyAmount<TInput>,
output: TOutput,
options: BestTradeOptions,
): Promise<TradeWithStableSwap<TInput, TOutput, TradeType> | null> {
const { provider } = options
// TODO invariant check input and output on the same chain
const {
currency: { chainId },
} = amountIn

const bestTradeV2 = await getBestTradeFromV2(amountIn, output, options)
if (!bestTradeV2) {
return null
}
import { BestTradeOptions } from './types'

const stableSwapPairs = getStableSwapPairs(chainId)
const bestTradeWithStableSwap = await getBestTradeWithStableSwap(bestTradeV2, stableSwapPairs, { provider })
const { outputAmount: outputAmountWithStableSwap } = bestTradeWithStableSwap
export const getBestTradeExactIn = createGetBestTrade(TradeType.EXACT_INPUT)

// If stable swap is not as good as best trade got from v2, then use v2
if (outputAmountWithStableSwap.lessThan(bestTradeV2.outputAmount)) {
return bestTradeV2
}
export const getBestTradeExactOut = createGetBestTrade(TradeType.EXACT_OUTPUT)

function createGetBestTrade<TTradeType extends TradeType>(tradeType: TTradeType) {
const getBestTradeFromV2 =
tradeType === TradeType.EXACT_INPUT ? getBestTradeFromV2ExactIn : getBestTradeFromV2ExactOut
return async function getBestTrade<In extends Currency, Out extends Currency>(
amountIn: CurrencyAmount<In>,
output: Out,
options: BestTradeOptions,
) {
const { provider } = options
// TODO invariant check input and output on the same chain
const {
currency: { chainId },
} = amountIn

const bestTradeV2 = await getBestTradeFromV2(amountIn, output, options)
if (!bestTradeV2) {
return null
}

return bestTradeWithStableSwap
const stableSwapPairs = getStableSwapPairs(chainId)
const bestTradeWithStableSwap = await getBestTradeWithStableSwap(bestTradeV2, stableSwapPairs, { provider })
const { outputAmount: outputAmountWithStableSwap } = bestTradeWithStableSwap

// If stable swap is not as good as best trade got from v2, then use v2
if (outputAmountWithStableSwap.lessThan(bestTradeV2.outputAmount)) {
return bestTradeV2
}

return bestTradeWithStableSwap
}
}
12 changes: 4 additions & 8 deletions packages/smart-router/evm/stableSwap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Currency, CurrencyAmount, Pair, Route, TradeType } from '@pancakeswap/sdk'

import { RouteWithStableSwap, StableSwapPair, TradeWithStableSwap } from './types'
import { StableSwapPair } from './types'

export function createStableSwapPair(pair: Pair, stableSwapAddress = ''): StableSwapPair {
const newPair = new Pair(pair.reserve0, pair.reserve1)
Expand All @@ -19,17 +19,13 @@ interface Options<TInput extends Currency, TOutput extends Currency, TTradeType
tradeType: TTradeType
}

export function createTradeWithStableSwap<
TInput extends Currency,
TOutput extends Currency,
TTradeType extends TradeType,
>({
export function createTradeWithStableSwap<TInput extends Currency, TOutput extends Currency>({
pairs,
inputAmount,
outputAmount,
tradeType,
}: Options<TInput, TOutput, TTradeType>): TradeWithStableSwap<TInput, TOutput, TTradeType> {
const route: RouteWithStableSwap<TInput, TOutput> = new Route(pairs, inputAmount.currency, outputAmount.currency)
}: Options<TInput, TOutput, TradeType.EXACT_INPUT> | Options<TOutput, TInput, TradeType.EXACT_OUTPUT>) {
const route = new Route(pairs, inputAmount.currency, outputAmount.currency)

return {
tradeType,
Expand Down
19 changes: 1 addition & 18 deletions packages/smart-router/evm/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
import { Currency, CurrencyAmount, Pair, Route, TradeType } from '@pancakeswap/sdk'

export * from './chain'

export * from './bestTrade'

export interface StableSwapPair extends Pair {
stableSwapAddress: string
}

export interface RouteWithStableSwap<TInput extends Currency, TOutput extends Currency> extends Route<TInput, TOutput> {
pairs: (Pair | StableSwapPair)[]
}

export interface TradeWithStableSwap<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType> {
tradeType: TTradeType
route: RouteWithStableSwap<TInput, TOutput>
inputAmount: CurrencyAmount<TInput>
outputAmount: CurrencyAmount<TOutput>
}
export * from './stableSwap'
16 changes: 16 additions & 0 deletions packages/smart-router/evm/types/stableSwap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Currency, CurrencyAmount, Pair, Route, TradeType } from '@pancakeswap/sdk'

export interface StableSwapPair extends Pair {
stableSwapAddress: string
}

export interface RouteWithStableSwap<TInput extends Currency, TOutput extends Currency> extends Route<TInput, TOutput> {
pairs: (Pair | StableSwapPair)[]
}

export interface TradeWithStableSwap<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType> {
tradeType: TTradeType
route: RouteWithStableSwap<TInput, TOutput>
inputAmount: CurrencyAmount<TInput>
outputAmount: CurrencyAmount<TOutput>
}

0 comments on commit 2265307

Please sign in to comment.