Commit 22453c0c authored by Moody Salem's avatar Moody Salem Committed by GitHub

Best trade (#20)

* First test for the best trade router

* Share the array in the recursion

* Comment

* Add comparator methods and more tests

* Fix the equalTo method on the fraction

* add a todo, rename `n`

* Best trade exact out, more tests

* Faster sorted insert

* Comment fix

* Handle insufficient reserves and input amount errors

* Test improvements, export best trade options

* Improvements to fraction, make inputOutputAmount reusable

* Make comparator more reusable
parent 8c7fb08a
......@@ -43,6 +43,9 @@ export class Fraction {
add(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.add(this.numerator, otherParsed.numerator), this.denominator)
}
return new Fraction(
JSBI.add(
JSBI.multiply(this.numerator, otherParsed.denominator),
......@@ -54,6 +57,9 @@ export class Fraction {
subtract(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.subtract(this.numerator, otherParsed.numerator), this.denominator)
}
return new Fraction(
JSBI.subtract(
JSBI.multiply(this.numerator, otherParsed.denominator),
......@@ -63,6 +69,30 @@ export class Fraction {
)
}
lessThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.lessThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}
equalTo(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.equal(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}
greaterThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.greaterThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}
multiply(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
......
......@@ -6,6 +6,9 @@ import { Route } from './route'
import { TokenAmount } from './fractions'
import { Price } from './fractions/price'
import { Percent } from './fractions/percent'
import { Token } from 'entities/token'
import { sortedInsert } from '../utils'
import { InsufficientReservesError, InsufficientInputAmountError } from '../errors'
function getSlippage(midPrice: Price, inputAmount: TokenAmount, outputAmount: TokenAmount): Percent {
const exactQuote = midPrice.raw.multiply(inputAmount.raw)
......@@ -14,6 +17,45 @@ function getSlippage(midPrice: Price, inputAmount: TokenAmount, outputAmount: To
return new Percent(slippage.numerator, slippage.denominator)
}
// minimal interface so the input output comparator may be shared across types
interface InputOutput {
readonly inputAmount: TokenAmount
readonly outputAmount: TokenAmount
}
// comparator function that allows sorting trades by their output amounts, in decreasing order, and then input amounts
// in increasing order. i.e. the best trades have the most outputs for the least inputs and are sorted first
export function inputOutputComparator(tradeA: InputOutput, tradeB: InputOutput): number {
// must have same input and output token for comparison
invariant(tradeA.inputAmount.token.equals(tradeB.inputAmount.token), 'INPUT_TOKEN')
invariant(tradeA.outputAmount.token.equals(tradeB.outputAmount.token), 'OUTPUT_TOKEN')
if (tradeA.outputAmount.equalTo(tradeB.outputAmount)) {
if (tradeA.inputAmount.equalTo(tradeB.inputAmount)) {
return 0
}
// trade A requires less input than trade B, so A should come first
if (tradeA.inputAmount.lessThan(tradeB.inputAmount)) {
return -1
} else {
return 1
}
} else {
// tradeA has less output than trade B, so should come second
if (tradeA.outputAmount.lessThan(tradeB.outputAmount)) {
return 1
} else {
return -1
}
}
}
export interface BestTradeOptions {
// how many results to return
maxNumResults?: number
// the maximum number of hops a trade should contain
maxHops?: number
}
export class Trade {
public readonly route: Route
public readonly tradeType: TradeType
......@@ -23,7 +65,7 @@ export class Trade {
public readonly nextMidPrice: Price
public readonly slippage: Percent
constructor(route: Route, amount: TokenAmount, tradeType: TradeType) {
public constructor(route: Route, amount: TokenAmount, tradeType: TradeType) {
invariant(amount.token.equals(tradeType === TradeType.EXACT_INPUT ? route.input : route.output), 'TOKEN')
const amounts: TokenAmount[] = new Array(route.path.length)
const nextPairs: Pair[] = new Array(route.pairs.length)
......@@ -52,8 +94,143 @@ export class Trade {
this.inputAmount = inputAmount
this.outputAmount = outputAmount
this.executionPrice = new Price(route.input, route.output, inputAmount.raw, outputAmount.raw)
const nextMidPrice = Price.fromRoute(new Route(nextPairs, route.input))
this.nextMidPrice = nextMidPrice
this.nextMidPrice = Price.fromRoute(new Route(nextPairs, route.input))
this.slippage = getSlippage(route.midPrice, inputAmount, outputAmount)
}
// given a list of pairs, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token
// amount to an output token, making at most `maxHops` hops
// note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
// the amount in among multiple routes.
public static bestTradeExactIn(
pairs: Pair[],
amountIn: TokenAmount,
tokenOut: Token,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {},
// used in recursion.
currentPairs: Pair[] = [],
originalAmountIn: TokenAmount = amountIn,
bestTrades: Trade[] = []
): Trade[] {
if (pairs.length === 0) {
return bestTrades
}
invariant(maxHops > 0, 'MAX_HOPS')
invariant(originalAmountIn === amountIn || currentPairs.length > 0, 'INVALID_RECURSION')
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
// pair irrelevant
if (!pair.token0.equals(amountIn.token) && !pair.token1.equals(amountIn.token)) continue
let amountOut: TokenAmount
try {
;[amountOut] = pair.getOutputAmount(amountIn)
} catch (error) {
if (error instanceof InsufficientInputAmountError) {
continue
}
throw error
}
// we have arrived at the output token, so this is the final trade of one of the paths
if (amountOut!.token.equals(tokenOut)) {
sortedInsert(
bestTrades,
new Trade(
new Route([...currentPairs, pair], originalAmountIn.token),
originalAmountIn,
TradeType.EXACT_INPUT
),
maxNumResults,
inputOutputComparator
)
} else if (maxHops > 1) {
const pairsExcludingThisPair = pairs.slice(0, i).concat(pairs.slice(i + 1, pairs.length))
// otherwise, consider all the other paths that lead from this token as long as we have not exceeded maxHops
Trade.bestTradeExactIn(
pairsExcludingThisPair,
amountOut!,
tokenOut,
{
maxNumResults,
maxHops: maxHops - 1
},
[...currentPairs, pair],
originalAmountIn,
bestTrades
)
}
}
return bestTrades
}
// similar to the above method but instead targets a fixed output amount
// given a list of pairs, and a fixed amount out, returns the top `maxNumResults` trades that go from an input token
// to an output token amount, making at most `maxHops` hops
// note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
// the amount in among multiple routes.
public static bestTradeExactOut(
pairs: Pair[],
tokenIn: Token,
amountOut: TokenAmount,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {},
// used in recursion.
currentPairs: Pair[] = [],
originalAmountOut: TokenAmount = amountOut,
bestTrades: Trade[] = []
): Trade[] {
if (pairs.length === 0) {
return bestTrades
}
invariant(maxHops > 0, 'MAX_HOPS')
invariant(originalAmountOut === amountOut || currentPairs.length > 0, 'INVALID_RECURSION')
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
// pair irrelevant
if (!pair.token0.equals(amountOut.token) && !pair.token1.equals(amountOut.token)) continue
let amountIn: TokenAmount
try {
;[amountIn] = pair.getInputAmount(amountOut)
} catch (error) {
// not enough liquidity in this pair
if (error instanceof InsufficientReservesError) {
continue
}
throw error
}
// we have arrived at the input token, so this is the first trade of one of the paths
if (amountIn!.token.equals(tokenIn)) {
sortedInsert(
bestTrades,
new Trade(new Route([pair, ...currentPairs], tokenIn), originalAmountOut, TradeType.EXACT_OUTPUT),
maxNumResults,
inputOutputComparator
)
} else if (maxHops > 1) {
const pairsExcludingThisPair = pairs.slice(0, i).concat(pairs.slice(i + 1, pairs.length))
// otherwise, consider all the other paths that arrive at this token as long as we have not exceeded maxHops
Trade.bestTradeExactOut(
pairsExcludingThisPair,
tokenIn,
amountIn!,
{
maxNumResults,
maxHops: maxHops - 1
},
[pair, ...currentPairs],
originalAmountOut,
bestTrades
)
}
}
return bestTrades
}
}
......@@ -46,3 +46,37 @@ export function sqrt(y: JSBI): JSBI {
}
return z
}
// given an array of items sorted by `comparator`, insert an item into its sort index and constrain the size to
// `maxSize` by removing the last item
export function sortedInsert<T>(items: T[], add: T, maxSize: number, comparator: (a: T, b: T) => number): T | null {
invariant(maxSize > 0, 'MAX_SIZE_ZERO')
// this is an invariant because the interface cannot return multiple removed items if items.length exceeds maxSize
invariant(items.length <= maxSize, 'ITEMS_SIZE')
// short circuit first item add
if (items.length === 0) {
items.push(add)
return null
} else {
const isFull = items.length === maxSize
// short circuit if full and the additional item does not come before the last item
if (isFull && comparator(items[items.length - 1], add) <= 0) {
return add
}
let lo = 0,
hi = items.length
while (lo < hi) {
const mid = (lo + hi) >>> 1
if (comparator(items[mid], add) <= 0) {
lo = mid + 1
} else {
hi = mid
}
}
items.splice(lo, 0, add)
return isFull ? items.pop()! : null
}
}
import { Fraction } from '../src'
import JSBI from 'jsbi'
describe.only('Fraction', () => {
describe('#quotient', () => {
it('floor division', () => {
expect(new Fraction(JSBI.BigInt(8), JSBI.BigInt(3)).quotient).toEqual(JSBI.BigInt(2)) // one below
expect(new Fraction(JSBI.BigInt(12), JSBI.BigInt(4)).quotient).toEqual(JSBI.BigInt(3)) // exact
expect(new Fraction(JSBI.BigInt(16), JSBI.BigInt(5)).quotient).toEqual(JSBI.BigInt(3)) // one above
})
})
describe('#invert', () => {
it('flips num and denom', () => {
expect(new Fraction(JSBI.BigInt(5), JSBI.BigInt(10)).invert().numerator).toEqual(JSBI.BigInt(10))
expect(new Fraction(JSBI.BigInt(5), JSBI.BigInt(10)).invert().denominator).toEqual(JSBI.BigInt(5))
})
})
describe('#add', () => {
it('multiples denoms and adds nums', () => {
expect(new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).add(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))).toEqual(
new Fraction(JSBI.BigInt(52), JSBI.BigInt(120))
)
})
it('same denom', () => {
expect(new Fraction(JSBI.BigInt(1), JSBI.BigInt(5)).add(new Fraction(JSBI.BigInt(2), JSBI.BigInt(5)))).toEqual(
new Fraction(JSBI.BigInt(3), JSBI.BigInt(5))
)
})
})
describe('#subtract', () => {
it('multiples denoms and subtracts nums', () => {
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).subtract(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(-28), JSBI.BigInt(120)))
})
it('same denom', () => {
expect(
new Fraction(JSBI.BigInt(3), JSBI.BigInt(5)).subtract(new Fraction(JSBI.BigInt(2), JSBI.BigInt(5)))
).toEqual(new Fraction(JSBI.BigInt(1), JSBI.BigInt(5)))
})
})
describe('#lessThan', () => {
it('correct', () => {
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).lessThan(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toBe(true)
expect(new Fraction(JSBI.BigInt(1), JSBI.BigInt(3)).lessThan(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))).toBe(
false
)
expect(
new Fraction(JSBI.BigInt(5), JSBI.BigInt(12)).lessThan(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toBe(false)
})
})
describe('#equalTo', () => {
it('correct', () => {
expect(new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).equalTo(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))).toBe(
false
)
expect(new Fraction(JSBI.BigInt(1), JSBI.BigInt(3)).equalTo(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))).toBe(
true
)
expect(new Fraction(JSBI.BigInt(5), JSBI.BigInt(12)).equalTo(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))).toBe(
false
)
})
})
describe('#greaterThan', () => {
it('correct', () => {
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).greaterThan(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toBe(false)
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(3)).greaterThan(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toBe(false)
expect(
new Fraction(JSBI.BigInt(5), JSBI.BigInt(12)).greaterThan(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toBe(true)
})
})
describe('#multiplty', () => {
it('correct', () => {
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).multiply(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(4), JSBI.BigInt(120)))
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(3)).multiply(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(4), JSBI.BigInt(36)))
expect(
new Fraction(JSBI.BigInt(5), JSBI.BigInt(12)).multiply(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(20), JSBI.BigInt(144)))
})
})
describe('#divide', () => {
it('correct', () => {
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(10)).divide(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(12), JSBI.BigInt(40)))
expect(
new Fraction(JSBI.BigInt(1), JSBI.BigInt(3)).divide(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(12), JSBI.BigInt(12)))
expect(
new Fraction(JSBI.BigInt(5), JSBI.BigInt(12)).divide(new Fraction(JSBI.BigInt(4), JSBI.BigInt(12)))
).toEqual(new Fraction(JSBI.BigInt(60), JSBI.BigInt(48)))
})
})
})
import { ChainId, Token, TokenAmount, Pair, InsufficientInputAmountError } from '../src'
import { sortedInsert } from '../src/utils'
describe('miscellaneous', () => {
it('getLiquidityMinted:0', async () => {
......@@ -103,4 +104,70 @@ describe('miscellaneous', () => {
expect(liquidityValue.token.equals(tokenA)).toBe(true)
expect(liquidityValue.raw.toString()).toBe('917') // ceiling(1000 - (500 * (1 / 6)))
})
describe('#sortedInsert', () => {
const comp = (a: number, b: number) => a - b
it('throws if maxSize is 0', () => {
expect(() => sortedInsert([], 1, 0, comp)).toThrow('MAX_SIZE_ZERO')
})
it('throws if items.length > maxSize', () => {
expect(() => sortedInsert([1, 2], 1, 1, comp)).toThrow('ITEMS_SIZE')
})
it('adds if empty', () => {
const arr: number[] = []
expect(sortedInsert(arr, 3, 2, comp)).toEqual(null)
expect(arr).toEqual([3])
})
it('adds if not full', () => {
const arr: number[] = [1, 5]
expect(sortedInsert(arr, 3, 3, comp)).toEqual(null)
expect(arr).toEqual([1, 3, 5])
})
it('adds if will not be full after', () => {
const arr: number[] = [1]
expect(sortedInsert(arr, 0, 3, comp)).toEqual(null)
expect(arr).toEqual([0, 1])
})
it('returns add if sorts after last', () => {
const arr = [1, 2, 3]
expect(sortedInsert(arr, 4, 3, comp)).toEqual(4)
expect(arr).toEqual([1, 2, 3])
})
it('removes from end if full', () => {
const arr = [1, 3, 4]
expect(sortedInsert(arr, 2, 3, comp)).toEqual(4)
expect(arr).toEqual([1, 2, 3])
})
it('uses comparator', () => {
const arr = [4, 2, 1]
expect(sortedInsert(arr, 3, 3, (a, b) => comp(a, b) * -1)).toEqual(1)
expect(arr).toEqual([4, 3, 2])
})
describe('maxSize of 1', () => {
it('empty add', () => {
const arr: number[] = []
expect(sortedInsert(arr, 3, 1, comp)).toEqual(null)
expect(arr).toEqual([3])
})
it('full add greater', () => {
const arr: number[] = [2]
expect(sortedInsert(arr, 3, 1, comp)).toEqual(3)
expect(arr).toEqual([2])
})
it('full add lesser', () => {
const arr: number[] = [4]
expect(sortedInsert(arr, 3, 1, comp)).toEqual(4)
expect(arr).toEqual([3])
})
})
})
})
import { ChainId, Token, TokenAmount, Pair, Trade } from '../src'
import JSBI from 'jsbi'
describe('Trade', () => {
const token0 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000001', 18, 't0')
const token1 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000002', 18, 't1')
const token2 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000003', 18, 't2')
const token3 = new Token(ChainId.MAINNET, '0x0000000000000000000000000000000000000004', 18, 't3')
const pair_0_1 = new Pair(new TokenAmount(token0, JSBI.BigInt(1000)), new TokenAmount(token1, JSBI.BigInt(1000)))
const pair_0_2 = new Pair(new TokenAmount(token0, JSBI.BigInt(1000)), new TokenAmount(token2, JSBI.BigInt(1100)))
const pair_0_3 = new Pair(new TokenAmount(token0, JSBI.BigInt(1000)), new TokenAmount(token3, JSBI.BigInt(900)))
const pair_1_2 = new Pair(new TokenAmount(token1, JSBI.BigInt(1200)), new TokenAmount(token2, JSBI.BigInt(1000)))
const pair_1_3 = new Pair(new TokenAmount(token1, JSBI.BigInt(1200)), new TokenAmount(token3, JSBI.BigInt(1300)))
describe('#bestTradeExactIn', () => {
it('provides best route', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new TokenAmount(token0, JSBI.BigInt(100)),
token2
)
expect(result).toHaveLength(2)
expect(result[0].route.pairs).toHaveLength(1) // 0 -> 2 at 10:11
expect(result[0].route.path).toEqual([token0, token2])
expect(result[0].inputAmount).toEqual(new TokenAmount(token0, JSBI.BigInt(100)))
expect(result[0].outputAmount).toEqual(new TokenAmount(token2, JSBI.BigInt(99)))
expect(result[1].route.pairs).toHaveLength(2) // 0 -> 1 -> 2 at 12:12:10
expect(result[1].route.path).toEqual([token0, token1, token2])
expect(result[1].inputAmount).toEqual(new TokenAmount(token0, JSBI.BigInt(100)))
expect(result[1].outputAmount).toEqual(new TokenAmount(token2, JSBI.BigInt(69)))
})
it('respects maxHops', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new TokenAmount(token0, JSBI.BigInt(10)),
token2,
{ maxHops: 1 }
)
expect(result).toHaveLength(1)
expect(result[0].route.pairs).toHaveLength(1) // 0 -> 2 at 10:11
expect(result[0].route.path).toEqual([token0, token2])
})
it('insufficient input for one pair', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new TokenAmount(token0, JSBI.BigInt(1)),
token2
)
expect(result).toHaveLength(1)
expect(result[0].route.pairs).toHaveLength(1) // 0 -> 2 at 10:11
expect(result[0].route.path).toEqual([token0, token2])
expect(result[0].outputAmount).toEqual(new TokenAmount(token2, JSBI.BigInt(1)))
})
it('respects n', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_2, pair_1_2],
new TokenAmount(token0, JSBI.BigInt(10)),
token2,
{ maxNumResults: 1 }
)
expect(result).toHaveLength(1)
})
it('no path', () => {
const result = Trade.bestTradeExactIn(
[pair_0_1, pair_0_3, pair_1_3],
new TokenAmount(token0, JSBI.BigInt(10)),
token2
)
expect(result).toHaveLength(0)
})
})
describe('#bestTradeExactOut', () => {
it('provides best route', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new TokenAmount(token2, JSBI.BigInt(100))
)
expect(result).toHaveLength(2)
expect(result[0].route.pairs).toHaveLength(1) // 0 -> 2 at 10:11
expect(result[0].route.path).toEqual([token0, token2])
expect(result[0].inputAmount).toEqual(new TokenAmount(token0, JSBI.BigInt(101)))
expect(result[0].outputAmount).toEqual(new TokenAmount(token2, JSBI.BigInt(100)))
expect(result[1].route.pairs).toHaveLength(2) // 0 -> 1 -> 2 at 12:12:10
expect(result[1].route.path).toEqual([token0, token1, token2])
expect(result[1].inputAmount).toEqual(new TokenAmount(token0, JSBI.BigInt(156)))
expect(result[1].outputAmount).toEqual(new TokenAmount(token2, JSBI.BigInt(100)))
})
it('respects maxHops', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new TokenAmount(token2, JSBI.BigInt(10)),
{ maxHops: 1 }
)
expect(result).toHaveLength(1)
expect(result[0].route.pairs).toHaveLength(1) // 0 -> 2 at 10:11
expect(result[0].route.path).toEqual([token0, token2])
})
it('insufficient liquidity', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new TokenAmount(token2, JSBI.BigInt(1200))
)
expect(result).toHaveLength(0)
})
it('insufficient liquidity in one pair but not the other', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new TokenAmount(token2, JSBI.BigInt(1050))
)
expect(result).toHaveLength(1)
})
it('respects n', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_2, pair_1_2],
token0,
new TokenAmount(token2, JSBI.BigInt(10)),
{ maxNumResults: 1 }
)
expect(result).toHaveLength(1)
})
it('no path', () => {
const result = Trade.bestTradeExactOut(
[pair_0_1, pair_0_3, pair_1_3],
token0,
new TokenAmount(token2, JSBI.BigInt(10))
)
expect(result).toHaveLength(0)
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment