405 lines
16 KiB
TypeScript
405 lines
16 KiB
TypeScript
import JSBI from 'jsbi'
|
|
import {
|
|
ChainId,
|
|
ETHER,
|
|
CurrencyAmount,
|
|
Pair,
|
|
Percent,
|
|
Route,
|
|
Token,
|
|
TokenAmount,
|
|
Trade,
|
|
TradeType,
|
|
WETH
|
|
} from '../src'
|
|
|
|
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)))
|
|
|
|
const pair_weth_0 = new Pair(
|
|
new TokenAmount(WETH[ChainId.MAINNET], JSBI.BigInt(1000)),
|
|
new TokenAmount(token0, JSBI.BigInt(1000))
|
|
)
|
|
|
|
const empty_pair_0_1 = new Pair(new TokenAmount(token0, JSBI.BigInt(0)), new TokenAmount(token1, JSBI.BigInt(0)))
|
|
|
|
it('can be constructed with ETHER as input', () => {
|
|
const trade = new Trade(
|
|
new Route([pair_weth_0], ETHER),
|
|
CurrencyAmount.ether(JSBI.BigInt(100)),
|
|
TradeType.EXACT_INPUT
|
|
)
|
|
expect(trade.inputAmount.currency).toEqual(ETHER)
|
|
expect(trade.outputAmount.currency).toEqual(token0)
|
|
})
|
|
it('can be constructed with ETHER as input for exact output', () => {
|
|
const trade = new Trade(
|
|
new Route([pair_weth_0], ETHER, token0),
|
|
new TokenAmount(token0, JSBI.BigInt(100)),
|
|
TradeType.EXACT_OUTPUT
|
|
)
|
|
expect(trade.inputAmount.currency).toEqual(ETHER)
|
|
expect(trade.outputAmount.currency).toEqual(token0)
|
|
})
|
|
|
|
it('can be constructed with ETHER as output', () => {
|
|
const trade = new Trade(
|
|
new Route([pair_weth_0], token0, ETHER),
|
|
CurrencyAmount.ether(JSBI.BigInt(100)),
|
|
TradeType.EXACT_OUTPUT
|
|
)
|
|
expect(trade.inputAmount.currency).toEqual(token0)
|
|
expect(trade.outputAmount.currency).toEqual(ETHER)
|
|
})
|
|
it('can be constructed with ETHER as output for exact input', () => {
|
|
const trade = new Trade(
|
|
new Route([pair_weth_0], token0, ETHER),
|
|
new TokenAmount(token0, JSBI.BigInt(100)),
|
|
TradeType.EXACT_INPUT
|
|
)
|
|
expect(trade.inputAmount.currency).toEqual(token0)
|
|
expect(trade.outputAmount.currency).toEqual(ETHER)
|
|
})
|
|
|
|
describe('#bestTradeExactIn', () => {
|
|
it('throws with empty pairs', () => {
|
|
expect(() => Trade.bestTradeExactIn([], new TokenAmount(token0, JSBI.BigInt(100)), token2)).toThrow('PAIRS')
|
|
})
|
|
it('throws with max hops of 0', () => {
|
|
expect(() =>
|
|
Trade.bestTradeExactIn([pair_0_2], new TokenAmount(token0, JSBI.BigInt(100)), token2, { maxHops: 0 })
|
|
).toThrow('MAX_HOPS')
|
|
})
|
|
|
|
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('doesnt throw for zero liquidity pairs', () => {
|
|
expect(Trade.bestTradeExactIn([empty_pair_0_1], new TokenAmount(token0, JSBI.BigInt(100)), token1)).toHaveLength(
|
|
0
|
|
)
|
|
})
|
|
|
|
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)
|
|
})
|
|
|
|
it('works for ETHER currency input', () => {
|
|
const result = Trade.bestTradeExactIn(
|
|
[pair_weth_0, pair_0_1, pair_0_3, pair_1_3],
|
|
CurrencyAmount.ether(JSBI.BigInt(100)),
|
|
token3
|
|
)
|
|
expect(result).toHaveLength(2)
|
|
expect(result[0].inputAmount.currency).toEqual(ETHER)
|
|
expect(result[0].route.path).toEqual([WETH[ChainId.MAINNET], token0, token1, token3])
|
|
expect(result[0].outputAmount.currency).toEqual(token3)
|
|
expect(result[1].inputAmount.currency).toEqual(ETHER)
|
|
expect(result[1].route.path).toEqual([WETH[ChainId.MAINNET], token0, token3])
|
|
expect(result[1].outputAmount.currency).toEqual(token3)
|
|
})
|
|
it('works for ETHER currency output', () => {
|
|
const result = Trade.bestTradeExactIn(
|
|
[pair_weth_0, pair_0_1, pair_0_3, pair_1_3],
|
|
new TokenAmount(token3, JSBI.BigInt(100)),
|
|
ETHER
|
|
)
|
|
expect(result).toHaveLength(2)
|
|
expect(result[0].inputAmount.currency).toEqual(token3)
|
|
expect(result[0].route.path).toEqual([token3, token0, WETH[ChainId.MAINNET]])
|
|
expect(result[0].outputAmount.currency).toEqual(ETHER)
|
|
expect(result[1].inputAmount.currency).toEqual(token3)
|
|
expect(result[1].route.path).toEqual([token3, token1, token0, WETH[ChainId.MAINNET]])
|
|
expect(result[1].outputAmount.currency).toEqual(ETHER)
|
|
})
|
|
})
|
|
|
|
describe('#maximumAmountIn', () => {
|
|
describe('tradeType = EXACT_INPUT', () => {
|
|
const exactIn = new Trade(
|
|
new Route([pair_0_1, pair_1_2], token0),
|
|
new TokenAmount(token0, JSBI.BigInt(100)),
|
|
TradeType.EXACT_INPUT
|
|
)
|
|
it('throws if less than 0', () => {
|
|
expect(() => exactIn.maximumAmountIn(new Percent(JSBI.BigInt(-1), JSBI.BigInt(100)))).toThrow(
|
|
'SLIPPAGE_TOLERANCE'
|
|
)
|
|
})
|
|
it('returns exact if 0', () => {
|
|
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(exactIn.inputAmount)
|
|
})
|
|
it('returns exact if nonzero', () => {
|
|
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token0, JSBI.BigInt(100))
|
|
)
|
|
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token0, JSBI.BigInt(100))
|
|
)
|
|
expect(exactIn.maximumAmountIn(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token0, JSBI.BigInt(100))
|
|
)
|
|
})
|
|
})
|
|
describe('tradeType = EXACT_OUTPUT', () => {
|
|
const exactOut = new Trade(
|
|
new Route([pair_0_1, pair_1_2], token0),
|
|
new TokenAmount(token2, JSBI.BigInt(100)),
|
|
TradeType.EXACT_OUTPUT
|
|
)
|
|
|
|
it('throws if less than 0', () => {
|
|
expect(() => exactOut.maximumAmountIn(new Percent(JSBI.BigInt(-1), JSBI.BigInt(100)))).toThrow(
|
|
'SLIPPAGE_TOLERANCE'
|
|
)
|
|
})
|
|
it('returns exact if 0', () => {
|
|
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(exactOut.inputAmount)
|
|
})
|
|
it('returns slippage amount if nonzero', () => {
|
|
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token0, JSBI.BigInt(156))
|
|
)
|
|
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token0, JSBI.BigInt(163))
|
|
)
|
|
expect(exactOut.maximumAmountIn(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token0, JSBI.BigInt(468))
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#minimumAmountOut', () => {
|
|
describe('tradeType = EXACT_INPUT', () => {
|
|
const exactIn = new Trade(
|
|
new Route([pair_0_1, pair_1_2], token0),
|
|
new TokenAmount(token0, JSBI.BigInt(100)),
|
|
TradeType.EXACT_INPUT
|
|
)
|
|
it('throws if less than 0', () => {
|
|
expect(() => exactIn.minimumAmountOut(new Percent(JSBI.BigInt(-1), JSBI.BigInt(100)))).toThrow(
|
|
'SLIPPAGE_TOLERANCE'
|
|
)
|
|
})
|
|
it('returns exact if 0', () => {
|
|
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(exactIn.outputAmount)
|
|
})
|
|
it('returns exact if nonzero', () => {
|
|
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token2, JSBI.BigInt(69))
|
|
)
|
|
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token2, JSBI.BigInt(65))
|
|
)
|
|
expect(exactIn.minimumAmountOut(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token2, JSBI.BigInt(23))
|
|
)
|
|
})
|
|
})
|
|
describe('tradeType = EXACT_OUTPUT', () => {
|
|
const exactOut = new Trade(
|
|
new Route([pair_0_1, pair_1_2], token0),
|
|
new TokenAmount(token2, JSBI.BigInt(100)),
|
|
TradeType.EXACT_OUTPUT
|
|
)
|
|
|
|
it('throws if less than 0', () => {
|
|
expect(() => exactOut.minimumAmountOut(new Percent(JSBI.BigInt(-1), JSBI.BigInt(100)))).toThrow(
|
|
'SLIPPAGE_TOLERANCE'
|
|
)
|
|
})
|
|
it('returns exact if 0', () => {
|
|
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(exactOut.outputAmount)
|
|
})
|
|
it('returns slippage amount if nonzero', () => {
|
|
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(0), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token2, JSBI.BigInt(100))
|
|
)
|
|
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token2, JSBI.BigInt(100))
|
|
)
|
|
expect(exactOut.minimumAmountOut(new Percent(JSBI.BigInt(200), JSBI.BigInt(100)))).toEqual(
|
|
new TokenAmount(token2, JSBI.BigInt(100))
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#bestTradeExactOut', () => {
|
|
it('throws with empty pairs', () => {
|
|
expect(() => Trade.bestTradeExactOut([], token0, new TokenAmount(token2, JSBI.BigInt(100)))).toThrow('PAIRS')
|
|
})
|
|
it('throws with max hops of 0', () => {
|
|
expect(() =>
|
|
Trade.bestTradeExactOut([pair_0_2], token0, new TokenAmount(token2, JSBI.BigInt(100)), { maxHops: 0 })
|
|
).toThrow('MAX_HOPS')
|
|
})
|
|
|
|
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('doesnt throw for zero liquidity pairs', () => {
|
|
expect(Trade.bestTradeExactOut([empty_pair_0_1], token1, new TokenAmount(token1, JSBI.BigInt(100)))).toHaveLength(
|
|
0
|
|
)
|
|
})
|
|
|
|
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)
|
|
})
|
|
|
|
it('works for ETHER currency input', () => {
|
|
const result = Trade.bestTradeExactOut(
|
|
[pair_weth_0, pair_0_1, pair_0_3, pair_1_3],
|
|
ETHER,
|
|
new TokenAmount(token3, JSBI.BigInt(100))
|
|
)
|
|
expect(result).toHaveLength(2)
|
|
expect(result[0].inputAmount.currency).toEqual(ETHER)
|
|
expect(result[0].route.path).toEqual([WETH[ChainId.MAINNET], token0, token1, token3])
|
|
expect(result[0].outputAmount.currency).toEqual(token3)
|
|
expect(result[1].inputAmount.currency).toEqual(ETHER)
|
|
expect(result[1].route.path).toEqual([WETH[ChainId.MAINNET], token0, token3])
|
|
expect(result[1].outputAmount.currency).toEqual(token3)
|
|
})
|
|
it('works for ETHER currency output', () => {
|
|
const result = Trade.bestTradeExactOut(
|
|
[pair_weth_0, pair_0_1, pair_0_3, pair_1_3],
|
|
token3,
|
|
CurrencyAmount.ether(JSBI.BigInt(100))
|
|
)
|
|
expect(result).toHaveLength(2)
|
|
expect(result[0].inputAmount.currency).toEqual(token3)
|
|
expect(result[0].route.path).toEqual([token3, token0, WETH[ChainId.MAINNET]])
|
|
expect(result[0].outputAmount.currency).toEqual(ETHER)
|
|
expect(result[1].inputAmount.currency).toEqual(token3)
|
|
expect(result[1].route.path).toEqual([token3, token1, token0, WETH[ChainId.MAINNET]])
|
|
expect(result[1].outputAmount.currency).toEqual(ETHER)
|
|
})
|
|
})
|
|
})
|